summaryrefslogtreecommitdiff
path: root/spec/frontend/vue_mr_widget
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 12:26:25 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 12:26:25 +0000
commita09983ae35713f5a2bbb100981116d31ce99826e (patch)
tree2ee2af7bd104d57086db360a7e6d8c9d5d43667a /spec/frontend/vue_mr_widget
parent18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff)
downloadgitlab-ce-a09983ae35713f5a2bbb100981116d31ce99826e.tar.gz
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'spec/frontend/vue_mr_widget')
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js391
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js57
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js93
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js54
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js65
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js35
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js300
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js86
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js107
-rw-r--r--spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js7
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js9
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js143
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js44
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js42
-rw-r--r--spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js10
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mock_data.js31
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js172
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js95
-rw-r--r--spec/frontend/vue_mr_widget/mock_data.js15
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js6
-rw-r--r--spec/frontend/vue_mr_widget/stores/get_state_key_spec.js8
22 files changed, 1304 insertions, 468 deletions
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js
new file mode 100644
index 00000000000..e39f66d3f30
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js
@@ -0,0 +1,391 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import Approvals from '~/vue_merge_request_widget/components/approvals/approvals.vue';
+import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
+import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
+import createFlash from '~/flash';
+import {
+ FETCH_LOADING,
+ FETCH_ERROR,
+ APPROVE_ERROR,
+ UNAPPROVE_ERROR,
+} from '~/vue_merge_request_widget/components/approvals/messages';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+
+jest.mock('~/flash');
+
+const TEST_HELP_PATH = 'help/path';
+const testApprovedBy = () => [1, 7, 10].map(id => ({ id }));
+const testApprovals = () => ({
+ approved: false,
+ approved_by: testApprovedBy().map(user => ({ user })),
+ approval_rules_left: [],
+ approvals_left: 4,
+ suggested_approvers: [],
+ user_can_approve: true,
+ user_has_approved: true,
+ require_password_to_approve: false,
+});
+const testApprovalRulesResponse = () => ({ rules: [{ id: 2 }] });
+
+// For some reason, the `Promise.resolve()` needs to be deferred
+// or the timing doesn't work.
+const tick = () => Promise.resolve();
+const waitForTick = done =>
+ tick()
+ .then(done)
+ .catch(done.fail);
+
+describe('MRWidget approvals', () => {
+ let wrapper;
+ let service;
+ let mr;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(Approvals, {
+ propsData: {
+ mr,
+ service,
+ ...props,
+ },
+ });
+ };
+
+ const findAction = () => wrapper.find(GlButton);
+ const findActionData = () => {
+ const action = findAction();
+
+ return !action.exists()
+ ? null
+ : {
+ variant: action.props('variant'),
+ category: action.props('category'),
+ text: action.text(),
+ };
+ };
+ const findSummary = () => wrapper.find(ApprovalsSummary);
+ const findOptionalSummary = () => wrapper.find(ApprovalsSummaryOptional);
+
+ beforeEach(() => {
+ service = {
+ ...{
+ fetchApprovals: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ fetchApprovalSettings: jest
+ .fn()
+ .mockReturnValue(Promise.resolve(testApprovalRulesResponse())),
+ approveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ unapproveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ approveMergeRequestWithAuth: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ },
+ };
+ mr = {
+ ...{
+ setApprovals: jest.fn(),
+ setApprovalRules: jest.fn(),
+ },
+ approvalsHelpPath: TEST_HELP_PATH,
+ approvals: testApprovals(),
+ approvalRules: [],
+ isOpen: true,
+ state: 'open',
+ };
+
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when created', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows loading message', () => {
+ wrapper.setData({ fetchingApprovals: true });
+
+ return tick().then(() => {
+ expect(wrapper.text()).toContain(FETCH_LOADING);
+ });
+ });
+
+ it('fetches approvals', () => {
+ expect(service.fetchApprovals).toHaveBeenCalled();
+ });
+ });
+
+ describe('when fetch approvals error', () => {
+ beforeEach(done => {
+ jest.spyOn(service, 'fetchApprovals').mockReturnValue(Promise.reject());
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('still shows loading message', () => {
+ expect(wrapper.text()).toContain(FETCH_LOADING);
+ });
+
+ it('flashes error', () => {
+ expect(createFlash).toHaveBeenCalledWith(FETCH_ERROR);
+ });
+ });
+
+ describe('action button', () => {
+ describe('when mr is closed', () => {
+ beforeEach(done => {
+ mr.isOpen = false;
+ mr.approvals.user_has_approved = false;
+ mr.approvals.user_can_approve = true;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('action is not rendered', () => {
+ expect(findActionData()).toBe(null);
+ });
+ });
+
+ describe('when user cannot approve', () => {
+ beforeEach(done => {
+ mr.approvals.user_has_approved = false;
+ mr.approvals.user_can_approve = false;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('action is not rendered', () => {
+ expect(findActionData()).toBe(null);
+ });
+ });
+
+ describe('when user can approve', () => {
+ beforeEach(() => {
+ mr.approvals.user_has_approved = false;
+ mr.approvals.user_can_approve = true;
+ });
+
+ describe('and MR is unapproved', () => {
+ beforeEach(done => {
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('approve action is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'info',
+ text: 'Approve',
+ category: 'primary',
+ });
+ });
+ });
+
+ describe('and MR is approved', () => {
+ beforeEach(() => {
+ mr.approvals.approved = true;
+ });
+
+ describe('with no approvers', () => {
+ beforeEach(done => {
+ mr.approvals.approved_by = [];
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('approve action (with inverted style) is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'info',
+ text: 'Approve',
+ category: 'secondary',
+ });
+ });
+ });
+
+ describe('with approvers', () => {
+ beforeEach(done => {
+ mr.approvals.approved_by = [{ user: { id: 7 } }];
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('approve additionally action is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'info',
+ text: 'Approve additionally',
+ category: 'secondary',
+ });
+ });
+ });
+ });
+
+ describe('when approve action is clicked', () => {
+ beforeEach(done => {
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('shows loading icon', () => {
+ jest.spyOn(service, 'approveMergeRequest').mockReturnValue(new Promise(() => {}));
+ const action = findAction();
+
+ expect(action.props('loading')).toBe(false);
+
+ action.vm.$emit('click');
+
+ return tick().then(() => {
+ expect(action.props('loading')).toBe(true);
+ });
+ });
+
+ describe('and after loading', () => {
+ beforeEach(done => {
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('calls service approve', () => {
+ expect(service.approveMergeRequest).toHaveBeenCalled();
+ });
+
+ it('emits to eventHub', () => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ });
+
+ it('calls store setApprovals', () => {
+ expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
+ });
+ });
+
+ describe('and error', () => {
+ beforeEach(done => {
+ jest.spyOn(service, 'approveMergeRequest').mockReturnValue(Promise.reject());
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('flashes error message', () => {
+ expect(createFlash).toHaveBeenCalledWith(APPROVE_ERROR);
+ });
+ });
+ });
+ });
+
+ describe('when user has approved', () => {
+ beforeEach(done => {
+ mr.approvals.user_has_approved = true;
+ mr.approvals.user_can_approve = false;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('revoke action is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'warning',
+ text: 'Revoke approval',
+ category: 'secondary',
+ });
+ });
+
+ describe('when revoke action is clicked', () => {
+ describe('and successful', () => {
+ beforeEach(done => {
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('calls service unapprove', () => {
+ expect(service.unapproveMergeRequest).toHaveBeenCalled();
+ });
+
+ it('emits to eventHub', () => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ });
+
+ it('calls store setApprovals', () => {
+ expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
+ });
+ });
+
+ describe('and error', () => {
+ beforeEach(done => {
+ jest.spyOn(service, 'unapproveMergeRequest').mockReturnValue(Promise.reject());
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('flashes error message', () => {
+ expect(createFlash).toHaveBeenCalledWith(UNAPPROVE_ERROR);
+ });
+ });
+ });
+ });
+ });
+
+ describe('approvals optional summary', () => {
+ describe('when no approvals required and no approvers', () => {
+ beforeEach(() => {
+ mr.approvals.approved_by = [];
+ mr.approvals.approvals_required = 0;
+ mr.approvals.user_has_approved = false;
+ });
+
+ describe('and can approve', () => {
+ beforeEach(done => {
+ mr.approvals.user_can_approve = true;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('is shown', () => {
+ expect(findSummary().exists()).toBe(false);
+ expect(findOptionalSummary().props()).toEqual({
+ canApprove: true,
+ helpPath: TEST_HELP_PATH,
+ });
+ });
+ });
+
+ describe('and cannot approve', () => {
+ beforeEach(done => {
+ mr.approvals.user_can_approve = false;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('is shown', () => {
+ expect(findSummary().exists()).toBe(false);
+ expect(findOptionalSummary().props()).toEqual({
+ canApprove: false,
+ helpPath: TEST_HELP_PATH,
+ });
+ });
+ });
+ });
+ });
+
+ describe('approvals summary', () => {
+ beforeEach(done => {
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('is rendered with props', () => {
+ const expected = testApprovals();
+ const summary = findSummary();
+
+ expect(findOptionalSummary().exists()).toBe(false);
+ expect(summary.exists()).toBe(true);
+ expect(summary.props()).toMatchObject({
+ approvalsLeft: expected.approvals_left,
+ rulesLeft: expected.approval_rules_left,
+ approvers: testApprovedBy(),
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js
new file mode 100644
index 00000000000..77fad7f51ab
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js
@@ -0,0 +1,57 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
+import {
+ OPTIONAL,
+ OPTIONAL_CAN_APPROVE,
+} from '~/vue_merge_request_widget/components/approvals/messages';
+import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
+
+const TEST_HELP_PATH = 'help/path';
+
+describe('MRWidget approvals summary optional', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(ApprovalsSummaryOptional, {
+ propsData: props,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findHelpLink = () => wrapper.find(GlLink);
+
+ describe('when can approve', () => {
+ beforeEach(() => {
+ createComponent({ canApprove: true, helpPath: TEST_HELP_PATH });
+ });
+
+ it('shows optional can approve message', () => {
+ expect(wrapper.text()).toEqual(OPTIONAL_CAN_APPROVE);
+ });
+
+ it('shows help link', () => {
+ const link = findHelpLink();
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(TEST_HELP_PATH);
+ });
+ });
+
+ describe('when cannot approve', () => {
+ beforeEach(() => {
+ createComponent({ canApprove: false, helpPath: TEST_HELP_PATH });
+ });
+
+ it('shows optional message', () => {
+ expect(wrapper.text()).toEqual(OPTIONAL);
+ });
+
+ it('does not show help link', () => {
+ expect(findHelpLink().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js
new file mode 100644
index 00000000000..822d075f28f
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js
@@ -0,0 +1,93 @@
+import { shallowMount } from '@vue/test-utils';
+import { APPROVED_MESSAGE } from '~/vue_merge_request_widget/components/approvals/messages';
+import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
+import { toNounSeriesText } from '~/lib/utils/grammar';
+import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
+
+const testApprovers = () => Array.from({ length: 5 }, (_, i) => i).map(id => ({ id }));
+const testRulesLeft = () => ['Lorem', 'Ipsum', 'dolar & sit'];
+const TEST_APPROVALS_LEFT = 3;
+
+describe('MRWidget approvals summary', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(ApprovalsSummary, {
+ propsData: {
+ approved: false,
+ approvers: testApprovers(),
+ approvalsLeft: TEST_APPROVALS_LEFT,
+ rulesLeft: testRulesLeft(),
+ ...props,
+ },
+ });
+ };
+
+ const findAvatars = () => wrapper.find(UserAvatarList);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when approved', () => {
+ beforeEach(() => {
+ createComponent({
+ approved: true,
+ });
+ });
+
+ it('shows approved message', () => {
+ expect(wrapper.text()).toContain(APPROVED_MESSAGE);
+ });
+
+ it('renders avatar list for approvers', () => {
+ const avatars = findAvatars();
+
+ expect(avatars.exists()).toBe(true);
+ expect(avatars.props()).toEqual(
+ expect.objectContaining({
+ items: testApprovers(),
+ }),
+ );
+ });
+ });
+
+ describe('when not approved', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('render message', () => {
+ const names = toNounSeriesText(testRulesLeft());
+
+ expect(wrapper.text()).toContain(
+ `Requires ${TEST_APPROVALS_LEFT} more approvals from ${names}.`,
+ );
+ });
+ });
+
+ describe('when no rulesLeft', () => {
+ beforeEach(() => {
+ createComponent({
+ rulesLeft: [],
+ });
+ });
+
+ it('renders message', () => {
+ expect(wrapper.text()).toContain(`Requires ${TEST_APPROVALS_LEFT} more approvals.`);
+ });
+ });
+
+ describe('when no approvers', () => {
+ beforeEach(() => {
+ createComponent({
+ approvers: [],
+ });
+ });
+
+ it('does not render avatar list', () => {
+ expect(wrapper.find(UserAvatarList).exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
index 05690aa1248..e7c10ab4c2d 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
@@ -1,39 +1,61 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue';
+window.gl = window.gl || {};
+
describe('MrWidgetAuthor', () => {
- let vm;
+ let wrapper;
+ let oldWindowGl;
+ const mockAuthor = {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ };
beforeEach(() => {
- const Component = Vue.extend(MrWidgetAuthor);
-
- vm = mountComponent(Component, {
- author: {
- name: 'Administrator',
- username: 'root',
- webUrl: 'http://localhost:3000/root',
- avatarUrl:
- 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ oldWindowGl = window.gl;
+ window.gl = {
+ mrWidgetData: {
+ defaultAvatarUrl: 'no_avatar.png',
+ },
+ };
+ wrapper = shallowMount(MrWidgetAuthor, {
+ propsData: {
+ author: mockAuthor,
},
});
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ window.gl = oldWindowGl;
});
it('renders link with the author web url', () => {
- expect(vm.$el.getAttribute('href')).toEqual('http://localhost:3000/root');
+ expect(wrapper.attributes('href')).toBe('http://localhost:3000/root');
});
it('renders image with avatar url', () => {
- expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(
+ expect(wrapper.find('img').attributes('src')).toBe(
'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
);
});
+ it('renders image with default avatar url when no avatarUrl is present in author', async () => {
+ wrapper.setProps({
+ author: {
+ ...mockAuthor,
+ avatarUrl: null,
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.find('img').attributes('src')).toBe('no_avatar.png');
+ });
+
it('renders author name', () => {
- expect(vm.$el.textContent.trim()).toEqual('Administrator');
+ expect(wrapper.find('span').text()).toBe('Administrator');
});
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js
new file mode 100644
index 00000000000..69a50899d4d
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js
@@ -0,0 +1,65 @@
+import { GlButton, GlCollapse, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import MrCollapsibleSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue';
+
+describe('MrWidgetExpanableSection', () => {
+ let wrapper;
+
+ const findButton = () => wrapper.find(GlButton);
+ const findCollapse = () => wrapper.find(GlCollapse);
+
+ beforeEach(() => {
+ wrapper = shallowMount(MrCollapsibleSection, {
+ slots: {
+ content: '<span>Collapsable Content</span>',
+ header: '<span>Header Content</span>',
+ },
+ });
+ });
+
+ it('renders Icon', () => {
+ expect(wrapper.contains(GlIcon)).toBe(true);
+ });
+
+ it('renders header slot', () => {
+ expect(wrapper.text()).toContain('Header Content');
+ });
+
+ it('renders content slot', () => {
+ expect(wrapper.text()).toContain('Collapsable Content');
+ });
+
+ describe('when collapse section is closed', () => {
+ it('renders button with expand text', () => {
+ expect(findButton().text()).toBe('Expand');
+ });
+
+ it('renders a collpased section with no visibility', () => {
+ const collapse = findCollapse();
+
+ expect(collapse.exists()).toBe(true);
+ expect(collapse.attributes('visible')).toBeUndefined();
+ });
+ });
+
+ describe('when collapse section is open', () => {
+ beforeEach(() => {
+ findButton().vm.$emit('click');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('renders button with collapse text', () => {
+ const button = findButton();
+
+ expect(button.exists()).toBe(true);
+ expect(button.text()).toBe('Collapse');
+ });
+
+ it('renders a collpased section with visible content', () => {
+ const collapse = findCollapse();
+
+ expect(collapse.exists()).toBe(true);
+ expect(collapse.attributes('visible')).toBe('true');
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
index b492a69fb3d..21058005d29 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
@@ -1,7 +1,13 @@
import Vue from 'vue';
+import Mousetrap from 'mousetrap';
import mountComponent from 'helpers/vue_mount_component_helper';
import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header.vue';
+jest.mock('mousetrap', () => ({
+ bind: jest.fn(),
+ unbind: jest.fn(),
+}));
+
describe('MRWidgetHeader', () => {
let vm;
let Component;
@@ -126,6 +132,35 @@ describe('MRWidgetHeader', () => {
it('renders target branch', () => {
expect(vm.$el.querySelector('.js-target-branch').textContent.trim()).toEqual('master');
});
+
+ describe('keyboard shortcuts', () => {
+ it('binds a keyboard shortcut handler to the "b" key', () => {
+ expect(Mousetrap.bind).toHaveBeenCalledWith('b', expect.any(Function));
+ });
+
+ it('triggers a click on the "copy to clipboard" button when the handler is executed', () => {
+ const testClickHandler = jest.fn();
+ vm.$refs.copyBranchNameButton.$el.addEventListener('click', testClickHandler);
+
+ // Get a reference to the function that was assigned to the "b" shortcut key.
+ const shortcutHandler = Mousetrap.bind.mock.calls[0][1];
+
+ expect(testClickHandler).not.toHaveBeenCalled();
+
+ // Simulate Mousetrap calling the function.
+ shortcutHandler();
+
+ expect(testClickHandler).toHaveBeenCalledTimes(1);
+ });
+
+ it('unbinds the keyboard shortcut when the component is destroyed', () => {
+ expect(Mousetrap.unbind).not.toHaveBeenCalled();
+
+ vm.$destroy();
+
+ expect(Mousetrap.unbind).toHaveBeenCalledWith('b');
+ });
+ });
});
describe('with an open merge request', () => {
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 309aec179d9..6486826c3ec 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -1,189 +1,182 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper';
-import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import { SUCCESS } from '~/vue_merge_request_widget/constants';
+import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue';
import mockData from '../mock_data';
describe('MRWidgetPipeline', () => {
- let vm;
- let Component;
-
- beforeEach(() => {
- Component = Vue.extend(pipelineComponent);
- });
+ let wrapper;
+
+ const defaultProps = {
+ pipeline: mockData.pipeline,
+ ciStatus: SUCCESS,
+ hasCi: true,
+ mrTroubleshootingDocsPath: 'help',
+ ciTroubleshootingDocsPath: 'ci-help',
+ };
+
+ const ciErrorMessage =
+ 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.';
+ const monitoringMessage = 'Checking pipeline status.';
+
+ const findCIErrorMessage = () => wrapper.find('[data-testid="ci-error-message"]');
+ const findPipelineID = () => wrapper.find('[data-testid="pipeline-id"]');
+ const findPipelineInfoContainer = () => wrapper.find('[data-testid="pipeline-info-container"]');
+ const findCommitLink = () => wrapper.find('[data-testid="commit-link"]');
+ const findPipelineGraph = () => wrapper.find('[data-testid="widget-mini-pipeline-graph"]');
+ const findAllPipelineStages = () => wrapper.findAll(PipelineStage);
+ const findPipelineCoverage = () => wrapper.find('[data-testid="pipeline-coverage"]');
+ const findPipelineCoverageDelta = () => wrapper.find('[data-testid="pipeline-coverage-delta"]');
+ const findMonitoringPipelineMessage = () =>
+ wrapper.find('[data-testid="monitoring-pipeline-message"]');
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
+ const createWrapper = (props, mountFn = shallowMount) => {
+ wrapper = mountFn(PipelineComponent, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ if (wrapper?.destroy) {
+ wrapper.destroy();
+ wrapper = null;
+ }
});
describe('computed', () => {
describe('hasPipeline', () => {
- it('should return true when there is a pipeline', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- ciStatus: 'success',
- hasCi: true,
- troubleshootingDocsPath: 'help',
- });
+ beforeEach(() => {
+ createWrapper();
+ });
- expect(vm.hasPipeline).toEqual(true);
+ it('should return true when there is a pipeline', () => {
+ expect(wrapper.vm.hasPipeline).toBe(true);
});
- it('should return false when there is no pipeline', () => {
- vm = mountComponent(Component, {
- pipeline: {},
- troubleshootingDocsPath: 'help',
- });
+ it('should return false when there is no pipeline', async () => {
+ wrapper.setProps({ pipeline: {} });
- expect(vm.hasPipeline).toEqual(false);
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.hasPipeline).toBe(false);
});
});
describe('hasCIError', () => {
- it('should return false when there is no CI error', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
+ beforeEach(() => {
+ createWrapper();
+ });
- expect(vm.hasCIError).toEqual(false);
+ it('should return false when there is no CI error', () => {
+ expect(wrapper.vm.hasCIError).toBe(false);
});
- it('should return true when there is a CI error', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- ciStatus: null,
- troubleshootingDocsPath: 'help',
- });
+ it('should return true when there is a pipeline, but no ci status', async () => {
+ wrapper.setProps({ ciStatus: null });
- expect(vm.hasCIError).toEqual(true);
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.hasCIError).toBe(true);
});
});
describe('coverageDeltaClass', () => {
- it('should return no class if there is no coverage change', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- pipelineCoverageDelta: '0',
- troubleshootingDocsPath: 'help',
- });
+ beforeEach(() => {
+ createWrapper({ pipelineCoverageDelta: '0' });
+ });
- expect(vm.coverageDeltaClass).toEqual('');
+ it('should return no class if there is no coverage change', async () => {
+ expect(wrapper.vm.coverageDeltaClass).toBe('');
});
- it('should return text-success if the coverage increased', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- pipelineCoverageDelta: '10',
- troubleshootingDocsPath: 'help',
- });
+ it('should return text-success if the coverage increased', async () => {
+ wrapper.setProps({ pipelineCoverageDelta: '10' });
- expect(vm.coverageDeltaClass).toEqual('text-success');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.coverageDeltaClass).toBe('text-success');
});
- it('should return text-danger if the coverage decreased', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- pipelineCoverageDelta: '-12',
- troubleshootingDocsPath: 'help',
- });
+ it('should return text-danger if the coverage decreased', async () => {
+ wrapper.setProps({ pipelineCoverageDelta: '-12' });
- expect(vm.coverageDeltaClass).toEqual('text-danger');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.coverageDeltaClass).toBe('text-danger');
});
});
});
describe('rendered output', () => {
- it('should render CI error', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- troubleshootingDocsPath: 'help',
- });
-
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.',
- );
+ beforeEach(() => {
+ createWrapper({ ciStatus: null }, mount);
});
- it('should render CI error when no pipeline is provided', () => {
- vm = mountComponent(Component, {
- pipeline: {},
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
-
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.',
- );
+ it('should render CI error if there is a pipeline, but no status', async () => {
+ expect(findCIErrorMessage().text()).toBe(ciErrorMessage);
});
- it('should render CI error when no CI is provided and pipeline must succeed is turned on', () => {
- vm = mountComponent(Component, {
+ it('should render a loading state when no pipeline is found', async () => {
+ wrapper.setProps({
pipeline: {},
hasCi: false,
pipelineMustSucceed: true,
- troubleshootingDocsPath: 'help',
});
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- 'No pipeline has been run for this commit.',
- );
+ await wrapper.vm.$nextTick();
+
+ expect(findMonitoringPipelineMessage().text()).toBe(monitoringMessage);
+ expect(findLoadingIcon().exists()).toBe(true);
});
describe('with a pipeline', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- ciStatus: 'success',
- pipelineCoverageDelta: mockData.pipelineCoverageDelta,
- troubleshootingDocsPath: 'help',
- });
+ createWrapper(
+ {
+ pipelineCoverageDelta: mockData.pipelineCoverageDelta,
+ },
+ mount,
+ );
});
it('should render pipeline ID', () => {
- expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
- );
+ expect(
+ findPipelineID()
+ .text()
+ .trim(),
+ ).toBe(`#${mockData.pipeline.id}`);
});
it('should render pipeline status and commit id', () => {
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- mockData.pipeline.details.status.label,
- );
+ expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label);
- expect(vm.$el.querySelector('.js-commit-link').textContent.trim()).toEqual(
- mockData.pipeline.commit.short_id,
- );
+ expect(
+ findCommitLink()
+ .text()
+ .trim(),
+ ).toBe(mockData.pipeline.commit.short_id);
- expect(vm.$el.querySelector('.js-commit-link').getAttribute('href')).toEqual(
- mockData.pipeline.commit.commit_path,
- );
+ expect(findCommitLink().attributes('href')).toBe(mockData.pipeline.commit.commit_path);
});
it('should render pipeline graph', () => {
- expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined();
- expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(
- mockData.pipeline.details.stages.length,
- );
+ expect(findPipelineGraph().exists()).toBe(true);
+ expect(findAllPipelineStages().length).toBe(mockData.pipeline.details.stages.length);
});
it('should render coverage information', () => {
- expect(vm.$el.querySelector('.media-body').textContent).toContain(
- `Coverage ${mockData.pipeline.coverage}`,
- );
+ expect(findPipelineCoverage().text()).toMatch(`Coverage ${mockData.pipeline.coverage}%`);
});
it('should render pipeline coverage delta information', () => {
- expect(vm.$el.querySelector('.js-pipeline-coverage-delta.text-danger')).toBeDefined();
- expect(vm.$el.querySelector('.js-pipeline-coverage-delta').textContent).toContain(
- `(${mockData.pipelineCoverageDelta}%)`,
- );
+ expect(findPipelineCoverageDelta().exists()).toBe(true);
+ expect(findPipelineCoverageDelta().text()).toBe(`(${mockData.pipelineCoverageDelta}%)`);
});
});
@@ -192,71 +185,61 @@ describe('MRWidgetPipeline', () => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.commit;
- vm = mountComponent(Component, {
- pipeline: mockCopy.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
+ createWrapper({}, mount);
});
it('should render pipeline ID', () => {
- expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
- );
+ expect(
+ findPipelineID()
+ .text()
+ .trim(),
+ ).toBe(`#${mockData.pipeline.id}`);
});
it('should render pipeline status', () => {
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- mockData.pipeline.details.status.label,
- );
-
- expect(vm.$el.querySelector('.js-commit-link')).toBeNull();
+ expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label);
});
it('should render pipeline graph', () => {
- expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined();
- expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(
- mockData.pipeline.details.stages.length,
- );
+ expect(findPipelineGraph().exists()).toBe(true);
+ expect(findAllPipelineStages().length).toBe(mockData.pipeline.details.stages.length);
});
it('should render coverage information', () => {
- expect(vm.$el.querySelector('.media-body').textContent).toContain(
- `Coverage ${mockData.pipeline.coverage}`,
- );
+ expect(findPipelineCoverage().text()).toMatch(`Coverage ${mockData.pipeline.coverage}%`);
});
});
describe('without coverage', () => {
- it('should not render a coverage', () => {
+ beforeEach(() => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.coverage;
- vm = mountComponent(Component, {
- pipeline: mockCopy.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
+ createWrapper(
+ {
+ pipeline: mockCopy.pipeline,
+ },
+ mount,
+ );
+ });
- expect(vm.$el.querySelector('.media-body').textContent).not.toContain('Coverage');
+ it('should not render a coverage component', () => {
+ expect(findPipelineCoverage().exists()).toBe(false);
});
});
describe('without a pipeline graph', () => {
- it('should not render a pipeline graph', () => {
+ beforeEach(() => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.details.stages;
- vm = mountComponent(Component, {
+ createWrapper({
pipeline: mockCopy.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
});
+ });
- expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null);
+ it('should not render a pipeline graph', () => {
+ expect(findPipelineGraph().exists()).toBe(false);
});
});
@@ -273,11 +256,8 @@ describe('MRWidgetPipeline', () => {
});
const factory = () => {
- vm = mountComponent(Component, {
+ createWrapper({
pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
sourceBranchLink: mockData.source_branch_link,
});
};
@@ -289,7 +269,7 @@ describe('MRWidgetPipeline', () => {
factory();
const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id} on ${mockData.source_branch_link}`;
- const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+ const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
});
@@ -302,7 +282,7 @@ describe('MRWidgetPipeline', () => {
factory();
const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
- const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+ const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
});
@@ -316,7 +296,7 @@ describe('MRWidgetPipeline', () => {
factory();
const expected = `Detached merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
- const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+ const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
index 8b0253dc01a..d6c996f7501 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
@@ -1,37 +1,44 @@
import { mount } from '@vue/test-utils';
-import { GlLink } from '@gitlab/ui';
+import { GlLink, GlSprintf } from '@gitlab/ui';
import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue';
-import stubChildren from 'helpers/stub_children';
-import PipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
+import { popoverProps, iconName } from './pipeline_tour_mock_data';
-describe('MRWidgetHeader', () => {
+describe('MRWidgetSuggestPipeline', () => {
let wrapper;
- const pipelinePath = '/foo/bar/add/pipeline/path';
- const pipelineSvgPath = '/foo/bar/pipeline/svg/path';
- const humanAccess = 'maintainer';
- const iconName = 'status_notfound';
+ let trackingSpy;
+
+ const mockTrackingOnWrapper = () => {
+ unmockTracking();
+ trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
+ };
beforeEach(() => {
+ document.body.dataset.page = 'projects:merge_requests:show';
+ trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
+
wrapper = mount(suggestPipelineComponent, {
- propsData: { pipelinePath, pipelineSvgPath, humanAccess },
+ propsData: popoverProps,
stubs: {
- ...stubChildren(PipelineTourState),
+ GlSprintf,
},
});
});
afterEach(() => {
wrapper.destroy();
+ unmockTracking();
});
describe('template', () => {
+ const findOkBtn = () => wrapper.find('[data-testid="ok"]');
+
it('renders add pipeline file link', () => {
const link = wrapper.find(GlLink);
expect(link.exists()).toBe(true);
- expect(link.attributes().href).toBe(pipelinePath);
+ expect(link.attributes().href).toBe(popoverProps.pipelinePath);
});
it('renders the expected text', () => {
@@ -51,25 +58,60 @@ describe('MRWidgetHeader', () => {
);
});
+ it('renders the show me how button', () => {
+ const button = findOkBtn();
+
+ expect(button.exists()).toBe(true);
+ expect(button.classes('btn-info')).toEqual(true);
+ expect(button.attributes('href')).toBe(popoverProps.pipelinePath);
+ });
+
+ it('renders the help link', () => {
+ const link = wrapper.find('[data-testid="help"]');
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(wrapper.vm.$options.helpURL);
+ });
+
+ it('renders the empty pipelines image', () => {
+ const image = wrapper.find('[data-testid="pipeline-image"]');
+
+ expect(image.exists()).toBe(true);
+ expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
+ });
+
describe('tracking', () => {
- let spy;
+ it('send event for basic view of the suggest pipeline widget', () => {
+ const expectedCategory = undefined;
+ const expectedAction = undefined;
- beforeEach(() => {
- spy = mockTracking('_category_', wrapper.element, jest.spyOn);
+ expect(trackingSpy).toHaveBeenCalledWith(expectedCategory, expectedAction, {
+ label: wrapper.vm.$options.trackLabel,
+ property: popoverProps.humanAccess,
+ });
});
- afterEach(() => {
- unmockTracking();
+ it('send an event when add pipeline link is clicked', () => {
+ mockTrackingOnWrapper();
+ const link = wrapper.find('[data-testid="add-pipeline-link"]');
+ triggerEvent(link.element);
+
+ expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
+ label: wrapper.vm.$options.trackLabel,
+ property: popoverProps.humanAccess,
+ value: '30',
+ });
});
it('send an event when ok button is clicked', () => {
- const link = wrapper.find(GlLink);
- triggerEvent(link.element);
+ mockTrackingOnWrapper();
+ const okBtn = findOkBtn();
+ triggerEvent(okBtn.element);
- expect(spy).toHaveBeenCalledWith('_category_', 'click_link', {
- label: 'no_pipeline_noticed',
- property: humanAccess,
- value: '30',
+ expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
+ label: wrapper.vm.$options.trackLabel,
+ property: popoverProps.humanAccess,
+ value: '10',
});
});
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js
deleted file mode 100644
index 62c5c8e8531..00000000000
--- a/spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import { GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import axios from '~/lib/utils/axios_utils';
-import MockAdapter from 'axios-mock-adapter';
-import MrWidgetTerraformPlan from '~/vue_merge_request_widget/components/mr_widget_terraform_plan.vue';
-import Poll from '~/lib/utils/poll';
-
-const plan = {
- create: 10,
- update: 20,
- delete: 30,
- job_path: '/path/to/ci/logs',
-};
-
-describe('MrWidgetTerraformPlan', () => {
- let mock;
- let wrapper;
-
- const propsData = { endpoint: '/path/to/terraform/report.json' };
-
- const mockPollingApi = (response, body, header) => {
- mock.onGet(propsData.endpoint).reply(response, body, header);
- };
-
- const mountWrapper = () => {
- wrapper = shallowMount(MrWidgetTerraformPlan, { propsData });
- return axios.waitForAll();
- };
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- wrapper.destroy();
- mock.restore();
- });
-
- describe('loading poll', () => {
- beforeEach(() => {
- mockPollingApi(200, { '123': plan }, {});
-
- return mountWrapper().then(() => {
- wrapper.setData({ loading: true });
- return wrapper.vm.$nextTick();
- });
- });
-
- it('Diplays loading icon when loading is true', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
-
- expect(wrapper.find(GlSprintf).exists()).toBe(false);
-
- expect(wrapper.text()).not.toContain(
- 'A terraform report was generated in your pipelines. Changes are unknown',
- );
- });
- });
-
- describe('successful poll', () => {
- let pollRequest;
- let pollStop;
-
- beforeEach(() => {
- pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
- pollStop = jest.spyOn(Poll.prototype, 'stop');
-
- mockPollingApi(200, { '123': plan }, {});
-
- return mountWrapper();
- });
-
- afterEach(() => {
- pollRequest.mockRestore();
- pollStop.mockRestore();
- });
-
- it('content change text', () => {
- expect(wrapper.find(GlSprintf).exists()).toBe(true);
- });
-
- it('renders button when url is found', () => {
- expect(wrapper.find(GlLink).exists()).toBe(true);
- });
-
- it('does not make additional requests after poll is successful', () => {
- expect(pollRequest).toHaveBeenCalledTimes(1);
- expect(pollStop).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('polling fails', () => {
- beforeEach(() => {
- mockPollingApi(500, null, {});
- return mountWrapper();
- });
-
- it('does not display changes text when api fails', () => {
- expect(wrapper.text()).toContain(
- 'A terraform report was generated in your pipelines. Changes are unknown',
- );
-
- expect(wrapper.find('.js-terraform-report-link').exists()).toBe(false);
- expect(wrapper.find(GlLink).exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js b/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js
new file mode 100644
index 00000000000..c749c434079
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js
@@ -0,0 +1,7 @@
+export const popoverProps = {
+ pipelinePath: '/foo/bar/add/pipeline/path',
+ pipelineSvgPath: 'assets/illustrations/something.svg',
+ humanAccess: 'maintainer',
+};
+
+export const iconName = 'status_notfound';
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
index e2caa6e8092..ae0f605c419 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
@@ -8,6 +8,7 @@ import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
describe('MRWidgetAutoMergeEnabled', () => {
let vm;
+ let oldWindowGl;
const targetBranchPath = '/foo/bar';
const targetBranch = 'foo';
const sha = '1EA2EZ34';
@@ -16,6 +17,13 @@ describe('MRWidgetAutoMergeEnabled', () => {
const Component = Vue.extend(autoMergeEnabledComponent);
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ oldWindowGl = window.gl;
+ window.gl = {
+ mrWidgetData: {
+ defaultAvatarUrl: 'no_avatar.png',
+ },
+ };
+
vm = mountComponent(Component, {
mr: {
shouldRemoveSourceBranch: false,
@@ -35,6 +43,7 @@ describe('MRWidgetAutoMergeEnabled', () => {
afterEach(() => {
vm.$destroy();
+ window.gl = oldWindowGl;
});
describe('computed', () => {
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 56d55c9afac..afe6bd0e767 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -25,7 +25,7 @@ describe('MRWidgetChecking', () => {
it('renders information about merging', () => {
expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual(
- 'Checking ability to merge automatically…',
+ 'Checking if merge request can be merged…',
);
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js
deleted file mode 100644
index e8f95e099cc..00000000000
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js
+++ /dev/null
@@ -1,143 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlPopover } from '@gitlab/ui';
-import Cookies from 'js-cookie';
-import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
-import pipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
-import { popoverProps, cookieKey } from './pipeline_tour_mock_data';
-
-describe('MRWidgetPipelineTour', () => {
- let wrapper;
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('template', () => {
- describe(`when ${cookieKey} cookie is set`, () => {
- beforeEach(() => {
- Cookies.set(cookieKey, true);
- wrapper = shallowMount(pipelineTourState, {
- propsData: popoverProps,
- });
- });
-
- it('does not render the popover', () => {
- const popover = wrapper.find(GlPopover);
-
- expect(popover.exists()).toBe(false);
- });
-
- describe('tracking', () => {
- let trackingSpy;
-
- beforeEach(() => {
- trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- });
- it('does not call tracking', () => {
- expect(trackingSpy).not.toHaveBeenCalled();
- });
- });
- });
-
- describe(`when ${cookieKey} cookie is not set`, () => {
- const findOkBtn = () => wrapper.find({ ref: 'ok' });
- const findDismissBtn = () => wrapper.find({ ref: 'no-thanks' });
-
- beforeEach(() => {
- Cookies.remove(cookieKey);
- wrapper = shallowMount(pipelineTourState, {
- propsData: popoverProps,
- });
- });
-
- it('renders the popover', () => {
- const popover = wrapper.find(GlPopover);
-
- expect(popover.exists()).toBe(true);
- });
-
- it('renders the show me how button', () => {
- const button = findOkBtn();
-
- expect(button.exists()).toBe(true);
- expect(button.attributes().category).toBe('primary');
- });
-
- it('renders the dismiss button', () => {
- const button = findDismissBtn();
-
- expect(button.exists()).toBe(true);
- expect(button.attributes().category).toBe('secondary');
- });
-
- it('renders the empty pipelines image', () => {
- const image = wrapper.find('img');
-
- expect(image.exists()).toBe(true);
- expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
- });
-
- describe('tracking', () => {
- let trackingSpy;
-
- beforeEach(() => {
- trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- });
-
- it('send event for basic view of popover', () => {
- document.body.dataset.page = 'projects:merge_requests:show';
-
- wrapper.vm.trackOnShow();
-
- expect(trackingSpy).toHaveBeenCalledWith(undefined, undefined, {
- label: popoverProps.trackLabel,
- property: popoverProps.humanAccess,
- });
- });
-
- it('send an event when ok button is clicked', () => {
- const okBtn = findOkBtn();
- triggerEvent(okBtn.element);
-
- expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
- label: popoverProps.trackLabel,
- property: popoverProps.humanAccess,
- value: '10',
- });
- });
-
- it('send an event when dismiss button is clicked', () => {
- const dismissBtn = findDismissBtn();
- triggerEvent(dismissBtn.element);
-
- expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
- label: popoverProps.trackLabel,
- property: popoverProps.humanAccess,
- value: '20',
- });
- });
- });
-
- describe('dismissPopover', () => {
- it('updates popoverDismissed', () => {
- const button = findDismissBtn();
- const popover = wrapper.find(GlPopover);
- button.vm.$emit('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(Cookies.get(cookieKey)).toBe('true');
- expect(popover.exists()).toBe(false);
- });
- });
- });
- });
- });
-});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 1f0d6a7378c..5eb24315ca6 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -34,6 +34,9 @@ const createTestMr = customConfig => {
ciStatus: null,
sha: '12345678',
squash: false,
+ squashIsEnabledByDefault: false,
+ squashIsReadonly: false,
+ squashIsSelected: false,
commitMessage,
squashCommitMessage,
commitMessageWithDescription,
@@ -694,6 +697,37 @@ describe('ReadyToMerge', () => {
expect(findCheckboxElement().exists()).toBeFalsy();
});
+
+ describe('squash options', () => {
+ it.each`
+ squashState | state | prop | expectation
+ ${'squashIsReadonly'} | ${'enabled'} | ${'isDisabled'} | ${false}
+ ${'squashIsSelected'} | ${'selected'} | ${'value'} | ${false}
+ ${'squashIsSelected'} | ${'unselected'} | ${'value'} | ${false}
+ `(
+ 'is $state when squashIsReadonly returns $expectation ',
+ ({ squashState, prop, expectation }) => {
+ createLocalComponent({
+ mr: { commitsCount: 2, enableSquashBeforeMerge: true, [squashState]: expectation },
+ });
+
+ expect(findCheckboxElement().props(prop)).toBe(expectation);
+ },
+ );
+
+ it('is not rendered for "Do not allow" option', () => {
+ createLocalComponent({
+ mr: {
+ commitsCount: 2,
+ enableSquashBeforeMerge: true,
+ squashIsReadonly: true,
+ squashIsSelected: false,
+ },
+ });
+
+ expect(findCheckboxElement().exists()).toBe(false);
+ });
+ });
});
describe('commits count collapsible header', () => {
@@ -709,7 +743,7 @@ describe('ReadyToMerge', () => {
mr: {
ffOnlyEnabled: true,
enableSquashBeforeMerge: true,
- squash: true,
+ squashIsSelected: true,
commitsCount: 2,
},
});
@@ -803,7 +837,7 @@ describe('ReadyToMerge', () => {
createLocalComponent({
mr: {
ffOnlyEnabled: true,
- squash: true,
+ squashIsSelected: true,
enableSquashBeforeMerge: true,
commitsCount: 2,
},
@@ -824,7 +858,7 @@ describe('ReadyToMerge', () => {
createLocalComponent({
mr: {
commitsCount: 2,
- squash: true,
+ squashIsSelected: true,
enableSquashBeforeMerge: true,
},
});
@@ -854,7 +888,7 @@ describe('ReadyToMerge', () => {
createLocalComponent({
mr: {
commitsCount: 2,
- squash: true,
+ squashIsSelected: true,
enableSquashBeforeMerge: true,
},
});
@@ -872,7 +906,7 @@ describe('ReadyToMerge', () => {
it('should be rendered if squash is enabled and there is more than 1 commit', () => {
createLocalComponent({
- mr: { enableSquashBeforeMerge: true, squash: true, commitsCount: 2 },
+ mr: { enableSquashBeforeMerge: true, squashIsSelected: true, commitsCount: 2 },
});
expect(findCommitDropdownElement().exists()).toBeTruthy();
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index b70d580ed04..1542b0939aa 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -19,6 +19,8 @@ describe('Squash before merge component', () => {
wrapper.destroy();
});
+ const findLabel = () => wrapper.find('[data-testid="squashLabel"]');
+
describe('checkbox', () => {
const findCheckbox = () => wrapper.find('.js-squash-checkbox');
@@ -63,6 +65,46 @@ describe('Squash before merge component', () => {
});
});
+ describe('label', () => {
+ describe.each`
+ isDisabled | expectation
+ ${true} | ${'grays out text if it is true'}
+ ${false} | ${'does not gray out text if it is false'}
+ `('isDisabled prop', ({ isDisabled, expectation }) => {
+ beforeEach(() => {
+ createComponent({
+ value: false,
+ isDisabled,
+ });
+ });
+
+ it(expectation, () => {
+ expect(findLabel().classes('gl-text-gray-600')).toBe(isDisabled);
+ });
+ });
+ });
+
+ describe('tooltip', () => {
+ const tooltipTitle = () => findLabel().element.dataset.title;
+
+ it('does not render when isDisabled is false', () => {
+ createComponent({
+ value: true,
+ isDisabled: false,
+ });
+ expect(tooltipTitle()).toBeUndefined();
+ });
+
+ it('display message when when isDisabled is true', () => {
+ createComponent({
+ value: true,
+ isDisabled: true,
+ });
+
+ expect(tooltipTitle()).toBe('Required in this project.');
+ });
+ });
+
describe('about link', () => {
it('is not rendered if no help path is passed', () => {
createComponent({
diff --git a/spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js b/spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js
deleted file mode 100644
index 39bc89e459c..00000000000
--- a/spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export const popoverProps = {
- pipelinePath: '/foo/bar/add/pipeline/path',
- pipelineSvgPath: 'assets/illustrations/something.svg',
- humanAccess: 'maintainer',
- popoverTarget: 'suggest-popover',
- popoverContainer: 'suggest-pipeline',
- trackLabel: 'some_tracking_label',
-};
-
-export const cookieKey = 'suggest_pipeline_dismissed';
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mock_data.js b/spec/frontend/vue_mr_widget/components/terraform/mock_data.js
new file mode 100644
index 00000000000..ae280146c22
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/terraform/mock_data.js
@@ -0,0 +1,31 @@
+export const invalidPlanWithName = {
+ job_name: 'Invalid Plan',
+ job_path: '/path/to/ci/logs/1',
+ tf_report_error: 'api_error',
+};
+
+export const invalidPlanWithoutName = {
+ tf_report_error: 'invalid_json_format',
+};
+
+export const validPlanWithName = {
+ create: 10,
+ update: 20,
+ delete: 30,
+ job_name: 'Valid Plan',
+ job_path: '/path/to/ci/logs/1',
+};
+
+export const validPlanWithoutName = {
+ create: 10,
+ update: 20,
+ delete: 30,
+ job_path: '/path/to/ci/logs/1',
+};
+
+export const plans = {
+ invalid_plan_one: invalidPlanWithName,
+ invalid_plan_two: invalidPlanWithName,
+ valid_plan_one: validPlanWithName,
+ valid_plan_two: validPlanWithoutName,
+};
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
new file mode 100644
index 00000000000..be43f10c03e
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
@@ -0,0 +1,172 @@
+import { GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
+import { invalidPlanWithName, plans, validPlanWithName } from './mock_data';
+import { shallowMount } from '@vue/test-utils';
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import MrWidgetExpanableSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue';
+import MrWidgetTerraformContainer from '~/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue';
+import Poll from '~/lib/utils/poll';
+import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue';
+
+describe('MrWidgetTerraformConainer', () => {
+ let mock;
+ let wrapper;
+
+ const propsData = { endpoint: '/path/to/terraform/report.json' };
+
+ const findHeader = () => wrapper.find('[data-testid="terraform-header-text"]');
+ const findPlans = () => wrapper.findAll(TerraformPlan).wrappers.map(x => x.props('plan'));
+
+ const mockPollingApi = (response, body, header) => {
+ mock.onGet(propsData.endpoint).reply(response, body, header);
+ };
+
+ const mountWrapper = () => {
+ wrapper = shallowMount(MrWidgetTerraformContainer, {
+ propsData,
+ stubs: { MrWidgetExpanableSection, GlSprintf },
+ });
+ return axios.waitForAll();
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mock.restore();
+ });
+
+ describe('when data is loading', () => {
+ beforeEach(() => {
+ mockPollingApi(200, plans, {});
+
+ return mountWrapper().then(() => {
+ wrapper.setData({ loading: true });
+ return wrapper.vm.$nextTick();
+ });
+ });
+
+ it('diplays loading skeleton', () => {
+ expect(wrapper.contains(GlSkeletonLoading)).toBe(true);
+ expect(wrapper.contains(MrWidgetExpanableSection)).toBe(false);
+ });
+ });
+
+ describe('when data has finished loading', () => {
+ beforeEach(() => {
+ mockPollingApi(200, plans, {});
+ return mountWrapper();
+ });
+
+ it('displays terraform content', () => {
+ expect(wrapper.contains(GlSkeletonLoading)).toBe(false);
+ expect(wrapper.contains(MrWidgetExpanableSection)).toBe(true);
+ expect(findPlans()).toEqual(Object.values(plans));
+ });
+
+ describe('when data includes one invalid plan', () => {
+ beforeEach(() => {
+ const invalidPlanGroup = { bad_plan: invalidPlanWithName };
+ mockPollingApi(200, invalidPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for one invalid plan', () => {
+ expect(findHeader().text()).toBe('1 Terraform report failed to generate');
+ });
+ });
+
+ describe('when data includes multiple invalid plans', () => {
+ beforeEach(() => {
+ const invalidPlanGroup = {
+ bad_plan_one: invalidPlanWithName,
+ bad_plan_two: invalidPlanWithName,
+ };
+
+ mockPollingApi(200, invalidPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for multiple invalid plans', () => {
+ expect(findHeader().text()).toBe('2 Terraform reports failed to generate');
+ });
+ });
+
+ describe('when data includes one valid plan', () => {
+ beforeEach(() => {
+ const validPlanGroup = { valid_plan: validPlanWithName };
+ mockPollingApi(200, validPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for one valid plans', () => {
+ expect(findHeader().text()).toBe('1 Terraform report was generated in your pipelines');
+ });
+ });
+
+ describe('when data includes multiple valid plans', () => {
+ beforeEach(() => {
+ const validPlanGroup = {
+ valid_plan_one: validPlanWithName,
+ valid_plan_two: validPlanWithName,
+ };
+ mockPollingApi(200, validPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for multiple valid plans', () => {
+ expect(findHeader().text()).toBe('2 Terraform reports were generated in your pipelines');
+ });
+ });
+ });
+
+ describe('polling', () => {
+ let pollRequest;
+ let pollStop;
+
+ beforeEach(() => {
+ pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
+ pollStop = jest.spyOn(Poll.prototype, 'stop');
+ });
+
+ afterEach(() => {
+ pollRequest.mockRestore();
+ pollStop.mockRestore();
+ });
+
+ describe('successful poll', () => {
+ beforeEach(() => {
+ mockPollingApi(200, plans, {});
+
+ return mountWrapper();
+ });
+
+ it('does not make additional requests after poll is successful', () => {
+ expect(pollRequest).toHaveBeenCalledTimes(1);
+ expect(pollStop).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('polling fails', () => {
+ beforeEach(() => {
+ mockPollingApi(500, null, {});
+ return mountWrapper();
+ });
+
+ it('stops loading', () => {
+ expect(wrapper.contains(GlSkeletonLoading)).toBe(false);
+ });
+
+ it('generates one broken plan', () => {
+ expect(findPlans()).toEqual([{ tf_report_error: 'api_error' }]);
+ });
+
+ it('does not make additional requests after poll is unsuccessful', () => {
+ expect(pollRequest).toHaveBeenCalledTimes(1);
+ expect(pollStop).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js
new file mode 100644
index 00000000000..cc68ba0d9df
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js
@@ -0,0 +1,95 @@
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue';
+import {
+ invalidPlanWithName,
+ invalidPlanWithoutName,
+ validPlanWithName,
+ validPlanWithoutName,
+} from './mock_data';
+
+describe('TerraformPlan', () => {
+ let wrapper;
+
+ const findIcon = () => wrapper.find('[data-testid="change-type-icon"]');
+ const findLogButton = () => wrapper.find('[data-testid="terraform-report-link"]');
+
+ const mountWrapper = propsData => {
+ wrapper = shallowMount(TerraformPlan, { stubs: { GlLink, GlSprintf }, propsData });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('valid plan with job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: validPlanWithName });
+ });
+
+ it('displays a document icon', () => {
+ expect(findIcon().attributes('name')).toBe('doc-changes');
+ });
+
+ it('diplays the header text with a name', () => {
+ expect(wrapper.text()).toContain(
+ `The Terraform report ${validPlanWithName.job_name} was generated in your pipelines.`,
+ );
+ });
+
+ it('diplays the reported changes', () => {
+ expect(wrapper.text()).toContain(
+ `Reported Resource Changes: ${validPlanWithName.create} to add, ${validPlanWithName.update} to change, ${validPlanWithName.delete} to delete`,
+ );
+ });
+
+ it('renders button when url is found', () => {
+ expect(findLogButton().exists()).toBe(true);
+ expect(findLogButton().text()).toEqual('View full log');
+ });
+ });
+
+ describe('valid plan without job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: validPlanWithoutName });
+ });
+
+ it('diplays the header text without a name', () => {
+ expect(wrapper.text()).toContain('A Terraform report was generated in your pipelines.');
+ });
+ });
+
+ describe('invalid plan with job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: invalidPlanWithName });
+ });
+
+ it('displays a warning icon', () => {
+ expect(findIcon().attributes('name')).toBe('warning');
+ });
+
+ it('diplays the header text with a name', () => {
+ expect(wrapper.text()).toContain(
+ `The Terraform report ${invalidPlanWithName.job_name} failed to generate.`,
+ );
+ });
+
+ it('diplays generic error since report values are missing', () => {
+ expect(wrapper.text()).toContain('Generating the report caused an error.');
+ });
+ });
+
+ describe('invalid plan with out job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: invalidPlanWithoutName });
+ });
+
+ it('diplays the header text without a name', () => {
+ expect(wrapper.text()).toContain('A Terraform report failed to generate.');
+ });
+
+ it('does not render button because url is missing', () => {
+ expect(findLogButton().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js
index 8ed153658fd..e00456a78b5 100644
--- a/spec/frontend/vue_mr_widget/mock_data.js
+++ b/spec/frontend/vue_mr_widget/mock_data.js
@@ -211,6 +211,15 @@ export default {
can_revert_on_current_merge_request: true,
can_cherry_pick_on_current_merge_request: true,
},
+ codeclimate: {
+ head_path: 'head.json',
+ base_path: 'base.json',
+ },
+ blob_path: {
+ base_path: 'blob_path',
+ head_path: 'blob_path',
+ },
+ codequality_help_path: 'code_quality.html',
target_branch_path: '/root/acets-app/branches/master',
source_branch_path: '/root/acets-app/branches/daaaa',
conflict_resolution_ui_path: '/root/acets-app/-/merge_requests/22/conflicts',
@@ -239,7 +248,8 @@ export default {
commit_change_content_path: '/root/acets-app/-/merge_requests/22/commit_change_content',
merge_commit_path:
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
- troubleshooting_docs_path: 'help',
+ mr_troubleshooting_docs_path: 'help',
+ ci_troubleshooting_docs_path: 'help2',
merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
merge_train_when_pipeline_succeeds_docs_path:
'/help/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/#startadd-to-merge-train-when-pipeline-succeeds',
@@ -312,7 +322,8 @@ export const mockStore = {
{ id: 0, name: 'prod', status: SUCCESS },
{ id: 1, name: 'prod-docs', status: SUCCESS },
],
- troubleshootingDocsPath: 'troubleshooting-docs-path',
+ mrTroubleshootingDocsPath: 'mr-troubleshooting-docs-path',
+ ciTroubleshootingDocsPath: 'ci-troubleshooting-docs-path',
ciStatus: 'ci-status',
hasCI: true,
exposedArtifactsPath: 'exposed_artifacts.json',
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index e022f68fdec..93659fa54fb 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -609,6 +609,12 @@ describe('mrWidgetOptions', () => {
});
});
+ describe('code quality widget', () => {
+ it('renders the component', () => {
+ expect(vm.$el.querySelector('.js-codequality-widget')).toExist();
+ });
+ });
+
describe('pipeline for target branch after merge', () => {
describe('with information for target branch pipeline', () => {
beforeEach(done => {
diff --git a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
index e54cd345a37..1cb2c6c669b 100644
--- a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
+++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
@@ -49,14 +49,18 @@ describe('getStateKey', () => {
expect(bound()).toEqual('unresolvedDiscussions');
+ data.work_in_progress = true;
+
+ expect(bound()).toEqual('workInProgress');
+
context.onlyAllowMergeIfPipelineSucceeds = true;
context.isPipelineFailed = true;
expect(bound()).toEqual('pipelineFailed');
- data.work_in_progress = true;
+ context.shouldBeRebased = true;
- expect(bound()).toEqual('workInProgress');
+ expect(bound()).toEqual('rebase');
data.has_conflicts = true;