diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-24 15:15:38 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-24 15:15:38 +0000 |
commit | efbf661c4224d481c57d0346e26983a805e5ec93 (patch) | |
tree | 4736f287350884cb49d84a09c52c8c2e1b851080 /spec/frontend | |
parent | 4720346c2e10e1ff62a20b39dfc9866eb88858e6 (diff) | |
download | gitlab-ce-efbf661c4224d481c57d0346e26983a805e5ec93.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
20 files changed, 305 insertions, 134 deletions
diff --git a/spec/frontend/__mocks__/mousetrap/index.js b/spec/frontend/__mocks__/mousetrap/index.js deleted file mode 100644 index 63c92fa9a09..00000000000 --- a/spec/frontend/__mocks__/mousetrap/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/* global Mousetrap */ -// `mousetrap` uses amd which webpack understands but Jest does not -// Thankfully it also writes to a global export so we can es6-ify it -import 'mousetrap'; - -export default Mousetrap; diff --git a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js index ba8b9dd1345..db87e8f09aa 100644 --- a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js +++ b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js @@ -36,10 +36,9 @@ describe('MessageForm', () => { const findSubmitButton = () => wrapper.findComponent('[data-testid=submit-button]'); const findForm = () => wrapper.findComponent(GlForm); - function createComponent({ broadcastMessage = {}, glFeatures = {} }) { + function createComponent({ broadcastMessage = {} } = {}) { wrapper = mount(MessageForm, { provide: { - glFeatures, targetAccessLevelOptions: MOCK_TARGET_ACCESS_LEVELS, messagesPath, previewPath: '_preview_path_', @@ -100,15 +99,10 @@ describe('MessageForm', () => { }); describe('target roles checkboxes', () => { - it('renders when roleTargetedBroadcastMessages feature is enabled', () => { - createComponent({ glFeatures: { roleTargetedBroadcastMessages: true } }); + it('renders target roles', () => { + createComponent(); expect(findTargetRoles().exists()).toBe(true); }); - - it('does not render when roleTargetedBroadcastMessages feature is disabled', () => { - createComponent({ glFeatures: { roleTargetedBroadcastMessages: false } }); - expect(findTargetRoles().exists()).toBe(false); - }); }); describe('form submit button', () => { diff --git a/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js b/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js index 432bfefeb18..6d536b2d0e4 100644 --- a/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js +++ b/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js @@ -9,11 +9,8 @@ describe('MessagesTable', () => { const findTargetRoles = () => wrapper.find('[data-testid="target-roles-th"]'); const findDeleteButton = (id) => wrapper.find(`[data-testid="delete-message-${id}"]`); - function createComponent(props = {}, glFeatures = {}) { + function createComponent(props = {}) { wrapper = mount(MessagesTable, { - provide: { - glFeatures, - }, propsData: { messages: MOCK_MESSAGES, ...props, @@ -27,14 +24,10 @@ describe('MessagesTable', () => { expect(findRows()).toHaveLength(MOCK_MESSAGES.length); }); - it('renders the "Target Roles" column when roleTargetedBroadcastMessages is enabled', () => { - createComponent({}, { roleTargetedBroadcastMessages: true }); - expect(findTargetRoles().exists()).toBe(true); - }); - - it('does not render the "Target Roles" column when roleTargetedBroadcastMessages is disabled', () => { + it('renders the "Target Roles" column', () => { createComponent(); - expect(findTargetRoles().exists()).toBe(false); + + expect(findTargetRoles().exists()).toBe(true); }); it('emits a delete-message event when a delete button is clicked', () => { diff --git a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js index 65336edd0d8..a454066b457 100644 --- a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js +++ b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js @@ -2,12 +2,10 @@ import { GlSprintf } from '@gitlab/ui'; import { s__ } from '~/locale'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createAlert, VARIANT_SUCCESS } from '~/alert'; import AdminNewRunnerApp from '~/ci/runner/admin_new_runner/admin_new_runner_app.vue'; import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage'; -import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue'; import { PARAM_KEY_PLATFORM, @@ -17,7 +15,7 @@ import { } from '~/ci/runner/constants'; import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue'; import { redirectTo } from '~/lib/utils/url_utility'; -import { runnerCreateResult, mockRegistrationToken } from '../mock_data'; +import { runnerCreateResult } from '../mock_data'; jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage'); jest.mock('~/alert'); @@ -31,19 +29,11 @@ const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner; describe('AdminNewRunnerApp', () => { let wrapper; - const findLegacyInstructionsLink = () => wrapper.findByTestId('legacy-instructions-link'); - const findRunnerInstructionsModal = () => wrapper.findComponent(RunnerInstructionsModal); const findRunnerPlatformsRadioGroup = () => wrapper.findComponent(RunnerPlatformsRadioGroup); const findRunnerCreateForm = () => wrapper.findComponent(RunnerCreateForm); const createComponent = () => { wrapper = shallowMountExtended(AdminNewRunnerApp, { - propsData: { - legacyRegistrationToken: mockRegistrationToken, - }, - directives: { - GlModal: createMockDirective('gl-modal'), - }, stubs: { GlSprintf, }, @@ -54,20 +44,6 @@ describe('AdminNewRunnerApp', () => { createComponent(); }); - describe('Shows legacy modal', () => { - it('passes legacy registration to modal', () => { - expect(findRunnerInstructionsModal().props('registrationToken')).toEqual( - mockRegistrationToken, - ); - }); - - it('opens a modal with the legacy instructions', () => { - const modalId = getBinding(findLegacyInstructionsLink().element, 'gl-modal').value; - - expect(findRunnerInstructionsModal().props('modalId')).toBe(modalId); - }); - }); - describe('Platform', () => { it('shows the platforms radio group', () => { expect(findRunnerPlatformsRadioGroup().props('value')).toBe(DEFAULT_PLATFORM); diff --git a/spec/frontend/ci/runner/components/registration/registration_compatibility_alert_spec.js b/spec/frontend/ci/runner/components/registration/registration_compatibility_alert_spec.js new file mode 100644 index 00000000000..ba8a1ac6744 --- /dev/null +++ b/spec/frontend/ci/runner/components/registration/registration_compatibility_alert_spec.js @@ -0,0 +1,32 @@ +import { GlAlert, GlLink } from '@gitlab/ui'; +import RegistrationCompatibilityAlert from '~/ci/runner/components/registration/registration_compatibility_alert.vue'; +import { CHANGELOG_URL } from '~/ci/runner/constants'; +import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper'; + +describe('RegistrationCompatibilityAlert', () => { + let wrapper; + + const findAlert = () => wrapper.findComponent(GlAlert); + const findLink = () => wrapper.findComponent(GlLink); + + const createComponent = (mountFn = shallowMountExtended) => { + wrapper = mountFn(RegistrationCompatibilityAlert); + }; + + it('alert has warning appearance', () => { + createComponent(); + + expect(findAlert().props()).toMatchObject({ + dismissible: false, + variant: 'warning', + title: expect.any(String), + }); + }); + + it('shows alert content and link', () => { + createComponent(mountExtended); + + expect(findAlert().text()).not.toBe(''); + expect(findLink().attributes('href')).toBe(CHANGELOG_URL); + }); +}); diff --git a/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js b/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js index d23723807b1..9df7a974af3 100644 --- a/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js +++ b/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js @@ -1,4 +1,4 @@ -import { GlModal, GlDropdown, GlDropdownItem, GlDropdownForm } from '@gitlab/ui'; +import { GlModal, GlDropdown, GlDropdownItem, GlDropdownForm, GlIcon } from '@gitlab/ui'; import { createWrapper } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -29,7 +29,7 @@ describe('RegistrationDropdown', () => { let wrapper; const findDropdown = () => wrapper.findComponent(GlDropdown); - + const findDropdownBtn = () => findDropdown().find('button'); const findRegistrationInstructionsDropdownItem = () => wrapper.findComponent(GlDropdownItem); const findTokenDropdownItem = () => wrapper.findComponent(GlDropdownForm); const findRegistrationToken = () => wrapper.findComponent(RegistrationToken); @@ -90,12 +90,25 @@ describe('RegistrationDropdown', () => { expect(wrapper.text()).toContain('Register an instance runner'); }); - it('Passes attributes to the dropdown component', () => { + it('Passes attributes to dropdown', () => { createComponent({ attrs: { right: true } }); expect(findDropdown().attributes()).toMatchObject({ right: 'true' }); }); + it('Passes default props and attributes to dropdown', () => { + createComponent(); + + expect(findDropdown().props()).toMatchObject({ + category: 'primary', + variant: 'confirm', + }); + + expect(findDropdown().attributes()).toMatchObject({ + toggleclass: '', + }); + }); + describe('Instructions dropdown item', () => { it('Displays "Show runner" dropdown item', () => { createComponent(); @@ -196,4 +209,51 @@ describe('RegistrationDropdown', () => { expect(findModalContent()).toContain(newToken); }); }); + + describe.each([ + { createRunnerWorkflowForAdmin: true }, + { createRunnerWorkflowForNamespace: true }, + ])('When showing a "deprecated" warning', (glFeatures) => { + it('Passes deprecated variant props and attributes to dropdown', () => { + createComponent({ + provide: { glFeatures }, + }); + + expect(findDropdown().props()).toMatchObject({ + category: 'tertiary', + variant: 'default', + text: '', + }); + + expect(findDropdown().attributes()).toMatchObject({ + toggleclass: 'gl-px-3!', + }); + }); + + it('shows warning text', () => { + createComponent( + { + provide: { glFeatures }, + }, + mountExtended, + ); + + const text = wrapper.findByText(s__('Runners|Support for registration tokens is deprecated')); + + expect(text.exists()).toBe(true); + }); + + it('button shows only ellipsis icon', () => { + createComponent( + { + provide: { glFeatures }, + }, + mountExtended, + ); + + expect(findDropdownBtn().text()).toBe(''); + expect(findDropdownBtn().findComponent(GlIcon).props('name')).toBe('ellipsis_v'); + expect(findDropdownBtn().findAllComponents(GlIcon)).toHaveLength(1); + }); + }); }); diff --git a/spec/frontend/ci/runner/components/registration/registration_token_spec.js b/spec/frontend/ci/runner/components/registration/registration_token_spec.js index fc659f7974f..869c032c0b5 100644 --- a/spec/frontend/ci/runner/components/registration/registration_token_spec.js +++ b/spec/frontend/ci/runner/components/registration/registration_token_spec.js @@ -13,13 +13,14 @@ describe('RegistrationToken', () => { const findInputCopyToggleVisibility = () => wrapper.findComponent(InputCopyToggleVisibility); - const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => { + const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => { wrapper = mountFn(RegistrationToken, { propsData: { value: mockRegistrationToken, inputId: 'token-value', ...props, }, + ...options, }); showToast = wrapper.vm.$toast ? jest.spyOn(wrapper.vm.$toast, 'show') : null; @@ -61,4 +62,23 @@ describe('RegistrationToken', () => { expect(showToast).toHaveBeenCalledWith('Registration token copied!'); }); }); + + describe('When slots are used', () => { + const slotName = 'label-description'; + const slotContent = 'Label Description'; + + beforeEach(() => { + createComponent({ + slots: { + [slotName]: slotContent, + }, + }); + }); + + it('passes slots to the input component', () => { + const slot = findInputCopyToggleVisibility().vm.$scopedSlots[slotName]; + + expect(slot()[0].text).toBe(slotContent); + }); + }); }); diff --git a/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js b/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js index 520c9eed003..add2e58045c 100644 --- a/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js +++ b/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js @@ -2,12 +2,10 @@ import { GlSprintf } from '@gitlab/ui'; import { s__ } from '~/locale'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createAlert, VARIANT_SUCCESS } from '~/alert'; import GroupRunnerRunnerApp from '~/ci/runner/group_new_runner/group_new_runner_app.vue'; import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage'; -import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue'; import { PARAM_KEY_PLATFORM, @@ -17,7 +15,7 @@ import { } from '~/ci/runner/constants'; import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue'; import { redirectTo } from '~/lib/utils/url_utility'; -import { runnerCreateResult, mockRegistrationToken } from '../mock_data'; +import { runnerCreateResult } from '../mock_data'; const mockGroupId = 'gid://gitlab/Group/72'; @@ -33,8 +31,6 @@ const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner; describe('GroupRunnerRunnerApp', () => { let wrapper; - const findLegacyInstructionsLink = () => wrapper.findByTestId('legacy-instructions-link'); - const findRunnerInstructionsModal = () => wrapper.findComponent(RunnerInstructionsModal); const findRunnerPlatformsRadioGroup = () => wrapper.findComponent(RunnerPlatformsRadioGroup); const findRunnerCreateForm = () => wrapper.findComponent(RunnerCreateForm); @@ -42,10 +38,6 @@ describe('GroupRunnerRunnerApp', () => { wrapper = shallowMountExtended(GroupRunnerRunnerApp, { propsData: { groupId: mockGroupId, - legacyRegistrationToken: mockRegistrationToken, - }, - directives: { - GlModal: createMockDirective('gl-modal'), }, stubs: { GlSprintf, @@ -57,20 +49,6 @@ describe('GroupRunnerRunnerApp', () => { createComponent(); }); - describe('Shows legacy modal', () => { - it('passes legacy registration to modal', () => { - expect(findRunnerInstructionsModal().props('registrationToken')).toEqual( - mockRegistrationToken, - ); - }); - - it('opens a modal with the legacy instructions', () => { - const modalId = getBinding(findLegacyInstructionsLink().element, 'gl-modal').value; - - expect(findRunnerInstructionsModal().props('modalId')).toBe(modalId); - }); - }); - describe('Platform', () => { it('shows the platforms radio group', () => { expect(findRunnerPlatformsRadioGroup().props('value')).toBe(DEFAULT_PLATFORM); diff --git a/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js b/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js index 701a93352ef..6949108fb1f 100644 --- a/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js +++ b/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js @@ -2,11 +2,9 @@ import { GlSprintf } from '@gitlab/ui'; import { s__ } from '~/locale'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createAlert, VARIANT_SUCCESS } from '~/alert'; import ProjectRunnerRunnerApp from '~/ci/runner/project_new_runner/project_new_runner_app.vue'; -import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue'; import { PROJECT_TYPE, DEFAULT_PLATFORM } from '~/ci/runner/constants'; import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue'; @@ -26,8 +24,6 @@ const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner; describe('ProjectRunnerRunnerApp', () => { let wrapper; - const findLegacyInstructionsLink = () => wrapper.findByTestId('legacy-instructions-link'); - const findRunnerInstructionsModal = () => wrapper.findComponent(RunnerInstructionsModal); const findRunnerPlatformsRadioGroup = () => wrapper.findComponent(RunnerPlatformsRadioGroup); const findRunnerCreateForm = () => wrapper.findComponent(RunnerCreateForm); @@ -37,9 +33,6 @@ describe('ProjectRunnerRunnerApp', () => { projectId: mockProjectId, legacyRegistrationToken: mockRegistrationToken, }, - directives: { - GlModal: createMockDirective('gl-modal'), - }, stubs: { GlSprintf, }, @@ -50,20 +43,6 @@ describe('ProjectRunnerRunnerApp', () => { createComponent(); }); - describe('Shows legacy modal', () => { - it('passes legacy registration to modal', () => { - expect(findRunnerInstructionsModal().props('registrationToken')).toEqual( - mockRegistrationToken, - ); - }); - - it('opens a modal with the legacy instructions', () => { - const modalId = getBinding(findLegacyInstructionsLink().element, 'gl-modal').value; - - expect(findRunnerInstructionsModal().props('modalId')).toBe(modalId); - }); - }); - describe('Platform', () => { it('shows the platforms radio group', () => { expect(findRunnerPlatformsRadioGroup().props('value')).toBe(DEFAULT_PLATFORM); diff --git a/spec/frontend/design_management/components/toolbar/design_navigation_spec.js b/spec/frontend/design_management/components/toolbar/design_navigation_spec.js index 28d6b7118be..a557a1a98f3 100644 --- a/spec/frontend/design_management/components/toolbar/design_navigation_spec.js +++ b/spec/frontend/design_management/components/toolbar/design_navigation_spec.js @@ -1,11 +1,10 @@ -/* global Mousetrap */ -import 'mousetrap'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { GlButtonGroup } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import DesignNavigation from '~/design_management/components/toolbar/design_navigation.vue'; import { DESIGN_ROUTE_NAME } from '~/design_management/router/constants'; +import { Mousetrap } from '~/lib/mousetrap'; import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index f24ce8ba4ce..e58082a798f 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -1,7 +1,6 @@ import { GlLoadingIcon, GlPagination } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; -import Mousetrap from 'mousetrap'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import setWindowLocation from 'helpers/set_window_location_helper'; @@ -19,6 +18,7 @@ import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; +import { Mousetrap } from '~/lib/mousetrap'; import * as urlUtils from '~/lib/utils/url_utility'; import { stubPerformanceWebAPI } from 'helpers/performance'; import createDiffsStore from '../create_diffs_store'; diff --git a/spec/frontend/jobs/components/job/stages_dropdown_spec.js b/spec/frontend/jobs/components/job/stages_dropdown_spec.js index f782d5600e6..9d01dc50e96 100644 --- a/spec/frontend/jobs/components/job/stages_dropdown_spec.js +++ b/spec/frontend/jobs/components/job/stages_dropdown_spec.js @@ -1,6 +1,6 @@ import { GlDropdown, GlDropdownItem, GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Mousetrap from 'mousetrap'; +import { Mousetrap } from '~/lib/mousetrap'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import StagesDropdown from '~/jobs/components/job/sidebar/stages_dropdown.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; diff --git a/spec/frontend/lib/mousetrap_spec.js b/spec/frontend/lib/mousetrap_spec.js new file mode 100644 index 00000000000..0ea221300a9 --- /dev/null +++ b/spec/frontend/lib/mousetrap_spec.js @@ -0,0 +1,113 @@ +// eslint-disable-next-line no-restricted-imports +import Mousetrap from 'mousetrap'; + +const originalMethodReturnValue = {}; +// Create a mock stopCallback method before ~/lib/utils/mousetrap overwrites +// it. This allows us to spy on calls to it. +const mockOriginalStopCallbackMethod = jest.fn().mockReturnValue(originalMethodReturnValue); +Mousetrap.prototype.stopCallback = mockOriginalStopCallbackMethod; + +describe('mousetrap utils', () => { + describe('addStopCallback', () => { + let addStopCallback; + let clearStopCallbacksForTests; + const mockMousetrapInstance = { isMockMousetrap: true }; + const mockKeyboardEvent = { type: 'keydown', key: 'Enter' }; + const mockCombo = 'enter'; + + const mockKeydown = ({ + instance = mockMousetrapInstance, + event = mockKeyboardEvent, + element = document, + combo = mockCombo, + } = {}) => Mousetrap.prototype.stopCallback.call(instance, event, element, combo); + + beforeEach(async () => { + // Import async since it mutates the Mousetrap instance, by design. + ({ addStopCallback, clearStopCallbacksForTests } = await import('~/lib/mousetrap')); + clearStopCallbacksForTests(); + }); + + it('delegates to the original stopCallback method when no additional callbacks added', () => { + const returnValue = mockKeydown(); + + expect(mockOriginalStopCallbackMethod).toHaveBeenCalledTimes(1); + + const [thisArg] = mockOriginalStopCallbackMethod.mock.contexts; + const [eventArg, element, combo] = mockOriginalStopCallbackMethod.mock.calls[0]; + + expect(thisArg).toBe(mockMousetrapInstance); + expect(eventArg).toBe(mockKeyboardEvent); + expect(element).toBe(document); + expect(combo).toBe(mockCombo); + + expect(returnValue).toBe(originalMethodReturnValue); + }); + + it('passes the expected arguments to the given stop callback', () => { + const callback = jest.fn(); + + addStopCallback(callback); + + mockKeydown(); + + expect(callback).toHaveBeenCalledTimes(1); + + const [thisArg] = callback.mock.contexts; + const [eventArg, element, combo] = callback.mock.calls[0]; + + expect(thisArg).toBe(mockMousetrapInstance); + expect(eventArg).toBe(mockKeyboardEvent); + expect(element).toBe(document); + expect(combo).toBe(mockCombo); + }); + + describe.each([true, false])('when a stop handler returns %p', (stopCallbackReturnValue) => { + let methodReturnValue; + const stopCallback = jest.fn().mockReturnValue(stopCallbackReturnValue); + + beforeEach(() => { + addStopCallback(stopCallback); + + methodReturnValue = mockKeydown(); + }); + + it(`returns ${stopCallbackReturnValue}`, () => { + expect(methodReturnValue).toBe(stopCallbackReturnValue); + }); + + it('calls stop callback', () => { + expect(stopCallback).toHaveBeenCalledTimes(1); + }); + + it('does not call mockOriginalStopCallbackMethod', () => { + expect(mockOriginalStopCallbackMethod).not.toHaveBeenCalled(); + }); + }); + + describe('when a stop handler returns undefined', () => { + let methodReturnValue; + const stopCallback = jest.fn().mockReturnValue(undefined); + + beforeEach(() => { + addStopCallback(stopCallback); + + methodReturnValue = mockKeydown(); + }); + + it('returns originalMethodReturnValue', () => { + expect(methodReturnValue).toBe(originalMethodReturnValue); + }); + + it('calls stop callback', () => { + expect(stopCallback).toHaveBeenCalledTimes(1); + }); + + // Because this is the only registered stop callback, the next callback + // is the original method. + it('does call original stopCallback method', () => { + expect(mockOriginalStopCallbackMethod).toHaveBeenCalledTimes(1); + }); + }); + }); +}); diff --git a/spec/frontend/notes/components/discussion_navigator_spec.js b/spec/frontend/notes/components/discussion_navigator_spec.js index 6e095f63003..14181287381 100644 --- a/spec/frontend/notes/components/discussion_navigator_spec.js +++ b/spec/frontend/notes/components/discussion_navigator_spec.js @@ -1,5 +1,3 @@ -/* global Mousetrap */ -import 'mousetrap'; import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import { @@ -7,6 +5,7 @@ import { MR_NEXT_UNRESOLVED_DISCUSSION, MR_PREVIOUS_UNRESOLVED_DISCUSSION, } from '~/behaviors/shortcuts/keybindings'; +import { Mousetrap } from '~/lib/mousetrap'; import DiscussionNavigator from '~/notes/components/discussion_navigator.vue'; import eventHub from '~/notes/event_hub'; diff --git a/spec/frontend/projects/settings/components/default_branch_selector_spec.js b/spec/frontend/projects/settings/components/default_branch_selector_spec.js index c1412d01b53..9baea5c5517 100644 --- a/spec/frontend/projects/settings/components/default_branch_selector_spec.js +++ b/spec/frontend/projects/settings/components/default_branch_selector_spec.js @@ -28,7 +28,6 @@ describe('projects/settings/components/default_branch_selector', () => { value: persistedDefaultBranch, enabledRefTypes: [REF_TYPE_BRANCHES], projectId, - refType: null, state: true, toggleButtonClass: null, translations: { diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js index 5e15ba26ece..290cde29866 100644 --- a/spec/frontend/ref/components/ref_selector_spec.js +++ b/spec/frontend/ref/components/ref_selector_spec.js @@ -22,8 +22,6 @@ import { REF_TYPE_BRANCHES, REF_TYPE_TAGS, REF_TYPE_COMMITS, - BRANCH_REF_TYPE, - TAG_REF_TYPE, } from '~/ref/constants'; import createStore from '~/ref/stores/'; @@ -323,7 +321,7 @@ describe('Ref selector component', () => { describe('branches', () => { describe('when the branches search returns results', () => { beforeEach(() => { - createComponent({}, { refType: BRANCH_REF_TYPE, useSymbolicRefNames: true }); + createComponent({}, { useSymbolicRefNames: true }); return waitForRequests(); }); @@ -386,7 +384,7 @@ describe('Ref selector component', () => { describe('tags', () => { describe('when the tags search returns results', () => { beforeEach(() => { - createComponent({}, { refType: TAG_REF_TYPE, useSymbolicRefNames: true }); + createComponent({}, { useSymbolicRefNames: true }); return waitForRequests(); }); diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js index d1371ca0ef9..4e74bdc5895 100644 --- a/spec/frontend/shortcuts_spec.js +++ b/spec/frontend/shortcuts_spec.js @@ -1,16 +1,8 @@ import $ from 'jquery'; import { flatten } from 'lodash'; +import { Mousetrap } from '~/lib/mousetrap'; import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; -import Shortcuts from '~/behaviors/shortcuts/shortcuts'; - -const mockMousetrap = { - bind: jest.fn(), - unbind: jest.fn(), -}; - -jest.mock('mousetrap', () => { - return jest.fn().mockImplementation(() => mockMousetrap); -}); +import Shortcuts, { LOCAL_MOUSETRAP_DATA_KEY } from '~/behaviors/shortcuts/shortcuts'; jest.mock('mousetrap/plugins/pause/mousetrap-pause', () => {}); @@ -20,6 +12,11 @@ describe('Shortcuts', () => { $.Event(type, { target, }); + let shortcuts; + + beforeAll(() => { + shortcuts = new Shortcuts(); + }); beforeEach(() => { loadHTMLFixture(fixtureName); @@ -28,7 +25,9 @@ describe('Shortcuts', () => { jest.spyOn(document.querySelector('.edit-note .js-md-preview-button'), 'focus'); jest.spyOn(document.querySelector('#search'), 'focus'); - new Shortcuts(); // eslint-disable-line no-new + jest.spyOn(Mousetrap.prototype, 'stopCallback'); + jest.spyOn(Mousetrap.prototype, 'bind').mockImplementation(); + jest.spyOn(Mousetrap.prototype, 'unbind').mockImplementation(); }); afterEach(() => { @@ -61,7 +60,7 @@ describe('Shortcuts', () => { }); describe('markdown shortcuts', () => { - let shortcuts; + let shortcutElements; beforeEach(() => { // Get all shortcuts specified with md-shortcuts attributes in the fixture. @@ -71,7 +70,7 @@ describe('Shortcuts', () => { // [ 'mod+i' ], // [ 'mod+k' ] // ] - shortcuts = $('.edit-note .js-md') + shortcutElements = $('.edit-note .js-md') .map(function getShortcutsFromToolbarBtn() { const mdShortcuts = $(this).data('md-shortcuts'); @@ -83,19 +82,26 @@ describe('Shortcuts', () => { }); describe('initMarkdownEditorShortcuts', () => { + let $textarea; + let localMousetrapInstance; + beforeEach(() => { - Shortcuts.initMarkdownEditorShortcuts($('.edit-note textarea')); + $textarea = $('.edit-note textarea'); + Shortcuts.initMarkdownEditorShortcuts($textarea); + localMousetrapInstance = $textarea.data(LOCAL_MOUSETRAP_DATA_KEY); }); it('attaches a Mousetrap handler for every markdown shortcut specified with md-shortcuts', () => { - const expectedCalls = shortcuts.map((s) => [s, expect.any(Function)]); + const expectedCalls = shortcutElements.map((s) => [s, expect.any(Function)]); - expect(mockMousetrap.bind.mock.calls).toEqual(expectedCalls); + expect(Mousetrap.prototype.bind.mock.calls).toEqual(expectedCalls); }); it('attaches a stopCallback that allows each markdown shortcut specified with md-shortcuts', () => { - flatten(shortcuts).forEach((s) => { - expect(mockMousetrap.stopCallback(null, null, s)).toBe(false); + flatten(shortcutElements).forEach((s) => { + expect( + localMousetrapInstance.stopCallback.call(localMousetrapInstance, null, null, s), + ).toBe(false); }); }); }); @@ -104,16 +110,16 @@ describe('Shortcuts', () => { it('does nothing if initMarkdownEditorShortcuts was not previous called', () => { Shortcuts.removeMarkdownEditorShortcuts($('.edit-note textarea')); - expect(mockMousetrap.unbind.mock.calls).toEqual([]); + expect(Mousetrap.prototype.unbind.mock.calls).toEqual([]); }); it('removes Mousetrap handlers for every markdown shortcut specified with md-shortcuts', () => { Shortcuts.initMarkdownEditorShortcuts($('.edit-note textarea')); Shortcuts.removeMarkdownEditorShortcuts($('.edit-note textarea')); - const expectedCalls = shortcuts.map((s) => [s]); + const expectedCalls = shortcutElements.map((s) => [s]); - expect(mockMousetrap.unbind.mock.calls).toEqual(expectedCalls); + expect(Mousetrap.prototype.unbind.mock.calls).toEqual(expectedCalls); }); }); }); @@ -136,4 +142,35 @@ describe('Shortcuts', () => { }); }); }); + + describe('bindCommand(s)', () => { + it('bindCommand calls Mousetrap.bind correctly', () => { + const mockCommand = { defaultKeys: ['m'] }; + const mockCallback = () => {}; + + shortcuts.bindCommand(mockCommand, mockCallback); + + expect(Mousetrap.prototype.bind).toHaveBeenCalledTimes(1); + const [callArguments] = Mousetrap.prototype.bind.mock.calls; + expect(callArguments[0]).toEqual(mockCommand.defaultKeys); + expect(callArguments[1]).toBe(mockCallback); + }); + + it('bindCommands calls Mousetrap.bind correctly', () => { + const mockCommandsAndCallbacks = [ + [{ defaultKeys: ['1'] }, () => {}], + [{ defaultKeys: ['2'] }, () => {}], + ]; + + shortcuts.bindCommands(mockCommandsAndCallbacks); + + expect(Mousetrap.prototype.bind).toHaveBeenCalledTimes(mockCommandsAndCallbacks.length); + const { calls } = Mousetrap.prototype.bind.mock; + + mockCommandsAndCallbacks.forEach(([mockCommand, mockCallback], i) => { + expect(calls[i][0]).toEqual(mockCommand.defaultKeys); + expect(calls[i][1]).toBe(mockCallback); + }); + }); + }); }); diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js index 2d535b0c727..16b8dfd211f 100644 --- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js +++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js @@ -1,5 +1,5 @@ import { nextTick } from 'vue'; -import Mousetrap from 'mousetrap'; +import { Mousetrap } from '~/lib/mousetrap'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import SuperSidebar from '~/super_sidebar/components/super_sidebar.vue'; import HelpCenter from '~/super_sidebar/components/help_center.vue'; diff --git a/spec/frontend/vue_shared/components/file_finder/index_spec.js b/spec/frontend/vue_shared/components/file_finder/index_spec.js index 9708d689245..bb0b12d205a 100644 --- a/spec/frontend/vue_shared/components/file_finder/index_spec.js +++ b/spec/frontend/vue_shared/components/file_finder/index_spec.js @@ -1,7 +1,7 @@ import { GlLoadingIcon } from '@gitlab/ui'; -import Mousetrap from 'mousetrap'; import { nextTick } from 'vue'; import VirtualList from 'vue-virtual-scroll-list'; +import { Mousetrap } from '~/lib/mousetrap'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { file } from 'jest/ide/helpers'; import FindFileComponent from '~/vue_shared/components/file_finder/index.vue'; diff --git a/spec/frontend/zen_mode_spec.js b/spec/frontend/zen_mode_spec.js index 025a92464f1..3e66380ac46 100644 --- a/spec/frontend/zen_mode_spec.js +++ b/spec/frontend/zen_mode_spec.js @@ -2,7 +2,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import Dropzone from 'dropzone'; import $ from 'jquery'; -import Mousetrap from 'mousetrap'; +import { Mousetrap } from '~/lib/mousetrap'; import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import GLForm from '~/gl_form'; import * as utils from '~/lib/utils/common_utils'; |