From 2017bc90a671eac669f0114b6ef508e151409c4f Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 19 Apr 2023 12:15:59 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/frontend/__helpers__/wait_for_text.js | 2 +- .../components/ci_variable_shared_spec.js | 2 +- .../registration/registration_instructions_spec.js | 2 +- .../design_notes/design_reply_form_spec.js | 8 +- spec/frontend/diffs/components/diff_view_spec.js | 19 +- .../frontend/pipelines/pipeline_operations_spec.js | 26 +- .../pipelines_manual_actions_legacy_spec.js | 168 ---------- .../repository/components/fork_info_spec.js | 2 +- .../repository/components/table/row_spec.js | 22 +- .../components/scope_new_navigation_spec.js | 83 +++++ .../super_sidebar/components/create_menu_spec.js | 24 ++ .../super_sidebar/components/help_center_spec.js | 6 + .../super_sidebar/components/user_menu_spec.js | 11 +- .../tags/components/delete_tag_modal_spec.js | 2 +- .../components/mr_widget_rebase_spec.js | 343 ++++++++++++--------- 15 files changed, 345 insertions(+), 375 deletions(-) delete mode 100644 spec/frontend/pipelines/pipelines_manual_actions_legacy_spec.js create mode 100644 spec/frontend/search/sidebar/components/scope_new_navigation_spec.js (limited to 'spec/frontend') diff --git a/spec/frontend/__helpers__/wait_for_text.js b/spec/frontend/__helpers__/wait_for_text.js index 6bed8a90a98..991adc5d6c0 100644 --- a/spec/frontend/__helpers__/wait_for_text.js +++ b/spec/frontend/__helpers__/wait_for_text.js @@ -1,3 +1,3 @@ import { findByText } from '@testing-library/dom'; -export const waitForText = async (text, container = document) => findByText(container, text); +export const waitForText = (text, container = document) => findByText(container, text); diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js index 06b3ec4aab8..a25d325f7a1 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js @@ -626,7 +626,7 @@ describe('Ci Variable Shared Component', () => { } }); - it('report custom validator error on wrong data', async () => { + it('report custom validator error on wrong data', () => { expect(() => assertProps( ciVariableShared, diff --git a/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js b/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js index 629272c0bf0..8c196d7b5e3 100644 --- a/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js +++ b/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js @@ -280,7 +280,7 @@ describe('RegistrationInstructions', () => { }); describe('when the page is closing', () => { - it('warns the user against closing', async () => { + it('warns the user against closing', () => { const { event, preventDefault, returnValueSetter } = mockBeforeunload(); expect(preventDefault).not.toHaveBeenCalled(); diff --git a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js index c8fa02cb6aa..f08efc0c685 100644 --- a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js +++ b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js @@ -124,7 +124,7 @@ describe('Design reply form component', () => { ${'gid://gitlab/DiffDiscussion/123'} | ${123} `( 'initializes autosave support on discussion with proper key', - async ({ discussionId, shortDiscussionId }) => { + ({ discussionId, shortDiscussionId }) => { createComponent({ props: { discussionId } }); expect(Autosave).toHaveBeenCalledWith(expect.any(Element), [ @@ -136,7 +136,7 @@ describe('Design reply form component', () => { ); describe('when form has no text', () => { - beforeEach(async () => { + beforeEach(() => { createComponent(); }); @@ -148,7 +148,7 @@ describe('Design reply form component', () => { key | keyData ${'ctrl'} | ${ctrlKey} ${'meta'} | ${metaKey} - `('does not perform mutation on textarea $key+enter keydown', async ({ keyData }) => { + `('does not perform mutation on textarea $key+enter keydown', ({ keyData }) => { findTextarea().trigger('keydown.enter', keyData); expect(mockMutationHandler).not.toHaveBeenCalled(); @@ -266,7 +266,7 @@ describe('Design reply form component', () => { expect(wrapper.emitted('cancel-form')).toHaveLength(1); }); - it('opens confirmation modal on Escape key when text has changed', async () => { + it('opens confirmation modal on Escape key when text has changed', () => { createComponent(); findTextarea().setValue(mockComment); diff --git a/spec/frontend/diffs/components/diff_view_spec.js b/spec/frontend/diffs/components/diff_view_spec.js index 9bff6bd14f1..cfc80e61b30 100644 --- a/spec/frontend/diffs/components/diff_view_spec.js +++ b/spec/frontend/diffs/components/diff_view_spec.js @@ -14,7 +14,7 @@ describe('DiffView', () => { const setSelectedCommentPosition = jest.fn(); const getDiffRow = (wrapper) => wrapper.findComponent(DiffRow).vm; - const createWrapper = (props, provide = {}) => { + const createWrapper = (props) => { Vue.use(Vuex); const batchComments = { @@ -48,7 +48,7 @@ describe('DiffView', () => { ...props, }; const stubs = { DiffExpansionCell, DiffRow, DiffCommentCell, DraftNote }; - return shallowMount(DiffView, { propsData, store, stubs, provide }); + return shallowMount(DiffView, { propsData, store, stubs }); }; it('does not render a diff-line component when there is no finding', () => { @@ -56,24 +56,13 @@ describe('DiffView', () => { expect(wrapper.findComponent(DiffLine).exists()).toBe(false); }); - it('does render a diff-line component with the correct props when there is a finding & refactorCodeQualityInlineFindings flag is true', async () => { - const wrapper = createWrapper(diffCodeQuality, { - glFeatures: { refactorCodeQualityInlineFindings: true }, - }); + it('does render a diff-line component with the correct props when there is a finding', async () => { + const wrapper = createWrapper(diffCodeQuality); wrapper.findComponent(DiffRow).vm.$emit('toggleCodeQualityFindings', 2); await nextTick(); expect(wrapper.findComponent(DiffLine).props('line')).toBe(diffCodeQuality.diffLines[2]); }); - it('does not render a diff-line component when there is a finding & refactorCodeQualityInlineFindings flag is false', async () => { - const wrapper = createWrapper(diffCodeQuality, { - glFeatures: { refactorCodeQualityInlineFindings: false }, - }); - wrapper.findComponent(DiffRow).vm.$emit('toggleCodeQualityFindings', 2); - await nextTick(); - expect(wrapper.findComponent(DiffLine).exists()).toBe(false); - }); - it.each` type | side | container | sides | total ${'parallel'} | ${'left'} | ${'.old'} | ${{ left: { lineDrafts: [], renderDiscussion: true }, right: { lineDrafts: [], renderDiscussion: true } }} | ${2} diff --git a/spec/frontend/pipelines/pipeline_operations_spec.js b/spec/frontend/pipelines/pipeline_operations_spec.js index 15fc23e8b54..b2191453824 100644 --- a/spec/frontend/pipelines/pipeline_operations_spec.js +++ b/spec/frontend/pipelines/pipeline_operations_spec.js @@ -1,6 +1,5 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue'; -import PipelinesManualActionsLegacy from '~/pipelines/components/pipelines_list/pipelines_manual_actions_legacy.vue'; import PipelineMultiActions from '~/pipelines/components/pipelines_list/pipeline_multi_actions.vue'; import PipelineOperations from '~/pipelines/components/pipelines_list/pipeline_operations.vue'; import eventHub from '~/pipelines/event_hub'; @@ -15,15 +14,6 @@ describe('Pipeline operations', () => { details: { has_manual_actions: true, has_scheduled_actions: false, - manual_actions: [ - { - name: 'dont-interrupt-me', - path: '/root/ci-project/-/jobs/3974323562/play', - playable: true, - scheduled: false, - }, - ], - scheduled_actions: [], }, flags: { retryable: true, @@ -34,20 +24,14 @@ describe('Pipeline operations', () => { }, }; - const createComponent = (props = defaultProps, flagState = true) => { + const createComponent = (props = defaultProps) => { wrapper = shallowMountExtended(PipelineOperations, { - provide: { - glFeatures: { - lazyLoadPipelineDropdownActions: flagState, - }, - }, propsData: { ...props, }, }); }; - const findLegacyManualActions = () => wrapper.findComponent(PipelinesManualActionsLegacy); const findManualActions = () => wrapper.findComponent(PipelinesManualActions); const findMultiActions = () => wrapper.findComponent(PipelineMultiActions); const findRetryBtn = () => wrapper.findByTestId('pipelines-retry-button'); @@ -57,14 +41,6 @@ describe('Pipeline operations', () => { createComponent(); expect(findManualActions().exists()).toBe(true); - expect(findLegacyManualActions().exists()).toBe(false); - }); - - it('should display legacy pipeline manual actions', () => { - createComponent(defaultProps, false); - - expect(findLegacyManualActions().exists()).toBe(true); - expect(findManualActions().exists()).toBe(false); }); it('should display pipeline multi actions', () => { diff --git a/spec/frontend/pipelines/pipelines_manual_actions_legacy_spec.js b/spec/frontend/pipelines/pipelines_manual_actions_legacy_spec.js deleted file mode 100644 index 50ff301060b..00000000000 --- a/spec/frontend/pipelines/pipelines_manual_actions_legacy_spec.js +++ /dev/null @@ -1,168 +0,0 @@ -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; -import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import { TEST_HOST } from 'spec/test_constants'; -import { createAlert } from '~/alert'; -import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; -import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; -import PipelinesManualActionsLegacy from '~/pipelines/components/pipelines_list/pipelines_manual_actions_legacy.vue'; -import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; -import { TRACKING_CATEGORIES } from '~/pipelines/constants'; - -jest.mock('~/alert'); -jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); - -describe('Pipelines Actions dropdown', () => { - let wrapper; - let mock; - - const createComponent = (props, mountFn = shallowMount) => { - wrapper = mountFn(PipelinesManualActionsLegacy, { - propsData: { - ...props, - }, - }); - }; - - const findDropdown = () => wrapper.findComponent(GlDropdown); - const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); - const findAllCountdowns = () => wrapper.findAllComponents(GlCountdown); - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - confirmAction.mockReset(); - }); - - describe('manual actions', () => { - const mockActions = [ - { - name: 'stop_review', - path: `${TEST_HOST}/root/review-app/builds/1893/play`, - }, - { - name: 'foo', - path: `${TEST_HOST}/disabled/pipeline/action`, - playable: false, - }, - ]; - - beforeEach(() => { - createComponent({ actions: mockActions }); - }); - - it('renders a dropdown with the provided actions', () => { - expect(findAllDropdownItems()).toHaveLength(mockActions.length); - }); - - it("renders a disabled action when it's not playable", () => { - expect(findAllDropdownItems().at(1).attributes('disabled')).toBe('true'); - }); - - describe('on click', () => { - it('makes a request and toggles the loading state', async () => { - mock.onPost(mockActions.path).reply(HTTP_STATUS_OK); - - findAllDropdownItems().at(0).vm.$emit('click'); - - await nextTick(); - expect(findDropdown().props('loading')).toBe(true); - - await waitForPromises(); - expect(findDropdown().props('loading')).toBe(false); - }); - - it('makes a failed request and toggles the loading state', async () => { - mock.onPost(mockActions.path).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); - - findAllDropdownItems().at(0).vm.$emit('click'); - - await nextTick(); - expect(findDropdown().props('loading')).toBe(true); - - await waitForPromises(); - expect(findDropdown().props('loading')).toBe(false); - expect(createAlert).toHaveBeenCalledTimes(1); - }); - }); - - describe('tracking', () => { - afterEach(() => { - unmockTracking(); - }); - - it('tracks manual actions click', () => { - const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - - findDropdown().vm.$emit('shown'); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_manual_actions', { - label: TRACKING_CATEGORIES.table, - }); - }); - }); - }); - - describe('scheduled jobs', () => { - const scheduledJobAction = { - name: 'scheduled action', - path: `${TEST_HOST}/scheduled/job/action`, - playable: true, - scheduled_at: '2063-04-05T00:42:00Z', - }; - const expiredJobAction = { - name: 'expired action', - path: `${TEST_HOST}/expired/job/action`, - playable: true, - scheduled_at: '2018-10-05T08:23:00Z', - }; - - beforeEach(() => { - jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime()); - createComponent({ actions: [scheduledJobAction, expiredJobAction] }); - }); - - it('makes post request after confirming', async () => { - mock.onPost(scheduledJobAction.path).reply(HTTP_STATUS_OK); - confirmAction.mockResolvedValueOnce(true); - - findAllDropdownItems().at(0).vm.$emit('click'); - - expect(confirmAction).toHaveBeenCalled(); - - await waitForPromises(); - - expect(mock.history.post).toHaveLength(1); - }); - - it('does not make post request if confirmation is cancelled', async () => { - mock.onPost(scheduledJobAction.path).reply(HTTP_STATUS_OK); - confirmAction.mockResolvedValueOnce(false); - - findAllDropdownItems().at(0).vm.$emit('click'); - - expect(confirmAction).toHaveBeenCalled(); - - await waitForPromises(); - - expect(mock.history.post).toHaveLength(0); - }); - - it('displays the remaining time in the dropdown', () => { - expect(findAllCountdowns().at(0).props('endDateString')).toBe( - scheduledJobAction.scheduled_at, - ); - }); - - it('displays 00:00:00 for expired jobs in the dropdown', () => { - expect(findAllCountdowns().at(1).props('endDateString')).toBe(expiredJobAction.scheduled_at); - }); - }); -}); diff --git a/spec/frontend/repository/components/fork_info_spec.js b/spec/frontend/repository/components/fork_info_spec.js index 6b3d7552bb3..8521f91a6c7 100644 --- a/spec/frontend/repository/components/fork_info_spec.js +++ b/spec/frontend/repository/components/fork_info_spec.js @@ -224,7 +224,7 @@ describe('ForkInfo component', () => { ); }); - it('does not render Update Fork button', async () => { + it('does not render Update Fork button', () => { expect(findUpdateForkButton().exists()).toBe(false); }); }); diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index 38260c5b1cd..02b505c828c 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -63,7 +63,7 @@ describe('Repository table row component', () => { const findRouterLink = () => wrapper.findComponent(RouterLinkStub); const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver); - it('renders table row', async () => { + it('renders table row', () => { factory({ propsData: { id: '1', @@ -77,7 +77,7 @@ describe('Repository table row component', () => { expect(wrapper.element).toMatchSnapshot(); }); - it('renders a symlink table row', async () => { + it('renders a symlink table row', () => { factory({ propsData: { id: '1', @@ -92,7 +92,7 @@ describe('Repository table row component', () => { expect(wrapper.element).toMatchSnapshot(); }); - it('renders table row for path with special character', async () => { + it('renders table row for path with special character', () => { factory({ propsData: { id: '1', @@ -128,7 +128,7 @@ describe('Repository table row component', () => { ${'tree'} | ${RouterLinkStub} | ${'RouterLink'} ${'blob'} | ${RouterLinkStub} | ${'RouterLink'} ${'commit'} | ${'a'} | ${'hyperlink'} - `('renders a $componentName for type $type', async ({ type, component }) => { + `('renders a $componentName for type $type', ({ type, component }) => { factory({ propsData: { id: '1', @@ -146,7 +146,7 @@ describe('Repository table row component', () => { path ${'test#'} ${'Ă„nderungen'} - `('renders link for $path', async ({ path }) => { + `('renders link for $path', ({ path }) => { factory({ propsData: { id: '1', @@ -162,7 +162,7 @@ describe('Repository table row component', () => { }); }); - it('renders link for directory with hash', async () => { + it('renders link for directory with hash', () => { factory({ propsData: { id: '1', @@ -176,7 +176,7 @@ describe('Repository table row component', () => { expect(wrapper.find('.tree-item-link').props('to')).toEqual({ path: '/-/tree/main/test%23' }); }); - it('renders commit ID for submodule', async () => { + it('renders commit ID for submodule', () => { factory({ propsData: { id: '1', @@ -190,7 +190,7 @@ describe('Repository table row component', () => { expect(wrapper.find('.commit-sha').text()).toContain('1'); }); - it('renders link with href', async () => { + it('renders link with href', () => { factory({ propsData: { id: '1', @@ -205,7 +205,7 @@ describe('Repository table row component', () => { expect(wrapper.find('a').attributes('href')).toEqual('https://test.com'); }); - it('renders LFS badge', async () => { + it('renders LFS badge', () => { factory({ propsData: { id: '1', @@ -220,7 +220,7 @@ describe('Repository table row component', () => { expect(findBadge().exists()).toBe(true); }); - it('renders commit and web links with href for submodule', async () => { + it('renders commit and web links with href for submodule', () => { factory({ propsData: { id: '1', @@ -237,7 +237,7 @@ describe('Repository table row component', () => { expect(wrapper.findComponent(GlLink).attributes('href')).toEqual('https://test.com/commit'); }); - it('renders lock icon', async () => { + it('renders lock icon', () => { factory({ propsData: { id: '1', diff --git a/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js new file mode 100644 index 00000000000..105beae8638 --- /dev/null +++ b/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js @@ -0,0 +1,83 @@ +import { shallowMount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; +import Vuex from 'vuex'; +import ScopeNewNavigation from '~/search/sidebar/components/scope_new_navigation.vue'; +import NavItem from '~/super_sidebar/components/nav_item.vue'; +import { MOCK_QUERY, MOCK_NAVIGATION, MOCK_NAVIGATION_ITEMS } from '../../mock_data'; + +Vue.use(Vuex); + +describe('ScopeNewNavigation', () => { + let wrapper; + + const actionSpies = { + fetchSidebarCount: jest.fn(), + }; + + const getterSpies = { + currentScope: jest.fn(() => 'issues'), + navigationItems: jest.fn(() => MOCK_NAVIGATION_ITEMS), + }; + + const createComponent = (initialState) => { + const store = new Vuex.Store({ + state: { + urlQuery: MOCK_QUERY, + navigation: MOCK_NAVIGATION, + ...initialState, + }, + actions: actionSpies, + getters: getterSpies, + }); + + wrapper = shallowMount(ScopeNewNavigation, { + store, + stubs: { + NavItem, + }, + }); + }; + + const findNavElement = () => wrapper.findComponent('nav'); + const findNavItems = () => wrapper.findAllComponents(NavItem); + const findNavItemActive = () => wrapper.find('[aria-current=page]'); + const findNavItemActiveLabel = () => + findNavItemActive().find('[class="gl-pr-3 gl-text-gray-900 gl-truncate-end"]'); + + describe('scope navigation', () => { + beforeEach(() => { + createComponent({ urlQuery: { ...MOCK_QUERY, search: 'test' } }); + }); + + it('renders section', () => { + expect(findNavElement().exists()).toBe(true); + }); + + it('calls proper action when rendered', async () => { + await nextTick(); + expect(actionSpies.fetchSidebarCount).toHaveBeenCalled(); + }); + + it('renders all nav item components', () => { + expect(findNavItems()).toHaveLength(9); + }); + + it('has all proper links', () => { + const linkAtPosition = 3; + const { link } = MOCK_NAVIGATION[Object.keys(MOCK_NAVIGATION)[linkAtPosition]]; + + expect(findNavItems().at(linkAtPosition).findComponent('a').attributes('href')).toBe(link); + }); + }); + + describe('scope navigation sets proper state with url scope set', () => { + beforeEach(() => { + createComponent(); + }); + + it('has correct active item', () => { + expect(findNavItemActive().exists()).toBe(true); + expect(findNavItemActiveLabel().text()).toBe('Issues'); + }); + }); +}); diff --git a/spec/frontend/super_sidebar/components/create_menu_spec.js b/spec/frontend/super_sidebar/components/create_menu_spec.js index b24c6b8de7f..e05b5d30e69 100644 --- a/spec/frontend/super_sidebar/components/create_menu_spec.js +++ b/spec/frontend/super_sidebar/components/create_menu_spec.js @@ -1,3 +1,4 @@ +import { nextTick } from 'vue'; import { GlDisclosureDropdown, GlTooltip } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { __ } from '~/locale'; @@ -23,6 +24,14 @@ describe('CreateMenu component', () => { createWrapper(); }); + it('passes popper options to the dropdown', () => { + createWrapper(); + + expect(findGlDisclosureDropdown().props('popperOptions')).toEqual({ + modifiers: [{ name: 'offset', options: { offset: [-147, 4] } }], + }); + }); + it("sets the toggle's label", () => { expect(findGlDisclosureDropdown().props('toggleText')).toBe(__('Create new...')); }); @@ -35,5 +44,20 @@ describe('CreateMenu component', () => { expect(findGlDisclosureDropdown().props('toggleId')).toBe(wrapper.vm.$options.toggleId); expect(findGlTooltip().props('target')).toBe(`#${wrapper.vm.$options.toggleId}`); }); + + it('hides the tooltip when the dropdown is opened', async () => { + findGlDisclosureDropdown().vm.$emit('shown'); + await nextTick(); + + expect(findGlTooltip().exists()).toBe(false); + }); + + it('shows the tooltip when the dropdown is closed', async () => { + findGlDisclosureDropdown().vm.$emit('shown'); + findGlDisclosureDropdown().vm.$emit('hidden'); + await nextTick(); + + expect(findGlTooltip().exists()).toBe(true); + }); }); }); diff --git a/spec/frontend/super_sidebar/components/help_center_spec.js b/spec/frontend/super_sidebar/components/help_center_spec.js index 839677f29d5..4c0e7a89a43 100644 --- a/spec/frontend/super_sidebar/components/help_center_spec.js +++ b/spec/frontend/super_sidebar/components/help_center_spec.js @@ -88,6 +88,12 @@ describe('HelpCenter component', () => { ]); }); + it('passes popper options to the dropdown', () => { + expect(findDropdown().props('popperOptions')).toEqual({ + modifiers: [{ name: 'offset', options: { offset: [-4, 4] } }], + }); + }); + describe('with Gitlab version check feature enabled', () => { beforeEach(() => { createWrapper({ ...sidebarData, show_version_check: true }); diff --git a/spec/frontend/super_sidebar/components/user_menu_spec.js b/spec/frontend/super_sidebar/components/user_menu_spec.js index 25bcd322d32..995095d0e35 100644 --- a/spec/frontend/super_sidebar/components/user_menu_spec.js +++ b/spec/frontend/super_sidebar/components/user_menu_spec.js @@ -14,7 +14,8 @@ describe('UserMenu component', () => { const GlEmoji = { template: '' }; const toggleNewNavEndpoint = invalidUrl; - const showDropdown = () => wrapper.findComponent(GlDisclosureDropdown).vm.$emit('shown'); + const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); + const showDropdown = () => findDropdown().vm.$emit('shown'); const createWrapper = (userDataChanges = {}) => { wrapper = mountExtended(UserMenu, { @@ -36,6 +37,14 @@ describe('UserMenu component', () => { trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); }; + it('passes popper options to the dropdown', () => { + createWrapper(); + + expect(findDropdown().props('popperOptions')).toEqual({ + modifiers: [{ name: 'offset', options: { offset: [-211, 4] } }], + }); + }); + describe('Toggle button', () => { let toggle; diff --git a/spec/frontend/tags/components/delete_tag_modal_spec.js b/spec/frontend/tags/components/delete_tag_modal_spec.js index 8438bdb7db0..8ec9925563a 100644 --- a/spec/frontend/tags/components/delete_tag_modal_spec.js +++ b/spec/frontend/tags/components/delete_tag_modal_spec.js @@ -69,7 +69,7 @@ describe('Delete tag modal', () => { expect(submitFormSpy).toHaveBeenCalled(); }); - it('calls show on the modal when a `openModal` event is received through the event hub', async () => { + it('calls show on the modal when a `openModal` event is received through the event hub', () => { const showSpy = jest.spyOn(wrapper.vm.$refs.modal, 'show'); eventHub.$emit('openModal', { diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js index f284ec98a73..9bd46267daa 100644 --- a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js @@ -1,56 +1,101 @@ +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; import { GlModal } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import BoldText from '~/vue_merge_request_widget/components/bold_text.vue'; import WidgetRebase from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue'; +import rebaseQuery from '~/vue_merge_request_widget/queries/states/rebase.query.graphql'; import eventHub from '~/vue_merge_request_widget/event_hub'; +import StateContainer from '~/vue_merge_request_widget/components/state_container.vue'; import toast from '~/vue_shared/plugins/global_toast'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { stubComponent } from 'helpers/stub_component'; jest.mock('~/vue_shared/plugins/global_toast'); let wrapper; - -function createWrapper(propsData, provideData) { - wrapper = mount(WidgetRebase, { - provide: { - ...provideData, +const showMock = jest.fn(); + +const mockPipelineNodes = [ + { + id: '1', + project: { + id: '2', + fullPath: 'user/forked', }, - propsData, - data() { - return { - state: { - rebaseInProgress: propsData.mr.rebaseInProgress, - targetBranch: propsData.mr.targetBranch, + }, +]; + +const mockQueryHandler = ({ + rebaseInProgress = false, + targetBranch = '', + pushToSourceBranch = false, + nodes = mockPipelineNodes, +} = {}) => + jest.fn().mockResolvedValue({ + data: { + project: { + id: '1', + mergeRequest: { + id: '2', + rebaseInProgress, + targetBranch, userPermissions: { - pushToSourceBranch: propsData.mr.canPushToSourceBranch, + pushToSourceBranch, + }, + pipelines: { + nodes, }, - pipelines: propsData.mr.pipelines, }, - }; + }, + }, + }); + +const createMockApolloProvider = (handler) => { + Vue.use(VueApollo); + + return createMockApollo([[rebaseQuery, handler]]); +}; + +function createWrapper({ propsData = {}, provideData = {}, handler = mockQueryHandler() } = {}) { + wrapper = shallowMountExtended(WidgetRebase, { + apolloProvider: createMockApolloProvider(handler), + provide: { + ...provideData, }, - mocks: { - $apollo: { - queries: { - state: { loading: false }, + propsData: { + mr: {}, + service: {}, + ...propsData, + }, + stubs: { + StateContainer, + GlModal: stubComponent(GlModal, { + methods: { + show: showMock, }, - }, + }), }, }); } describe('Merge request widget rebase component', () => { - const findRebaseMessage = () => wrapper.find('[data-testid="rebase-message"]'); + const findRebaseMessage = () => wrapper.findByTestId('rebase-message'); + const findBoldText = () => wrapper.findComponent(BoldText); const findRebaseMessageText = () => findRebaseMessage().text(); - const findStandardRebaseButton = () => wrapper.find('[data-testid="standard-rebase-button"]'); - const findRebaseWithoutCiButton = () => wrapper.find('[data-testid="rebase-without-ci-button"]'); + const findStandardRebaseButton = () => wrapper.findByTestId('standard-rebase-button'); + const findRebaseWithoutCiButton = () => wrapper.findByTestId('rebase-without-ci-button'); const findModal = () => wrapper.findComponent(GlModal); describe('while rebasing', () => { - it('should show progress message', () => { + it('should show progress message', async () => { createWrapper({ - mr: { rebaseInProgress: true }, - service: {}, + handler: mockQueryHandler({ rebaseInProgress: true }), }); + await waitForPromises(); + expect(findRebaseMessageText()).toContain('Rebase in progress'); }); }); @@ -59,95 +104,110 @@ describe('Merge request widget rebase component', () => { const rebaseMock = jest.fn().mockResolvedValue(); const pollMock = jest.fn().mockResolvedValue({}); - it('renders the warning message', () => { + it('renders the warning message', async () => { createWrapper({ - mr: { + handler: mockQueryHandler({ rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, + pushToSourceBranch: false, + }), }); - const text = findRebaseMessageText(); + await waitForPromises(); - expect(text).toContain('Merge blocked'); - expect(text.replace(/\s\s+/g, ' ')).toContain( + expect(findBoldText().props('message')).toContain('Merge blocked'); + expect(findBoldText().props('message').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, + propsData: { + service: { + rebase: jest.fn().mockRejectedValue({ + response: { + data: { + merge_error: 'Something went wrong!', + }, + }, + }), + }, }, + handler: mockQueryHandler({ pushToSourceBranch: true }), }); + await waitForPromises(); - // 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!' }); + findStandardRebaseButton().vm.$emit('click'); - await nextTick(); + await waitForPromises(); expect(findRebaseMessageText()).toContain('Something went wrong!'); }); describe('Rebase buttons', () => { - beforeEach(() => { + it('renders both buttons', async () => { createWrapper({ - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, - }, + handler: mockQueryHandler({ pushToSourceBranch: true }), }); - }); - it('renders both buttons', () => { + await waitForPromises(); + expect(findRebaseWithoutCiButton().exists()).toBe(true); expect(findStandardRebaseButton().exists()).toBe(true); }); it('starts the rebase when clicking', async () => { - findStandardRebaseButton().vm.$emit('click'); + createWrapper({ + propsData: { + service: { + rebase: rebaseMock, + poll: pollMock, + }, + }, + handler: mockQueryHandler({ pushToSourceBranch: true }), + }); - await nextTick(); + await waitForPromises(); + + findStandardRebaseButton().vm.$emit('click'); expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false }); }); it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => { - findRebaseWithoutCiButton().vm.$emit('click'); + createWrapper({ + propsData: { + service: { + rebase: rebaseMock, + poll: pollMock, + }, + }, + handler: mockQueryHandler({ pushToSourceBranch: true }), + }); - await nextTick(); + await waitForPromises(); + + findRebaseWithoutCiButton().vm.$emit('click'); expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true }); }); }); describe('Rebase when pipelines must succeed is enabled', () => { - beforeEach(() => { + beforeEach(async () => { createWrapper({ - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - onlyAllowMergeIfPipelineSucceeds: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, + propsData: { + mr: { + onlyAllowMergeIfPipelineSucceeds: true, + }, + service: { + rebase: rebaseMock, + poll: pollMock, + }, }, + handler: mockQueryHandler({ pushToSourceBranch: true }), }); + + await waitForPromises(); }); it('renders only the rebase button', () => { @@ -165,19 +225,22 @@ describe('Merge request widget rebase component', () => { }); describe('Rebase when pipelines must succeed and skipped pipelines are considered successful are enabled', () => { - beforeEach(() => { + beforeEach(async () => { createWrapper({ - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - onlyAllowMergeIfPipelineSucceeds: true, - allowMergeOnSkippedPipeline: true, - }, - service: { - rebase: rebaseMock, - poll: pollMock, + propsData: { + mr: { + onlyAllowMergeIfPipelineSucceeds: true, + allowMergeOnSkippedPipeline: true, + }, + service: { + rebase: rebaseMock, + poll: pollMock, + }, }, + handler: mockQueryHandler({ pushToSourceBranch: true }), }); + + await waitForPromises(); }); it('renders both rebase buttons', () => { @@ -203,51 +266,36 @@ describe('Merge request widget rebase component', () => { }); describe('security modal', () => { - it('displays modal and rebases after confirming', () => { - createWrapper( - { + it('displays modal and rebases after confirming', async () => { + createWrapper({ + propsData: { mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, sourceProjectFullPath: 'user/forked', targetProjectFullPath: 'root/original', - pipelines: { - nodes: [ - { - id: '1', - project: { - id: '2', - fullPath: 'user/forked', - }, - }, - ], - }, }, service: { rebase: rebaseMock, poll: pollMock, }, }, - { canCreatePipelineInTargetProject: true }, - ); + provideData: { canCreatePipelineInTargetProject: true }, + handler: mockQueryHandler({ pushToSourceBranch: true }), + }); - findModal().vm.show = jest.fn(); + await waitForPromises(); findStandardRebaseButton().vm.$emit('click'); - - expect(findModal().vm.show).toHaveBeenCalled(); + expect(showMock).toHaveBeenCalled(); findModal().vm.$emit('primary'); expect(rebaseMock).toHaveBeenCalled(); }); - it('does not display modal', () => { - createWrapper( - { + it('does not display modal', async () => { + createWrapper({ + propsData: { mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, sourceProjectFullPath: 'user/forked', targetProjectFullPath: 'root/original', }, @@ -256,14 +304,15 @@ describe('Merge request widget rebase component', () => { poll: pollMock, }, }, - { canCreatePipelineInTargetProject: false }, - ); + provideData: { canCreatePipelineInTargetProject: false }, + handler: mockQueryHandler({ pushToSourceBranch: true }), + }); - findModal().vm.show = jest.fn(); + await waitForPromises(); findStandardRebaseButton().vm.$emit('click'); - expect(findModal().vm.show).not.toHaveBeenCalled(); + expect(showMock).not.toHaveBeenCalled(); expect(rebaseMock).toHaveBeenCalled(); }); }); @@ -273,42 +322,41 @@ describe('Merge request widget rebase component', () => { const exampleTargetBranch = 'fake-branch-to-test-with'; describe('UI text', () => { - beforeEach(() => { + beforeEach(async () => { createWrapper({ - mr: { - rebaseInProgress: false, - canPushToSourceBranch: false, + handler: mockQueryHandler({ + pushToSourceBranch: false, targetBranch: exampleTargetBranch, - }, - service: {}, + }), }); + + await waitForPromises(); }); it('renders a message explaining user does not have permissions', () => { - const text = findRebaseMessageText(); - - expect(text).toContain('Merge blocked:'); - expect(text).toContain('the source branch must be rebased'); + expect(findBoldText().props('message')).toContain('Merge blocked'); + expect(findBoldText().props('message')).toContain('the source branch must be rebased'); }); it('renders the correct target branch name', () => { - const text = findRebaseMessageText(); - - expect(text).toContain('Merge blocked:'); - expect(text).toContain('the source branch must be rebased onto the target branch.'); + expect(findBoldText().props('message')).toContain('Merge blocked:'); + expect(findBoldText().props('message')).toContain( + 'the source branch must be rebased onto the target branch.', + ); }); }); - it('does render the "Rebase without pipeline" button', () => { + it('does render the "Rebase without pipeline" button', async () => { createWrapper({ - mr: { + handler: mockQueryHandler({ rebaseInProgress: false, - canPushToSourceBranch: false, + pushToSourceBranch: false, targetBranch: exampleTargetBranch, - }, - service: {}, + }), }); + await waitForPromises(); + expect(findRebaseWithoutCiButton().exists()).toBe(true); }); }); @@ -317,24 +365,27 @@ describe('Merge request widget rebase component', () => { 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, - }, - }); + propsData: { + service: { + rebase() { + return Promise.resolve(); + }, + poll() { + return Promise.resolve({ + data: { + rebase_in_progress: false, + should_be_rebased: false, + merge_error: null, + }, + }); + }, }, }, }); - wrapper.vm.rebase(); + await waitForPromises(); + + findRebaseWithoutCiButton().vm.$emit('click'); // Wait for the rebase request await nextTick(); -- cgit v1.2.1