Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-10 18:08:08 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-10 18:08:08 +0300
commit7c38405be9e79099f399aa429503ea7b463bbf5a (patch)
tree30944a8baf135021395574e081f53ed5f756ace0 /spec/frontend/vue_mr_widget
parent1fa79760ad2d4bd67f5c5a27f372a7533b9b7c69 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/vue_mr_widget')
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_action_button_spec.js124
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js220
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js45
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_spec.js69
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js2
5 files changed, 418 insertions, 42 deletions
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_action_button_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_action_button_spec.js
new file mode 100644
index 00000000000..1b14ee694fe
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_action_button_spec.js
@@ -0,0 +1,124 @@
+import { mount } from '@vue/test-utils';
+import { GlIcon, GlLoadingIcon, GlButton } from '@gitlab/ui';
+import DeploymentActionButton from '~/vue_merge_request_widget/components/deployment/deployment_action_button.vue';
+import {
+ CREATED,
+ RUNNING,
+ DEPLOYING,
+ REDEPLOYING,
+} from '~/vue_merge_request_widget/components/deployment/constants';
+import { actionButtonMocks } from './deployment_mock_data';
+
+const baseProps = {
+ actionsConfiguration: actionButtonMocks[DEPLOYING],
+ actionInProgress: null,
+ computedDeploymentStatus: CREATED,
+};
+
+describe('Deployment action button', () => {
+ let wrapper;
+
+ const factory = (options = {}) => {
+ wrapper = mount(DeploymentActionButton, {
+ ...options,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when passed only icon', () => {
+ beforeEach(() => {
+ factory({
+ propsData: baseProps,
+ slots: { default: ['<gl-icon name="stop" />'] },
+ stubs: {
+ 'gl-icon': GlIcon,
+ },
+ });
+ });
+
+ it('renders slot correctly', () => {
+ expect(wrapper.find(GlIcon).exists()).toBe(true);
+ });
+ });
+
+ describe('when passed multiple items', () => {
+ beforeEach(() => {
+ factory({
+ propsData: baseProps,
+ slots: {
+ default: ['<gl-icon name="play" />', `<span>${actionButtonMocks[DEPLOYING]}</span>`],
+ },
+ stubs: {
+ 'gl-icon': GlIcon,
+ },
+ });
+ });
+
+ it('renders slot correctly', () => {
+ expect(wrapper.find(GlIcon).exists()).toBe(true);
+ expect(wrapper.text()).toContain(actionButtonMocks[DEPLOYING]);
+ });
+ });
+
+ describe('when its action is in progress', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ ...baseProps,
+ actionInProgress: actionButtonMocks[DEPLOYING].actionName,
+ },
+ });
+ });
+
+ it('is disabled and shows the loading icon', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.find(GlButton).props('disabled')).toBe(true);
+ });
+ });
+
+ describe('when another action is in progress', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ ...baseProps,
+ actionInProgress: actionButtonMocks[REDEPLOYING].actionName,
+ },
+ });
+ });
+ it('is disabled and does not show the loading icon', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.find(GlButton).props('disabled')).toBe(true);
+ });
+ });
+
+ describe('when action status is running', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ ...baseProps,
+ actionInProgress: actionButtonMocks[REDEPLOYING].actionName,
+ computedDeploymentStatus: RUNNING,
+ },
+ });
+ });
+ it('is disabled and does not show the loading icon', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.find(GlButton).props('disabled')).toBe(true);
+ });
+ });
+
+ describe('when no action is in progress', () => {
+ beforeEach(() => {
+ factory({
+ propsData: baseProps,
+ });
+ });
+ it('is not disabled nor does it show the loading icon', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.find(GlButton).props('disabled')).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js
new file mode 100644
index 00000000000..6449272e6ed
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js
@@ -0,0 +1,220 @@
+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,
+ );
+ });
+ });
+ });
+ });
+ });
+ },
+ );
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js b/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js
index f8f4cb627dd..ff29022b75d 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js
@@ -1,4 +1,33 @@
-import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
+import {
+ DEPLOYING,
+ REDEPLOYING,
+ SUCCESS,
+ STOPPING,
+} from '~/vue_merge_request_widget/components/deployment/constants';
+
+const actionButtonMocks = {
+ [STOPPING]: {
+ actionName: STOPPING,
+ buttonText: 'Stop environment',
+ busyText: 'This environment is being deployed',
+ confirmMessage: 'Are you sure you want to stop this environment?',
+ errorMessage: 'Something went wrong while stopping this environment. Please try again.',
+ },
+ [DEPLOYING]: {
+ actionName: DEPLOYING,
+ buttonText: 'Deploy',
+ busyText: 'This environment is being deployed',
+ confirmMessage: 'Are you sure you want to deploy this environment?',
+ errorMessage: 'Something went wrong while deploying this environment. Please try again.',
+ },
+ [REDEPLOYING]: {
+ actionName: REDEPLOYING,
+ buttonText: 'Re-deploy',
+ busyText: 'This environment is being re-deployed',
+ confirmMessage: 'Are you sure you want to re-deploy this environment?',
+ errorMessage: 'Something went wrong while deploying this environment. Please try again.',
+ },
+};
const deploymentMockData = {
id: 15,
@@ -29,4 +58,16 @@ const deploymentMockData = {
],
};
-export default deploymentMockData;
+const playDetails = {
+ playable_build: {
+ play_path: '/root/test-deployments/-/jobs/1131/play',
+ },
+};
+
+const retryDetails = {
+ playable_build: {
+ retry_path: '/root/test-deployments/-/jobs/1131/retry',
+ },
+};
+
+export { actionButtonMocks, deploymentMockData, playDetails, retryDetails };
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
index ec7be6b64fc..ce395de3b5d 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
@@ -2,7 +2,6 @@ import { mount } from '@vue/test-utils';
import DeploymentComponent from '~/vue_merge_request_widget/components/deployment/deployment.vue';
import DeploymentInfo from '~/vue_merge_request_widget/components/deployment/deployment_info.vue';
import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
-import DeploymentStopButton from '~/vue_merge_request_widget/components/deployment/deployment_stop_button.vue';
import {
CREATED,
RUNNING,
@@ -10,15 +9,7 @@ import {
FAILED,
CANCELED,
} from '~/vue_merge_request_widget/components/deployment/constants';
-import deploymentMockData from './deployment_mock_data';
-
-const deployDetail = {
- playable_build: {
- retry_path: '/root/test-deployments/-/jobs/1131/retry',
- play_path: '/root/test-deployments/-/jobs/1131/play',
- },
- isManual: true,
-};
+import { deploymentMockData, playDetails, retryDetails } from './deployment_mock_data';
describe('Deployment component', () => {
let wrapper;
@@ -30,6 +21,7 @@ describe('Deployment component', () => {
}
wrapper = mount(DeploymentComponent, {
...options,
+ provide: { glFeatures: { deployFromFooter: true } },
});
};
@@ -53,28 +45,39 @@ describe('Deployment component', () => {
describe('status message and buttons', () => {
const noActions = [];
const noDetails = { isManual: false };
- const deployGroup = [DeploymentViewButton, DeploymentStopButton];
+ const deployDetail = {
+ ...playDetails,
+ isManual: true,
+ };
+
+ const retryDetail = {
+ ...retryDetails,
+ isManual: true,
+ };
+ const defaultGroup = ['.js-deploy-url', '.js-stop-env'];
+ const manualDeployGroup = ['.js-manual-deploy-action', ...defaultGroup];
+ const manualRedeployGroup = ['.js-manual-redeploy-action', ...defaultGroup];
describe.each`
status | previous | deploymentDetails | text | actionButtons
- ${CREATED} | ${true} | ${deployDetail} | ${'Can be manually deployed to'} | ${deployGroup}
- ${CREATED} | ${true} | ${noDetails} | ${'Will deploy to'} | ${deployGroup}
+ ${CREATED} | ${true} | ${deployDetail} | ${'Can be manually deployed to'} | ${manualDeployGroup}
+ ${CREATED} | ${true} | ${noDetails} | ${'Will deploy to'} | ${defaultGroup}
${CREATED} | ${false} | ${deployDetail} | ${'Can be manually deployed to'} | ${noActions}
${CREATED} | ${false} | ${noDetails} | ${'Will deploy to'} | ${noActions}
- ${RUNNING} | ${true} | ${deployDetail} | ${'Deploying to'} | ${deployGroup}
- ${RUNNING} | ${true} | ${noDetails} | ${'Deploying to'} | ${deployGroup}
+ ${RUNNING} | ${true} | ${deployDetail} | ${'Deploying to'} | ${defaultGroup}
+ ${RUNNING} | ${true} | ${noDetails} | ${'Deploying to'} | ${defaultGroup}
${RUNNING} | ${false} | ${deployDetail} | ${'Deploying to'} | ${noActions}
${RUNNING} | ${false} | ${noDetails} | ${'Deploying to'} | ${noActions}
- ${SUCCESS} | ${true} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
- ${SUCCESS} | ${true} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
- ${SUCCESS} | ${false} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
- ${SUCCESS} | ${false} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
- ${FAILED} | ${true} | ${deployDetail} | ${'Failed to deploy to'} | ${deployGroup}
- ${FAILED} | ${true} | ${noDetails} | ${'Failed to deploy to'} | ${deployGroup}
- ${FAILED} | ${false} | ${deployDetail} | ${'Failed to deploy to'} | ${noActions}
+ ${SUCCESS} | ${true} | ${deployDetail} | ${'Deployed to'} | ${defaultGroup}
+ ${SUCCESS} | ${true} | ${noDetails} | ${'Deployed to'} | ${defaultGroup}
+ ${SUCCESS} | ${false} | ${deployDetail} | ${'Deployed to'} | ${defaultGroup}
+ ${SUCCESS} | ${false} | ${noDetails} | ${'Deployed to'} | ${defaultGroup}
+ ${FAILED} | ${true} | ${retryDetail} | ${'Failed to deploy to'} | ${manualRedeployGroup}
+ ${FAILED} | ${true} | ${noDetails} | ${'Failed to deploy to'} | ${defaultGroup}
+ ${FAILED} | ${false} | ${retryDetail} | ${'Failed to deploy to'} | ${noActions}
${FAILED} | ${false} | ${noDetails} | ${'Failed to deploy to'} | ${noActions}
- ${CANCELED} | ${true} | ${deployDetail} | ${'Canceled deployment to'} | ${deployGroup}
- ${CANCELED} | ${true} | ${noDetails} | ${'Canceled deployment to'} | ${deployGroup}
+ ${CANCELED} | ${true} | ${deployDetail} | ${'Canceled deployment to'} | ${defaultGroup}
+ ${CANCELED} | ${true} | ${noDetails} | ${'Canceled deployment to'} | ${defaultGroup}
${CANCELED} | ${false} | ${deployDetail} | ${'Canceled deployment to'} | ${noActions}
${CANCELED} | ${false} | ${noDetails} | ${'Canceled deployment to'} | ${noActions}
`(
@@ -112,7 +115,7 @@ describe('Deployment component', () => {
if (actionButtons.length > 0) {
describe('renders the expected button group', () => {
actionButtons.forEach(button => {
- it(`renders ${button.name}`, () => {
+ it(`renders ${button}`, () => {
expect(wrapper.find(button).exists()).toBe(true);
});
});
@@ -121,8 +124,8 @@ describe('Deployment component', () => {
if (actionButtons.length === 0) {
describe('does not render the button group', () => {
- [DeploymentViewButton, DeploymentStopButton].forEach(button => {
- it(`does not render ${button.name}`, () => {
+ defaultGroup.forEach(button => {
+ it(`does not render ${button}`, () => {
expect(wrapper.find(button).exists()).toBe(false);
});
});
@@ -144,10 +147,6 @@ describe('Deployment component', () => {
describe('hasExternalUrls', () => {
describe('when deployment has both external_url_formatted and external_url', () => {
- it('should return true', () => {
- expect(wrapper.vm.hasExternalUrls).toEqual(true);
- });
-
it('should render the View Button', () => {
expect(wrapper.find(DeploymentViewButton).exists()).toBe(true);
});
@@ -163,10 +162,6 @@ describe('Deployment component', () => {
});
});
- it('should return false', () => {
- expect(wrapper.vm.hasExternalUrls).toEqual(false);
- });
-
it('should not render the View Button', () => {
expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
});
@@ -182,10 +177,6 @@ describe('Deployment component', () => {
});
});
- it('should return false', () => {
- expect(wrapper.vm.hasExternalUrls).toEqual(false);
- });
-
it('should not render the View Button', () => {
expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
});
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js
index 5e0f38459b0..a12757d4cce 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
import ReviewAppLink from '~/vue_merge_request_widget/components/review_app_link.vue';
-import deploymentMockData from './deployment_mock_data';
+import { deploymentMockData } from './deployment_mock_data';
const appButtonText = {
text: 'View app',