import { mount } from '@vue/test-utils'; import createFlash from '~/flash'; import { visitUrl } from '~/lib/utils/url_utility'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; import DeploymentActions from '~/vue_merge_request_widget/components/deployment/deployment_actions.vue'; import { CREATED, MANUAL_DEPLOY, FAILED, DEPLOYING, REDEPLOYING, STOPPING, } from '~/vue_merge_request_widget/components/deployment/constants'; import { actionButtonMocks, deploymentMockData, playDetails, retryDetails, } from './deployment_mock_data'; jest.mock('~/flash'); jest.mock('~/lib/utils/url_utility'); describe('DeploymentAction component', () => { let wrapper; let executeActionSpy; const factory = (options = {}) => { // This destroys any wrappers created before a nested call to factory reassigns it if (wrapper && wrapper.destroy) { wrapper.destroy(); } wrapper = mount(DeploymentActions, { ...options, provide: { glFeatures: { deployFromFooter: true } }, }); }; const findStopButton = () => wrapper.find('.js-stop-env'); const findDeployButton = () => wrapper.find('.js-manual-deploy-action'); const findRedeployButton = () => wrapper.find('.js-manual-redeploy-action'); beforeEach(() => { executeActionSpy = jest.spyOn(MRWidgetService, 'executeInlineAction'); factory({ propsData: { computedDeploymentStatus: CREATED, deployment: deploymentMockData, showVisualReviewApp: false, }, }); }); afterEach(() => { wrapper.destroy(); }); describe('actions do not appear when conditions are unmet', () => { describe('when there is no stop_url', () => { beforeEach(() => { factory({ propsData: { computedDeploymentStatus: CREATED, deployment: { ...deploymentMockData, stop_url: null, }, showVisualReviewApp: false, }, }); }); it('the stop button does not appear', () => { expect(findStopButton().exists()).toBe(false); }); }); describe('when there is no play_path in details', () => { it('the manual deploy button does not appear', () => { expect(findDeployButton().exists()).toBe(false); }); }); describe('when there is no retry_path in details', () => { it('the manual redeploy button does not appear', () => { expect(findRedeployButton().exists()).toBe(false); }); }); }); describe('when conditions are met', () => { describe.each` configConst | computedDeploymentStatus | displayConditionChanges | finderFn | endpoint ${STOPPING} | ${CREATED} | ${{}} | ${findStopButton} | ${deploymentMockData.stop_url} ${DEPLOYING} | ${MANUAL_DEPLOY} | ${playDetails} | ${findDeployButton} | ${playDetails.playable_build.play_path} ${REDEPLOYING} | ${FAILED} | ${retryDetails} | ${findRedeployButton} | ${retryDetails.playable_build.retry_path} `( '$configConst action', ({ configConst, computedDeploymentStatus, displayConditionChanges, finderFn, endpoint }) => { describe(`${configConst} action`, () => { const confirmAction = () => { jest.spyOn(window, 'confirm').mockReturnValueOnce(true); finderFn().trigger('click'); }; const rejectAction = () => { jest.spyOn(window, 'confirm').mockReturnValueOnce(false); finderFn().trigger('click'); }; beforeEach(() => { factory({ propsData: { computedDeploymentStatus, deployment: { ...deploymentMockData, details: displayConditionChanges, }, showVisualReviewApp: false, }, }); }); it('the button is rendered', () => { expect(finderFn().exists()).toBe(true); }); describe('when clicked', () => { describe('should show a confirm dialog but not call executeInlineAction when declined', () => { beforeEach(() => { executeActionSpy.mockResolvedValueOnce(); rejectAction(); }); it('should show the confirm dialog', () => { expect(window.confirm).toHaveBeenCalled(); expect(window.confirm).toHaveBeenCalledWith( actionButtonMocks[configConst].confirmMessage, ); }); it('should not execute the action', () => { expect(MRWidgetService.executeInlineAction).not.toHaveBeenCalled(); }); }); describe('should show a confirm dialog and call executeInlineAction when accepted', () => { beforeEach(() => { executeActionSpy.mockResolvedValueOnce(); confirmAction(); }); it('should show the confirm dialog', () => { expect(window.confirm).toHaveBeenCalled(); expect(window.confirm).toHaveBeenCalledWith( actionButtonMocks[configConst].confirmMessage, ); }); it('should execute the action with expected URL', () => { expect(MRWidgetService.executeInlineAction).toHaveBeenCalled(); expect(MRWidgetService.executeInlineAction).toHaveBeenCalledWith(endpoint); }); it('should not throw an error', () => { expect(createFlash).not.toHaveBeenCalled(); }); describe('response includes redirect_url', () => { const url = '/root/example'; beforeEach(() => { executeActionSpy.mockResolvedValueOnce({ data: { redirect_url: url }, }); confirmAction(); }); it('calls visit url with the redirect_url', () => { expect(visitUrl).toHaveBeenCalled(); expect(visitUrl).toHaveBeenCalledWith(url); }); }); describe('it should call the executeAction method ', () => { beforeEach(() => { jest.spyOn(wrapper.vm, 'executeAction').mockImplementation(); confirmAction(); }); it('calls with the expected arguments', () => { expect(wrapper.vm.executeAction).toHaveBeenCalled(); expect(wrapper.vm.executeAction).toHaveBeenCalledWith( endpoint, actionButtonMocks[configConst], ); }); }); describe('when executeInlineAction errors', () => { beforeEach(() => { executeActionSpy.mockRejectedValueOnce(); confirmAction(); }); it('should call createFlash with error message', () => { expect(createFlash).toHaveBeenCalled(); expect(createFlash).toHaveBeenCalledWith( actionButtonMocks[configConst].errorMessage, ); }); }); }); }); }); }, ); }); });