diff options
Diffstat (limited to 'spec/frontend/vue_shared')
30 files changed, 635 insertions, 229 deletions
diff --git a/spec/frontend/vue_shared/alert_details/alert_status_spec.js b/spec/frontend/vue_shared/alert_details/alert_status_spec.js index 90d29f0bfd4..478df81a966 100644 --- a/spec/frontend/vue_shared/alert_details/alert_status_spec.js +++ b/spec/frontend/vue_shared/alert_details/alert_status_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import createMockApollo from 'helpers/mock_apollo_helper'; import updateAlertStatusMutation from '~/graphql_shared//mutations/alert_status_update.mutation.graphql'; @@ -34,13 +34,13 @@ describe('AlertManagementStatus', () => { }, }); - const findStatusDropdown = () => wrapper.findComponent(GlDropdown); - const findFirstStatusOption = () => findStatusDropdown().findComponent(GlDropdownItem); - const findAllStatusOptions = () => findStatusDropdown().findAllComponents(GlDropdownItem); - const findStatusDropdownHeader = () => wrapper.findByTestId('dropdown-header'); + const findStatusDropdown = () => wrapper.findComponent(GlCollapsibleListbox); + const findFirstStatusOption = () => findStatusDropdown().findComponent(GlListboxItem); + const findAllStatusOptions = () => findStatusDropdown().findAllComponents(GlListboxItem); + const findStatusDropdownHeader = () => wrapper.findByTestId('listbox-header-text'); const selectFirstStatusOption = () => { - findFirstStatusOption().vm.$emit('click'); + findFirstStatusOption().vm.$emit('select', new Event('click')); return waitForPromises(); }; @@ -57,7 +57,7 @@ describe('AlertManagementStatus', () => { provide = {}, handler = mockUpdatedMutationResult(), } = {}) { - wrapper = shallowMountExtended(AlertManagementStatus, { + wrapper = mountExtended(AlertManagementStatus, { apolloProvider: createMockApolloProvider(handler), propsData: { alert: { ...mockAlert }, @@ -82,7 +82,7 @@ describe('AlertManagementStatus', () => { it('shows the dropdown', () => { mountComponent({ props: { isSidebar: true, isDropdownShowing: true } }); - expect(wrapper.classes()).toContain('show'); + expect(wrapper.classes()).not.toContain('gl-display-none'); }); }); @@ -92,8 +92,7 @@ describe('AlertManagementStatus', () => { }); it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', async () => { - findFirstStatusOption().vm.$emit('click'); - await waitForPromises(); + await selectFirstStatusOption(); expect(requestHandler).toHaveBeenCalledWith({ iid, @@ -194,9 +193,7 @@ describe('AlertManagementStatus', () => { handler: mockUpdatedMutationResult({ nodes: mockAlerts }), }); Tracking.event.mockClear(); - findFirstStatusOption().vm.$emit('click'); - - await waitForPromises(); + await selectFirstStatusOption(); const status = findFirstStatusOption().text(); const { category, action, label } = trackAlertStatusUpdateOptions; diff --git a/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap b/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap index 359aaacde0b..499a971d791 100644 --- a/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap +++ b/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap @@ -2,22 +2,15 @@ exports[`Beta badge component renders the badge 1`] = ` <div> - <gl-badge-stub - class="gl-cursor-pointer" + <a + class="badge badge-neutral badge-pill gl-badge gl-cursor-pointer md" href="#" - iconsize="md" - size="md" - variant="neutral" + target="_self" > Beta - </gl-badge-stub> - <gl-popover-stub - cssclasses="" - data-testid="beta-badge" - showclosebutton="true" - target="[Function]" - title="What's Beta?" - triggers="hover focus click" + </a> + <div + class="gl-popover" > <p> A Beta feature is not production-ready, but is unlikely to change drastically before it's released. We encourage users to try Beta features and provide feedback. @@ -43,6 +36,6 @@ exports[`Beta badge component renders the badge 1`] = ` Is complete or near completion. </li> </ul> - </gl-popover-stub> + </div> </div> `; diff --git a/spec/frontend/vue_shared/components/badges/__snapshots__/experiment_badge_spec.js.snap b/spec/frontend/vue_shared/components/badges/__snapshots__/experiment_badge_spec.js.snap new file mode 100644 index 00000000000..4ad70338f3c --- /dev/null +++ b/spec/frontend/vue_shared/components/badges/__snapshots__/experiment_badge_spec.js.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Experiment badge component renders the badge 1`] = ` +<div> + <a + class="badge badge-neutral badge-pill gl-badge gl-cursor-pointer md" + href="#" + target="_self" + > + Experiment + </a> + <div + class="gl-popover" + > + <p> + An Experiment is a feature that's in the process of being developed. It's not production-ready. We encourage users to try Experimental features and provide feedback. + </p> + <p + class="gl-mb-0" + > + An Experiment: + </p> + <ul + class="gl-pl-4" + > + <li> + May be unstable. + </li> + <li> + Can cause data loss. + </li> + <li> + Has no support and might not be documented. + </li> + <li> + Can be removed at any time. + </li> + </ul> + </div> +</div> +`; diff --git a/spec/frontend/vue_shared/components/badges/beta_badge_spec.js b/spec/frontend/vue_shared/components/badges/beta_badge_spec.js index c930c6d5708..d826ca5c7c0 100644 --- a/spec/frontend/vue_shared/components/badges/beta_badge_spec.js +++ b/spec/frontend/vue_shared/components/badges/beta_badge_spec.js @@ -1,4 +1,4 @@ -import { shallowMount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { GlBadge } from '@gitlab/ui'; import BetaBadge from '~/vue_shared/components/badges/beta_badge.vue'; @@ -7,7 +7,7 @@ describe('Beta badge component', () => { const findBadge = () => wrapper.findComponent(GlBadge); const createWrapper = (props = {}) => { - wrapper = shallowMount(BetaBadge, { + wrapper = mount(BetaBadge, { propsData: { ...props }, }); }; diff --git a/spec/frontend/vue_shared/components/badges/experiment_badge_spec.js b/spec/frontend/vue_shared/components/badges/experiment_badge_spec.js new file mode 100644 index 00000000000..3239578a173 --- /dev/null +++ b/spec/frontend/vue_shared/components/badges/experiment_badge_spec.js @@ -0,0 +1,32 @@ +import { mount } from '@vue/test-utils'; +import { GlBadge } from '@gitlab/ui'; +import ExperimentBadge from '~/vue_shared/components/badges/experiment_badge.vue'; + +describe('Experiment badge component', () => { + let wrapper; + + const findBadge = () => wrapper.findComponent(GlBadge); + const createWrapper = (props = {}) => { + wrapper = mount(ExperimentBadge, { + propsData: { ...props }, + }); + }; + + it('renders the badge', () => { + createWrapper(); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('passes default size to badge', () => { + createWrapper(); + + expect(findBadge().props('size')).toBe('md'); + }); + + it('passes given size to badge', () => { + createWrapper({ size: 'sm' }); + + expect(findBadge().props('size')).toBe('sm'); + }); +}); diff --git a/spec/frontend/vue_shared/components/badges/hover_badge_spec.js b/spec/frontend/vue_shared/components/badges/hover_badge_spec.js new file mode 100644 index 00000000000..68f368215c0 --- /dev/null +++ b/spec/frontend/vue_shared/components/badges/hover_badge_spec.js @@ -0,0 +1,50 @@ +import { mount } from '@vue/test-utils'; +import { GlBadge, GlPopover } from '@gitlab/ui'; +import HoverBadge from '~/vue_shared/components/badges/hover_badge.vue'; + +describe('Hover badge component', () => { + let wrapper; + + const findBadge = () => wrapper.findComponent(GlBadge); + const findPopover = () => wrapper.findComponent(GlPopover); + const createWrapper = ({ props = {}, slots } = {}) => { + wrapper = mount(HoverBadge, { + propsData: { + label: 'Label', + title: 'Title', + ...props, + }, + slots, + }); + }; + + it('passes label to popover', () => { + createWrapper(); + + expect(findBadge().text()).toBe('Label'); + }); + + it('passes title to popover', () => { + createWrapper(); + + expect(findPopover().props('title')).toBe('Title'); + }); + + it('renders the default slot', () => { + createWrapper({ slots: { default: '<p>This is an awesome content</p>' } }); + + expect(findPopover().text()).toContain('This is an awesome content'); + }); + + it('passes default size to badge', () => { + createWrapper(); + + expect(findBadge().props('size')).toBe('md'); + }); + + it('passes given size to badge', () => { + createWrapper({ props: { size: 'sm' } }); + + expect(findBadge().props('size')).toBe('sm'); + }); +}); diff --git a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js index eadcd452929..c1109f21b47 100644 --- a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js +++ b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js @@ -60,6 +60,7 @@ describe('Blob Rich Viewer component', () => { expect(wrapper.text()).toContain('Line: 10'); expect(wrapper.text()).toContain('Line: 50'); expect(wrapper.emitted(CONTENT_LOADED_EVENT)).toHaveLength(1); + expect(handleLocationHash).toHaveBeenCalled(); expect(findMarkdownFieldView().props('isLoading')).toBe(false); }); diff --git a/spec/frontend/vue_shared/components/ci_badge_link_spec.js b/spec/frontend/vue_shared/components/ci_badge_link_spec.js index c74964c13f5..e1660225a5c 100644 --- a/spec/frontend/vue_shared/components/ci_badge_link_spec.js +++ b/spec/frontend/vue_shared/components/ci_badge_link_spec.js @@ -149,4 +149,10 @@ describe('CI Badge Link Component', () => { expect(findBadge().props('size')).toBe('lg'); }); + + it('should have class `gl-px-2` when `showText` is false', () => { + createComponent({ status: statuses.success, size: 'md', showText: false }); + + expect(findBadge().classes()).toContain('gl-px-2'); + }); }); diff --git a/spec/frontend/vue_shared/components/clone_dropdown/clone_dropdown_item_spec.js b/spec/frontend/vue_shared/components/clone_dropdown/clone_dropdown_item_spec.js index e0dfa084f3e..341afa03f80 100644 --- a/spec/frontend/vue_shared/components/clone_dropdown/clone_dropdown_item_spec.js +++ b/spec/frontend/vue_shared/components/clone_dropdown/clone_dropdown_item_spec.js @@ -6,11 +6,11 @@ describe('Clone Dropdown Button', () => { let wrapper; const link = 'ssh://foo.bar'; const label = 'SSH'; - const qaSelector = 'some-selector'; + const testId = 'some-selector'; const defaultPropsData = { link, label, - qaSelector, + testId, }; const findCopyButton = () => wrapper.findComponent(GlButton); @@ -46,7 +46,7 @@ describe('Clone Dropdown Button', () => { }); it('sets the qa selector', () => { - expect(findCopyButton().attributes('data-qa-selector')).toBe(qaSelector); + expect(findCopyButton().attributes('data-testid')).toBe(testId); }); }); }); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js index a22ad4c450e..7c9f3a3546a 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js @@ -97,6 +97,19 @@ export const projectMilestonesResponse = { }, }; +export const projectUsersResponse = { + data: { + project: { + id: 'gid://gitlab/Project/1', + attributes: { + nodes: mockUsers, + __typename: 'UserConnection', + }, + __typename: 'Project', + }, + }, +}; + export const mockCrmContacts = [ { __typename: 'CustomerRelationsContact', @@ -247,8 +260,8 @@ export const mockAuthorToken = { symbol: '@', token: UserToken, operators: OPERATORS_IS, - fetchPath: 'gitlab-org/gitlab-test', - fetchUsers: Api.projectUsers.bind(Api), + fullPath: 'gitlab-org/gitlab-test', + isProject: true, }; export const mockLabelToken = { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js index 63eacaabd0c..72e3475df75 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js @@ -420,6 +420,12 @@ describe('BaseToken', () => { expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); + it('renders `footer` slot when present', () => { + wrapper = createComponent({ slots: { footer: "<div class='custom-footer' />" } }); + + expect(wrapper.find('.custom-footer').exists()).toBe(true); + }); + describe('events', () => { describe('when activeToken has been selected', () => { beforeEach(() => { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js index e4ca7dcb19a..0229d00eb91 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js @@ -6,16 +6,21 @@ import { } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; +import usersAutocompleteQuery from '~/graphql_shared/queries/users_autocomplete.query.graphql'; import { OPTIONS_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants'; import UserToken from '~/vue_shared/components/filtered_search_bar/tokens/user_token.vue'; import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; -import { mockAuthorToken, mockUsers } from '../mock_data'; +import { mockAuthorToken, mockUsers, projectUsersResponse } from '../mock_data'; + +Vue.use(VueApollo); jest.mock('~/alert'); const defaultStubs = { @@ -37,6 +42,9 @@ const mockPreloadedUsers = [ }, ]; +const usersQueryHandler = jest.fn().mockResolvedValue(projectUsersResponse); +const mockApollo = createMockApollo([[usersAutocompleteQuery, usersQueryHandler]]); + function createComponent(options = {}) { const { config = mockAuthorToken, @@ -47,6 +55,7 @@ function createComponent(options = {}) { listeners = {}, } = options; return mount(UserToken, { + apolloProvider: mockApollo, propsData: { config, value, @@ -145,6 +154,33 @@ describe('UserToken', () => { expect(findBaseToken().props('suggestionsLoading')).toBe(false); }); }); + + describe('default - when fetchMilestones function is not provided in config', () => { + beforeEach(() => { + wrapper = createComponent({}); + return triggerFetchUsers(); + }); + + it('calls searchMilestonesQuery to fetch milestones', () => { + expect(usersQueryHandler).toHaveBeenCalledWith({ + fullPath: mockAuthorToken.fullPath, + isProject: mockAuthorToken.isProject, + search: null, + }); + }); + + it('calls searchMilestonesQuery with search parameter when provided', async () => { + const searchTerm = 'foo'; + + await triggerFetchUsers(searchTerm); + + expect(usersQueryHandler).toHaveBeenCalledWith({ + fullPath: mockAuthorToken.fullPath, + isProject: mockAuthorToken.isProject, + search: searchTerm, + }); + }); + }); }); }); diff --git a/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js b/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js index eee85ce4fd3..72a0eb98a07 100644 --- a/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js +++ b/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js @@ -363,13 +363,13 @@ describe('InputCopyToggleVisibility', () => { it('passes no `size` prop', () => { createComponent(); - expect(findFormInput().props('size')).toBe(null); + expect(findFormInput().props('width')).toBe(null); }); it('passes `size` prop to the input', () => { createComponent({ props: { size: 'md' } }); - expect(findFormInput().props('size')).toBe('md'); + expect(findFormInput().props('width')).toBe('md'); }); }); diff --git a/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js b/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js index 712e78458c6..57f54f7e7d3 100644 --- a/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js +++ b/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js @@ -1,41 +1,22 @@ import { nextTick } from 'vue'; -import { GlButton, GlLink, GlPopover } from '@gitlab/ui'; +import { GlButton } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue'; -import { counter } from '~/vue_shared/components/markdown/utils'; -import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; -import { stubComponent } from 'helpers/stub_component'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; -jest.mock('~/vue_shared/components/markdown/utils', () => ({ - counter: jest.fn().mockReturnValue(0), -})); - describe('vue_shared/component/markdown/editor_mode_switcher', () => { let wrapper; useLocalStorageSpy(); - const createComponent = ({ - value, - userCalloutDismisserSlotProps = { dismiss: jest.fn() }, - } = {}) => { + const createComponent = ({ value } = {}) => { wrapper = mount(EditorModeSwitcher, { propsData: { value, }, - stubs: { - UserCalloutDismisser: stubComponent(UserCalloutDismisser, { - render() { - return this.$scopedSlots.default(userCalloutDismisserSlotProps); - }, - }), - }, }); }; const findSwitcherButton = () => wrapper.findComponent(GlButton); - const findUserCalloutDismisser = () => wrapper.findComponent(UserCalloutDismisser); - const findCalloutPopover = () => wrapper.findComponent(GlPopover); describe.each` value | buttonText @@ -54,62 +35,7 @@ describe('vue_shared/component/markdown/editor_mode_switcher', () => { await nextTick(); findSwitcherButton().vm.$emit('click'); - expect(wrapper.emitted().switch).toEqual([[false]]); - }); - }); - - describe('rich text editor callout', () => { - let dismiss; - - beforeEach(() => { - dismiss = jest.fn(); - createComponent({ value: 'markdown', userCalloutDismisserSlotProps: { dismiss } }); - }); - - it('does not skip the user_callout_dismisser query', () => { - expect(findUserCalloutDismisser().props()).toMatchObject({ - skipQuery: false, - featureName: 'rich_text_editor', - }); - }); - - it('mounts new rich text editor popover', () => { - expect(findCalloutPopover().props()).toMatchObject({ - showCloseButton: '', - triggers: 'manual', - target: 'switch-to-rich-text-editor', - }); - }); - - it('dismisses the callout and emits "switch" event when popover close button is clicked', async () => { - await findCalloutPopover().findComponent(GlLink).vm.$emit('click'); - - expect(wrapper.emitted().switch).toEqual([[true]]); - expect(dismiss).toHaveBeenCalled(); - }); - - it('dismisses the callout when action button is clicked', () => { - findSwitcherButton().vm.$emit('click'); - - expect(dismiss).toHaveBeenCalled(); - }); - - it('does not show the callout if rich text is already enabled', async () => { - await wrapper.setProps({ value: 'richText' }); - - expect(findCalloutPopover().props()).toMatchObject({ - show: false, - }); - }); - - it('does not show the callout if already displayed once on the page', () => { - counter.mockReturnValue(1); - - createComponent({ value: 'markdown' }); - - expect(findCalloutPopover().props()).toMatchObject({ - show: false, - }); + expect(wrapper.emitted().switch).toEqual([[]]); }); }); }); diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js index c69b18bca88..b4c90fe49d1 100644 --- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js +++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js @@ -7,6 +7,8 @@ import { EDITING_MODE_MARKDOWN_FIELD, EDITING_MODE_CONTENT_EDITOR, CLEAR_AUTOSAVE_ENTRY_EVENT, + CONTENT_EDITOR_READY_EVENT, + MARKDOWN_EDITOR_READY_EVENT, } from '~/vue_shared/constants'; import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; @@ -83,22 +85,23 @@ describe('vue_shared/component/markdown/markdown_editor', () => { const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); const findContentEditor = () => { const result = wrapper.findComponent(ContentEditor); - // In Vue.js 3 there are nuances stubbing component with custom stub on mount // So we try to search for stub also return result.exists() ? result : wrapper.findComponent(ContentEditorStub); }; - const enableContentEditor = async () => { - findMarkdownField().vm.$emit('enableContentEditor'); - await nextTick(); - await waitForPromises(); + const enableContentEditor = () => { + return new Promise((resolve) => { + markdownEditorEventHub.$once(CONTENT_EDITOR_READY_EVENT, resolve); + findMarkdownField().vm.$emit('enableContentEditor'); + }); }; - const enableMarkdownEditor = async () => { - findContentEditor().vm.$emit('enableMarkdownEditor'); - await nextTick(); - await waitForPromises(); + const enableMarkdownEditor = () => { + return new Promise((resolve) => { + markdownEditorEventHub.$once(MARKDOWN_EDITOR_READY_EVENT, resolve); + findContentEditor().vm.$emit('enableMarkdownEditor'); + }); }; beforeEach(() => { @@ -128,9 +131,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => { }); }); - // quarantine: https://gitlab.com/gitlab-org/gitlab/-/issues/412618 - // eslint-disable-next-line jest/no-disabled-tests - it.skip('passes render_quick_actions param to renderMarkdownPath if quick actions are enabled', async () => { + it('passes render_quick_actions param to renderMarkdownPath if quick actions are enabled', async () => { buildWrapper({ propsData: { supportsQuickActions: true } }); await enableContentEditor(); @@ -139,9 +140,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => { expect(mock.history.post[0].url).toContain(`render_quick_actions=true`); }); - // quarantine: https://gitlab.com/gitlab-org/gitlab/-/issues/411565 - // eslint-disable-next-line jest/no-disabled-tests - it.skip('does not pass render_quick_actions param to renderMarkdownPath if quick actions are disabled', async () => { + it('does not pass render_quick_actions param to renderMarkdownPath if quick actions are disabled', async () => { buildWrapper({ propsData: { supportsQuickActions: false } }); await enableContentEditor(); @@ -213,9 +212,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => { expect(findMarkdownField().find('textarea').attributes('disabled')).toBe(undefined); }); - // quarantine: https://gitlab.com/gitlab-org/gitlab/-/issues/404734 - // eslint-disable-next-line jest/no-disabled-tests - it.skip('disables content editor when disabled prop is true', async () => { + it('disables content editor when disabled prop is true', async () => { buildWrapper({ propsData: { disabled: true } }); await enableContentEditor(); @@ -358,9 +355,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => { }); it(`emits ${EDITING_MODE_MARKDOWN_FIELD} event when enableMarkdownEditor emitted from content editor`, async () => { - buildWrapper({ - stubs: { ContentEditor: ContentEditorStub }, - }); + buildWrapper(); await enableContentEditor(); await enableMarkdownEditor(); @@ -494,12 +489,62 @@ describe('vue_shared/component/markdown/markdown_editor', () => { expect(findContentEditor().props().autofocus).toBe(false); }); - it('bubbles up keydown event', () => { - const event = new Event('keydown'); + describe('when keydown event is fired', () => { + let event; + beforeEach(() => { + event = new Event('keydown'); + window.getSelection = jest.fn(() => ({ + toString: jest.fn(() => 'test'), + removeAllRanges: jest.fn(), + })); + Object.assign(event, { preventDefault: jest.fn() }); + }); + it('bubbles up keydown event', () => { + findContentEditor().vm.$emit('keydown', event); + + expect(wrapper.emitted('keydown')).toEqual([[event]]); + }); + + it('bubbles up keydown event for meta key with default behaviour intact', () => { + event.metaKey = true; + findContentEditor().vm.$emit('keydown', event); - findContentEditor().vm.$emit('keydown', event); + expect(wrapper.emitted('keydown')).toEqual([[event]]); + expect(event.preventDefault).toHaveBeenCalledTimes(0); + }); + + it('bubbles up keydown event for meta + k key on selected text with default behaviour prevented', () => { + event.metaKey = true; + event.key = 'k'; + findContentEditor().vm.$emit('keydown', event); + + expect(wrapper.emitted('keydown')).toEqual([[event]]); + expect(event.preventDefault).toHaveBeenCalledTimes(1); + }); + + it('bubbles up keydown event for meta + k key without text selection with default behaviour prevented', () => { + event.metaKey = true; + event.key = 'k'; + window.getSelection = jest.fn(() => ({ + toString: jest.fn(() => ''), + removeAllRanges: jest.fn(), + })); + + findContentEditor().vm.$emit('keydown', event); - expect(wrapper.emitted('keydown')).toEqual([[event]]); + expect(wrapper.emitted('keydown')).toEqual([[event]]); + expect(event.preventDefault).toHaveBeenCalledTimes(1); + }); + + it('bubbles up keydown event for meta + non-k key with default behaviour intact', () => { + event.metaKey = true; + event.key = 'l'; + + findContentEditor().vm.$emit('keydown', event); + + expect(wrapper.emitted('keydown')).toEqual([[event]]); + expect(event.preventDefault).toHaveBeenCalledTimes(0); + }); }); describe(`when richText editor triggers enableMarkdownEditor event`, () => { diff --git a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js index 90d8ce3b500..59f01b7ff7f 100644 --- a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js +++ b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js @@ -3,7 +3,6 @@ import Toolbar from '~/vue_shared/components/markdown/toolbar.vue'; import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue'; import { updateText } from '~/lib/utils/text_markdown'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; -import { PROMO_URL } from 'jh_else_ce/lib/utils/url_utility'; jest.mock('~/lib/utils/text_markdown'); @@ -83,28 +82,5 @@ describe('toolbar', () => { expect(wrapper.emitted('enableContentEditor')).toEqual([[]]); expect(updateText).not.toHaveBeenCalled(); }); - - it('does not insert a template text if textarea has some value', () => { - wrapper.findComponent(EditorModeSwitcher).vm.$emit('switch', true); - - expect(updateText).not.toHaveBeenCalled(); - }); - - it('inserts a "getting started with rich text" template when switched for the first time', () => { - document.querySelector('textarea').value = ''; - - wrapper.findComponent(EditorModeSwitcher).vm.$emit('switch', true); - - expect(updateText).toHaveBeenCalledWith( - expect.objectContaining({ - tag: `### Rich text editor - -Try out **styling** _your_ content right here or read the [direction](${PROMO_URL}/direction/plan/knowledge/content_editor/).`, - textArea: document.querySelector('textarea'), - cursorOffset: 0, - wrap: false, - }), - ); - }); }); }); diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js index c6cd963fc33..67aa57a019b 100644 --- a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js +++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js @@ -1,5 +1,5 @@ -import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { GlAlert, GlListboxItem, GlLoadingIcon } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; @@ -32,7 +32,7 @@ describe('RunnerCliInstructions component', () => { const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findAlert = () => wrapper.findComponent(GlAlert); - const findArchitectureDropdownItems = () => wrapper.findAllByTestId('architecture-dropdown-item'); + const findArchitectureDropdownItems = () => wrapper.findAllComponents(GlListboxItem); const findBinaryDownloadButton = () => wrapper.findByTestId('binary-download-button'); const findBinaryInstructions = () => wrapper.findByTestId('binary-instructions'); const findRegisterCommand = () => wrapper.findByTestId('register-command'); @@ -43,7 +43,7 @@ describe('RunnerCliInstructions component', () => { fakeApollo = createMockApollo(requestHandlers); wrapper = extendedWrapper( - shallowMount(RunnerCliInstructions, { + mount(RunnerCliInstructions, { propsData: { platform: mockPlatform, registrationToken: 'MY_TOKEN', diff --git a/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js b/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js index c1feb64dacb..623a8739907 100644 --- a/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js +++ b/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js @@ -10,6 +10,7 @@ const DEFAULT_OPTIONS = [ ]; describe('~/vue_shared/components/segmented_control_button_group.vue', () => { + let consoleSpy; let wrapper; const createComponent = (props = {}, scopedSlots = {}) => { @@ -97,4 +98,34 @@ describe('~/vue_shared/components/segmented_control_button_group.vue', () => { ); }); }); + + describe('options prop validation', () => { + beforeEach(() => { + consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + }); + + it.each([ + [[{ disabled: true }]], + [[{ value: '1', disabled: 'false' }]], + [[{ value: null, disabled: 'true' }]], + [[[{ value: true }, null]]], + ])('with options=%j, fails validation', (options) => { + createComponent({ options }); + + expect(consoleSpy).toHaveBeenCalledTimes(1); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid prop: custom validator check failed for prop "options"'), + ); + }); + + it.each([ + [[{ value: '1' }]], + [[{ value: 1, disabled: true }]], + [[{ value: true, disabled: false }]], + ])('with options=%j, passes validation', (options) => { + createComponent({ options }); + + expect(consoleSpy).not.toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/source_viewer/__snapshots__/utils_spec.js.snap b/spec/frontend/vue_shared/components/source_viewer/__snapshots__/utils_spec.js.snap new file mode 100644 index 00000000000..e75b07dcf71 --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/__snapshots__/utils_spec.js.snap @@ -0,0 +1,88 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SourceViewer utils toggleBlameClasses adds classes 1`] = ` +<div + class="content" +> + <div + class="gl-border-gray-500 gl-border-t gl-pt-3!" + > + <div + id="reference-0" + > + 1 + </div> + <div + id="reference-1" + > + 2 + </div> + <div + id="reference-2" + > + 3 + </div> + </div> + <div> + <div + class="gl-border-gray-500 gl-border-t gl-pt-3!" + id="reference-3" + > + Content 1 + </div> + <div + class="gl-border-gray-500 gl-border-t gl-pt-3!" + id="reference-4" + > + Content 2 + </div> + <div + class="gl-border-gray-500 gl-border-t gl-pt-3!" + id="reference-5" + > + Content 3 + </div> + </div> +</div> +`; + +exports[`SourceViewer utils toggleBlameClasses removes classes 1`] = ` +<div + class="content" +> + <div> + <div + id="reference-0" + > + 1 + </div> + <div + id="reference-1" + > + 2 + </div> + <div + id="reference-2" + > + 3 + </div> + </div> + <div> + <div + id="reference-3" + > + Content 1 + </div> + <div + id="reference-4" + > + Content 2 + </div> + <div + id="reference-5" + > + Content 3 + </div> + </div> +</div> +`; diff --git a/spec/frontend/vue_shared/components/source_viewer/components/blame_info_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/blame_info_spec.js new file mode 100644 index 00000000000..ff8b2be9634 --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/components/blame_info_spec.js @@ -0,0 +1,63 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { setHTMLFixture } from 'helpers/fixtures'; +import CommitInfo from '~/repository/components/commit_info.vue'; +import BlameInfo from '~/vue_shared/components/source_viewer/components/blame_info.vue'; +import * as utils from '~/vue_shared/components/source_viewer/utils'; +import { SOURCE_CODE_CONTENT_MOCK, BLAME_DATA_MOCK } from '../mock_data'; + +describe('BlameInfo component', () => { + let wrapper; + + const createComponent = () => { + wrapper = shallowMountExtended(BlameInfo, { + propsData: { blameData: BLAME_DATA_MOCK }, + }); + }; + + beforeEach(() => { + setHTMLFixture(SOURCE_CODE_CONTENT_MOCK); + jest.spyOn(utils, 'toggleBlameClasses'); + createComponent(); + }); + + const findCommitInfoComponents = () => wrapper.findAllComponents(CommitInfo); + + it('adds the necessary classes to the DOM', () => { + expect(utils.toggleBlameClasses).toHaveBeenCalledWith(BLAME_DATA_MOCK, true); + }); + + it('renders a CommitInfo component for each blame entry', () => { + expect(findCommitInfoComponents().length).toBe(BLAME_DATA_MOCK.length); + }); + + it.each(BLAME_DATA_MOCK)( + 'sets the correct data and positioning for the commitInfo', + ({ lineno, commit, index }) => { + const commitInfoComponent = findCommitInfoComponents().at(index); + + expect(commitInfoComponent.props('commit')).toEqual(commit); + expect(commitInfoComponent.element.style.top).toBe(utils.calculateBlameOffset(lineno)); + }, + ); + + describe('commitInfo component styling', () => { + const borderTopClassName = 'gl-border-t'; + + it('does not add a top border for the first entry', () => { + expect(findCommitInfoComponents().at(0).element.classList).not.toContain(borderTopClassName); + }); + + it('add a top border for the rest of the entries', () => { + expect(findCommitInfoComponents().at(1).element.classList).toContain(borderTopClassName); + expect(findCommitInfoComponents().at(2).element.classList).toContain(borderTopClassName); + }); + }); + + describe('when component is destroyed', () => { + beforeEach(() => wrapper.destroy()); + + it('resets the DOM to its original state', () => { + expect(utils.toggleBlameClasses).toHaveBeenCalledWith(BLAME_DATA_MOCK, false); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/source_viewer/mock_data.js b/spec/frontend/vue_shared/components/source_viewer/mock_data.js index f35e9607d5c..b3516f7ed72 100644 --- a/spec/frontend/vue_shared/components/source_viewer/mock_data.js +++ b/spec/frontend/vue_shared/components/source_viewer/mock_data.js @@ -22,3 +22,24 @@ export const CHUNK_2 = { startingFrom: 70, blamePath, }; + +export const SOURCE_CODE_CONTENT_MOCK = ` +<div class="content"> + <div> + <div id="L1">1</div> + <div id="L2">2</div> + <div id="L3">3</div> + </div> + + <div> + <div id="LC1">Content 1</div> + <div id="LC2">Content 2</div> + <div id="LC3">Content 3</div> + </div> +</div>`; + +export const BLAME_DATA_MOCK = [ + { lineno: 1, commit: { author: 'Peter' }, index: 0 }, + { lineno: 2, commit: { author: 'Sarah' }, index: 1 }, + { lineno: 3, commit: { author: 'Peter' }, index: 2 }, +]; diff --git a/spec/frontend/vue_shared/components/source_viewer/utils_spec.js b/spec/frontend/vue_shared/components/source_viewer/utils_spec.js new file mode 100644 index 00000000000..0ac72aa9afb --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/utils_spec.js @@ -0,0 +1,35 @@ +import { setHTMLFixture } from 'helpers/fixtures'; +import { + calculateBlameOffset, + toggleBlameClasses, +} from '~/vue_shared/components/source_viewer/utils'; +import { SOURCE_CODE_CONTENT_MOCK, BLAME_DATA_MOCK } from './mock_data'; + +describe('SourceViewer utils', () => { + beforeEach(() => setHTMLFixture(SOURCE_CODE_CONTENT_MOCK)); + + const findContent = () => document.querySelector('.content'); + + describe('calculateBlameOffset', () => { + it('returns an offset of zero if line number === 1', () => { + expect(calculateBlameOffset(1)).toBe('0px'); + }); + + it('calculates an offset for the blame component', () => { + const { offsetTop } = document.querySelector('#LC3'); + expect(calculateBlameOffset(3)).toBe(`${offsetTop}px`); + }); + }); + + describe('toggleBlameClasses', () => { + it('adds classes', () => { + toggleBlameClasses(BLAME_DATA_MOCK, true); + expect(findContent()).toMatchSnapshot(); + }); + + it('removes classes', () => { + toggleBlameClasses(BLAME_DATA_MOCK, false); + expect(findContent()).toMatchSnapshot(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js index 17a363ad8b1..41cf1d2b2e8 100644 --- a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js +++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { GlTruncate } from '@gitlab/ui'; import timezoneMock from 'timezone-mock'; import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; @@ -36,6 +37,14 @@ describe('Time ago with tooltip component', () => { expect(vm.text()).toEqual(timeAgoTimestamp); }); + it('should render truncated value with gl-truncate as true', () => { + buildVm({ + enableTruncation: true, + }); + + expect(vm.findComponent(GlTruncate).exists()).toBe(true); + }); + it('should render provided html class', () => { buildVm({ cssClass: 'foo', diff --git a/spec/frontend/vue_shared/components/toggle_labels_spec.js b/spec/frontend/vue_shared/components/toggle_labels_spec.js new file mode 100644 index 00000000000..e4b4b7f9e0c --- /dev/null +++ b/spec/frontend/vue_shared/components/toggle_labels_spec.js @@ -0,0 +1,56 @@ +import { GlToggle } from '@gitlab/ui'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; + +import ToggleLabels from '~/vue_shared/components/toggle_labels.vue'; +import isShowingLabelsQuery from '~/graphql_shared/client/is_showing_labels.query.graphql'; + +Vue.use(VueApollo); + +describe('ToggleLabels', () => { + let wrapper; + + const findToggle = () => wrapper.findComponent(GlToggle); + + const mockSetIsShowingLabelsResolver = jest.fn(); + const mockApollo = createMockApollo([], { + Mutation: { + setIsShowingLabels: mockSetIsShowingLabelsResolver, + }, + }); + + const createComponent = () => { + mockApollo.clients.defaultClient.cache.writeQuery({ + query: isShowingLabelsQuery, + data: { + isShowingLabels: true, + }, + }); + wrapper = shallowMountExtended(ToggleLabels, { + apolloProvider: mockApollo, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + it('calls setIsShowingLabelsMutation on toggle', async () => { + expect(findToggle().props('value')).toBe(true); + findToggle().vm.$emit('change', false); + + await waitForPromises(); + + expect(mockSetIsShowingLabelsResolver).toHaveBeenCalledWith( + {}, + { + isShowingLabels: false, + }, + expect.anything(), + expect.anything(), + ); + }); +}); diff --git a/spec/frontend/vue_shared/components/vuex_module_provider_spec.js b/spec/frontend/vue_shared/components/vuex_module_provider_spec.js index e24c5a4609d..95f557b10c1 100644 --- a/spec/frontend/vue_shared/components/vuex_module_provider_spec.js +++ b/spec/frontend/vue_shared/components/vuex_module_provider_spec.js @@ -1,6 +1,4 @@ import { mount } from '@vue/test-utils'; -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; import VuexModuleProvider from '~/vue_shared/components/vuex_module_provider.vue'; const TestComponent = { @@ -38,12 +36,4 @@ describe('~/vue_shared/components/vuex_module_provider', () => { }); expect(findProvidedVuexModule()).toBe(TEST_VUEX_MODULE); }); - - it('does not blow up when used with vue-apollo', () => { - // See https://github.com/vuejs/vue-apollo/pull/1153 for details - Vue.use(VueApollo); - - createComponent(); - expect(findProvidedVuexModule()).toBe(TEST_VUEX_MODULE); - }); }); diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js index 03f509a3fa3..35e3564c599 100644 --- a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js +++ b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js @@ -5,6 +5,7 @@ import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import IssuableCreateRoot from '~/vue_shared/issuable/create/components/issuable_create_root.vue'; import IssuableForm from '~/vue_shared/issuable/create/components/issuable_form.vue'; +import { TYPE_TEST_CASE } from '~/issues/constants'; Vue.use(VueApollo); @@ -13,6 +14,7 @@ const createComponent = ({ descriptionHelpPath = '/help/user/markdown', labelsFetchPath = '/gitlab-org/gitlab-shell/-/labels.json', labelsManagePath = '/gitlab-org/gitlab-shell/-/labels', + issuableType = TYPE_TEST_CASE, } = {}) => { return mount(IssuableCreateRoot, { propsData: { @@ -20,6 +22,7 @@ const createComponent = ({ descriptionHelpPath, labelsFetchPath, labelsManagePath, + issuableType, }, apolloProvider: createMockApollo(), slots: { diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js index 62361705843..61185f913d9 100644 --- a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js +++ b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js @@ -1,9 +1,10 @@ -import { GlFormInput } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { GlFormInput, GlFormGroup, GlFormCheckbox } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import IssuableForm from '~/vue_shared/issuable/create/components/issuable_form.vue'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; import LabelsSelect from '~/sidebar/components/labels/labels_select_vue/labels_select_root.vue'; +import { TYPE_TEST_CASE } from '~/issues/constants'; import { __ } from '~/locale'; const createComponent = ({ @@ -11,13 +12,15 @@ const createComponent = ({ descriptionHelpPath = '/help/user/markdown', labelsFetchPath = '/gitlab-org/gitlab-shell/-/labels.json', labelsManagePath = '/gitlab-org/gitlab-shell/-/labels', + issuableType = TYPE_TEST_CASE, } = {}) => { - return shallowMount(IssuableForm, { + return shallowMountExtended(IssuableForm, { propsData: { descriptionPreviewPath, descriptionHelpPath, labelsFetchPath, labelsManagePath, + issuableType, }, slots: { actions: ` @@ -58,7 +61,7 @@ describe('IssuableForm', () => { describe('template', () => { it('renders issuable title input field', () => { - const titleFieldEl = wrapper.find('[data-testid="issuable-title"]'); + const titleFieldEl = wrapper.findByTestId('issuable-title'); expect(titleFieldEl.exists()).toBe(true); expect(titleFieldEl.find('label').text()).toBe('Title'); @@ -68,7 +71,7 @@ describe('IssuableForm', () => { }); it('renders issuable description input field', () => { - const descriptionFieldEl = wrapper.find('[data-testid="issuable-description"]'); + const descriptionFieldEl = wrapper.findByTestId('issuable-description'); expect(descriptionFieldEl.exists()).toBe(true); expect(descriptionFieldEl.find('label').text()).toBe('Description'); @@ -88,8 +91,23 @@ describe('IssuableForm', () => { }); }); + it('renders issuable confidential checkbox', () => { + const confidentialCheckboxEl = wrapper.findByTestId('issuable-confidential'); + expect(confidentialCheckboxEl.exists()).toBe(true); + + expect(confidentialCheckboxEl.findComponent(GlFormGroup).exists()).toBe(true); + expect(confidentialCheckboxEl.findComponent(GlFormGroup).attributes('label')).toBe( + 'Confidentiality', + ); + + expect(confidentialCheckboxEl.findComponent(GlFormCheckbox).exists()).toBe(true); + expect(confidentialCheckboxEl.findComponent(GlFormCheckbox).text()).toBe( + 'This test case is confidential and should only be visible to team members with at least Reporter access.', + ); + }); + it('renders labels select field', () => { - const labelsSelectEl = wrapper.find('[data-testid="issuable-labels"]'); + const labelsSelectEl = wrapper.findByTestId('issuable-labels'); expect(labelsSelectEl.exists()).toBe(true); expect(labelsSelectEl.find('label').text()).toBe('Labels'); @@ -111,7 +129,7 @@ describe('IssuableForm', () => { it('renders contents for slot "actions"', () => { const buttonEl = wrapper - .find('[data-testid="issuable-create-actions"]') + .findByTestId('issuable-create-actions') .find('button.js-issuable-save'); expect(buttonEl.exists()).toBe(true); diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js index 9f7254ba0e6..47da111b604 100644 --- a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js +++ b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js @@ -1,6 +1,5 @@ import { GlLink, GlLabel, GlIcon, GlFormCheckbox, GlSprintf } from '@gitlab/ui'; import { nextTick } from 'vue'; -import { escape } from 'lodash'; import { useFakeDate } from 'helpers/fake_date'; import { shallowMountExtended as shallowMount } from 'helpers/vue_test_utils_helper'; import IssuableItem from '~/vue_shared/issuable/list/components/issuable_item.vue'; @@ -288,23 +287,10 @@ describe('IssuableItem', () => { expect(titleEl.exists()).toBe(true); expect(titleEl.findComponent(GlLink).attributes('href')).toBe(expectedHref); expect(titleEl.findComponent(GlLink).attributes('target')).toBe(expectedTarget); - expect(titleEl.findComponent(GlLink).html()).toContain(mockIssuable.titleHtml); + expect(titleEl.findComponent(GlLink).text()).toBe(mockIssuable.title); }, ); - it('renders issuable title with escaped markup when issue tracker is external', () => { - const mockTitle = '<script>foobar</script>'; - wrapper = createComponent({ - issuable: { - ...mockIssuable, - title: mockTitle, - externalTracker: 'jira', - }, - }); - - expect(wrapper.findByTestId('issuable-title').html()).toContain(escape(mockTitle)); - }); - it('renders checkbox when `showCheckbox` prop is true', async () => { wrapper = createComponent({ showCheckbox: true, @@ -366,7 +352,7 @@ describe('IssuableItem', () => { expect(hiddenIcon.props('name')).toBe('spam'); expect(hiddenIcon.attributes()).toMatchObject({ - title: 'This issue is hidden because its author has been banned', + title: 'This issue is hidden because its author has been banned.', arialabel: 'Hidden', }); }); diff --git a/spec/frontend/vue_shared/issuable/list/mock_data.js b/spec/frontend/vue_shared/issuable/list/mock_data.js index b39d177f292..f8cf3ba5271 100644 --- a/spec/frontend/vue_shared/issuable/list/mock_data.js +++ b/spec/frontend/vue_shared/issuable/list/mock_data.js @@ -42,7 +42,7 @@ export const mockCurrentUserTodo = { export const mockIssuable = { iid: '30', title: 'Dismiss Cipher with no integrity', - titleHtml: '<gl-emoji title="party-parrot"></gl-emoji>Dismiss Cipher with no integrity', + titleHtml: 'Dismiss Cipher with no integrity', description: 'fortitudinis _fomentis_ dolor mitigari solet.', descriptionHtml: 'fortitudinis <i>fomentis</i> dolor mitigari solet.', state: 'opened', diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js index 3b6f06d835b..03395e5dfc0 100644 --- a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js +++ b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js @@ -2,6 +2,8 @@ import { GlBadge, GlButton, GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import HiddenBadge from '~/issuable/components/hidden_badge.vue'; +import LockedBadge from '~/issuable/components/locked_badge.vue'; import { STATUS_CLOSED, STATUS_OPEN, STATUS_REOPENED, TYPE_ISSUE } from '~/issues/constants'; import { __ } from '~/locale'; import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; @@ -23,8 +25,8 @@ describe('IssuableHeader component', () => { wrapper.findAllComponents(GlIcon).filter((component) => component.props('name') === name); const findIcon = (name) => findGlIconWithName(name).exists() ? findGlIconWithName(name).at(0) : undefined; - const findBlockedIcon = () => findIcon('lock'); - const findHiddenIcon = () => findIcon('spam'); + const findBlockedBadge = () => wrapper.findComponent(LockedBadge); + const findHiddenBadge = () => wrapper.findComponent(HiddenBadge); const findExternalLinkIcon = () => findIcon('external-link'); const findFirstContributionIcon = () => findIcon('first-contribution'); const findComponentTooltip = (component) => getBinding(component.element, 'gl-tooltip'); @@ -111,49 +113,31 @@ describe('IssuableHeader component', () => { }); }); - describe('blocked icon', () => { + describe('blocked badge', () => { it('renders when issuable is blocked', () => { createComponent({ blocked: true }); - expect(findBlockedIcon().props('ariaLabel')).toBe('Blocked'); - }); - - it('has tooltip', () => { - createComponent({ blocked: true }); - - expect(findComponentTooltip(findBlockedIcon())).toBeDefined(); - expect(findBlockedIcon().attributes('title')).toBe( - 'This issue is locked. Only project members can comment.', - ); + expect(findBlockedBadge().props('issuableType')).toBe('issue'); }); it('does not render when issuable is not blocked', () => { createComponent({ blocked: false }); - expect(findBlockedIcon()).toBeUndefined(); + expect(findBlockedBadge().exists()).toBe(false); }); }); - describe('hidden icon', () => { + describe('hidden badge', () => { it('renders when issuable is hidden', () => { createComponent({ isHidden: true }); - expect(findHiddenIcon().props('ariaLabel')).toBe('Hidden'); - }); - - it('has tooltip', () => { - createComponent({ isHidden: true }); - - expect(findComponentTooltip(findHiddenIcon())).toBeDefined(); - expect(findHiddenIcon().attributes('title')).toBe( - 'This issue is hidden because its author has been banned', - ); + expect(findHiddenBadge().props('issuableType')).toBe('issue'); }); it('does not render when issuable is not hidden', () => { createComponent({ isHidden: false }); - expect(findHiddenIcon()).toBeUndefined(); + expect(findHiddenBadge().exists()).toBe(false); }); }); |