diff options
Diffstat (limited to 'spec/frontend/vue_mr_widget/components')
59 files changed, 0 insertions, 6227 deletions
diff --git a/spec/frontend/vue_mr_widget/components/added_commit_message_spec.js b/spec/frontend/vue_mr_widget/components/added_commit_message_spec.js deleted file mode 100644 index 150680caa7e..00000000000 --- a/spec/frontend/vue_mr_widget/components/added_commit_message_spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import AddedCommentMessage from '~/vue_merge_request_widget/components/added_commit_message.vue'; - -let wrapper; - -function factory(propsData) { - wrapper = shallowMount(AddedCommentMessage, { - propsData: { - isFastForwardEnabled: false, - targetBranch: 'main', - ...propsData, - }, - provide: { - glFeatures: { - restructuredMrWidget: true.valueOf, - }, - }, - }); -} - -describe('Widget added commit message', () => { - afterEach(() => { - wrapper.destroy(); - }); - - it('displays changes where not merged when state is closed', () => { - factory({ state: 'closed' }); - - expect(wrapper.element.outerHTML).toContain('The changes were not merged'); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js deleted file mode 100644 index 05cd1bb5b3d..00000000000 --- a/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js +++ /dev/null @@ -1,424 +0,0 @@ -import { nextTick } from 'vue'; -import { GlButton, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import createFlash from '~/flash'; -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 { - 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 RULE_NAME = 'first_rule'; -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, - invalid_approvers_rules: [], -}); -const testApprovalRulesResponse = () => ({ rules: [{ id: 2 }] }); - -describe('MRWidget approvals', () => { - let wrapper; - let service; - let mr; - - const createComponent = (props = {}) => { - wrapper = shallowMount(Approvals, { - propsData: { - mr, - service, - ...props, - }, - stubs: { - GlSprintf, - }, - }); - }; - - 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); - const findInvalidRules = () => wrapper.find('[data-testid="invalid-rules"]'); - - 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', () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ fetchingApprovals: true }); - - return nextTick().then(() => { - expect(wrapper.text()).toContain(FETCH_LOADING); - }); - }); - - it('fetches approvals', () => { - expect(service.fetchApprovals).toHaveBeenCalled(); - }); - }); - - describe('when fetch approvals error', () => { - beforeEach(() => { - jest.spyOn(service, 'fetchApprovals').mockReturnValue(Promise.reject()); - createComponent(); - return nextTick(); - }); - - it('still shows loading message', () => { - expect(wrapper.text()).toContain(FETCH_LOADING); - }); - - it('flashes error', () => { - expect(createFlash).toHaveBeenCalledWith({ message: FETCH_ERROR }); - }); - }); - - describe('action button', () => { - describe('when mr is closed', () => { - beforeEach(() => { - mr.isOpen = false; - mr.approvals.user_has_approved = false; - mr.approvals.user_can_approve = true; - - createComponent(); - return nextTick(); - }); - - it('action is not rendered', () => { - expect(findActionData()).toBe(null); - }); - }); - - describe('when user cannot approve', () => { - beforeEach(() => { - mr.approvals.user_has_approved = false; - mr.approvals.user_can_approve = false; - - createComponent(); - return nextTick(); - }); - - 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(() => { - createComponent(); - return nextTick(); - }); - - it('approve action is rendered', () => { - expect(findActionData()).toEqual({ - variant: 'confirm', - text: 'Approve', - category: 'primary', - }); - }); - }); - - describe('and MR is approved', () => { - beforeEach(() => { - mr.approvals.approved = true; - }); - - describe('with no approvers', () => { - beforeEach(() => { - mr.approvals.approved_by = []; - createComponent(); - return nextTick(); - }); - - it('approve action (with inverted style) is rendered', () => { - expect(findActionData()).toEqual({ - variant: 'confirm', - text: 'Approve', - category: 'secondary', - }); - }); - }); - - describe('with approvers', () => { - beforeEach(() => { - mr.approvals.approved_by = [{ user: { id: 7 } }]; - createComponent(); - return nextTick(); - }); - - it('approve additionally action is rendered', () => { - expect(findActionData()).toEqual({ - variant: 'confirm', - text: 'Approve additionally', - category: 'secondary', - }); - }); - }); - }); - - describe('when approve action is clicked', () => { - beforeEach(() => { - createComponent(); - return nextTick(); - }); - - 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 nextTick().then(() => { - expect(action.props('loading')).toBe(true); - }); - }); - - describe('and after loading', () => { - beforeEach(() => { - findAction().vm.$emit('click'); - return nextTick(); - }); - - 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(() => { - jest.spyOn(service, 'approveMergeRequest').mockReturnValue(Promise.reject()); - findAction().vm.$emit('click'); - return nextTick(); - }); - - it('flashes error message', () => { - expect(createFlash).toHaveBeenCalledWith({ message: APPROVE_ERROR }); - }); - }); - }); - }); - - describe('when user has approved', () => { - beforeEach(() => { - mr.approvals.user_has_approved = true; - mr.approvals.user_can_approve = false; - - createComponent(); - return nextTick(); - }); - - it('revoke action is rendered', () => { - expect(findActionData()).toEqual({ - category: 'primary', - variant: 'default', - text: 'Revoke approval', - }); - }); - - describe('when revoke action is clicked', () => { - describe('and successful', () => { - beforeEach(() => { - findAction().vm.$emit('click'); - return nextTick(); - }); - - 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(() => { - jest.spyOn(service, 'unapproveMergeRequest').mockReturnValue(Promise.reject()); - findAction().vm.$emit('click'); - return nextTick(); - }); - - it('flashes error message', () => { - expect(createFlash).toHaveBeenCalledWith({ message: 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(() => { - mr.approvals.user_can_approve = true; - - createComponent(); - return nextTick(); - }); - - it('is shown', () => { - expect(findSummary().exists()).toBe(false); - expect(findOptionalSummary().props()).toEqual({ - canApprove: true, - helpPath: TEST_HELP_PATH, - }); - }); - }); - - describe('and cannot approve', () => { - beforeEach(() => { - mr.approvals.user_can_approve = false; - - createComponent(); - return nextTick(); - }); - - it('is shown', () => { - expect(findSummary().exists()).toBe(false); - expect(findOptionalSummary().props()).toEqual({ - canApprove: false, - helpPath: TEST_HELP_PATH, - }); - }); - }); - }); - }); - - describe('approvals summary', () => { - beforeEach(() => { - createComponent(); - return nextTick(); - }); - - 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(), - }); - }); - }); - - describe('invalid rules', () => { - beforeEach(() => { - mr.approvals.merge_request_approvers_available = true; - createComponent(); - }); - - it('does not render related components', () => { - expect(findInvalidRules().exists()).toBe(false); - }); - - describe('when invalid rules are present', () => { - beforeEach(() => { - mr.approvals.invalid_approvers_rules = [{ name: RULE_NAME }]; - createComponent(); - }); - - it('renders related components', () => { - const invalidRules = findInvalidRules(); - - expect(invalidRules.exists()).toBe(true); - - const invalidRulesText = invalidRules.text(); - - expect(invalidRulesText).toContain(RULE_NAME); - expect(invalidRulesText).toContain( - 'GitLab has approved this rule automatically to unblock the merge request.', - ); - expect(invalidRulesText).toContain('Learn more.'); - }); - }); - }); -}); 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 deleted file mode 100644 index 65cafc647e0..00000000000 --- a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js +++ /dev/null @@ -1,45 +0,0 @@ -import { GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -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 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('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 deleted file mode 100644 index c2606346292..00000000000 --- a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js +++ /dev/null @@ -1,142 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { toNounSeriesText } from '~/lib/utils/grammar'; -import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue'; -import { - APPROVED_BY_OTHERS, - APPROVED_BY_YOU, - APPROVED_BY_YOU_AND_OTHERS, -} from '~/vue_merge_request_widget/components/approvals/messages'; -import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue'; - -const exampleUserId = 1; -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', () => { - const originalUserId = gon.current_user_id; - 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; - gon.current_user_id = originalUserId; - }); - - describe('when approved', () => { - beforeEach(() => { - createComponent({ - approved: true, - }); - }); - - it('shows approved message', () => { - expect(wrapper.text()).toContain(APPROVED_BY_OTHERS); - }); - - it('renders avatar list for approvers', () => { - const avatars = findAvatars(); - - expect(avatars.exists()).toBe(true); - expect(avatars.props()).toEqual( - expect.objectContaining({ - items: testApprovers(), - }), - ); - }); - - describe('by the current user', () => { - beforeEach(() => { - gon.current_user_id = exampleUserId; - createComponent({ - approvers: [{ id: exampleUserId }], - approved: true, - }); - }); - - it('shows "Approved by you" message', () => { - expect(wrapper.text()).toContain(APPROVED_BY_YOU); - }); - }); - - describe('by the current user and others', () => { - beforeEach(() => { - gon.current_user_id = exampleUserId; - createComponent({ - approvers: [{ id: exampleUserId }, { id: exampleUserId + 1 }], - approved: true, - }); - }); - - it('shows "Approved by you and others" message', () => { - expect(wrapper.text()).toContain(APPROVED_BY_YOU_AND_OTHERS); - }); - }); - - describe('by other users than the current user', () => { - beforeEach(() => { - gon.current_user_id = exampleUserId; - createComponent({ - approvers: [{ id: exampleUserId + 1 }], - approved: true, - }); - }); - - it('shows "Approved by others" message', () => { - expect(wrapper.text()).toContain(APPROVED_BY_OTHERS); - }); - }); - }); - - describe('when not approved', () => { - beforeEach(() => { - createComponent(); - }); - - it('render message', () => { - const names = toNounSeriesText(testRulesLeft()); - - expect(wrapper.text()).toContain(`Requires ${TEST_APPROVALS_LEFT} approvals from ${names}.`); - }); - }); - - describe('when no rulesLeft', () => { - beforeEach(() => { - createComponent({ - rulesLeft: [], - }); - }); - - it('renders message', () => { - expect(wrapper.text()).toContain( - `Requires ${TEST_APPROVALS_LEFT} approvals from eligible users`, - ); - }); - }); - - 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/approvals/humanized_text_spec.js b/spec/frontend/vue_mr_widget/components/approvals/humanized_text_spec.js deleted file mode 100644 index d6776c00b29..00000000000 --- a/spec/frontend/vue_mr_widget/components/approvals/humanized_text_spec.js +++ /dev/null @@ -1,18 +0,0 @@ -import { humanizeInvalidApproversRules } from '~/vue_merge_request_widget/components/approvals/humanized_text'; - -const testRules = [{ name: 'Lorem' }, { name: 'Ipsum' }, { name: 'Dolar' }]; - -describe('humanizeInvalidApproversRules', () => { - it('returns text in regards to a single rule', () => { - const [singleRule] = testRules; - expect(humanizeInvalidApproversRules([singleRule])).toBe('"Lorem"'); - }); - - it('returns empty text when there is no rule', () => { - expect(humanizeInvalidApproversRules([])).toBe(''); - }); - - it('returns text in regards to multiple rules', () => { - expect(humanizeInvalidApproversRules(testRules)).toBe('"Lorem", "Ipsum" and "Dolar"'); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/artifacts_list_app_spec.js b/spec/frontend/vue_mr_widget/components/artifacts_list_app_spec.js deleted file mode 100644 index e2386bc7f2b..00000000000 --- a/spec/frontend/vue_mr_widget/components/artifacts_list_app_spec.js +++ /dev/null @@ -1,126 +0,0 @@ -import { GlLoadingIcon } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import Vue, { nextTick } from 'vue'; -import Vuex from 'vuex'; -import { TEST_HOST as FAKE_ENDPOINT } from 'helpers/test_constants'; -import axios from '~/lib/utils/axios_utils'; -import ArtifactsListApp from '~/vue_merge_request_widget/components/artifacts_list_app.vue'; -import { getStoreConfig } from '~/vue_merge_request_widget/stores/artifacts_list'; -import { artifacts } from '../mock_data'; - -Vue.use(Vuex); - -describe('Merge Requests Artifacts list app', () => { - let wrapper; - let store; - let mock; - - const actionSpies = { - fetchArtifacts: jest.fn(), - }; - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - wrapper.destroy(); - mock.restore(); - }); - - const createComponent = () => { - const storeConfig = getStoreConfig(); - store = new Vuex.Store({ - ...storeConfig, - actions: { - ...storeConfig.actions, - ...actionSpies, - }, - }); - - wrapper = mount(ArtifactsListApp, { - propsData: { - endpoint: FAKE_ENDPOINT, - }, - store, - }); - }; - - const findButtons = () => wrapper.findAll('button'); - const findTitle = () => wrapper.find('[data-testid="mr-collapsible-title"]'); - const findErrorMessage = () => wrapper.find('.js-error-state'); - const findTableRows = () => wrapper.findAll('tbody tr'); - - describe('while loading', () => { - beforeEach(() => { - createComponent(); - store.dispatch('requestArtifacts'); - return nextTick(); - }); - - it('renders a loading icon', () => { - const loadingIcon = wrapper.find(GlLoadingIcon); - expect(loadingIcon.exists()).toBe(true); - }); - - it('renders loading text', () => { - expect(findTitle().text()).toBe('Loading artifacts'); - }); - - it('renders disabled buttons', () => { - const buttons = findButtons(); - expect(buttons.at(0).attributes('disabled')).toBe('disabled'); - expect(buttons.at(1).attributes('disabled')).toBe('disabled'); - }); - }); - - describe('with results', () => { - beforeEach(() => { - createComponent(); - mock.onGet(FAKE_ENDPOINT).reply(200, artifacts, {}); - store.dispatch('receiveArtifactsSuccess', { - data: artifacts, - status: 200, - }); - return nextTick(); - }); - - it('renders a title with the number of artifacts', () => { - expect(findTitle().text()).toBe('View 2 exposed artifacts'); - }); - - it('renders both buttons enabled', () => { - const buttons = findButtons(); - expect(buttons.at(0).attributes('disabled')).toBe(undefined); - expect(buttons.at(1).attributes('disabled')).toBe(undefined); - }); - - describe('on click', () => { - it('renders the list of artifacts', async () => { - findTitle().trigger('click'); - await nextTick(); - - expect(findTableRows().length).toEqual(2); - }); - }); - }); - - describe('with error', () => { - beforeEach(() => { - createComponent(); - mock.onGet(FAKE_ENDPOINT).reply(500, {}, {}); - store.dispatch('receiveArtifactsError'); - return nextTick(); - }); - - it('renders the error state', () => { - expect(findErrorMessage().text()).toBe('An error occurred while fetching the artifacts'); - }); - - it('does not render buttons', () => { - const buttons = findButtons(); - expect(buttons.exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/artifacts_list_spec.js b/spec/frontend/vue_mr_widget/components/artifacts_list_spec.js deleted file mode 100644 index 712abfe228a..00000000000 --- a/spec/frontend/vue_mr_widget/components/artifacts_list_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import { GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import ArtifactsList from '~/vue_merge_request_widget/components/artifacts_list.vue'; -import { artifacts } from '../mock_data'; - -describe('Artifacts List', () => { - let wrapper; - - const data = { - artifacts, - }; - - const mountComponent = (props) => { - wrapper = shallowMount(ArtifactsList, { - propsData: { - ...props, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - beforeEach(() => { - mountComponent(data); - }); - - it('renders list of artifacts', () => { - expect(wrapper.findAll('tbody tr').length).toEqual(data.artifacts.length); - }); - - it('renders link for the artifact', () => { - expect(wrapper.find(GlLink).attributes('href')).toEqual(data.artifacts[0].url); - }); - - it('renders artifact name', () => { - expect(wrapper.find(GlLink).text()).toEqual(data.artifacts[0].text); - }); - - it('renders job url', () => { - expect(wrapper.findAll(GlLink).at(1).attributes('href')).toEqual(data.artifacts[0].job_path); - }); - - it('renders job name', () => { - expect(wrapper.findAll(GlLink).at(1).text()).toEqual(data.artifacts[0].job_name); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/extensions/actions_spec.js b/spec/frontend/vue_mr_widget/components/extensions/actions_spec.js deleted file mode 100644 index a13db2f4d72..00000000000 --- a/spec/frontend/vue_mr_widget/components/extensions/actions_spec.js +++ /dev/null @@ -1,47 +0,0 @@ -import { GlButton, GlDropdownItem } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import Actions from '~/vue_merge_request_widget/components/extensions/actions.vue'; - -let wrapper; - -function factory(propsData = {}) { - wrapper = shallowMount(Actions, { - propsData: { ...propsData, widget: 'test' }, - }); -} - -describe('MR widget extension actions', () => { - afterEach(() => { - wrapper.destroy(); - }); - - describe('tertiaryButtons', () => { - it('renders buttons', () => { - factory({ - tertiaryButtons: [{ text: 'hello world', href: 'https://gitlab.com', target: '_blank' }], - }); - - expect(wrapper.findAllComponents(GlButton)).toHaveLength(1); - }); - - it('calls action click handler', async () => { - const onClick = jest.fn(); - - factory({ - tertiaryButtons: [{ text: 'hello world', onClick }], - }); - - await wrapper.findComponent(GlButton).vm.$emit('click'); - - expect(onClick).toHaveBeenCalled(); - }); - - it('renders tertiary actions in dropdown', () => { - factory({ - tertiaryButtons: [{ text: 'hello world', href: 'https://gitlab.com', target: '_blank' }], - }); - - expect(wrapper.findAllComponents(GlDropdownItem)).toHaveLength(1); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/extensions/child_content_spec.js b/spec/frontend/vue_mr_widget/components/extensions/child_content_spec.js deleted file mode 100644 index 198a4c2823a..00000000000 --- a/spec/frontend/vue_mr_widget/components/extensions/child_content_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import ChildContent from '~/vue_merge_request_widget/components/extensions/child_content.vue'; - -let wrapper; -const mockData = () => ({ - header: 'Test header', - text: 'Test content', - icon: { - name: 'error', - }, -}); - -function factory(propsData) { - wrapper = shallowMount(ChildContent, { - propsData: { - ...propsData, - widgetLabel: 'Test', - }, - }); -} - -describe('MR widget extension child content', () => { - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - it('renders child components', () => { - factory({ - data: { - ...mockData(), - children: [mockData()], - }, - level: 2, - }); - - expect(wrapper.find('[data-testid="child-content"]').exists()).toBe(true); - expect(wrapper.find('[data-testid="child-content"]').props('level')).toBe(3); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/extensions/index_spec.js b/spec/frontend/vue_mr_widget/components/extensions/index_spec.js deleted file mode 100644 index dc25596655a..00000000000 --- a/spec/frontend/vue_mr_widget/components/extensions/index_spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import { - registerExtension, - registeredExtensions, -} from '~/vue_merge_request_widget/components/extensions'; -import ExtensionBase from '~/vue_merge_request_widget/components/extensions/base.vue'; - -describe('MR widget extension registering', () => { - it('registers a extension', () => { - registerExtension({ - name: 'Test', - props: ['helloWorld'], - computed: { - test() {}, - }, - methods: { - test() {}, - }, - }); - - expect(registeredExtensions.extensions[0]).toEqual( - expect.objectContaining({ - extends: ExtensionBase, - name: 'Test', - computed: { - helloWorld: expect.any(Function), - test: expect.any(Function), - }, - methods: { - test: expect.any(Function), - }, - }), - ); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/extensions/status_icon_spec.js b/spec/frontend/vue_mr_widget/components/extensions/status_icon_spec.js deleted file mode 100644 index f3aa5bb774f..00000000000 --- a/spec/frontend/vue_mr_widget/components/extensions/status_icon_spec.js +++ /dev/null @@ -1,36 +0,0 @@ -import { GlIcon, GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue'; - -let wrapper; - -function factory(propsData = {}) { - wrapper = shallowMount(StatusIcon, { - propsData, - }); -} - -describe('MR widget extensions status icon', () => { - afterEach(() => { - wrapper.destroy(); - }); - - it('renders loading icon', () => { - factory({ name: 'test', isLoading: true, iconName: 'failed' }); - - expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); - }); - - it('renders status icon', () => { - factory({ name: 'test', isLoading: false, iconName: 'failed' }); - - expect(wrapper.findComponent(GlIcon).exists()).toBe(true); - expect(wrapper.findComponent(GlIcon).props('name')).toBe('status-failed'); - }); - - it('sets aria-label for status icon', () => { - factory({ name: 'test', isLoading: false, iconName: 'failed' }); - - expect(wrapper.findComponent(GlIcon).props('ariaLabel')).toBe('Failed test'); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/extensions/utils_spec.js b/spec/frontend/vue_mr_widget/components/extensions/utils_spec.js deleted file mode 100644 index 5799799ad5e..00000000000 --- a/spec/frontend/vue_mr_widget/components/extensions/utils_spec.js +++ /dev/null @@ -1,20 +0,0 @@ -import { generateText } from '~/vue_merge_request_widget/components/extensions/utils'; - -describe('generateText', () => { - it.each` - text | expectedText - ${'%{strong_start}Hello world%{strong_end}'} | ${'<span class="gl-font-weight-bold">Hello world</span>'} - ${'%{success_start}Hello world%{success_end}'} | ${'<span class="gl-font-weight-bold gl-text-green-500">Hello world</span>'} - ${'%{danger_start}Hello world%{danger_end}'} | ${'<span class="gl-font-weight-bold gl-text-red-500">Hello world</span>'} - ${'%{critical_start}Hello world%{critical_end}'} | ${'<span class="gl-font-weight-bold gl-text-red-800">Hello world</span>'} - ${'%{same_start}Hello world%{same_end}'} | ${'<span class="gl-font-weight-bold gl-text-gray-700">Hello world</span>'} - ${'%{small_start}Hello world%{small_end}'} | ${'<span class="gl-font-sm gl-text-gray-700">Hello world</span>'} - ${'%{strong_start}%{danger_start}Hello world%{danger_end}%{strong_end}'} | ${'<span class="gl-font-weight-bold"><span class="gl-font-weight-bold gl-text-red-500">Hello world</span></span>'} - ${'%{no_exist_start}Hello world%{no_exist_end}'} | ${'Hello world'} - ${{ text: 'Hello world', href: 'http://www.example.com' }} | ${'<a class="gl-text-decoration-underline" href="http://www.example.com">Hello world</a>'} - ${{ prependText: 'Hello', text: 'world', href: 'http://www.example.com' }} | ${'Hello <a class="gl-text-decoration-underline" href="http://www.example.com">world</a>'} - ${['array']} | ${null} - `('generates $expectedText from $text', ({ text, expectedText }) => { - expect(generateText(text)).toBe(expectedText); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js b/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js deleted file mode 100644 index 01fbcb2154f..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js +++ /dev/null @@ -1,97 +0,0 @@ -import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import MrCollapsibleSection from '~/vue_merge_request_widget/components/mr_collapsible_extension.vue'; - -describe('Merge Request Collapsible Extension', () => { - let wrapper; - const data = { - title: 'View artifacts', - }; - - const mountComponent = (props) => { - wrapper = mount(MrCollapsibleSection, { - propsData: { - ...props, - }, - slots: { - default: '<div class="js-slot">Foo</div>', - header: '<span data-testid="collapsed-header">hello there</span>', - }, - }); - }; - - const findTitle = () => wrapper.find('[data-testid="mr-collapsible-title"]'); - const findErrorMessage = () => wrapper.find('.js-error-state'); - const findIcon = () => wrapper.find(GlIcon); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('while collapsed', () => { - beforeEach(() => { - mountComponent(data); - }); - - it('renders provided title', () => { - expect(findTitle().text()).toBe(data.title); - }); - - it('renders the header slot', () => { - expect(wrapper.find('[data-testid="collapsed-header"]').text()).toBe('hello there'); - }); - - it('renders chevron-lg-right icon', () => { - expect(findIcon().props('name')).toBe('chevron-lg-right'); - }); - - describe('onClick', () => { - beforeEach(async () => { - wrapper.find('button').trigger('click'); - await nextTick(); - }); - - it('rendes the provided slot', () => { - expect(wrapper.find('.js-slot').isVisible()).toBe(true); - }); - - it('renders `Collapse` as the title', () => { - expect(findTitle().text()).toBe('Collapse'); - }); - - it('renders chevron-lg-down icon', () => { - expect(findIcon().props('name')).toBe('chevron-lg-down'); - }); - }); - }); - - describe('while loading', () => { - beforeEach(() => { - mountComponent({ ...data, isLoading: true }); - }); - - it('renders the buttons disabled', () => { - expect(wrapper.findAll('button').at(0).attributes('disabled')).toEqual('disabled'); - expect(wrapper.findAll('button').at(1).attributes('disabled')).toEqual('disabled'); - }); - - it('renders loading spinner', () => { - expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true); - }); - }); - - describe('with error', () => { - beforeEach(() => { - mountComponent({ ...data, hasError: true }); - }); - - it('does not render the buttons', () => { - expect(wrapper.findAll('button').exists()).toBe(false); - }); - - it('renders title message provided', () => { - expect(findErrorMessage().text()).toBe(data.title); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_alert_message_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_alert_message_spec.js deleted file mode 100644 index 5d923d0383f..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_alert_message_spec.js +++ /dev/null @@ -1,45 +0,0 @@ -import { GlLink, GlAlert } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import MrWidgetAlertMessage from '~/vue_merge_request_widget/components/mr_widget_alert_message.vue'; - -let wrapper; - -function createComponent(propsData = {}) { - wrapper = shallowMount(MrWidgetAlertMessage, { - propsData, - }); -} - -describe('MrWidgetAlertMessage', () => { - afterEach(() => { - wrapper.destroy(); - }); - - it('should render a GlAert', () => { - createComponent({ type: 'danger' }); - - expect(wrapper.findComponent(GlAlert).exists()).toBe(true); - expect(wrapper.findComponent(GlAlert).props('variant')).toBe('danger'); - }); - - describe('when helpPath is not provided', () => { - it('should not render a help link', () => { - createComponent({ type: 'info' }); - - const link = wrapper.findComponent(GlLink); - - expect(link.exists()).toBe(false); - }); - }); - - describe('when helpPath is provided', () => { - it('should render a help link', () => { - createComponent({ type: 'info', helpPath: 'https://gitlab.com' }); - - const link = wrapper.findComponent(GlLink); - - expect(link.exists()).toBe(true); - expect(link.attributes('href')).toBe('https://gitlab.com'); - }); - }); -}); 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 deleted file mode 100644 index 8a42e2e2ce7..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js +++ /dev/null @@ -1,62 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue'; - -window.gl = window.gl || {}; - -describe('MrWidgetAuthor', () => { - 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(() => { - oldWindowGl = window.gl; - window.gl = { - mrWidgetData: { - defaultAvatarUrl: 'no_avatar.png', - }, - }; - wrapper = shallowMount(MrWidgetAuthor, { - propsData: { - author: mockAuthor, - }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - window.gl = oldWindowGl; - }); - - it('renders link with the author web url', () => { - expect(wrapper.attributes('href')).toBe('http://localhost:3000/root'); - }); - - it('renders image with avatar url', () => { - 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 nextTick(); - - expect(wrapper.find('img').attributes('src')).toBe('no_avatar.png'); - }); - - it('renders author name', () => { - expect(wrapper.find('span').text()).toBe('Administrator'); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_author_time_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_author_time_spec.js deleted file mode 100644 index 8fd93809e01..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_author_time_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue'; -import MrWidgetAuthorTime from '~/vue_merge_request_widget/components/mr_widget_author_time.vue'; - -describe('MrWidgetAuthorTime', () => { - let wrapper; - - const defaultProps = { - actionText: 'Merged by', - author: { - name: 'Administrator', - username: 'root', - webUrl: 'http://localhost:3000/root', - avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }, - dateTitle: '2017-03-23T23:02:00.807Z', - dateReadable: '12 hours ago', - }; - - beforeEach(() => { - wrapper = shallowMount(MrWidgetAuthorTime, { - propsData: defaultProps, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders provided action text', () => { - expect(wrapper.text()).toContain('Merged by'); - }); - - it('renders author', () => { - expect(wrapper.find(MrWidgetAuthor).props('author')).toStrictEqual(defaultProps.author); - }); - - it('renders provided time', () => { - expect(wrapper.find('time').attributes('title')).toBe('2017-03-23T23:02:00.807Z'); - - expect(wrapper.find('time').text().trim()).toBe('12 hours ago'); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js deleted file mode 100644 index 4e3e918f7fb..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import MrWidgetContainer from '~/vue_merge_request_widget/components/mr_widget_container.vue'; - -const BODY_HTML = '<div class="test-body">Hello World</div>'; -const FOOTER_HTML = '<div class="test-footer">Goodbye!</div>'; - -describe('MrWidgetContainer', () => { - let wrapper; - - const factory = (options = {}) => { - wrapper = shallowMount(MrWidgetContainer, { - ...options, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('has layout', () => { - factory(); - - expect(wrapper.classes()).toContain('mr-widget-heading'); - expect(wrapper.find('.mr-widget-content').exists()).toBe(true); - }); - - it('accepts default slot', () => { - factory({ - slots: { - default: BODY_HTML, - }, - }); - - expect(wrapper.find('.mr-widget-content .test-body').exists()).toBe(true); - }); - - it('accepts footer slot', () => { - factory({ - slots: { - default: BODY_HTML, - footer: FOOTER_HTML, - }, - }); - - expect(wrapper.find('.mr-widget-content .test-body').exists()).toBe(true); - expect(wrapper.find('.test-footer').exists()).toBe(true); - }); -}); 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 deleted file mode 100644 index 631aef412a6..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js +++ /dev/null @@ -1,66 +0,0 @@ -import { GlButton, GlCollapse, GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -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.find(GlIcon).exists()).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(async () => { - findButton().vm.$emit('click'); - await 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_icon_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js deleted file mode 100644 index ebd10f31fa7..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import { GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue'; - -const TEST_ICON = 'commit'; - -describe('MrWidgetIcon', () => { - let wrapper; - - beforeEach(() => { - wrapper = shallowMount(MrWidgetIcon, { - propsData: { - name: TEST_ICON, - }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders icon and container', () => { - expect(wrapper.element.className).toContain('circle-icon-container'); - expect(wrapper.find(GlIcon).props('name')).toEqual(TEST_ICON); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js deleted file mode 100644 index f0106914674..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js +++ /dev/null @@ -1,227 +0,0 @@ -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; -import Vue, { nextTick } from 'vue'; -import waitForPromises from 'helpers/wait_for_promises'; -import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue'; -import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; - -const url = '/root/acets-review-apps/environments/15/deployments/1/metrics'; -const monitoringUrl = '/root/acets-review-apps/environments/15/metrics'; - -const metricsMockData = { - success: true, - metrics: { - memory_before: [ - { - metric: {}, - value: [1495785220.607, '9572875.906976745'], - }, - ], - memory_after: [ - { - metric: {}, - value: [1495787020.607, '4485853.130206379'], - }, - ], - memory_values: [ - { - metric: {}, - values: [[1493716685, '4.30859375']], - }, - ], - }, - last_update: '2017-05-02T12:34:49.628Z', - deployment_time: 1493718485, -}; - -const createComponent = () => { - const Component = Vue.extend(MemoryUsage); - - return new Component({ - el: document.createElement('div'), - propsData: { - metricsUrl: url, - metricsMonitoringUrl: monitoringUrl, - memoryMetrics: [], - deploymentTime: 0, - hasMetrics: false, - loadFailed: false, - loadingMetrics: true, - backOffRequestCounter: 0, - }, - }); -}; - -const messages = { - loadingMetrics: 'Loading deployment statistics', - hasMetrics: 'Memory usage is unchanged at 0MB', - loadFailed: 'Failed to load deployment statistics', - metricsUnavailable: 'Deployment statistics are not available currently', -}; - -describe('MemoryUsage', () => { - let vm; - let el; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onGet(`${url}.json`).reply(200); - - vm = createComponent(); - el = vm.$el; - }); - - afterEach(() => { - mock.restore(); - }); - - describe('data', () => { - it('should have default data', () => { - const data = MemoryUsage.data(); - - expect(Array.isArray(data.memoryMetrics)).toBeTruthy(); - expect(data.memoryMetrics.length).toBe(0); - - expect(typeof data.deploymentTime).toBe('number'); - expect(data.deploymentTime).toBe(0); - - expect(typeof data.hasMetrics).toBe('boolean'); - expect(data.hasMetrics).toBeFalsy(); - - expect(typeof data.loadFailed).toBe('boolean'); - expect(data.loadFailed).toBeFalsy(); - - expect(typeof data.loadingMetrics).toBe('boolean'); - expect(data.loadingMetrics).toBeTruthy(); - - expect(typeof data.backOffRequestCounter).toBe('number'); - expect(data.backOffRequestCounter).toBe(0); - }); - }); - - describe('computed', () => { - describe('memoryChangeMessage', () => { - it('should contain "increased" if memoryFrom value is less than memoryTo value', () => { - vm.memoryFrom = 4.28; - vm.memoryTo = 9.13; - - expect(vm.memoryChangeMessage.indexOf('increased')).not.toEqual('-1'); - }); - - it('should contain "decreased" if memoryFrom value is less than memoryTo value', () => { - vm.memoryFrom = 9.13; - vm.memoryTo = 4.28; - - expect(vm.memoryChangeMessage.indexOf('decreased')).not.toEqual('-1'); - }); - - it('should contain "unchanged" if memoryFrom value equal to memoryTo value', () => { - vm.memoryFrom = 1; - vm.memoryTo = 1; - - expect(vm.memoryChangeMessage.indexOf('unchanged')).not.toEqual('-1'); - }); - }); - }); - - describe('methods', () => { - const { metrics, deployment_time } = metricsMockData; - - describe('getMegabytes', () => { - it('should return Megabytes from provided Bytes value', () => { - const memoryInBytes = '9572875.906976745'; - - expect(vm.getMegabytes(memoryInBytes)).toEqual('9.13'); - }); - }); - - describe('computeGraphData', () => { - it('should populate sparkline graph', () => { - // ignore BoostrapVue warnings - jest.spyOn(console, 'warn').mockImplementation(); - - vm.computeGraphData(metrics, deployment_time); - const { hasMetrics, memoryMetrics, deploymentTime, memoryFrom, memoryTo } = vm; - - expect(hasMetrics).toBeTruthy(); - expect(memoryMetrics.length).toBeGreaterThan(0); - expect(deploymentTime).toEqual(deployment_time); - expect(memoryFrom).toEqual('9.13'); - expect(memoryTo).toEqual('4.28'); - }); - }); - - describe('loadMetrics', () => { - it('should load metrics data using MRWidgetService', async () => { - jest.spyOn(MRWidgetService, 'fetchMetrics').mockResolvedValue({ - data: metricsMockData, - }); - jest.spyOn(vm, 'computeGraphData').mockImplementation(() => {}); - - vm.loadMetrics(); - - await waitForPromises(); - - expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url); - expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time); - }); - }); - }); - - describe('template', () => { - it('should render template elements correctly', () => { - expect(el.classList.contains('mr-memory-usage')).toBeTruthy(); - expect(el.querySelector('.js-usage-info')).toBeDefined(); - }); - - it('should show loading metrics message while metrics are being loaded', async () => { - vm.loadingMetrics = true; - vm.hasMetrics = false; - vm.loadFailed = false; - - await nextTick(); - - expect(el.querySelector('.js-usage-info.usage-info-loading')).toBeDefined(); - expect(el.querySelector('.js-usage-info .usage-info-load-spinner')).toBeDefined(); - expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadingMetrics); - }); - - it('should show deployment memory usage when metrics are loaded', async () => { - // ignore BoostrapVue warnings - jest.spyOn(console, 'warn').mockImplementation(); - - vm.loadingMetrics = false; - vm.hasMetrics = true; - vm.loadFailed = false; - vm.memoryMetrics = metricsMockData.metrics.memory_values[0].values; - - await nextTick(); - - expect(el.querySelector('.memory-graph-container')).toBeDefined(); - expect(el.querySelector('.js-usage-info').innerText).toContain(messages.hasMetrics); - }); - - it('should show failure message when metrics loading failed', async () => { - vm.loadingMetrics = false; - vm.hasMetrics = false; - vm.loadFailed = true; - - await nextTick(); - - expect(el.querySelector('.js-usage-info.usage-info-failed')).toBeDefined(); - expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadFailed); - }); - - it('should show metrics unavailable message when metrics loading failed', async () => { - vm.loadingMetrics = false; - vm.hasMetrics = false; - vm.loadFailed = false; - - await nextTick(); - - expect(el.querySelector('.js-usage-info.usage-info-unavailable')).toBeDefined(); - expect(el.querySelector('.js-usage-info').innerText).toContain(messages.metricsUnavailable); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js deleted file mode 100644 index efe2bf75c3f..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js +++ /dev/null @@ -1,131 +0,0 @@ -import { mount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import axios from '~/lib/utils/axios_utils'; -import ArtifactsApp from '~/vue_merge_request_widget/components/artifacts_list_app.vue'; -import DeploymentList from '~/vue_merge_request_widget/components/deployment/deployment_list.vue'; -import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; -import MrWidgetPipelineContainer from '~/vue_merge_request_widget/components/mr_widget_pipeline_container.vue'; -import { mockStore } from '../mock_data'; - -describe('MrWidgetPipelineContainer', () => { - let wrapper; - let mock; - - const factory = (props = {}) => { - wrapper = extendedWrapper( - mount(MrWidgetPipelineContainer, { - propsData: { - mr: { ...mockStore }, - ...props, - }, - }), - ); - }; - - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onGet().reply(200, {}); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - const findDeploymentList = () => wrapper.findComponent(DeploymentList); - const findCIErrorMessage = () => wrapper.findByTestId('ci-error-message'); - - describe('when pre merge', () => { - beforeEach(() => { - factory(); - }); - - it('renders pipeline', () => { - expect(wrapper.find(MrWidgetPipeline).exists()).toBe(true); - expect(wrapper.find(MrWidgetPipeline).props()).toMatchObject({ - pipeline: mockStore.pipeline, - pipelineCoverageDelta: mockStore.pipelineCoverageDelta, - ciStatus: mockStore.ciStatus, - hasCi: mockStore.hasCI, - sourceBranch: mockStore.sourceBranch, - sourceBranchLink: mockStore.sourceBranchLink, - }); - }); - - it('renders deployments', () => { - const expectedProps = mockStore.deployments.map((dep) => - expect.objectContaining({ - deployment: dep, - showMetrics: false, - }), - ); - - const deployments = wrapper.findAll('.mr-widget-extension .js-pre-deployment'); - - expect(findDeploymentList().exists()).toBe(true); - expect(findDeploymentList().props('deployments')).toBe(mockStore.deployments); - - expect(deployments.wrappers.map((x) => x.props())).toEqual(expectedProps); - }); - }); - - describe('when post merge', () => { - beforeEach(() => { - factory({ - isPostMerge: true, - mr: { - ...mockStore, - pipeline: {}, - ciStatus: undefined, - }, - }); - }); - - it('renders pipeline', () => { - expect(wrapper.find(MrWidgetPipeline).exists()).toBe(true); - expect(findCIErrorMessage().exists()).toBe(false); - expect(wrapper.find(MrWidgetPipeline).props()).toMatchObject({ - pipeline: mockStore.mergePipeline, - pipelineCoverageDelta: mockStore.pipelineCoverageDelta, - ciStatus: mockStore.mergePipeline.details.status.text, - hasCi: mockStore.hasCI, - sourceBranch: mockStore.targetBranch, - sourceBranchLink: mockStore.targetBranch, - }); - }); - - it('sanitizes the targetBranch', () => { - factory({ - isPostMerge: true, - mr: { - ...mockStore, - targetBranch: 'Foo<script>alert("XSS")</script>', - }, - }); - expect(wrapper.find(MrWidgetPipeline).props().sourceBranchLink).toBe('Foo'); - }); - - it('renders deployments', () => { - const expectedProps = mockStore.postMergeDeployments.map((dep) => - expect.objectContaining({ - deployment: dep, - showMetrics: true, - }), - ); - - const deployments = wrapper.findAll('.mr-widget-extension .js-post-deployment'); - - expect(findDeploymentList().exists()).toBe(true); - expect(findDeploymentList().props('deployments')).toBe(mockStore.postMergeDeployments); - expect(deployments.wrappers.map((x) => x.props())).toEqual(expectedProps); - }); - }); - - describe('with artifacts path', () => { - it('renders the artifacts app', () => { - factory(); - - expect(wrapper.find(ArtifactsApp).isVisible()).toBe(true); - }); - }); -}); 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 deleted file mode 100644 index 6347e3c3be3..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ /dev/null @@ -1,282 +0,0 @@ -import { GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount, mount } from '@vue/test-utils'; -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; -import { trimText } from 'helpers/text_helper'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; -import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue'; -import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; -import { SUCCESS } from '~/vue_merge_request_widget/constants'; -import mockData from '../mock_data'; - -describe('MRWidgetPipeline', () => { - 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.findByTestId('ci-error-message'); - const findPipelineID = () => wrapper.findByTestId('pipeline-id'); - const findPipelineInfoContainer = () => wrapper.findByTestId('pipeline-info-container'); - const findCommitLink = () => wrapper.findByTestId('commit-link'); - const findPipelineFinishedAt = () => wrapper.findByTestId('finished-at'); - const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); - const findAllPipelineStages = () => wrapper.findAllComponents(PipelineStage); - const findPipelineCoverage = () => wrapper.findByTestId('pipeline-coverage'); - const findPipelineCoverageDelta = () => wrapper.findByTestId('pipeline-coverage-delta'); - const findPipelineCoverageTooltipText = () => - wrapper.findByTestId('pipeline-coverage-tooltip').text(); - const findPipelineCoverageDeltaTooltipText = () => - wrapper.findByTestId('pipeline-coverage-delta-tooltip').text(); - const findMonitoringPipelineMessage = () => wrapper.findByTestId('monitoring-pipeline-message'); - const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - - const mockArtifactsRequest = () => new MockAdapter(axios).onGet().reply(200, []); - - const createWrapper = (props = {}, mountFn = shallowMount) => { - wrapper = extendedWrapper( - mountFn(PipelineComponent, { - propsData: { - ...defaultProps, - ...props, - }, - }), - ); - }; - - afterEach(() => { - if (wrapper?.destroy) { - wrapper.destroy(); - wrapper = null; - } - }); - - it('should render CI error if there is a pipeline, but no status', () => { - createWrapper({ ciStatus: null }, mount); - expect(findCIErrorMessage().text()).toBe(ciErrorMessage); - }); - - it('should render a loading state when no pipeline is found', () => { - createWrapper({ pipeline: {} }, mount); - - expect(findMonitoringPipelineMessage().text()).toBe(monitoringMessage); - expect(findLoadingIcon().exists()).toBe(true); - }); - - describe('with a pipeline', () => { - beforeEach(() => { - mockArtifactsRequest(); - - createWrapper( - { - pipelineCoverageDelta: mockData.pipelineCoverageDelta, - buildsWithCoverage: mockData.buildsWithCoverage, - }, - mount, - ); - }); - - it('should render pipeline ID', () => { - expect(findPipelineID().text().trim()).toBe(`#${mockData.pipeline.id}`); - }); - - it('should render pipeline status and commit id', () => { - expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label); - - expect(findCommitLink().text().trim()).toBe(mockData.pipeline.commit.short_id); - - expect(findCommitLink().attributes('href')).toBe(mockData.pipeline.commit.commit_path); - }); - - it('should render pipeline finished timestamp', () => { - expect(findPipelineFinishedAt().attributes()).toMatchObject({ - title: 'Apr 7, 2017 2:00pm UTC', - datetime: mockData.pipeline.details.finished_at, - }); - }); - - it('should render pipeline graph', () => { - expect(findPipelineMiniGraph().exists()).toBe(true); - expect(findAllPipelineStages()).toHaveLength(mockData.pipeline.details.stages.length); - }); - - describe('should render pipeline coverage information', () => { - it('should render coverage percentage', () => { - expect(findPipelineCoverage().text()).toMatch( - `Test coverage ${mockData.pipeline.coverage}%`, - ); - }); - - it('should render coverage delta', () => { - expect(findPipelineCoverageDelta().exists()).toBe(true); - expect(findPipelineCoverageDelta().text()).toBe(`(${mockData.pipelineCoverageDelta}%)`); - }); - - it('should render tooltip for jobs contributing to code coverage', () => { - const tooltipText = findPipelineCoverageTooltipText(); - const expectedDescription = `Test coverage value for this pipeline was calculated by averaging the resulting coverage values of ${mockData.buildsWithCoverage.length} jobs.`; - - expect(tooltipText).toContain(expectedDescription); - }); - - it.each(mockData.buildsWithCoverage)( - 'should have name and coverage for build %s listed in tooltip', - (build) => { - const tooltipText = findPipelineCoverageTooltipText(); - - expect(tooltipText).toContain(`${build.name} (${build.coverage}%)`); - }, - ); - - describe.each` - style | coverageState | coverageChangeText | styleClass | pipelineCoverageDelta - ${'no special'} | ${'the same'} | ${'not change'} | ${''} | ${'0'} - ${'success'} | ${'increased'} | ${'increase'} | ${'text-success'} | ${'10'} - ${'danger'} | ${'decreased'} | ${'decrease'} | ${'text-danger'} | ${'-10'} - `( - 'if test coverage is $coverageState', - ({ style, styleClass, coverageChangeText, pipelineCoverageDelta }) => { - it(`coverage delta should have ${style}`, () => { - createWrapper({ pipelineCoverageDelta }); - expect(findPipelineCoverageDelta().classes()).toEqual(styleClass ? [styleClass] : []); - }); - - it(`coverage delta tooltip should say that the coverage will ${coverageChangeText}`, () => { - createWrapper({ pipelineCoverageDelta }); - expect(findPipelineCoverageDeltaTooltipText()).toContain(coverageChangeText); - }); - }, - ); - }); - }); - - describe('without commit path', () => { - beforeEach(() => { - const mockCopy = JSON.parse(JSON.stringify(mockData)); - delete mockCopy.pipeline.commit; - - createWrapper({}, mount); - }); - - it('should render pipeline ID', () => { - expect(findPipelineID().text().trim()).toBe(`#${mockData.pipeline.id}`); - }); - - it('should render pipeline status', () => { - expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label); - }); - - it('should render pipeline graph with correct styles', () => { - const stagesCount = mockData.pipeline.details.stages.length; - - expect(findPipelineMiniGraph().exists()).toBe(true); - expect(findPipelineMiniGraph().findAll('.mr-widget-pipeline-stages')).toHaveLength( - stagesCount, - ); - - expect(findAllPipelineStages()).toHaveLength(stagesCount); - }); - - it('should render coverage information', () => { - expect(findPipelineCoverage().text()).toMatch(`Test coverage ${mockData.pipeline.coverage}%`); - }); - }); - - describe('without coverage', () => { - beforeEach(() => { - const mockCopy = JSON.parse(JSON.stringify(mockData)); - delete mockCopy.pipeline.coverage; - - createWrapper({ pipeline: mockCopy.pipeline }); - }); - - it('should not render a coverage component', () => { - expect(findPipelineCoverage().exists()).toBe(false); - }); - }); - - describe('without a pipeline graph', () => { - beforeEach(() => { - const mockCopy = JSON.parse(JSON.stringify(mockData)); - delete mockCopy.pipeline.details.stages; - - createWrapper({ - pipeline: mockCopy.pipeline, - }); - }); - - it('should not render a pipeline graph', () => { - expect(findPipelineMiniGraph().exists()).toBe(false); - }); - }); - - describe('for each type of pipeline', () => { - let pipeline; - - beforeEach(() => { - ({ pipeline } = JSON.parse(JSON.stringify(mockData))); - - pipeline.details.name = 'Pipeline'; - pipeline.merge_request_event_type = undefined; - pipeline.ref.tag = false; - pipeline.ref.branch = false; - }); - - const factory = () => { - createWrapper({ - pipeline, - sourceBranchLink: mockData.source_branch_link, - }); - }; - - describe('for a branch pipeline', () => { - it('renders a pipeline widget that reads "Pipeline <ID> <status> for <SHA> on <branch>"', () => { - pipeline.ref.branch = true; - - factory(); - - const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id} on ${mockData.source_branch_link}`; - const actual = trimText(findPipelineInfoContainer().text()); - - expect(actual).toBe(expected); - }); - }); - - describe('for a tag pipeline', () => { - it('renders a pipeline widget that reads "Pipeline <ID> <status> for <SHA> on <branch>"', () => { - pipeline.ref.tag = true; - - factory(); - - const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`; - const actual = trimText(findPipelineInfoContainer().text()); - - expect(actual).toBe(expected); - }); - }); - - describe('for a detached merge request pipeline', () => { - it('renders a pipeline widget that reads "Detached merge request pipeline <ID> <status> for <SHA>"', () => { - pipeline.details.name = 'Detached merge request pipeline'; - pipeline.merge_request_event_type = 'detached'; - - factory(); - - const expected = `Detached merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`; - const actual = trimText(findPipelineInfoContainer().text()); - - expect(actual).toBe(expected); - }); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_rebase_spec.js deleted file mode 100644 index 6db82cedd80..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_rebase_spec.js +++ /dev/null @@ -1,292 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import WidgetRebase from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue'; -import eventHub from '~/vue_merge_request_widget/event_hub'; -import toast from '~/vue_shared/plugins/global_toast'; - -jest.mock('~/vue_shared/plugins/global_toast'); - -let wrapper; - -function createWrapper(propsData, mergeRequestWidgetGraphql, rebaseWithoutCiUi) { - wrapper = shallowMount(WidgetRebase, { - propsData, - data() { - return { - state: { - rebaseInProgress: propsData.mr.rebaseInProgress, - targetBranch: propsData.mr.targetBranch, - userPermissions: { - pushToSourceBranch: propsData.mr.canPushToSourceBranch, - }, - }, - }; - }, - provide: { glFeatures: { mergeRequestWidgetGraphql, rebaseWithoutCiUi } }, - mocks: { - $apollo: { - queries: { - state: { loading: false }, - }, - }, - }, - }); -} - -describe('Merge request widget rebase component', () => { - const findRebaseMessage = () => wrapper.find('[data-testid="rebase-message"]'); - const findRebaseMessageText = () => findRebaseMessage().text(); - const findStandardRebaseButton = () => wrapper.find('[data-testid="standard-rebase-button"]'); - const findRebaseWithoutCiButton = () => wrapper.find('[data-testid="rebase-without-ci-button"]'); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - [true, false].forEach((mergeRequestWidgetGraphql) => { - describe(`widget graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'disabled'}`, () => { - describe('while rebasing', () => { - it('should show progress message', () => { - createWrapper( - { - mr: { rebaseInProgress: true }, - service: {}, - }, - mergeRequestWidgetGraphql, - ); - - expect(findRebaseMessageText()).toContain('Rebase in progress'); - }); - }); - - describe('with permissions', () => { - const rebaseMock = jest.fn().mockResolvedValue(); - const pollMock = jest.fn().mockResolvedValue({}); - - it('renders the warning message', () => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, - }, - mergeRequestWidgetGraphql, - ); - - const text = findRebaseMessageText(); - - expect(text).toContain('Merge blocked'); - expect(text.replace(/\s\s+/g, ' ')).toContain( - 'the source branch must be rebased onto the target branch', - ); - }); - - it('renders an error message when rebasing has failed', async () => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, - }, - mergeRequestWidgetGraphql, - ); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ rebasingError: 'Something went wrong!' }); - - await nextTick(); - expect(findRebaseMessageText()).toContain('Something went wrong!'); - }); - - describe('Rebase buttons with flag rebaseWithoutCiUi', () => { - beforeEach(() => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, - }, - mergeRequestWidgetGraphql, - { rebaseWithoutCiUi: true }, - ); - }); - - it('renders both buttons', () => { - expect(findRebaseWithoutCiButton().exists()).toBe(true); - expect(findStandardRebaseButton().exists()).toBe(true); - }); - - it('starts the rebase when clicking', async () => { - findStandardRebaseButton().vm.$emit('click'); - - await nextTick(); - - expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false }); - }); - - it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => { - findRebaseWithoutCiButton().vm.$emit('click'); - - await nextTick(); - - expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true }); - }); - }); - - describe('Rebase button with rebaseWithoutCiUI flag disabled', () => { - beforeEach(() => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, - }, - mergeRequestWidgetGraphql, - ); - }); - - it('standard rebase button is rendered', () => { - expect(findStandardRebaseButton().exists()).toBe(true); - expect(findRebaseWithoutCiButton().exists()).toBe(false); - }); - - it('calls rebase method with skip_ci false', () => { - findStandardRebaseButton().vm.$emit('click'); - - expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false }); - }); - }); - }); - - describe('without permissions', () => { - const exampleTargetBranch = 'fake-branch-to-test-with'; - - describe('UI text', () => { - beforeEach(() => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: false, - targetBranch: exampleTargetBranch, - }, - service: {}, - }, - mergeRequestWidgetGraphql, - ); - }); - - it('renders a message explaining user does not have permissions', () => { - const text = findRebaseMessageText(); - - expect(text).toContain( - 'Merge blocked: the source branch must be rebased onto the target branch.', - ); - expect(text).toContain('the source branch must be rebased'); - }); - - it('renders the correct target branch name', () => { - const elem = findRebaseMessage(); - - expect(elem.text()).toContain( - 'Merge blocked: the source branch must be rebased onto the target branch.', - ); - }); - }); - - it('does not render the "Rebase without pipeline" button with rebaseWithoutCiUI flag enabled', () => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: false, - targetBranch: exampleTargetBranch, - }, - service: {}, - }, - mergeRequestWidgetGraphql, - { rebaseWithoutCiUi: true }, - ); - - expect(findRebaseWithoutCiButton().exists()).toBe(false); - }); - - it('does not render the standard rebase button with rebaseWithoutCiUI flag disabled', () => { - createWrapper( - { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: false, - targetBranch: exampleTargetBranch, - }, - service: {}, - }, - mergeRequestWidgetGraphql, - ); - - expect(findStandardRebaseButton().exists()).toBe(false); - }); - }); - - describe('methods', () => { - it('checkRebaseStatus', async () => { - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - createWrapper( - { - mr: {}, - service: { - rebase() { - return Promise.resolve(); - }, - poll() { - return Promise.resolve({ - data: { - rebase_in_progress: false, - should_be_rebased: false, - merge_error: null, - }, - }); - }, - }, - }, - mergeRequestWidgetGraphql, - ); - - wrapper.vm.rebase(); - - // Wait for the rebase request - await nextTick(); - // Wait for the polling request - await nextTick(); - // Wait for the eventHub to be called - await nextTick(); - - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess'); - expect(toast).toHaveBeenCalledWith('Rebase completed'); - }); - }); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js deleted file mode 100644 index 15522f7ac1d..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js +++ /dev/null @@ -1,114 +0,0 @@ -import { GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import RelatedLinks from '~/vue_merge_request_widget/components/mr_widget_related_links.vue'; - -describe('MRWidgetRelatedLinks', () => { - let wrapper; - - const createComponent = (propsData = {}) => { - wrapper = shallowMount(RelatedLinks, { propsData }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('computed', () => { - describe('closesText', () => { - it('returns Closes text for open merge request', () => { - createComponent({ state: 'open', relatedLinks: {} }); - - expect(wrapper.vm.closesText).toBe('Closes issues'); - }); - - it('returns correct text for closed merge request', () => { - createComponent({ state: 'closed', relatedLinks: {} }); - - expect(wrapper.vm.closesText).toBe('Did not close'); - }); - - it('returns correct tense for merged request', () => { - createComponent({ state: 'merged', relatedLinks: {} }); - - expect(wrapper.vm.closesText).toBe('Closed'); - }); - }); - }); - - it('should have only have closing issues text', () => { - createComponent({ - relatedLinks: { - closing: '<a href="#">#23</a> and <a>#42</a>', - closingCount: 2, - }, - }); - const content = wrapper - .text() - .replace(/\n(\s)+/g, ' ') - .trim(); - - expect(content).toContain('Closes issues #23 and #42'); - expect(content).not.toContain('Mentions'); - }); - - it('should have only have mentioned issues text', () => { - createComponent({ - relatedLinks: { - mentioned: '<a href="#">#7</a>', - mentionedCount: 1, - }, - }); - - const content = wrapper - .text() - .replace(/\n(\s)+/g, ' ') - .trim(); - - expect(content).toContain('Mentions issue #7'); - expect(content).not.toContain('Closes issues'); - }); - - it('should have closing and mentioned issues at the same time', () => { - createComponent({ - relatedLinks: { - closing: '<a href="#">#7</a>', - mentioned: '<a href="#">#23</a> and <a>#42</a>', - closingCount: 1, - mentionedCount: 2, - }, - }); - const content = wrapper - .text() - .replace(/\n(\s)+/g, ' ') - .trim(); - - expect(content).toContain('Closes issue #7'); - expect(content).toContain('Mentions issues #23 and #42'); - }); - - describe('should have correct assign issues link', () => { - it.each([ - [1, 'Assign yourself to this issue'], - [2, 'Assign yourself to these issues'], - ])('when issue count is %s, link displays correct text', (unassignedCount, text) => { - const assignToMe = '/assign'; - - createComponent({ - relatedLinks: { assignToMe, unassignedCount }, - }); - - const glLinkWrapper = wrapper.findComponent(GlLink); - - expect(glLinkWrapper.attributes('href')).toBe(assignToMe); - expect(glLinkWrapper.text()).toBe(text); - }); - - it('when no link is present', () => { - createComponent({ - relatedLinks: { assignToMe: '#', unassignedCount: 0 }, - }); - - expect(wrapper.findComponent(GlLink).exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_status_icon_spec.js deleted file mode 100644 index c25e10c5249..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_status_icon_spec.js +++ /dev/null @@ -1,60 +0,0 @@ -import { GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount, mount } from '@vue/test-utils'; -import mrStatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; - -describe('MR widget status icon component', () => { - let wrapper; - - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); - const findDisabledMergeButton = () => wrapper.find('[data-testid="disabled-merge-button"]'); - - const createWrapper = (props, mountFn = shallowMount) => { - wrapper = mountFn(mrStatusIcon, { - propsData: { - ...props, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('while loading', () => { - it('renders loading icon', () => { - createWrapper({ status: 'loading' }); - - expect(findLoadingIcon().exists()).toBe(true); - }); - }); - - describe('with status icon', () => { - it('renders success status icon', () => { - createWrapper({ status: 'success' }, mount); - - expect(wrapper.find('[data-testid="status_success-icon"]').exists()).toBe(true); - }); - - it('renders failed status icon', () => { - createWrapper({ status: 'failed' }, mount); - - expect(wrapper.find('[data-testid="status_failed-icon"]').exists()).toBe(true); - }); - }); - - describe('with disabled button', () => { - it('renders a disabled button', () => { - createWrapper({ status: 'failed', showDisabledButton: true }); - - expect(findDisabledMergeButton().exists()).toBe(true); - }); - }); - - describe('without disabled button', () => { - it('does not render a disabled button', () => { - createWrapper({ status: 'failed' }); - - expect(findDisabledMergeButton().exists()).toBe(false); - }); - }); -}); 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 deleted file mode 100644 index 352bc1a08ea..00000000000 --- a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js +++ /dev/null @@ -1,135 +0,0 @@ -import { GlSprintf } from '@gitlab/ui'; -import { mount, shallowMount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper'; -import axios from '~/lib/utils/axios_utils'; -import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue'; -import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue'; -import { - SP_TRACK_LABEL, - SP_SHOW_TRACK_EVENT, - SP_SHOW_TRACK_VALUE, - SP_HELP_URL, -} from '~/vue_merge_request_widget/constants'; -import dismissibleContainer from '~/vue_shared/components/dismissible_container.vue'; -import { suggestProps, iconName } from './pipeline_tour_mock_data'; - -describe('MRWidgetSuggestPipeline', () => { - describe('template', () => { - let wrapper; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('core functionality', () => { - const findOkBtn = () => wrapper.find('[data-testid="ok"]'); - let trackingSpy; - let mockAxios; - - const mockTrackingOnWrapper = () => { - unmockTracking(); - trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn); - }; - - beforeEach(() => { - mockAxios = new MockAdapter(axios); - document.body.dataset.page = 'projects:merge_requests:show'; - trackingSpy = mockTracking('_category_', undefined, jest.spyOn); - - wrapper = mount(suggestPipelineComponent, { - propsData: suggestProps, - stubs: { - GlSprintf, - }, - }); - }); - - afterEach(() => { - unmockTracking(); - mockAxios.restore(); - }); - - it('renders the expected text', () => { - const messageText = /Looks like there's no pipeline here./; - - expect(wrapper.text()).toMatch(messageText); - }); - - it('renders widget icon', () => { - const icon = wrapper.find(MrWidgetIcon); - - expect(icon.exists()).toBe(true); - expect(icon.props()).toEqual( - expect.objectContaining({ - name: iconName, - }), - ); - }); - - it('renders the show me how button', () => { - const button = findOkBtn(); - - expect(button.exists()).toBe(true); - expect(button.classes('btn-confirm')).toEqual(true); - expect(button.attributes('href')).toBe(suggestProps.pipelinePath); - }); - - it('renders the help link', () => { - const link = wrapper.find('[data-testid="help"]'); - - expect(link.exists()).toBe(true); - expect(link.attributes('href')).toBe(SP_HELP_URL); - }); - - it('renders the empty pipelines image', () => { - const image = wrapper.find('[data-testid="pipeline-image"]'); - - expect(image.exists()).toBe(true); - expect(image.attributes().src).toBe(suggestProps.pipelineSvgPath); - }); - - describe('tracking', () => { - it('send event for basic view of the suggest pipeline widget', () => { - const expectedCategory = undefined; - const expectedAction = undefined; - - expect(trackingSpy).toHaveBeenCalledWith(expectedCategory, expectedAction, { - label: SP_TRACK_LABEL, - property: suggestProps.humanAccess, - }); - }); - - it('send an event when ok button is clicked', () => { - mockTrackingOnWrapper(); - const okBtn = findOkBtn(); - triggerEvent(okBtn.element); - - expect(trackingSpy).toHaveBeenCalledWith('_category_', SP_SHOW_TRACK_EVENT, { - label: SP_TRACK_LABEL, - property: suggestProps.humanAccess, - value: SP_SHOW_TRACK_VALUE.toString(), - }); - }); - }); - }); - - describe('dismissible', () => { - const findDismissContainer = () => wrapper.find(dismissibleContainer); - - beforeEach(() => { - wrapper = shallowMount(suggestPipelineComponent, { propsData: suggestProps }); - }); - - it('renders the dismissal container', () => { - expect(findDismissContainer().exists()).toBe(true); - }); - - it('emits dismiss upon dismissal button click', () => { - findDismissContainer().vm.$emit('dismiss'); - - expect(wrapper.emitted().dismiss).toBeTruthy(); - }); - }); - }); -}); 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 deleted file mode 100644 index eef087d62b8..00000000000 --- a/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js +++ /dev/null @@ -1,9 +0,0 @@ -export const suggestProps = { - pipelinePath: '/foo/bar/add/pipeline/path', - pipelineSvgPath: 'assets/illustrations/something.svg', - humanAccess: 'maintainer', - userCalloutsPath: 'some/callout/path', - userCalloutFeatureId: 'suggest_pipeline', -}; - -export const iconName = 'status_notfound'; diff --git a/spec/frontend/vue_mr_widget/components/review_app_link_spec.js b/spec/frontend/vue_mr_widget/components/review_app_link_spec.js deleted file mode 100644 index e393b56034d..00000000000 --- a/spec/frontend/vue_mr_widget/components/review_app_link_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { mockTracking, triggerEvent } from 'helpers/tracking_helper'; -import ReviewAppLink from '~/vue_merge_request_widget/components/review_app_link.vue'; - -describe('review app link', () => { - const props = { - link: '/review', - cssClass: 'js-link', - display: { - text: 'View app', - tooltip: '', - }, - }; - let wrapper; - - beforeEach(() => { - wrapper = shallowMount(ReviewAppLink, { propsData: props }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders provided link as href attribute', () => { - expect(wrapper.attributes('href')).toBe(props.link); - }); - - it('renders provided cssClass as class attribute', () => { - expect(wrapper.classes('js-link')).toBe(true); - }); - - it('renders View app text', () => { - expect(wrapper.text().trim()).toBe('View app'); - }); - - it('renders svg icon', () => { - expect(wrapper.find('svg')).not.toBeNull(); - }); - - it('tracks an event when clicked', () => { - const spy = mockTracking('_category_', wrapper.element, jest.spyOn); - triggerEvent(wrapper.element); - - expect(spy).toHaveBeenCalledWith('_category_', 'open_review_app', { - label: 'review_app', - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap deleted file mode 100644 index 56a0218b374..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap +++ /dev/null @@ -1,145 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have correct elements 1`] = ` -<div - class="mr-widget-body media" -> - <gl-icon-stub - class="gl-text-blue-500 gl-mr-3 gl-mt-1" - name="status_scheduled" - size="24" - /> - - <div - class="media-body" - > - <h4 - class="gl-display-flex" - > - <span - class="gl-mr-3" - > - <gl-sprintf-stub - data-testid="statusText" - message="Set by %{merge_author} to be merged automatically when the pipeline succeeds" - /> - </span> - - <gl-button-stub - buttontextclasses="" - category="primary" - class="js-cancel-auto-merge" - data-qa-selector="cancel_auto_merge_button" - data-testid="cancelAutomaticMergeButton" - icon="" - size="small" - variant="default" - > - - Cancel auto-merge - - </gl-button-stub> - </h4> - - <section - class="mr-info-list" - > - <p - class="gl-display-flex" - > - <span - class="gl-mr-3" - > - Does not delete the source branch - </span> - - <gl-button-stub - buttontextclasses="" - category="primary" - class="js-remove-source-branch" - data-testid="removeSourceBranchButton" - icon="" - size="small" - variant="default" - > - - Delete source branch - - </gl-button-stub> - </p> - </section> - </div> -</div> -`; - -exports[`MRWidgetAutoMergeEnabled when graphql is enabled template should have correct elements 1`] = ` -<div - class="mr-widget-body media" -> - <gl-icon-stub - class="gl-text-blue-500 gl-mr-3 gl-mt-1" - name="status_scheduled" - size="24" - /> - - <div - class="media-body" - > - <h4 - class="gl-display-flex" - > - <span - class="gl-mr-3" - > - <gl-sprintf-stub - data-testid="statusText" - message="Set by %{merge_author} to be merged automatically when the pipeline succeeds" - /> - </span> - - <gl-button-stub - buttontextclasses="" - category="primary" - class="js-cancel-auto-merge" - data-qa-selector="cancel_auto_merge_button" - data-testid="cancelAutomaticMergeButton" - icon="" - size="small" - variant="default" - > - - Cancel auto-merge - - </gl-button-stub> - </h4> - - <section - class="mr-info-list" - > - <p - class="gl-display-flex" - > - <span - class="gl-mr-3" - > - Does not delete the source branch - </span> - - <gl-button-stub - buttontextclasses="" - category="primary" - class="js-remove-source-branch" - data-testid="removeSourceBranchButton" - icon="" - size="small" - variant="default" - > - - Delete source branch - - </gl-button-stub> - </p> - </section> - </div> -</div> -`; diff --git a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap deleted file mode 100644 index 98297630792..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PipelineFailed should render error message with a disabled merge button 1`] = ` -<div - class="mr-widget-body media" -> - <status-icon-stub - showdisabledbutton="true" - status="warning" - /> - - <div - class="media-body space-children" - > - <span - class="bold" - > - <gl-sprintf-stub - message="Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}" - /> - </span> - </div> -</div> -`; diff --git a/spec/frontend/vue_mr_widget/components/states/__snapshots__/new_ready_to_merge_spec.js.snap b/spec/frontend/vue_mr_widget/components/states/__snapshots__/new_ready_to_merge_spec.js.snap deleted file mode 100644 index f9936f22ea3..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/__snapshots__/new_ready_to_merge_spec.js.snap +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`New ready to merge state component renders permission text if canMerge (false) is false 1`] = ` -<div - class="mr-widget-body media" -> - <status-icon-stub - status="success" - /> - - <p - class="media-body gl-m-0! gl-font-weight-bold gl-text-gray-900!" - > - - Ready to merge by members who can write to the target branch. - - </p> -</div> -`; - -exports[`New ready to merge state component renders permission text if canMerge (true) is false 1`] = ` -<div - class="mr-widget-body media" -> - <status-icon-stub - status="success" - /> - - <p - class="media-body gl-m-0! gl-font-weight-bold gl-text-gray-900!" - > - - Ready to merge! - - </p> -</div> -`; diff --git a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js deleted file mode 100644 index c0add94e6ed..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js +++ /dev/null @@ -1,76 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; - -const testCommitMessage = 'Test commit message'; -const testLabel = 'Test label'; -const testInputId = 'test-input-id'; - -describe('Commits edit component', () => { - let wrapper; - - const createComponent = (slots = {}) => { - wrapper = shallowMount(CommitEdit, { - propsData: { - value: testCommitMessage, - label: testLabel, - inputId: testInputId, - }, - slots: { - ...slots, - }, - }); - }; - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - const findTextarea = () => wrapper.find('.form-control'); - - it('has a correct label', () => { - const labelElement = wrapper.find('.col-form-label'); - - expect(labelElement.text()).toBe(testLabel); - }); - - describe('textarea', () => { - it('has a correct ID', () => { - expect(findTextarea().attributes('id')).toBe(testInputId); - }); - - it('has a correct value', () => { - expect(findTextarea().element.value).toBe(testCommitMessage); - }); - - it('emits an input event and receives changed value', async () => { - const changedCommitMessage = 'Changed commit message'; - - findTextarea().element.value = changedCommitMessage; - findTextarea().trigger('input'); - - await nextTick(); - expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]); - expect(findTextarea().element.value).toBe(changedCommitMessage); - }); - }); - - describe('when slots are present', () => { - beforeEach(() => { - createComponent({ - header: `<div class="test-header">${testCommitMessage}</div>`, - }); - }); - - it('renders header slot correctly', () => { - const headerSlotElement = wrapper.find('.test-header'); - - expect(headerSlotElement.exists()).toBe(true); - expect(headerSlotElement.text()).toBe(testCommitMessage); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/merge_checks_failed_spec.js b/spec/frontend/vue_mr_widget/components/states/merge_checks_failed_spec.js deleted file mode 100644 index 1900b53ac11..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/merge_checks_failed_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import MergeChecksFailed from '~/vue_merge_request_widget/components/states/merge_checks_failed.vue'; - -let wrapper; - -function factory(propsData = {}) { - wrapper = shallowMount(MergeChecksFailed, { - propsData, - }); -} - -describe('Merge request widget merge checks failed state component', () => { - afterEach(() => { - wrapper.destroy(); - }); - - it.each` - mrState | displayText - ${{ approvals: true, isApproved: false }} | ${'approvalNeeded'} - ${{ blockingMergeRequests: { total_count: 1 } }} | ${'blockingMergeRequests'} - `('display $displayText text for $mrState', ({ mrState, displayText }) => { - factory({ mr: mrState }); - - expect(wrapper.text()).toContain(MergeChecksFailed.i18n[displayText]); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js b/spec/frontend/vue_mr_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js deleted file mode 100644 index 0e1c38437f0..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js +++ /dev/null @@ -1,78 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import MergeFailedPipelineConfirmationDialog from '~/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog.vue'; -import { trimText } from 'helpers/text_helper'; - -describe('MergeFailedPipelineConfirmationDialog', () => { - let wrapper; - - const GlModal = { - template: ` - <div> - <slot></slot> - <slot name="modal-footer"></slot> - </div> - `, - methods: { - hide: jest.fn(), - }, - }; - - const createComponent = () => { - wrapper = shallowMount(MergeFailedPipelineConfirmationDialog, { - propsData: { - visible: true, - }, - stubs: { - GlModal, - }, - attachTo: document.body, - }); - }; - - const findModal = () => wrapper.findComponent(GlModal); - const findMergeBtn = () => wrapper.find('[data-testid="merge-unverified-changes"]'); - const findCancelBtn = () => wrapper.find('[data-testid="merge-cancel-btn"]'); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should render informational text explaining why merging immediately can be dangerous', () => { - expect(trimText(wrapper.text())).toContain( - 'The latest pipeline for this merge request did not succeed. The latest changes are unverified. Are you sure you want to attempt to merge?', - ); - }); - - it('should emit the mergeWithFailedPipeline event', () => { - findMergeBtn().vm.$emit('click'); - - expect(wrapper.emitted('mergeWithFailedPipeline')).toBeTruthy(); - }); - - it('when the cancel button is clicked should emit cancel and call hide', () => { - jest.spyOn(findModal().vm, 'hide'); - - findCancelBtn().vm.$emit('click'); - - expect(wrapper.emitted('cancel')).toBeTruthy(); - expect(findModal().vm.hide).toHaveBeenCalled(); - }); - - it('should emit cancel when the hide event is emitted', () => { - findModal().vm.$emit('hide'); - - expect(wrapper.emitted('cancel')).toBeTruthy(); - }); - - it('when modal is shown it will focus the cancel button', () => { - jest.spyOn(findCancelBtn().element, 'focus'); - - findModal().vm.$emit('shown'); - - expect(findCancelBtn().element.focus).toHaveBeenCalled(); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_archived_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_archived_spec.js deleted file mode 100644 index f3061d792d0..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_archived_spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; -import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue'; - -describe('MRWidgetArchived', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(archivedComponent); - vm = mountComponent(Component); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders a ci status failed icon', () => { - expect(vm.$el.querySelector('.ci-status-icon')).not.toBeNull(); - }); - - it('renders a disabled button', () => { - expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled'); - expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Merge'); - }); - - it('renders information', () => { - expect(vm.$el.querySelector('.bold').textContent.trim()).toEqual( - 'Merge unavailable: merge requests are read-only on archived projects.', - ); - }); -}); 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 deleted file mode 100644 index 7387ed2d5e9..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js +++ /dev/null @@ -1,328 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import { trimText } from 'helpers/text_helper'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue'; -import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; -import eventHub from '~/vue_merge_request_widget/event_hub'; -import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; - -let wrapper; -let mergeRequestWidgetGraphqlEnabled = false; - -function convertPropsToGraphqlState(props) { - return { - autoMergeStrategy: props.autoMergeStrategy, - cancelAutoMergePath: 'http://text.com', - mergeUser: { - id: props.mergeUserId, - ...props.setToAutoMergeBy, - }, - targetBranch: props.targetBranch, - targetBranchCommitsPath: props.targetBranchPath, - shouldRemoveSourceBranch: props.shouldRemoveSourceBranch, - forceRemoveSourceBranch: props.shouldRemoveSourceBranch, - userPermissions: { - removeSourceBranch: props.canRemoveSourceBranch, - }, - }; -} - -function factory(propsData, stateOverride = {}) { - let state = {}; - - if (mergeRequestWidgetGraphqlEnabled) { - state = { ...convertPropsToGraphqlState(propsData), ...stateOverride }; - } - - wrapper = extendedWrapper( - shallowMount(autoMergeEnabledComponent, { - propsData: { - mr: propsData, - service: new MRWidgetService({}), - }, - data() { - return { state }; - }, - provide: { glFeatures: { mergeRequestWidgetGraphql: mergeRequestWidgetGraphqlEnabled } }, - mocks: { - $apollo: { - queries: { - state: { loading: false }, - }, - }, - }, - }), - ); -} - -const targetBranchPath = '/foo/bar'; -const targetBranch = 'foo'; -const sha = '1EA2EZ34'; -const defaultMrProps = () => ({ - shouldRemoveSourceBranch: false, - canRemoveSourceBranch: true, - canCancelAutomaticMerge: true, - mergeUserId: 1, - currentUserId: 1, - setToAutoMergeBy: {}, - sha, - targetBranchPath, - targetBranch, - autoMergeStrategy: MWPS_MERGE_STRATEGY, -}); - -const getStatusText = () => wrapper.findByTestId('statusText').attributes('message'); - -describe('MRWidgetAutoMergeEnabled', () => { - let oldWindowGl; - - beforeEach(() => { - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - - oldWindowGl = window.gl; - window.gl = { - mrWidgetData: { - defaultAvatarUrl: 'no_avatar.png', - }, - }; - }); - - afterEach(() => { - window.gl = oldWindowGl; - wrapper.destroy(); - wrapper = null; - }); - - [true, false].forEach((mergeRequestWidgetGraphql) => { - describe(`when graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'disabled'}`, () => { - beforeEach(() => { - mergeRequestWidgetGraphqlEnabled = mergeRequestWidgetGraphql; - }); - - describe('computed', () => { - describe('canRemoveSourceBranch', () => { - it('should return true when user is able to remove source branch', () => { - factory({ - ...defaultMrProps(), - }); - - expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(true); - }); - - it.each` - mergeUserId | currentUserId - ${2} | ${1} - ${1} | ${2} - `( - 'should return false when user id is not the same with who set the MWPS', - ({ mergeUserId, currentUserId }) => { - factory({ - ...defaultMrProps(), - mergeUserId, - currentUserId, - }); - - expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(false); - }, - ); - - it('should not find "Delete" button when shouldRemoveSourceBranch set to true', () => { - factory({ - ...defaultMrProps(), - shouldRemoveSourceBranch: true, - }); - - expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(false); - }); - - it('should find "Delete" button when shouldRemoveSourceBranch overrides state.forceRemoveSourceBranch', () => { - factory( - { - ...defaultMrProps(), - shouldRemoveSourceBranch: false, - }, - { - forceRemoveSourceBranch: true, - }, - ); - - expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(true); - }); - - it('should find "Delete" button when shouldRemoveSourceBranch set to false', () => { - factory({ - ...defaultMrProps(), - shouldRemoveSourceBranch: false, - }); - - expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(true); - }); - - it('should return false if user is not able to remove the source branch', () => { - factory({ - ...defaultMrProps(), - canRemoveSourceBranch: false, - }); - - expect(wrapper.findByTestId('removeSourceBranchButton').exists()).toBe(false); - }); - }); - - describe('cancelButtonText', () => { - it('should return "Cancel" if MWPS is selected', () => { - factory({ - ...defaultMrProps(), - autoMergeStrategy: MWPS_MERGE_STRATEGY, - }); - - expect(wrapper.findByTestId('cancelAutomaticMergeButton').text()).toBe( - 'Cancel auto-merge', - ); - }); - }); - }); - - describe('methods', () => { - describe('cancelAutomaticMerge', () => { - it('should set flag and call service then tell main component to update the widget with data', async () => { - factory({ - ...defaultMrProps(), - }); - const mrObj = { - is_new_mr_data: true, - }; - jest.spyOn(wrapper.vm.service, 'cancelAutomaticMerge').mockReturnValue( - new Promise((resolve) => { - resolve({ - data: mrObj, - }); - }), - ); - - wrapper.vm.cancelAutomaticMerge(); - - await waitForPromises(); - - expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy(); - if (mergeRequestWidgetGraphql) { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - } else { - expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - } - }); - }); - - describe('removeSourceBranch', () => { - it('should set flag and call service then request main component to update the widget', async () => { - factory({ - ...defaultMrProps(), - }); - jest.spyOn(wrapper.vm.service, 'merge').mockReturnValue( - Promise.resolve({ - data: { - status: MWPS_MERGE_STRATEGY, - }, - }), - ); - - wrapper.vm.removeSourceBranch(); - - await waitForPromises(); - - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(wrapper.vm.service.merge).toHaveBeenCalledWith({ - sha, - auto_merge_strategy: MWPS_MERGE_STRATEGY, - should_remove_source_branch: true, - }); - }); - }); - }); - - describe('template', () => { - it('should have correct elements', () => { - factory({ - ...defaultMrProps(), - }); - - expect(wrapper.element).toMatchSnapshot(); - }); - - it('should disable cancel auto merge button when the action is in progress', async () => { - factory({ - ...defaultMrProps(), - }); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - isCancellingAutoMerge: true, - }); - - await nextTick(); - - expect(wrapper.find('.js-cancel-auto-merge').props('loading')).toBe(true); - }); - - it('should show source branch will be deleted text when it source branch set to remove', () => { - factory({ - ...defaultMrProps(), - shouldRemoveSourceBranch: true, - }); - - const normalizedText = wrapper.text().replace(/\s+/g, ' '); - - expect(normalizedText).toContain('Deletes the source branch'); - expect(normalizedText).not.toContain('Does not delete the source branch'); - }); - - it('should not show delete source branch button when user not able to delete source branch', () => { - factory({ - ...defaultMrProps(), - currentUserId: 4, - }); - - expect(wrapper.find('.js-remove-source-branch').exists()).toBe(false); - }); - - it('should disable delete source branch button when the action is in progress', async () => { - factory({ - ...defaultMrProps(), - }); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - isRemovingSourceBranch: true, - }); - - await nextTick(); - - expect(wrapper.find('.js-remove-source-branch').props('loading')).toBe(true); - }); - - it('should render the status text as "...to merged automatically" if MWPS is selected', () => { - factory({ - ...defaultMrProps(), - autoMergeStrategy: MWPS_MERGE_STRATEGY, - }); - - expect(getStatusText()).toBe( - 'Set by %{merge_author} to be merged automatically when the pipeline succeeds', - ); - }); - - it('should render the cancel button as "Cancel" if MWPS is selected', () => { - factory({ - ...defaultMrProps(), - autoMergeStrategy: MWPS_MERGE_STRATEGY, - }); - - const cancelButtonText = trimText(wrapper.find('.js-cancel-auto-merge').text()); - - expect(cancelButtonText).toBe('Cancel auto-merge'); - }); - }); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js deleted file mode 100644 index 24198096564..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js +++ /dev/null @@ -1,68 +0,0 @@ -import { GlLoadingIcon, GlButton } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import AutoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue'; -import eventHub from '~/vue_merge_request_widget/event_hub'; - -describe('MRWidgetAutoMergeFailed', () => { - let wrapper; - const mergeError = 'This is the merge error'; - const findButton = () => wrapper.find(GlButton); - - const createComponent = (props = {}, mergeRequestWidgetGraphql = false) => { - wrapper = shallowMount(AutoMergeFailedComponent, { - propsData: { ...props }, - data() { - if (mergeRequestWidgetGraphql) { - return { mergeError: props.mr?.mergeError }; - } - - return {}; - }, - provide: { - glFeatures: { mergeRequestWidgetGraphql }, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - [true, false].forEach((mergeRequestWidgetGraphql) => { - describe(`when graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'dislabed'}`, () => { - beforeEach(() => { - createComponent( - { - mr: { mergeError }, - }, - mergeRequestWidgetGraphql, - ); - }); - - it('renders failed message', () => { - expect(wrapper.text()).toContain('This merge request failed to be merged automatically'); - }); - - it('renders merge error provided', () => { - expect(wrapper.text()).toContain(mergeError); - }); - - it('render refresh button', () => { - expect(findButton().text()).toBe('Refresh'); - }); - - it('emits event and shows loading icon when button is clicked', async () => { - jest.spyOn(eventHub, '$emit'); - findButton().vm.$emit('click'); - - expect(eventHub.$emit.mock.calls[0][0]).toBe('MRWidgetUpdateRequested'); - - await nextTick(); - - expect(findButton().attributes('disabled')).toBe('true'); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); - }); - }); - }); -}); 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 deleted file mode 100644 index afe6bd0e767..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; -import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue'; - -describe('MRWidgetChecking', () => { - let Component; - let vm; - - beforeEach(() => { - Component = Vue.extend(checkingComponent); - vm = mountComponent(Component); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders disabled button', () => { - expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled'); - }); - - it('renders loading icon', () => { - expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner'); - }); - - it('renders information about merging', () => { - expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual( - 'Checking if merge request can be merged…', - ); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_closed_spec.js deleted file mode 100644 index 6ae218ce6f8..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_closed_spec.js +++ /dev/null @@ -1,63 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; -import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue'; - -describe('MRWidgetClosed', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(closedComponent); - vm = mountComponent(Component, { - mr: { - metrics: { - mergedBy: {}, - closedBy: { - name: 'Administrator', - username: 'root', - webUrl: 'http://localhost:3000/root', - avatarUrl: - 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }, - mergedAt: 'Jan 24, 2018 1:02pm UTC', - closedAt: 'Jan 24, 2018 1:02pm UTC', - readableMergedAt: '', - readableClosedAt: 'less than a minute ago', - }, - targetBranchPath: '/twitter/flight/commits/so_long_jquery', - targetBranch: 'so_long_jquery', - }, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders warning icon', () => { - expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull(); - }); - - it('renders closed by information with author and time', () => { - expect( - vm.$el.querySelector('.js-mr-widget-author').textContent.trim().replace(/\s\s+/g, ' '), - ).toContain('Closed by Administrator less than a minute ago'); - }); - - it('links to the user that closed the MR', () => { - expect(vm.$el.querySelector('.author-link').getAttribute('href')).toEqual( - 'http://localhost:3000/root', - ); - }); - - it('renders information about the changes not being merged', () => { - expect( - vm.$el.querySelector('.mr-info-list').textContent.trim().replace(/\s\s+/g, ' '), - ).toContain('The changes were not merged into so_long_jquery'); - }); - - it('renders link for target branch', () => { - expect(vm.$el.querySelector('.label-branch').getAttribute('href')).toEqual( - '/twitter/flight/commits/so_long_jquery', - ); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js deleted file mode 100644 index 663fabb761c..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import { GlDropdownItem } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; - -const commits = [ - { - title: 'Commit 1', - short_id: '78d5b7', - message: 'Update test.txt', - }, - { - title: 'Commit 2', - short_id: '34cbe28b', - message: 'Fixed test', - }, - { - title: 'Commit 3', - short_id: 'fa42932a', - message: 'Added changelog', - }, -]; - -describe('Commits message dropdown component', () => { - let wrapper; - - const createComponent = () => { - wrapper = shallowMount(CommitMessageDropdown, { - propsData: { - commits, - }, - }); - }; - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - const findDropdownElements = () => wrapper.findAll(GlDropdownItem); - const findFirstDropdownElement = () => findDropdownElements().at(0); - - it('should have 3 elements in dropdown list', () => { - expect(findDropdownElements().length).toBe(3); - }); - - it('should have correct message for the first dropdown list element', () => { - expect(findFirstDropdownElement().text()).toContain('78d5b7'); - expect(findFirstDropdownElement().text()).toContain('Commit 1'); - }); - - it('should emit a commit title on selecting commit', async () => { - findFirstDropdownElement().vm.$emit('click'); - - await nextTick(); - expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js deleted file mode 100644 index 2796403b7d0..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js +++ /dev/null @@ -1,136 +0,0 @@ -import { mount } from '@vue/test-utils'; -import { GlSprintf } from '@gitlab/ui'; -import { nextTick } from 'vue'; -import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue'; - -describe('Commits header component', () => { - let wrapper; - - const createComponent = (props) => { - wrapper = mount(CommitsHeader, { - stubs: { - GlSprintf, - }, - propsData: { - isSquashEnabled: false, - targetBranch: 'main', - commitsCount: 5, - isFastForwardEnabled: false, - ...props, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - const findHeaderWrapper = () => wrapper.find('.js-mr-widget-commits-count'); - const findCommitToggle = () => wrapper.find('.commit-edit-toggle'); - const findCommitsCountMessage = () => wrapper.find('.commits-count-message'); - const findTargetBranchMessage = () => wrapper.find('.label-branch'); - const findModifyButton = () => wrapper.find('.modify-message-button'); - - describe('when fast-forward is enabled', () => { - beforeEach(() => { - createComponent({ - isFastForwardEnabled: true, - isSquashEnabled: true, - }); - }); - - it('has commits count message showing 1 commit', () => { - expect(findCommitsCountMessage().text()).toBe('1 commit'); - }); - - it('has button with modify commit message', () => { - expect(findModifyButton().text()).toBe('Modify commit message'); - }); - - it('does not have merge commit part of the message', () => { - expect(findHeaderWrapper().text()).not.toContain('1 merge commit'); - }); - }); - - describe('when collapsed', () => { - it('toggle has aria-label equal to Expand', () => { - createComponent(); - - expect(findCommitToggle().attributes('aria-label')).toBe('Expand'); - }); - - it('has a chevron-right icon', async () => { - createComponent(); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ expanded: false }); - - await nextTick(); - expect(findCommitToggle().props('icon')).toBe('chevron-right'); - }); - - describe('when squash is disabled', () => { - beforeEach(() => { - createComponent(); - }); - - it('has commits count message showing correct amount of commits', () => { - expect(findCommitsCountMessage().text()).toBe('5 commits'); - }); - - it('has button with modify merge commit message', () => { - expect(findModifyButton().text()).toBe('Modify merge commit'); - }); - }); - - describe('when squash is enabled', () => { - beforeEach(() => { - createComponent({ isSquashEnabled: true }); - }); - - it('has commits count message showing one commit when squash is enabled', () => { - expect(findCommitsCountMessage().text()).toBe('1 commit'); - }); - - it('has button with modify commit messages text', () => { - expect(findModifyButton().text()).toBe('Modify commit messages'); - }); - }); - - it('has correct target branch displayed', () => { - createComponent(); - - expect(findTargetBranchMessage().text()).toBe('main'); - }); - - it('does has merge commit part of the message', () => { - createComponent(); - - expect(findHeaderWrapper().text()).toContain('1 merge commit'); - }); - }); - - describe('when expanded', () => { - beforeEach(() => { - createComponent(); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ expanded: true }); - }); - - it('toggle has aria-label equal to collapse', async () => { - await nextTick(); - expect(findCommitToggle().attributes('aria-label')).toBe('Collapse'); - }); - - it('has a chevron-down icon', async () => { - await nextTick(); - expect(findCommitToggle().props('icon')).toBe('chevron-down'); - }); - - it('has a collapse text', async () => { - await nextTick(); - expect(findHeaderWrapper().text()).toBe('Collapse'); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js deleted file mode 100644 index 7a92484695c..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ /dev/null @@ -1,252 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import { TEST_HOST } from 'helpers/test_constants'; -import { removeBreakLine } from 'helpers/text_helper'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue'; - -describe('MRWidgetConflicts', () => { - let wrapper; - let mergeRequestWidgetGraphql = null; - const path = '/conflicts'; - - const findResolveButton = () => wrapper.findByTestId('resolve-conflicts-button'); - const findMergeLocalButton = () => wrapper.findByTestId('merge-locally-button'); - - const mergeConflictsText = 'Merge blocked: merge conflicts must be resolved.'; - const fastForwardMergeText = - 'Merge blocked: fast-forward merge is not possible. To merge this request, first rebase locally.'; - const userCannotMergeText = - 'Users who can write to the source or target branches can resolve the conflicts.'; - const resolveConflictsBtnText = 'Resolve conflicts'; - const mergeLocallyBtnText = 'Resolve locally'; - - async function createComponent(propsData = {}) { - wrapper = extendedWrapper( - shallowMount(ConflictsComponent, { - propsData, - provide: { - glFeatures: { - mergeRequestWidgetGraphql, - }, - }, - mocks: { - $apollo: { - queries: { - userPermissions: { loading: false }, - stateData: { loading: false }, - }, - }, - }, - }), - ); - - if (mergeRequestWidgetGraphql) { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - userPermissions: { - canMerge: propsData.mr.canMerge, - pushToSourceBranch: propsData.mr.canPushToSourceBranch, - }, - stateData: { - shouldBeRebased: propsData.mr.shouldBeRebased, - sourceBranchProtected: propsData.mr.sourceBranchProtected, - }, - }); - } - - await nextTick(); - } - - afterEach(() => { - mergeRequestWidgetGraphql = null; - wrapper.destroy(); - }); - - [false, true].forEach((featureEnabled) => { - describe(`with GraphQL feature flag ${featureEnabled ? 'enabled' : 'disabled'}`, () => { - beforeEach(() => { - mergeRequestWidgetGraphql = featureEnabled; - }); - - // There are two permissions we need to consider: - // - // 1. Is the user allowed to merge to the target branch? - // 2. Is the user allowed to push to the source branch? - // - // This yields 4 possible permutations that we need to test, and - // we test them below. A user who can push to the source - // branch should be allowed to resolve conflicts. This is - // consistent with what the backend does. - describe('when allowed to merge but not allowed to push to source branch', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: false, - conflictResolutionPath: path, - conflictsDocsPath: '', - }, - }); - }); - - it('should tell you about conflicts without bothering other people', () => { - expect(wrapper.text()).toContain(mergeConflictsText); - expect(wrapper.text()).not.toContain(userCannotMergeText); - }); - - it('should not allow you to resolve the conflicts', () => { - expect(wrapper.text()).not.toContain(resolveConflictsBtnText); - }); - - it('should have merge buttons', () => { - expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText); - }); - }); - - describe('when not allowed to merge but allowed to push to source branch', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: false, - canPushToSourceBranch: true, - conflictResolutionPath: path, - conflictsDocsPath: '', - }, - }); - }); - - it('should tell you about conflicts', () => { - expect(wrapper.text()).toContain(mergeConflictsText); - expect(wrapper.text()).toContain(userCannotMergeText); - }); - - it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().text()).toContain(resolveConflictsBtnText); - expect(findResolveButton().attributes('href')).toEqual(path); - }); - - it('should not have merge buttons', () => { - expect(wrapper.text()).not.toContain(mergeLocallyBtnText); - }); - }); - - describe('when allowed to merge and push to source branch', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: true, - conflictResolutionPath: path, - conflictsDocsPath: '', - }, - }); - }); - - it('should tell you about conflicts without bothering other people', () => { - expect(wrapper.text()).toContain(mergeConflictsText); - expect(wrapper.text()).not.toContain(userCannotMergeText); - }); - - it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().text()).toContain(resolveConflictsBtnText); - expect(findResolveButton().attributes('href')).toEqual(path); - }); - - it('should have merge buttons', () => { - expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText); - }); - }); - - describe('when user does not have permission to push to source branch', () => { - it('should show proper message', async () => { - await createComponent({ - mr: { - canMerge: false, - canPushToSourceBranch: false, - conflictsDocsPath: '', - }, - }); - - expect(wrapper.text().trim().replace(/\s\s+/g, ' ')).toContain(userCannotMergeText); - }); - - it('should not have action buttons', async () => { - await createComponent({ - mr: { - canMerge: false, - canPushToSourceBranch: false, - conflictsDocsPath: '', - }, - }); - - expect(findResolveButton().exists()).toBe(false); - expect(findMergeLocalButton().exists()).toBe(false); - }); - - it('should not have resolve button when no conflict resolution path', async () => { - await createComponent({ - mr: { - canMerge: true, - conflictResolutionPath: null, - conflictsDocsPath: '', - }, - }); - - expect(findResolveButton().exists()).toBe(false); - }); - }); - - describe('when fast-forward or semi-linear merge enabled', () => { - it('should tell you to rebase locally', async () => { - await createComponent({ - mr: { - shouldBeRebased: true, - conflictsDocsPath: '', - }, - }); - - expect(removeBreakLine(wrapper.text()).trim()).toContain(fastForwardMergeText); - }); - }); - - describe('when source branch protected', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: true, - conflictResolutionPath: TEST_HOST, - sourceBranchProtected: true, - conflictsDocsPath: '', - }, - }); - }); - - it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().exists()).toBe(true); - }); - }); - - describe('when source branch not protected', () => { - beforeEach(async () => { - await createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: true, - conflictResolutionPath: TEST_HOST, - sourceBranchProtected: false, - conflictsDocsPath: '', - }, - }); - }); - - it('should allow you to resolve the conflicts', () => { - expect(findResolveButton().text()).toContain(resolveConflictsBtnText); - expect(findResolveButton().attributes('href')).toEqual(TEST_HOST); - }); - }); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js deleted file mode 100644 index 6d8e7056366..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js +++ /dev/null @@ -1,158 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; -import MrWidgetFailedToMerge from '~/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue'; -import eventHub from '~/vue_merge_request_widget/event_hub'; - -describe('MRWidgetFailedToMerge', () => { - const dummyIntervalId = 1337; - let wrapper; - - const createComponent = (props = {}, data = {}) => { - wrapper = shallowMount(MrWidgetFailedToMerge, { - propsData: { - mr: { - mergeError: 'Merge error happened', - }, - ...props, - }, - data() { - return data; - }, - }); - }; - - beforeEach(() => { - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(window, 'setInterval').mockReturnValue(dummyIntervalId); - jest.spyOn(window, 'clearInterval').mockImplementation(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('interval', () => { - it('sets interval to refresh', () => { - createComponent(); - - expect(window.setInterval).toHaveBeenCalledWith(wrapper.vm.updateTimer, 1000); - expect(wrapper.vm.intervalId).toBe(dummyIntervalId); - }); - - it('clears interval when destroying ', () => { - createComponent(); - wrapper.destroy(); - - expect(window.clearInterval).toHaveBeenCalledWith(dummyIntervalId); - }); - }); - - describe('mergeError', () => { - it('removes forced line breaks', async () => { - createComponent({ mr: { mergeError: 'contains<br />line breaks<br />' } }); - - await nextTick(); - - expect(wrapper.vm.mergeError).toBe('contains line breaks.'); - }); - }); - - describe('created', () => { - it('should disable polling', () => { - createComponent(); - - expect(eventHub.$emit).toHaveBeenCalledWith('DisablePolling'); - }); - }); - - describe('methods', () => { - describe('refresh', () => { - it('should emit event to request component refresh', () => { - createComponent(); - - expect(wrapper.vm.isRefreshing).toBe(false); - - wrapper.vm.refresh(); - - expect(wrapper.vm.isRefreshing).toBe(true); - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(eventHub.$emit).toHaveBeenCalledWith('EnablePolling'); - }); - }); - - describe('updateTimer', () => { - it('should update timer and emit event when timer end', () => { - createComponent(); - - jest.spyOn(wrapper.vm, 'refresh').mockImplementation(() => {}); - - expect(wrapper.vm.timer).toEqual(10); - - for (let i = 0; i < 10; i += 1) { - expect(wrapper.vm.timer).toEqual(10 - i); - wrapper.vm.updateTimer(); - } - - expect(wrapper.vm.refresh).toHaveBeenCalled(); - }); - }); - }); - - describe('while it is refreshing', () => { - it('renders Refresing now', async () => { - createComponent({}, { isRefreshing: true }); - - await nextTick(); - - expect(wrapper.find('.js-refresh-label').text().trim()).toBe('Refreshing now'); - }); - }); - - describe('while it is not regresing', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders warning icon and disabled merge button', () => { - expect(wrapper.find('.js-ci-status-icon-warning')).not.toBeNull(); - expect(wrapper.find(StatusIcon).props('showDisabledButton')).toBe(true); - }); - - it('renders given error', () => { - expect(wrapper.find('.has-error-message').text().trim()).toBe('Merge error happened.'); - }); - - it('renders refresh button', () => { - expect( - wrapper.find('[data-testid="merge-request-failed-refresh-button"]').text().trim(), - ).toBe('Refresh now'); - }); - - it('renders remaining time', () => { - expect(wrapper.find('.has-custom-error').text().trim()).toBe( - 'Refreshing in 10 seconds to show the updated status...', - ); - }); - }); - - it('should just generic merge failed message if merge_error is not available', async () => { - createComponent({ mr: { mergeError: null } }); - - await nextTick(); - - expect(wrapper.text().trim()).toContain('Merge failed.'); - expect(wrapper.text().trim()).not.toContain('Merge error happened.'); - }); - - it('should show refresh label when refresh requested', async () => { - createComponent(); - - wrapper.vm.refresh(); - - await nextTick(); - - expect(wrapper.text().trim()).not.toContain('Merge failed. Refreshing'); - expect(wrapper.text().trim()).toContain('Refreshing now'); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js deleted file mode 100644 index 29ee7e0010f..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ /dev/null @@ -1,235 +0,0 @@ -import { getByRole } from '@testing-library/dom'; -import Vue, { nextTick } from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants'; -import modalEventHub from '~/projects/commit/event_hub'; -import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue'; -import eventHub from '~/vue_merge_request_widget/event_hub'; - -describe('MRWidgetMerged', () => { - let vm; - const targetBranch = 'foo'; - const selectors = { - get copyMergeShaButton() { - return vm.$el.querySelector('button.js-mr-merged-copy-sha'); - }, - get mergeCommitShaLink() { - return vm.$el.querySelector('a.js-mr-merged-commit-sha'); - }, - }; - - beforeEach(() => { - jest.spyOn(document, 'dispatchEvent'); - const Component = Vue.extend(mergedComponent); - const mr = { - isRemovingSourceBranch: false, - cherryPickInForkPath: false, - canCherryPickInCurrentMR: true, - revertInForkPath: false, - canRevertInCurrentMR: true, - canRemoveSourceBranch: true, - sourceBranchRemoved: true, - metrics: { - mergedBy: { - name: 'Administrator', - username: 'root', - webUrl: 'http://localhost:3000/root', - avatarUrl: - 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }, - mergedAt: 'Jan 24, 2018 1:02pm UTC', - readableMergedAt: '', - closedBy: {}, - closedAt: 'Jan 24, 2018 1:02pm UTC', - readableClosedAt: '', - }, - updatedAt: 'mergedUpdatedAt', - shortMergeCommitSha: '958c0475', - mergeCommitSha: '958c047516e182dfc52317f721f696e8a1ee85ed', - mergeCommitPath: - 'http://localhost:3000/root/nautilus/commit/f7ce827c314c9340b075657fd61c789fb01cf74d', - sourceBranch: 'bar', - targetBranch, - }; - - const service = { - removeSourceBranch() {}, - }; - - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - - vm = mountComponent(Component, { mr, service }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('shouldShowRemoveSourceBranch', () => { - it('returns true when sourceBranchRemoved is false', () => { - vm.mr.sourceBranchRemoved = false; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(true); - }); - - it('returns false when sourceBranchRemoved is true', () => { - vm.mr.sourceBranchRemoved = true; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - }); - - it('returns false when canRemoveSourceBranch is false', () => { - vm.mr.sourceBranchRemoved = false; - vm.mr.canRemoveSourceBranch = false; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - }); - - it('returns false when is making request', () => { - vm.mr.canRemoveSourceBranch = true; - vm.isMakingRequest = true; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - }); - - it('returns true when all are true', () => { - vm.mr.isRemovingSourceBranch = true; - vm.mr.canRemoveSourceBranch = true; - vm.isMakingRequest = true; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - }); - }); - - describe('shouldShowSourceBranchRemoving', () => { - it('should correct value when fields changed', () => { - vm.mr.sourceBranchRemoved = false; - - expect(vm.shouldShowSourceBranchRemoving).toEqual(false); - - vm.mr.sourceBranchRemoved = true; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - - vm.mr.sourceBranchRemoved = false; - vm.isMakingRequest = true; - - expect(vm.shouldShowSourceBranchRemoving).toEqual(true); - - vm.isMakingRequest = false; - vm.mr.isRemovingSourceBranch = true; - - expect(vm.shouldShowSourceBranchRemoving).toEqual(true); - }); - }); - }); - - describe('methods', () => { - describe('removeSourceBranch', () => { - it('should set flag and call service then request main component to update the widget', async () => { - jest.spyOn(vm.service, 'removeSourceBranch').mockReturnValue( - new Promise((resolve) => { - resolve({ - data: { - message: 'Branch was deleted', - }, - }); - }), - ); - - vm.removeSourceBranch(); - - await waitForPromises(); - - const args = eventHub.$emit.mock.calls[0]; - - expect(vm.isMakingRequest).toEqual(true); - expect(args[0]).toEqual('MRWidgetUpdateRequested'); - expect(args[1]).not.toThrow(); - }); - }); - }); - - it('calls dispatchDocumentEvent to load in the modal component', () => { - expect(document.dispatchEvent).toHaveBeenCalledWith(new CustomEvent('merged:UpdateActions')); - }); - - it('emits event to open the revert modal on revert button click', () => { - const eventHubSpy = jest.spyOn(modalEventHub, '$emit'); - - getByRole(vm.$el, 'button', { name: /Revert/i }).click(); - - expect(eventHubSpy).toHaveBeenCalledWith(OPEN_REVERT_MODAL); - }); - - it('emits event to open the cherry-pick modal on cherry-pick button click', () => { - const eventHubSpy = jest.spyOn(modalEventHub, '$emit'); - - getByRole(vm.$el, 'button', { name: /Cherry-pick/i }).click(); - - expect(eventHubSpy).toHaveBeenCalledWith(OPEN_CHERRY_PICK_MODAL); - }); - - it('has merged by information', () => { - expect(vm.$el.textContent).toContain('Merged by'); - expect(vm.$el.textContent).toContain('Administrator'); - }); - - it('renders branch information', () => { - expect(vm.$el.textContent).toContain('The changes were merged into'); - expect(vm.$el.textContent).toContain(targetBranch); - }); - - it('renders information about branch being deleted', () => { - expect(vm.$el.textContent).toContain('The source branch has been deleted'); - }); - - it('shows revert and cherry-pick buttons', () => { - expect(vm.$el.textContent).toContain('Revert'); - expect(vm.$el.textContent).toContain('Cherry-pick'); - }); - - it('shows button to copy commit SHA to clipboard', () => { - expect(selectors.copyMergeShaButton).not.toBe(null); - expect(selectors.copyMergeShaButton.dataset.clipboardText).toBe(vm.mr.mergeCommitSha); - }); - - it('hides button to copy commit SHA if SHA does not exist', async () => { - vm.mr.mergeCommitSha = null; - - await nextTick(); - - expect(selectors.copyMergeShaButton).toBe(null); - expect(vm.$el.querySelector('.mr-info-list').innerText).not.toContain('with'); - }); - - it('shows merge commit SHA link', () => { - expect(selectors.mergeCommitShaLink).not.toBe(null); - expect(selectors.mergeCommitShaLink.text).toContain(vm.mr.shortMergeCommitSha); - expect(selectors.mergeCommitShaLink.href).toBe(vm.mr.mergeCommitPath); - }); - - it('should not show source branch deleted text', async () => { - vm.mr.sourceBranchRemoved = false; - - await nextTick(); - - expect(vm.$el.innerText).not.toContain('The source branch has been deleted'); - }); - - it('should show source branch deleting text', async () => { - vm.mr.isRemovingSourceBranch = true; - vm.mr.sourceBranchRemoved = false; - - await nextTick(); - - expect(vm.$el.innerText).toContain('The source branch is being deleted'); - expect(vm.$el.innerText).not.toContain('The source branch has been deleted'); - }); - - it('should use mergedEvent mergedAt as tooltip title', () => { - expect(vm.$el.querySelector('time').getAttribute('title')).toBe('Jan 24, 2018 1:02pm UTC'); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js deleted file mode 100644 index e16c897a49b..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js +++ /dev/null @@ -1,74 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import simplePoll from '~/lib/utils/simple_poll'; -import MrWidgetMerging from '~/vue_merge_request_widget/components/states/mr_widget_merging.vue'; - -jest.mock('~/lib/utils/simple_poll', () => - jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default), -); - -describe('MRWidgetMerging', () => { - let wrapper; - - const GlEmoji = { template: '<img />' }; - beforeEach(() => { - wrapper = shallowMount(MrWidgetMerging, { - propsData: { - mr: { - targetBranchPath: '/branch-path', - targetBranch: 'branch', - transitionStateMachine() {}, - }, - service: { - poll: jest.fn().mockResolvedValue(), - }, - }, - stubs: { - GlEmoji, - }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders information about merge request being merged', () => { - expect( - wrapper - .find('.media-body') - .text() - .trim() - .replace(/\s\s+/g, ' ') - .replace(/[\r\n]+/g, ' '), - ).toContain('Merging!'); - }); - - it('renders branch information', () => { - expect( - wrapper - .find('.mr-info-list') - .text() - .trim() - .replace(/\s\s+/g, ' ') - .replace(/[\r\n]+/g, ' '), - ).toEqual('Merges changes into branch'); - - expect(wrapper.find('a').attributes('href')).toBe('/branch-path'); - }); - - describe('initiateMergePolling', () => { - it('should call simplePoll', () => { - wrapper.vm.initiateMergePolling(); - - expect(simplePoll).toHaveBeenCalledWith(expect.any(Function), { timeout: 0 }); - }); - - it('should call handleMergePolling', () => { - jest.spyOn(wrapper.vm, 'handleMergePolling').mockImplementation(() => {}); - - wrapper.vm.initiateMergePolling(); - - expect(wrapper.vm.handleMergePolling).toHaveBeenCalled(); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js deleted file mode 100644 index ddce07954ab..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js +++ /dev/null @@ -1,49 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import MissingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue'; - -let wrapper; - -async function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) { - wrapper = shallowMount(MissingBranchComponent, { - propsData: { - mr: { sourceBranchRemoved }, - }, - provide: { - glFeatures: { mergeRequestWidgetGraphql }, - }, - }); - - if (mergeRequestWidgetGraphql) { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ state: { sourceBranchExists: !sourceBranchRemoved } }); - } - - await nextTick(); -} - -describe('MRWidgetMissingBranch', () => { - afterEach(() => { - wrapper.destroy(); - }); - - [true, false].forEach((mergeRequestWidgetGraphql) => { - describe(`widget GraphQL feature flag is ${ - mergeRequestWidgetGraphql ? 'enabled' : 'disabled' - }`, () => { - it.each` - sourceBranchRemoved | branchName - ${true} | ${'source'} - ${false} | ${'target'} - `( - 'should set missing branch name as $branchName when sourceBranchRemoved is $sourceBranchRemoved', - async ({ sourceBranchRemoved, branchName }) => { - await factory(sourceBranchRemoved, mergeRequestWidgetGraphql); - - expect(wrapper.find('[data-testid="widget-content"]').text()).toContain(branchName); - }, - ); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js deleted file mode 100644 index 63e93074857..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; -import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue'; - -describe('MRWidgetNotAllowed', () => { - let vm; - beforeEach(() => { - const Component = Vue.extend(notAllowedComponent); - vm = mountComponent(Component); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders success icon', () => { - expect(vm.$el.querySelector('.ci-status-icon-success')).not.toBe(null); - }); - - it('renders informative text', () => { - expect(vm.$el.innerText).toContain('Ready to be merged automatically.'); - expect(vm.$el.innerText).toContain( - 'Ask someone with write access to this repository to merge this request', - ); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js deleted file mode 100644 index c7c0b69425d..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js +++ /dev/null @@ -1,28 +0,0 @@ -import Vue, { nextTick } from 'vue'; -import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue'; - -describe('NothingToMerge', () => { - describe('template', () => { - const Component = Vue.extend(NothingToMerge); - const newBlobPath = '/foo'; - const vm = new Component({ - el: document.createElement('div'), - propsData: { - mr: { newBlobPath }, - }, - }); - - it('should have correct elements', () => { - expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(vm.$el.querySelector('[data-testid="createFileButton"]').href).toContain(newBlobPath); - expect(vm.$el.innerText).toContain('Use merge requests to propose changes to your project'); - }); - - it('should not show new blob link if there is no link available', () => { - vm.mr.newBlobPath = null; - nextTick(() => { - expect(vm.$el.querySelector('[data-testid="createFileButton"]')).toEqual(null); - }); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js deleted file mode 100644 index 9b10b078e89..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js +++ /dev/null @@ -1,28 +0,0 @@ -import { shallowMount, mount } from '@vue/test-utils'; -import PipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue'; - -describe('MRWidgetPipelineBlocked', () => { - let wrapper; - - const createWrapper = (mountFn = shallowMount) => { - wrapper = mountFn(PipelineBlockedComponent); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders warning icon', () => { - createWrapper(mount); - - expect(wrapper.find('.ci-status-icon-warning').exists()).toBe(true); - }); - - it('renders information text', () => { - createWrapper(); - - expect(wrapper.text()).toBe( - "Merge blocked: pipeline must succeed. It's waiting for a manual action to continue.", - ); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js deleted file mode 100644 index 3e0840fef4e..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js +++ /dev/null @@ -1,30 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; -import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue'; - -describe('PipelineFailed', () => { - let wrapper; - - const createComponent = () => { - wrapper = shallowMount(PipelineFailed); - }; - - const findStatusIcon = () => wrapper.find(statusIcon); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - it('should render error message with a disabled merge button', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - it('merge button should be disabled', () => { - expect(findStatusIcon().props('showDisabledButton')).toBe(true); - }); -}); 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 deleted file mode 100644 index 46d90ddc83c..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ /dev/null @@ -1,905 +0,0 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import { GlSprintf } from '@gitlab/ui'; -import VueApollo from 'vue-apollo'; -import produce from 'immer'; -import readyToMergeResponse from 'test_fixtures/graphql/merge_requests/states/ready_to_merge.query.graphql.json'; -import waitForPromises from 'helpers/wait_for_promises'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql'; -import simplePoll from '~/lib/utils/simple_poll'; -import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; -import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; -import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue'; -import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue'; -import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; -import MergeFailedPipelineConfirmationDialog from '~/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog.vue'; -import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; -import eventHub from '~/vue_merge_request_widget/event_hub'; - -jest.mock('~/lib/utils/simple_poll', () => - jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default), -); -jest.mock('~/commons/nav/user_merge_requests', () => ({ - refreshUserMergeRequestCounts: jest.fn(), -})); - -const commitMessage = readyToMergeResponse.data.project.mergeRequest.defaultMergeCommitMessage; -const squashCommitMessage = - readyToMergeResponse.data.project.mergeRequest.defaultSquashCommitMessage; -const commitMessageWithDescription = - readyToMergeResponse.data.project.mergeRequest.defaultMergeCommitMessageWithDescription; -const createTestMr = (customConfig) => { - const mr = { - isPipelineActive: false, - pipeline: null, - isPipelineFailed: false, - isPipelinePassing: false, - isMergeAllowed: true, - isApproved: true, - onlyAllowMergeIfPipelineSucceeds: false, - ffOnlyEnabled: false, - hasCI: false, - ciStatus: null, - sha: '12345678', - squash: false, - squashIsEnabledByDefault: false, - squashIsReadonly: false, - squashIsSelected: false, - commitMessage, - squashCommitMessage, - commitMessageWithDescription, - defaultMergeCommitMessage: commitMessage, - defaultSquashCommitMessage: squashCommitMessage, - shouldRemoveSourceBranch: true, - canRemoveSourceBranch: false, - targetBranch: 'main', - preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY, - availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY], - mergeImmediatelyDocsPath: 'path/to/merge/immediately/docs', - transitionStateMachine: (transition) => eventHub.$emit('StateMachineValueChanged', transition), - translateStateToMachine: () => this.transitionStateMachine(), - state: 'open', - }; - - Object.assign(mr, customConfig.mr); - - return mr; -}; - -const createTestService = () => ({ - merge: jest.fn(), - poll: jest.fn().mockResolvedValue(), -}); -const localVue = createLocalVue(); -localVue.use(VueApollo); - -let wrapper; -let readyToMergeResponseSpy; - -const findMergeButton = () => wrapper.find('[data-testid="merge-button"]'); -const findPipelineFailedConfirmModal = () => - wrapper.findComponent(MergeFailedPipelineConfirmationDialog); - -const createReadyToMergeResponse = (customMr) => { - return produce(readyToMergeResponse, (draft) => { - Object.assign(draft.data.project.mergeRequest, customMr); - }); -}; - -const createComponent = ( - customConfig = {}, - mergeRequestWidgetGraphql = false, - restructuredMrWidget = false, -) => { - wrapper = shallowMount(ReadyToMerge, { - localVue, - propsData: { - mr: createTestMr(customConfig), - service: createTestService(), - }, - provide: { - glFeatures: { - mergeRequestWidgetGraphql, - restructuredMrWidget, - }, - }, - stubs: { - CommitEdit, - }, - apolloProvider: createMockApollo([[readyToMergeQuery, readyToMergeResponseSpy]]), - }); -}; - -const findCheckboxElement = () => wrapper.find(SquashBeforeMerge); -const findCommitsHeaderElement = () => wrapper.find(CommitsHeader); -const findCommitEditElements = () => wrapper.findAll(CommitEdit); -const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown); -const findFirstCommitEditLabel = () => findCommitEditElements().at(0).props('label'); -const findTipLink = () => wrapper.find(GlSprintf); -const findCommitEditWithInputId = (inputId) => - findCommitEditElements().wrappers.find((x) => x.props('inputId') === inputId); -const findMergeCommitMessage = () => findCommitEditWithInputId('merge-message-edit').props('value'); -const findSquashCommitMessage = () => - findCommitEditWithInputId('squash-message-edit').props('value'); - -const triggerApprovalUpdated = () => eventHub.$emit('ApprovalUpdated'); - -describe('ReadyToMerge', () => { - beforeEach(() => { - readyToMergeResponseSpy = jest.fn().mockResolvedValueOnce(readyToMergeResponse); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('computed', () => { - describe('isAutoMergeAvailable', () => { - it('should return true when at least one merge strategy is available', () => { - createComponent(); - - expect(wrapper.vm.isAutoMergeAvailable).toBe(true); - }); - - it('should return false when no merge strategies are available', () => { - createComponent({ mr: { availableAutoMergeStrategies: [] } }); - - expect(wrapper.vm.isAutoMergeAvailable).toBe(false); - }); - }); - - describe('status', () => { - it('defaults to success', () => { - createComponent({ mr: { pipeline: true, availableAutoMergeStrategies: [] } }); - - expect(wrapper.vm.status).toEqual('success'); - }); - - it('returns failed when MR has CI but also has an unknown status', () => { - createComponent({ mr: { hasCI: true } }); - - expect(wrapper.vm.status).toEqual('failed'); - }); - - it('returns default when MR has no pipeline', () => { - createComponent({ mr: { availableAutoMergeStrategies: [] } }); - - expect(wrapper.vm.status).toEqual('success'); - }); - - it('returns pending when pipeline is active', () => { - createComponent({ mr: { pipeline: {}, isPipelineActive: true } }); - - expect(wrapper.vm.status).toEqual('pending'); - }); - - it('returns failed when pipeline is failed', () => { - createComponent({ - mr: { pipeline: {}, isPipelineFailed: true, availableAutoMergeStrategies: [] }, - }); - - expect(wrapper.vm.status).toEqual('failed'); - }); - }); - - describe('Merge Button Variant', () => { - it('defaults to confirm class', () => { - createComponent({ - mr: { availableAutoMergeStrategies: [] }, - }); - - expect(findMergeButton().attributes('variant')).toBe('confirm'); - }); - }); - - describe('status icon', () => { - it('defaults to tick icon', () => { - createComponent(); - - expect(wrapper.vm.iconClass).toEqual('success'); - }); - - it('shows tick for success status', () => { - createComponent({ mr: { pipeline: true } }); - - expect(wrapper.vm.iconClass).toEqual('success'); - }); - - it('shows tick for pending status', () => { - createComponent({ mr: { pipeline: {}, isPipelineActive: true } }); - - expect(wrapper.vm.iconClass).toEqual('success'); - }); - }); - - describe('mergeButtonText', () => { - it('should return "Merge" when no auto merge strategies are available', () => { - createComponent({ mr: { availableAutoMergeStrategies: [] } }); - - expect(wrapper.vm.mergeButtonText).toEqual('Merge'); - }); - - it('should return "Merge in progress"', async () => { - createComponent(); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ isMergingImmediately: true }); - - await nextTick(); - - expect(wrapper.vm.mergeButtonText).toEqual('Merge in progress'); - }); - - it('should return "Merge when pipeline succeeds" when the MWPS auto merge strategy is available', () => { - createComponent({ - mr: { isMergingImmediately: false, preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY }, - }); - - expect(wrapper.vm.mergeButtonText).toEqual('Merge when pipeline succeeds'); - }); - }); - - describe('autoMergeText', () => { - it('should return Merge when pipeline succeeds', () => { - createComponent({ mr: { preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY } }); - - expect(wrapper.vm.autoMergeText).toEqual('Merge when pipeline succeeds'); - }); - }); - - describe('shouldShowMergeImmediatelyDropdown', () => { - it('should return false if no pipeline is active', () => { - createComponent({ - mr: { isPipelineActive: false, onlyAllowMergeIfPipelineSucceeds: false }, - }); - - expect(wrapper.vm.shouldShowMergeImmediatelyDropdown).toBe(false); - }); - - it('should return false if "Pipelines must succeed" is enabled for the current project', () => { - createComponent({ mr: { isPipelineActive: true, onlyAllowMergeIfPipelineSucceeds: true } }); - - expect(wrapper.vm.shouldShowMergeImmediatelyDropdown).toBe(false); - }); - }); - - describe('isMergeButtonDisabled', () => { - it('should return false with initial data', () => { - createComponent({ mr: { isMergeAllowed: true } }); - - expect(wrapper.vm.isMergeButtonDisabled).toBe(false); - }); - - it('should return true when there is no commit message', () => { - createComponent({ mr: { isMergeAllowed: true, commitMessage: '' } }); - - expect(wrapper.vm.isMergeButtonDisabled).toBe(true); - }); - - it('should return true if merge is not allowed', () => { - createComponent({ - mr: { - isMergeAllowed: false, - availableAutoMergeStrategies: [], - onlyAllowMergeIfPipelineSucceeds: true, - }, - }); - - expect(wrapper.vm.isMergeButtonDisabled).toBe(true); - }); - - it('should return true when the vm instance is making request', async () => { - createComponent({ mr: { isMergeAllowed: true } }); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ isMakingRequest: true }); - - await nextTick(); - - expect(wrapper.vm.isMergeButtonDisabled).toBe(true); - }); - }); - }); - - describe('methods', () => { - describe('handleMergeButtonClick', () => { - const response = (status) => ({ - data: { - status, - }, - }); - - beforeEach(() => { - readyToMergeResponseSpy = jest - .fn() - .mockResolvedValueOnce(createReadyToMergeResponse({ squash: true, squashOnMerge: true })) - .mockResolvedValue( - createReadyToMergeResponse({ - squash: true, - squashOnMerge: true, - defaultMergeCommitMessage: '', - defaultSquashCommitMessage: '', - }), - ); - }); - - it('should handle merge when pipeline succeeds', async () => { - createComponent(); - - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest - .spyOn(wrapper.vm.service, 'merge') - .mockResolvedValue(response('merge_when_pipeline_succeeds')); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ removeSourceBranch: false }); - - wrapper.vm.handleMergeButtonClick(true); - - await waitForPromises(); - - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { - transition: 'start-auto-merge', - }); - - const params = wrapper.vm.service.merge.mock.calls[0][0]; - - expect(params).toEqual( - expect.objectContaining({ - sha: wrapper.vm.mr.sha, - commit_message: wrapper.vm.mr.commitMessage, - should_remove_source_branch: false, - auto_merge_strategy: 'merge_when_pipeline_succeeds', - }), - ); - }); - - it('should handle merge failed', async () => { - createComponent(); - - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('failed')); - wrapper.vm.handleMergeButtonClick(false, true); - - await waitForPromises(); - - expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); - - const params = wrapper.vm.service.merge.mock.calls[0][0]; - - expect(params.should_remove_source_branch).toBeTruthy(); - expect(params.auto_merge_strategy).toBeUndefined(); - }); - - it('should handle merge action accepted case', async () => { - createComponent(); - - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('success')); - jest.spyOn(wrapper.vm.mr, 'transitionStateMachine'); - wrapper.vm.handleMergeButtonClick(); - - expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { - transition: 'start-merge', - }); - - await waitForPromises(); - - expect(wrapper.vm.mr.transitionStateMachine).toHaveBeenCalledWith({ - transition: 'start-merge', - }); - - const params = wrapper.vm.service.merge.mock.calls[0][0]; - - expect(params.should_remove_source_branch).toBeTruthy(); - expect(params.auto_merge_strategy).toBeUndefined(); - }); - - it('hides edit commit message', async () => { - createComponent({}, true, true); - - await waitForPromises(); - - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('success')); - - await wrapper - .findComponent('[data-testid="widget_edit_commit_message"]') - .vm.$emit('input', true); - - expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(true); - - wrapper.vm.handleMergeButtonClick(); - - await waitForPromises(); - - expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(false); - }); - }); - - describe('initiateRemoveSourceBranchPolling', () => { - it('should emit event and call simplePoll', () => { - createComponent(); - - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - - wrapper.vm.initiateRemoveSourceBranchPolling(); - - expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]); - expect(simplePoll).toHaveBeenCalled(); - }); - }); - - describe('handleRemoveBranchPolling', () => { - const response = (state) => ({ - data: { - source_branch_exists: state, - }, - }); - - it('should call start and stop polling when MR merged', async () => { - createComponent(); - - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(false)); - - let cpc = false; // continuePollingCalled - let spc = false; // stopPollingCalled - - wrapper.vm.handleRemoveBranchPolling( - () => { - cpc = true; - }, - () => { - spc = true; - }, - ); - - await waitForPromises(); - - expect(wrapper.vm.service.poll).toHaveBeenCalled(); - - const args = eventHub.$emit.mock.calls[0]; - - expect(args[0]).toEqual('MRWidgetUpdateRequested'); - expect(args[1]).toBeDefined(); - args[1](); - - expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]); - - expect(cpc).toBeFalsy(); - expect(spc).toBeTruthy(); - }); - - it('should continue polling until MR is merged', async () => { - createComponent(); - - jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(true)); - - let cpc = false; // continuePollingCalled - let spc = false; // stopPollingCalled - - wrapper.vm.handleRemoveBranchPolling( - () => { - cpc = true; - }, - () => { - spc = true; - }, - ); - - await waitForPromises(); - - expect(cpc).toBeTruthy(); - expect(spc).toBeFalsy(); - }); - }); - }); - - describe('Remove source branch checkbox', () => { - describe('when user can merge but cannot delete branch', () => { - it('should be disabled in the rendered output', () => { - createComponent(); - - expect(wrapper.find('#remove-source-branch-input').exists()).toBe(false); - }); - }); - - describe('when user can merge and can delete branch', () => { - beforeEach(() => { - createComponent({ - mr: { canRemoveSourceBranch: true }, - }); - }); - - it('isRemoveSourceBranchButtonDisabled should be false', () => { - expect(wrapper.find('#remove-source-branch-input').props('disabled')).toBe(undefined); - }); - }); - }); - - describe('render children components', () => { - describe('squash checkbox', () => { - it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => { - createComponent({ - mr: { commitsCount: 2, enableSquashBeforeMerge: true }, - }); - - expect(findCheckboxElement().exists()).toBeTruthy(); - }); - - it('should not be rendered when squash before merge is disabled', () => { - createComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } }); - - expect(findCheckboxElement().exists()).toBeFalsy(); - }); - - it('should be rendered when there is only 1 commit', () => { - createComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } }); - - expect(findCheckboxElement().exists()).toBe(true); - }); - - 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 }) => { - createComponent({ - mr: { commitsCount: 2, enableSquashBeforeMerge: true, [squashState]: expectation }, - }); - - expect(findCheckboxElement().props(prop)).toBe(expectation); - }, - ); - - it('is not rendered for "Do not allow" option', () => { - createComponent({ - mr: { - commitsCount: 2, - enableSquashBeforeMerge: true, - squashIsReadonly: true, - squashIsSelected: false, - }, - }); - - expect(findCheckboxElement().exists()).toBe(false); - }); - }); - }); - - describe('commits count collapsible header', () => { - it('should be rendered when fast-forward is disabled', () => { - createComponent(); - - expect(findCommitsHeaderElement().exists()).toBeTruthy(); - }); - - describe('when fast-forward is enabled', () => { - it('should be rendered if squash and squash before are enabled and there is more than 1 commit', () => { - createComponent({ - mr: { - ffOnlyEnabled: true, - enableSquashBeforeMerge: true, - squashIsSelected: true, - commitsCount: 2, - }, - }); - - expect(findCommitsHeaderElement().exists()).toBeTruthy(); - }); - - it('should not be rendered if squash before merge is disabled', () => { - createComponent({ - mr: { - ffOnlyEnabled: true, - enableSquashBeforeMerge: false, - squash: true, - commitsCount: 2, - }, - }); - - expect(findCommitsHeaderElement().exists()).toBeFalsy(); - }); - - it('should not be rendered if squash is disabled', () => { - createComponent({ - mr: { - ffOnlyEnabled: true, - squash: false, - enableSquashBeforeMerge: true, - commitsCount: 2, - }, - }); - - expect(findCommitsHeaderElement().exists()).toBeFalsy(); - }); - - it('should not be rendered if commits count is 1', () => { - createComponent({ - mr: { - ffOnlyEnabled: true, - squash: true, - enableSquashBeforeMerge: true, - commitsCount: 1, - }, - }); - - expect(findCommitsHeaderElement().exists()).toBeFalsy(); - }); - }); - }); - - describe('commits edit components', () => { - describe('when fast-forward merge is enabled', () => { - it('should not be rendered if squash is disabled', () => { - createComponent({ - mr: { - ffOnlyEnabled: true, - squash: false, - enableSquashBeforeMerge: true, - commitsCount: 2, - }, - }); - - expect(findCommitEditElements().length).toBe(0); - }); - - it('should not be rendered if squash before merge is disabled', () => { - createComponent({ - mr: { - ffOnlyEnabled: true, - squash: true, - enableSquashBeforeMerge: false, - commitsCount: 2, - }, - }); - - expect(findCommitEditElements().length).toBe(0); - }); - - it('should not be rendered if there is only one commit', () => { - createComponent({ - mr: { - ffOnlyEnabled: true, - squash: true, - enableSquashBeforeMerge: true, - commitsCount: 1, - }, - }); - - expect(findCommitEditElements().length).toBe(0); - }); - - it('should have one edit component if squash is enabled and there is more than 1 commit', () => { - createComponent({ - mr: { - ffOnlyEnabled: true, - squashIsSelected: true, - enableSquashBeforeMerge: true, - commitsCount: 2, - }, - }); - - expect(findCommitEditElements().length).toBe(1); - expect(findFirstCommitEditLabel()).toBe('Squash commit message'); - }); - }); - - it('should have one edit component when squash is disabled', () => { - createComponent(); - - expect(findCommitEditElements().length).toBe(1); - }); - - it('should have two edit components when squash is enabled and there is more than 1 commit', () => { - createComponent({ - mr: { - commitsCount: 2, - squashIsSelected: true, - enableSquashBeforeMerge: true, - }, - }); - - expect(findCommitEditElements().length).toBe(2); - }); - - it('should have two edit components when squash is enabled and there is more than 1 commit and mergeRequestWidgetGraphql is enabled', async () => { - createComponent( - { - mr: { - commitsCount: 2, - squashIsSelected: true, - enableSquashBeforeMerge: true, - }, - }, - true, - ); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - loading: false, - state: { - ...createTestMr({}), - userPermissions: {}, - squash: true, - mergeable: true, - commitCount: 2, - commitsWithoutMergeCommits: {}, - }, - }); - await nextTick(); - - expect(findCommitEditElements().length).toBe(2); - }); - - it('should have one edit components when squash is enabled and there is 1 commit only', () => { - createComponent({ - mr: { - commitsCount: 1, - squash: true, - enableSquashBeforeMerge: true, - }, - }); - - expect(findCommitEditElements().length).toBe(1); - }); - - it('should have correct edit merge commit label', () => { - createComponent(); - - expect(findFirstCommitEditLabel()).toBe('Merge commit message'); - }); - - it('should have correct edit squash commit label', () => { - createComponent({ - mr: { - commitsCount: 2, - squashIsSelected: true, - enableSquashBeforeMerge: true, - }, - }); - - expect(findFirstCommitEditLabel()).toBe('Squash commit message'); - }); - }); - - describe('commits dropdown', () => { - it('should not be rendered if squash is disabled', () => { - createComponent(); - - expect(findCommitDropdownElement().exists()).toBeFalsy(); - }); - - it('should be rendered if squash is enabled and there is more than 1 commit', () => { - createComponent({ - mr: { enableSquashBeforeMerge: true, squashIsSelected: true, commitsCount: 2 }, - }); - - expect(findCommitDropdownElement().exists()).toBeTruthy(); - }); - }); - - it('renders a tip including a link to docs on templates', () => { - createComponent(); - - expect(findTipLink().exists()).toBe(true); - }); - }); - - describe('Merge request project settings', () => { - describe('when the merge commit merge method is enabled', () => { - beforeEach(() => { - createComponent({ - mr: { ffOnlyEnabled: false }, - }); - }); - - it('should not show fast forward message', () => { - expect(wrapper.find('.mr-fast-forward-message').exists()).toBe(false); - }); - }); - - describe('when the fast-forward merge method is enabled', () => { - beforeEach(() => { - createComponent({ - mr: { ffOnlyEnabled: true }, - }); - }); - - it('should show fast forward message', () => { - expect(wrapper.find('.mr-fast-forward-message').exists()).toBe(true); - }); - }); - }); - - describe('Merge button when pipeline has failed', () => { - beforeEach(() => { - createComponent({ - mr: { pipeline: {}, isPipelineFailed: true, availableAutoMergeStrategies: [] }, - }); - }); - - it('should display the correct merge text', () => { - expect(findMergeButton().text()).toBe('Merge...'); - }); - - it('should display confirmation modal when merge button is clicked', async () => { - expect(findPipelineFailedConfirmModal().props()).toEqual({ visible: false }); - - await findMergeButton().vm.$emit('click'); - - expect(findPipelineFailedConfirmModal().props()).toEqual({ visible: true }); - }); - }); - - describe('updating graphql data triggers commit message update when default changed', () => { - const UPDATED_MERGE_COMMIT_MESSAGE = 'New merge message from BE'; - const UPDATED_SQUASH_COMMIT_MESSAGE = 'New squash message from BE'; - const USER_COMMIT_MESSAGE = 'Merge message provided manually by user'; - - const createDefaultGqlComponent = () => - createComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: true } }, true); - - beforeEach(() => { - readyToMergeResponseSpy = jest - .fn() - .mockResolvedValueOnce(createReadyToMergeResponse({ squash: true, squashOnMerge: true })) - .mockResolvedValue( - createReadyToMergeResponse({ - squash: true, - squashOnMerge: true, - defaultMergeCommitMessage: UPDATED_MERGE_COMMIT_MESSAGE, - defaultSquashCommitMessage: UPDATED_SQUASH_COMMIT_MESSAGE, - }), - ); - }); - - describe.each` - desc | finderFn | initialValue | updatedValue | inputId - ${'merge commit message'} | ${findMergeCommitMessage} | ${commitMessage} | ${UPDATED_MERGE_COMMIT_MESSAGE} | ${'#merge-message-edit'} - ${'squash commit message'} | ${findSquashCommitMessage} | ${squashCommitMessage} | ${UPDATED_SQUASH_COMMIT_MESSAGE} | ${'#squash-message-edit'} - `('with $desc', ({ finderFn, initialValue, updatedValue, inputId }) => { - it('should have initial value', async () => { - createDefaultGqlComponent(); - - await waitForPromises(); - - expect(finderFn()).toBe(initialValue); - }); - - it('should have updated value after graphql refetch', async () => { - createDefaultGqlComponent(); - await waitForPromises(); - - triggerApprovalUpdated(); - await waitForPromises(); - - expect(finderFn()).toBe(updatedValue); - }); - - it('should not update if user has touched', async () => { - createDefaultGqlComponent(); - await waitForPromises(); - - const input = wrapper.find(inputId); - input.element.value = USER_COMMIT_MESSAGE; - input.trigger('input'); - - triggerApprovalUpdated(); - await waitForPromises(); - - expect(finderFn()).toBe(USER_COMMIT_MESSAGE); - }); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js deleted file mode 100644 index 2a343997cf5..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js +++ /dev/null @@ -1,42 +0,0 @@ -import { mount } from '@vue/test-utils'; -import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue'; -import { I18N_SHA_MISMATCH } from '~/vue_merge_request_widget/i18n'; - -function createComponent({ path = '' } = {}) { - return mount(ShaMismatch, { - propsData: { - mr: { - mergeRequestDiffsPath: path, - }, - }, - }); -} - -describe('ShaMismatch', () => { - let wrapper; - const findActionButton = () => wrapper.find('[data-testid="action-button"]'); - - beforeEach(() => { - wrapper = createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should render warning message', () => { - expect(wrapper.element.innerText).toContain(I18N_SHA_MISMATCH.warningMessage); - }); - - it('action button should have correct label', () => { - expect(findActionButton().text()).toBe(I18N_SHA_MISMATCH.actionButtonLabel); - }); - - it('action button should link to the diff path', () => { - const DIFF_PATH = '/gitlab-org/gitlab-test/-/merge_requests/6/diffs'; - - wrapper = createComponent({ path: DIFF_PATH }); - - expect(findActionButton().attributes('href')).toBe(DIFF_PATH); - }); -}); 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 deleted file mode 100644 index 6ea2e8675d3..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js +++ /dev/null @@ -1,104 +0,0 @@ -import { GlFormCheckbox, GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; -import { SQUASH_BEFORE_MERGE } from '~/vue_merge_request_widget/i18n'; - -describe('Squash before merge component', () => { - let wrapper; - - const createComponent = (props) => { - wrapper = shallowMount(SquashBeforeMerge, { - propsData: { - ...props, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - const findCheckbox = () => wrapper.find(GlFormCheckbox); - - describe('checkbox', () => { - it('is unchecked if passed value prop is false', () => { - createComponent({ - value: false, - }); - - expect(findCheckbox().vm.$attrs.checked).toBe(false); - }); - - it('is checked if passed value prop is true', () => { - createComponent({ - value: true, - }); - - expect(findCheckbox().vm.$attrs.checked).toBe(true); - }); - - it('is disabled if isDisabled prop is true', () => { - createComponent({ - value: false, - isDisabled: true, - }); - - expect(findCheckbox().vm.$attrs.disabled).toBe(true); - }); - }); - - describe('tooltip', () => { - const tooltipTitle = () => findCheckbox().attributes('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(SQUASH_BEFORE_MERGE.tooltipTitle); - }); - }); - - describe('about link', () => { - it('is not rendered if no help path is passed', () => { - createComponent({ - value: false, - }); - - const aboutLink = wrapper.findComponent(GlLink); - - expect(aboutLink.exists()).toBe(false); - }); - - it('is rendered if help path is passed', () => { - createComponent({ - value: false, - helpPath: 'test-path', - }); - - const aboutLink = wrapper.findComponent(GlLink); - - expect(aboutLink.exists()).toBe(true); - }); - - it('should have a correct help path if passed', () => { - createComponent({ - value: false, - helpPath: 'test-path', - }); - - const aboutLink = wrapper.findComponent(GlLink); - - expect(aboutLink.attributes('href')).toEqual('test-path'); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js deleted file mode 100644 index e2d79c61b9b..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js +++ /dev/null @@ -1,64 +0,0 @@ -import { mount } from '@vue/test-utils'; -import { TEST_HOST } from 'helpers/test_constants'; -import notesEventHub from '~/notes/event_hub'; -import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue'; - -function createComponent({ path = '' } = {}) { - return mount(UnresolvedDiscussions, { - propsData: { - mr: { - createIssueToResolveDiscussionsPath: path, - }, - }, - }); -} - -describe('UnresolvedDiscussions', () => { - let wrapper; - - beforeEach(() => { - wrapper = createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('triggers the correct notes event when the jump to first unresolved discussion button is clicked', () => { - jest.spyOn(notesEventHub, '$emit'); - - wrapper.find('[data-testid="jump-to-first"]').trigger('click'); - - expect(notesEventHub.$emit).toHaveBeenCalledWith('jumpToFirstUnresolvedDiscussion'); - }); - - describe('with threads path', () => { - beforeEach(() => { - wrapper = createComponent({ path: TEST_HOST }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should have correct elements', () => { - expect(wrapper.element.innerText).toContain(`Merge blocked: all threads must be resolved.`); - - expect(wrapper.element.innerText).toContain('Jump to first unresolved thread'); - expect(wrapper.element.innerText).toContain('Create issue to resolve all threads'); - expect(wrapper.element.querySelector('.js-create-issue').getAttribute('href')).toEqual( - TEST_HOST, - ); - }); - }); - - describe('without threads path', () => { - it('should not show create issue link if user cannot create issue', () => { - expect(wrapper.element.innerText).toContain(`Merge blocked: all threads must be resolved.`); - - expect(wrapper.element.innerText).toContain('Jump to first unresolved thread'); - expect(wrapper.element.innerText).not.toContain('Create issue to resolve all threads'); - expect(wrapper.element.querySelector('.js-create-issue')).toEqual(null); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js deleted file mode 100644 index 4998147c6b6..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ /dev/null @@ -1,103 +0,0 @@ -import Vue, { nextTick } from 'vue'; -import waitForPromises from 'helpers/wait_for_promises'; -import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue'; -import toast from '~/vue_shared/plugins/global_toast'; -import eventHub from '~/vue_merge_request_widget/event_hub'; - -jest.mock('~/vue_shared/plugins/global_toast'); - -const createComponent = () => { - const Component = Vue.extend(WorkInProgress); - const mr = { - title: 'The best MR ever', - removeWIPPath: '/path/to/remove/wip', - }; - const service = { - removeWIP() {}, - }; - return new Component({ - el: document.createElement('div'), - propsData: { mr, service }, - }); -}; - -describe('Wip', () => { - describe('props', () => { - it('should have props', () => { - const { mr, service } = WorkInProgress.props; - - expect(mr.type instanceof Object).toBeTruthy(); - expect(mr.required).toBeTruthy(); - - expect(service.type instanceof Object).toBeTruthy(); - expect(service.required).toBeTruthy(); - }); - }); - - describe('data', () => { - it('should have default data', () => { - const vm = createComponent(); - - expect(vm.isMakingRequest).toBeFalsy(); - }); - }); - - describe('methods', () => { - const mrObj = { - is_new_mr_data: true, - }; - - describe('handleRemoveDraft', () => { - it('should make a request to service and handle response', async () => { - const vm = createComponent(); - - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - jest.spyOn(vm.service, 'removeWIP').mockReturnValue( - new Promise((resolve) => { - resolve({ - data: mrObj, - }); - }), - ); - - vm.handleRemoveDraft(); - - await waitForPromises(); - - expect(vm.isMakingRequest).toBeTruthy(); - expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - expect(toast).toHaveBeenCalledWith('Marked as ready. Merging is now allowed.'); - }); - }); - }); - - describe('template', () => { - let vm; - let el; - - beforeEach(() => { - vm = createComponent(); - el = vm.$el; - }); - - it('should have correct elements', () => { - expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.innerText).toContain( - "Merge blocked: merge request must be marked as ready. It's still marked as draft.", - ); - expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(el.querySelector('button').innerText).toContain('Merge'); - expect(el.querySelector('.js-remove-draft').innerText.replace(/\s\s+/g, ' ')).toContain( - 'Mark as ready', - ); - }); - - it('should not show removeWIP button is user cannot update MR', async () => { - vm.mr.removeWIPPath = ''; - - await nextTick(); - - expect(el.querySelector('.js-remove-draft')).toEqual(null); - }); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/states/new_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/new_ready_to_merge_spec.js deleted file mode 100644 index 5ec9654a4af..00000000000 --- a/spec/frontend/vue_mr_widget/components/states/new_ready_to_merge_spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import ReadyToMerge from '~/vue_merge_request_widget/components/states/new_ready_to_merge.vue'; - -let wrapper; - -function factory({ canMerge }) { - wrapper = shallowMount(ReadyToMerge, { - propsData: { - mr: {}, - }, - data() { - return { canMerge }; - }, - }); -} - -describe('New ready to merge state component', () => { - afterEach(() => { - wrapper.destroy(); - }); - - it.each` - canMerge - ${true} - ${false} - `('renders permission text if canMerge ($canMerge) is false', ({ canMerge }) => { - factory({ canMerge }); - - expect(wrapper.element).toMatchSnapshot(); - }); -}); diff --git a/spec/frontend/vue_mr_widget/components/terraform/mock_data.js b/spec/frontend/vue_mr_widget/components/terraform/mock_data.js deleted file mode 100644 index 8e46af5dfd6..00000000000 --- a/spec/frontend/vue_mr_widget/components/terraform/mock_data.js +++ /dev/null @@ -1,31 +0,0 @@ -export const invalidPlanWithName = { - job_name: 'Invalid Plan', - job_path: '/path/to/ci/logs/3', - 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/2', -}; - -export const plans = { - invalid_plan_one: invalidPlanWithName, - invalid_plan_two: invalidPlanWithoutName, - 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 deleted file mode 100644 index 8f20d6a8fc9..00000000000 --- a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js +++ /dev/null @@ -1,174 +0,0 @@ -import { GlSkeletonLoader, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; -import axios from '~/lib/utils/axios_utils'; -import Poll from '~/lib/utils/poll'; -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 TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue'; -import { invalidPlanWithName, plans, validPlanWithName } from './mock_data'; - -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(async () => { - mockPollingApi(200, plans, {}); - - await mountWrapper(); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ loading: true }); - await nextTick(); - }); - - it('diplays loading skeleton', () => { - expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true); - expect(wrapper.find(MrWidgetExpanableSection).exists()).toBe(false); - }); - }); - - describe('when data has finished loading', () => { - beforeEach(() => { - mockPollingApi(200, plans, {}); - return mountWrapper(); - }); - - it('displays terraform content', () => { - expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false); - expect(wrapper.find(MrWidgetExpanableSection).exists()).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.findComponent(GlSkeletonLoader).exists()).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 deleted file mode 100644 index 3c9f6c2e165..00000000000 --- a/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js +++ /dev/null @@ -1,93 +0,0 @@ -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 job ${validPlanWithName.job_name} generated a report.`); - }); - - 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 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 job ${invalidPlanWithName.job_name} failed to generate a report.`, - ); - }); - - 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 report failed to generate.'); - }); - - it('does not render button because url is missing', () => { - expect(findLogButton().exists()).toBe(false); - }); - }); -}); |