From d612723c35d7fdaeb8b09e91232053e04850c2ae Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 25 Nov 2022 18:10:56 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../assignees/assignees_realtime_spec.js | 2 +- .../assignees/sidebar_assignees_widget_spec.js | 4 +- .../copy/copy_email_to_clipboard_spec.js | 17 + .../sidebar/components/copy/copyable_field_spec.js | 77 ++++ .../copy/sidebar_reference_widget_spec.js | 93 +++++ .../copy_email/copy_email_to_clipboard_spec.js | 17 - .../components/crm_contacts/crm_contacts_spec.js | 4 +- .../components/move/issuable_move_dropdown_spec.js | 388 +++++++++++++++++++++ .../components/move/move_issues_button_spec.js | 2 +- .../reference/sidebar_reference_widget_spec.js | 93 ----- .../components/severity/sidebar_severity_spec.js | 2 +- .../components/time_tracking/report_spec.js | 6 +- .../todo_toggle/sidebar_todo_widget_spec.js | 2 +- .../components/todo_toggle/todo_button_spec.js | 68 ++++ .../components/toggle/toggle_sidebar_spec.js | 46 +++ 15 files changed, 700 insertions(+), 121 deletions(-) create mode 100644 spec/frontend/sidebar/components/copy/copy_email_to_clipboard_spec.js create mode 100644 spec/frontend/sidebar/components/copy/copyable_field_spec.js create mode 100644 spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js delete mode 100644 spec/frontend/sidebar/components/copy_email/copy_email_to_clipboard_spec.js create mode 100644 spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js delete mode 100644 spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js create mode 100644 spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js create mode 100644 spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js (limited to 'spec/frontend/sidebar') diff --git a/spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js b/spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js index 5b6f4d3b557..080171fb2ea 100644 --- a/spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js +++ b/spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js @@ -6,7 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import AssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue'; import issuableAssigneesSubscription from '~/sidebar/queries/issuable_assignees.subscription.graphql'; import SidebarMediator from '~/sidebar/sidebar_mediator'; -import getIssueAssigneesQuery from '~/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql'; +import getIssueAssigneesQuery from '~/sidebar/queries/get_issue_assignees.query.graphql'; import Mock, { issuableQueryResponse, subscriptionNullResponse, diff --git a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js index cbb4c41dd14..3aca346ff5f 100644 --- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js +++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js @@ -12,8 +12,8 @@ import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue'; import SidebarInviteMembers from '~/sidebar/components/assignees/sidebar_invite_members.vue'; import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; -import getIssueAssigneesQuery from '~/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql'; -import updateIssueAssigneesMutation from '~/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql'; +import getIssueAssigneesQuery from '~/sidebar/queries/get_issue_assignees.query.graphql'; +import updateIssueAssigneesMutation from '~/sidebar/queries/update_issue_assignees.mutation.graphql'; import UserSelect from '~/vue_shared/components/user_select/user_select.vue'; import { issuableQueryResponse, updateIssueAssigneesMutationResponse } from '../../mock_data'; diff --git a/spec/frontend/sidebar/components/copy/copy_email_to_clipboard_spec.js b/spec/frontend/sidebar/components/copy/copy_email_to_clipboard_spec.js new file mode 100644 index 00000000000..5b6db43a366 --- /dev/null +++ b/spec/frontend/sidebar/components/copy/copy_email_to_clipboard_spec.js @@ -0,0 +1,17 @@ +import { shallowMount } from '@vue/test-utils'; +import CopyEmailToClipboard from '~/sidebar/components/copy/copy_email_to_clipboard.vue'; +import CopyableField from '~/sidebar/components/copy/copyable_field.vue'; + +describe('CopyEmailToClipboard component', () => { + const mockIssueEmailAddress = 'sample+email@test.com'; + + const wrapper = shallowMount(CopyEmailToClipboard, { + propsData: { + issueEmailAddress: mockIssueEmailAddress, + }, + }); + + it('sets CopyableField `value` prop to issueEmailAddress', () => { + expect(wrapper.findComponent(CopyableField).props('value')).toBe(mockIssueEmailAddress); + }); +}); diff --git a/spec/frontend/sidebar/components/copy/copyable_field_spec.js b/spec/frontend/sidebar/components/copy/copyable_field_spec.js new file mode 100644 index 00000000000..7790d77bc65 --- /dev/null +++ b/spec/frontend/sidebar/components/copy/copyable_field_spec.js @@ -0,0 +1,77 @@ +import { GlLoadingIcon, GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import CopyableField from '~/sidebar/components/copy/copyable_field.vue'; + +describe('SidebarCopyableField', () => { + let wrapper; + + const defaultProps = { + value: 'Gl-1', + name: 'Reference', + }; + + const createComponent = (propsData = defaultProps) => { + wrapper = shallowMount(CopyableField, { + propsData, + stubs: { + GlSprintf, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findClipboardButton = () => wrapper.findComponent(ClipboardButton); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + + describe('template', () => { + describe('when `isLoading` prop is `false`', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders copyable field', () => { + expect(wrapper.text()).toContain('Reference: Gl-1'); + }); + + it('renders ClipboardButton with correct props', () => { + const clipboardButton = findClipboardButton(); + + expect(clipboardButton.exists()).toBe(true); + expect(clipboardButton.props('title')).toBe(`Copy ${defaultProps.name}`); + expect(clipboardButton.props('text')).toBe(defaultProps.value); + }); + + it('does not render loading icon', () => { + expect(findLoadingIcon().exists()).toBe(false); + }); + }); + + describe('when `isLoading` prop is `true`', () => { + beforeEach(() => { + createComponent({ ...defaultProps, isLoading: true }); + }); + + it('renders loading icon', () => { + expect(findLoadingIcon().exists()).toBe(true); + expect(findLoadingIcon().props('label')).toBe('Loading Reference'); + }); + + it('does not render clipboard button', () => { + expect(findClipboardButton().exists()).toBe(false); + }); + }); + + describe('with `clipboardTooltipText` prop', () => { + it('sets ClipboardButton `title` prop to `clipboardTooltipText` value', () => { + const mockClipboardTooltipText = 'Copy my custom value'; + createComponent({ ...defaultProps, clipboardTooltipText: mockClipboardTooltipText }); + + expect(findClipboardButton().props('title')).toBe(mockClipboardTooltipText); + }); + }); + }); +}); diff --git a/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js b/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js new file mode 100644 index 00000000000..c5161a748a9 --- /dev/null +++ b/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js @@ -0,0 +1,93 @@ +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { IssuableType } from '~/issues/constants'; +import SidebarReferenceWidget from '~/sidebar/components/copy/sidebar_reference_widget.vue'; +import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql'; +import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql'; +import CopyableField from '~/sidebar/components/copy/copyable_field.vue'; +import { issueReferenceResponse } from '../../mock_data'; + +describe('Sidebar Reference Widget', () => { + let wrapper; + let fakeApollo; + + const mockReferenceValue = 'reference-1234'; + + const findCopyableField = () => wrapper.findComponent(CopyableField); + + const createComponent = ({ + issuableType = IssuableType.Issue, + referenceQuery = issueReferenceQuery, + referenceQueryHandler = jest.fn().mockResolvedValue(issueReferenceResponse(mockReferenceValue)), + } = {}) => { + Vue.use(VueApollo); + + fakeApollo = createMockApollo([[referenceQuery, referenceQueryHandler]]); + + wrapper = shallowMount(SidebarReferenceWidget, { + apolloProvider: fakeApollo, + provide: { + fullPath: 'group/project', + iid: '1', + }, + propsData: { + issuableType, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when reference is loading', () => { + it('sets CopyableField `is-loading` prop to `true`', () => { + createComponent({ referenceQueryHandler: jest.fn().mockReturnValue(new Promise(() => {})) }); + expect(findCopyableField().props('isLoading')).toBe(true); + }); + }); + + describe.each([ + [IssuableType.Issue, issueReferenceQuery], + [IssuableType.MergeRequest, mergeRequestReferenceQuery], + ])('when issuableType is %s', (issuableType, referenceQuery) => { + it('sets CopyableField `value` prop to reference value', async () => { + createComponent({ + issuableType, + referenceQuery, + }); + + await waitForPromises(); + + expect(findCopyableField().props('value')).toBe(mockReferenceValue); + }); + + describe('when error occurs', () => { + it('calls createFlash with correct parameters', async () => { + const mockError = new Error('mayday'); + + createComponent({ + issuableType, + referenceQuery, + referenceQueryHandler: jest.fn().mockRejectedValue(mockError), + }); + + await waitForPromises(); + + const [ + [ + { + message, + error: { networkError }, + }, + ], + ] = wrapper.emitted('fetch-error'); + expect(message).toBe('An error occurred while fetching reference'); + expect(networkError).toEqual(mockError); + }); + }); + }); +}); diff --git a/spec/frontend/sidebar/components/copy_email/copy_email_to_clipboard_spec.js b/spec/frontend/sidebar/components/copy_email/copy_email_to_clipboard_spec.js deleted file mode 100644 index 9f94a1b3f3a..00000000000 --- a/spec/frontend/sidebar/components/copy_email/copy_email_to_clipboard_spec.js +++ /dev/null @@ -1,17 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import CopyEmailToClipboard from '~/sidebar/components/copy_email/copy_email_to_clipboard.vue'; -import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue'; - -describe('CopyEmailToClipboard component', () => { - const mockIssueEmailAddress = 'sample+email@test.com'; - - const wrapper = shallowMount(CopyEmailToClipboard, { - propsData: { - issueEmailAddress: mockIssueEmailAddress, - }, - }); - - it('sets CopyableField `value` prop to issueEmailAddress', () => { - expect(wrapper.findComponent(CopyableField).props('value')).toBe(mockIssueEmailAddress); - }); -}); diff --git a/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js b/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js index 2281b38cc53..ca43c219d92 100644 --- a/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js +++ b/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js @@ -5,8 +5,8 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/flash'; import CrmContacts from '~/sidebar/components/crm_contacts/crm_contacts.vue'; -import getIssueCrmContactsQuery from '~/sidebar/components/crm_contacts/queries/get_issue_crm_contacts.query.graphql'; -import issueCrmContactsSubscription from '~/sidebar/components/crm_contacts/queries/issue_crm_contacts.subscription.graphql'; +import getIssueCrmContactsQuery from '~/sidebar/queries/get_issue_crm_contacts.query.graphql'; +import issueCrmContactsSubscription from '~/sidebar/queries/issue_crm_contacts.subscription.graphql'; import { getIssueCrmContactsQueryResponse, issueCrmContactsUpdateResponse, diff --git a/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js b/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js new file mode 100644 index 00000000000..72279f44e80 --- /dev/null +++ b/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js @@ -0,0 +1,388 @@ +import { + GlIcon, + GlLoadingIcon, + GlDropdown, + GlDropdownForm, + GlDropdownItem, + GlSearchBoxByType, + GlButton, +} from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; + +import { nextTick } from 'vue'; +import axios from '~/lib/utils/axios_utils'; +import IssuableMoveDropdown from '~/sidebar/components/move/issuable_move_dropdown.vue'; + +const mockProjects = [ + { + id: 2, + name_with_namespace: 'Gitlab Org / Gitlab Shell', + full_path: 'gitlab-org/gitlab-shell', + }, + { + id: 3, + name_with_namespace: 'Gnuwget / Wget2', + full_path: 'gnuwget/wget2', + }, + { + id: 4, + name_with_namespace: 'Commit451 / Lab Coat', + full_path: 'Commit451/lab-coat', + }, +]; + +const mockProps = { + projectsFetchPath: '/-/autocomplete/projects?project_id=1', + dropdownButtonTitle: 'Move issuable', + dropdownHeaderTitle: 'Move issuable', + moveInProgress: false, + disabled: false, +}; + +const mockEvent = { + stopPropagation: jest.fn(), + preventDefault: jest.fn(), +}; + +describe('IssuableMoveDropdown', () => { + let mock; + let wrapper; + + const createComponent = (propsData = mockProps) => { + wrapper = shallowMount(IssuableMoveDropdown, { + propsData, + }); + wrapper.vm.$refs.dropdown.hide = jest.fn(); + wrapper.vm.$refs.searchInput.focusInput = jest.fn(); + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + mock.restore(); + }); + + describe('watch', () => { + describe('searchKey', () => { + it('calls `fetchProjects` with value of the prop', async () => { + jest.spyOn(wrapper.vm, 'fetchProjects'); + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + searchKey: 'foo', + }); + + await nextTick(); + + expect(wrapper.vm.fetchProjects).toHaveBeenCalledWith('foo'); + }); + }); + }); + + describe('methods', () => { + describe('fetchProjects', () => { + it('sets projectsListLoading to true and projectsListLoadFailed to false', () => { + wrapper.vm.fetchProjects(); + + expect(wrapper.vm.projectsListLoading).toBe(true); + expect(wrapper.vm.projectsListLoadFailed).toBe(false); + }); + + it('calls `axios.get` with `projectsFetchPath` and query param `search`', () => { + jest.spyOn(axios, 'get').mockResolvedValue({ + data: mockProjects, + }); + + wrapper.vm.fetchProjects('foo'); + + expect(axios.get).toHaveBeenCalledWith( + mockProps.projectsFetchPath, + expect.objectContaining({ + params: { + search: 'foo', + }, + }), + ); + }); + + it('sets response to `projects` and focuses on searchInput when request is successful', async () => { + jest.spyOn(axios, 'get').mockResolvedValue({ + data: mockProjects, + }); + + await wrapper.vm.fetchProjects('foo'); + + expect(wrapper.vm.projects).toBe(mockProjects); + expect(wrapper.vm.$refs.searchInput.focusInput).toHaveBeenCalled(); + }); + + it('sets projectsListLoadFailed to true when request fails', async () => { + jest.spyOn(axios, 'get').mockRejectedValue({}); + + await wrapper.vm.fetchProjects('foo'); + + expect(wrapper.vm.projectsListLoadFailed).toBe(true); + }); + + it('sets projectsListLoading to false when request completes', async () => { + jest.spyOn(axios, 'get').mockResolvedValue({ + data: mockProjects, + }); + + await wrapper.vm.fetchProjects('foo'); + + expect(wrapper.vm.projectsListLoading).toBe(false); + }); + }); + + describe('isSelectedProject', () => { + it.each` + project | selectedProject | title | returnValue + ${mockProjects[0]} | ${mockProjects[0]} | ${'are same projects'} | ${true} + ${mockProjects[0]} | ${mockProjects[1]} | ${'are different projects'} | ${false} + `( + 'returns $returnValue when selectedProject and provided project param $title', + async ({ project, selectedProject, returnValue }) => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + selectedProject, + }); + + await nextTick(); + + expect(wrapper.vm.isSelectedProject(project)).toBe(returnValue); + }, + ); + + it('returns false when selectedProject is null', async () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + selectedProject: null, + }); + + await nextTick(); + + expect(wrapper.vm.isSelectedProject(mockProjects[0])).toBe(false); + }); + }); + }); + + describe('template', () => { + const findDropdownEl = () => wrapper.findComponent(GlDropdown); + + it('renders collapsed state element with icon', () => { + const collapsedEl = wrapper.find('[data-testid="move-collapsed"]'); + + expect(collapsedEl.exists()).toBe(true); + expect(collapsedEl.attributes('title')).toBe(mockProps.dropdownButtonTitle); + expect(collapsedEl.findComponent(GlIcon).exists()).toBe(true); + expect(collapsedEl.findComponent(GlIcon).props('name')).toBe('arrow-right'); + }); + + describe('gl-dropdown component', () => { + it('renders component container element', () => { + expect(findDropdownEl().exists()).toBe(true); + expect(findDropdownEl().props('block')).toBe(true); + }); + + it('renders gl-dropdown-form component', () => { + expect(findDropdownEl().findComponent(GlDropdownForm).exists()).toBe(true); + }); + + it('renders disabled dropdown when `disabled` is true', () => { + createComponent({ ...mockProps, disabled: true }); + + expect(findDropdownEl().attributes('disabled')).toBe('true'); + }); + + it('renders header element', () => { + const headerEl = findDropdownEl().find('[data-testid="header"]'); + + expect(headerEl.exists()).toBe(true); + expect(headerEl.find('span').text()).toBe(mockProps.dropdownHeaderTitle); + expect(headerEl.findComponent(GlButton).props('icon')).toBe('close'); + }); + + it('renders gl-search-box-by-type component', () => { + const searchEl = findDropdownEl().findComponent(GlSearchBoxByType); + + expect(searchEl.exists()).toBe(true); + expect(searchEl.attributes()).toMatchObject({ + placeholder: 'Search project', + debounce: '300', + }); + }); + + it('renders gl-loading-icon component when projectsListLoading prop is true', async () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + projectsListLoading: true, + }); + + await nextTick(); + + expect(findDropdownEl().findComponent(GlLoadingIcon).exists()).toBe(true); + }); + + it('renders gl-dropdown-item components for available projects', async () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + projects: mockProjects, + selectedProject: mockProjects[0], + }); + + await nextTick(); + + const dropdownItems = wrapper.findAllComponents(GlDropdownItem); + + expect(dropdownItems).toHaveLength(mockProjects.length); + expect(dropdownItems.at(0).props()).toMatchObject({ + isCheckItem: true, + isChecked: true, + }); + expect(dropdownItems.at(0).text()).toBe(mockProjects[0].name_with_namespace); + }); + + it('renders string "No matching results" when search does not yield any matches', async () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + searchKey: 'foo', + }); + + // Wait for `searchKey` watcher to run. + await nextTick(); + + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + projects: [], + projectsListLoading: false, + }); + + await nextTick(); + + const dropdownContentEl = wrapper.find('[data-testid="content"]'); + + expect(dropdownContentEl.text()).toContain('No matching results'); + }); + + it('renders string "Failed to load projects" when loading projects list fails', async () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + projects: [], + projectsListLoading: false, + projectsListLoadFailed: true, + }); + + await nextTick(); + + const dropdownContentEl = wrapper.find('[data-testid="content"]'); + + expect(dropdownContentEl.text()).toContain('Failed to load projects'); + }); + + it('renders gl-button within footer', async () => { + const moveButtonEl = wrapper.find('[data-testid="footer"]').findComponent(GlButton); + + expect(moveButtonEl.text()).toBe('Move'); + expect(moveButtonEl.attributes('disabled')).toBe('true'); + + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + selectedProject: mockProjects[0], + }); + + await nextTick(); + + expect( + wrapper.find('[data-testid="footer"]').findComponent(GlButton).attributes('disabled'), + ).not.toBeDefined(); + }); + }); + + describe('events', () => { + it('collapsed state element emits `toggle-collapse` event on component when clicked', () => { + wrapper.find('[data-testid="move-collapsed"]').trigger('click'); + + expect(wrapper.emitted('toggle-collapse')).toHaveLength(1); + }); + + it('gl-dropdown component calls `fetchProjects` on `shown` event', () => { + jest.spyOn(axios, 'get').mockResolvedValue({ + data: mockProjects, + }); + + findDropdownEl().vm.$emit('shown'); + + expect(axios.get).toHaveBeenCalled(); + }); + + it('gl-dropdown component prevents dropdown body from closing on `hide` event when `projectItemClick` prop is true', async () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + projectItemClick: true, + }); + + findDropdownEl().vm.$emit('hide', mockEvent); + + expect(mockEvent.preventDefault).toHaveBeenCalled(); + expect(wrapper.vm.projectItemClick).toBe(false); + }); + + it('gl-dropdown component emits `dropdown-close` event on component from `hide` event', async () => { + findDropdownEl().vm.$emit('hide'); + + expect(wrapper.emitted('dropdown-close')).toHaveLength(1); + }); + + it('close icon in dropdown header closes the dropdown when clicked', () => { + wrapper.find('[data-testid="header"]').findComponent(GlButton).vm.$emit('click', mockEvent); + + expect(wrapper.vm.$refs.dropdown.hide).toHaveBeenCalled(); + }); + + it('sets project for clicked gl-dropdown-item to selectedProject', async () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + projects: mockProjects, + }); + + await nextTick(); + + wrapper.findAllComponents(GlDropdownItem).at(0).vm.$emit('click', mockEvent); + + expect(wrapper.vm.selectedProject).toBe(mockProjects[0]); + }); + + it('hides dropdown and emits `move-issuable` event when move button is clicked', async () => { + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax + wrapper.setData({ + selectedProject: mockProjects[0], + }); + + await nextTick(); + + wrapper.find('[data-testid="footer"]').findComponent(GlButton).vm.$emit('click'); + + expect(wrapper.vm.$refs.dropdown.hide).toHaveBeenCalled(); + expect(wrapper.emitted('move-issuable')).toHaveLength(1); + expect(wrapper.emitted('move-issuable')[0]).toEqual([mockProjects[0]]); + }); + }); + }); +}); diff --git a/spec/frontend/sidebar/components/move/move_issues_button_spec.js b/spec/frontend/sidebar/components/move/move_issues_button_spec.js index 4eed5785977..7431686a3f4 100644 --- a/spec/frontend/sidebar/components/move/move_issues_button_spec.js +++ b/spec/frontend/sidebar/components/move/move_issues_button_spec.js @@ -8,7 +8,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import createFlash from '~/flash'; import { logError } from '~/lib/logger'; -import IssuableMoveDropdown from '~/vue_shared/components/sidebar/issuable_move_dropdown.vue'; +import IssuableMoveDropdown from '~/sidebar/components/move/issuable_move_dropdown.vue'; import issuableEventHub from '~/issues/list/eventhub'; import MoveIssuesButton from '~/sidebar/components/move/move_issues_button.vue'; import moveIssueMutation from '~/sidebar/queries/move_issue.mutation.graphql'; diff --git a/spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js b/spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js deleted file mode 100644 index 69e35cd1d05..00000000000 --- a/spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js +++ /dev/null @@ -1,93 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import { IssuableType } from '~/issues/constants'; -import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue'; -import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql'; -import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql'; -import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue'; -import { issueReferenceResponse } from '../../mock_data'; - -describe('Sidebar Reference Widget', () => { - let wrapper; - let fakeApollo; - - const mockReferenceValue = 'reference-1234'; - - const findCopyableField = () => wrapper.findComponent(CopyableField); - - const createComponent = ({ - issuableType = IssuableType.Issue, - referenceQuery = issueReferenceQuery, - referenceQueryHandler = jest.fn().mockResolvedValue(issueReferenceResponse(mockReferenceValue)), - } = {}) => { - Vue.use(VueApollo); - - fakeApollo = createMockApollo([[referenceQuery, referenceQueryHandler]]); - - wrapper = shallowMount(SidebarReferenceWidget, { - apolloProvider: fakeApollo, - provide: { - fullPath: 'group/project', - iid: '1', - }, - propsData: { - issuableType, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when reference is loading', () => { - it('sets CopyableField `is-loading` prop to `true`', () => { - createComponent({ referenceQueryHandler: jest.fn().mockReturnValue(new Promise(() => {})) }); - expect(findCopyableField().props('isLoading')).toBe(true); - }); - }); - - describe.each([ - [IssuableType.Issue, issueReferenceQuery], - [IssuableType.MergeRequest, mergeRequestReferenceQuery], - ])('when issuableType is %s', (issuableType, referenceQuery) => { - it('sets CopyableField `value` prop to reference value', async () => { - createComponent({ - issuableType, - referenceQuery, - }); - - await waitForPromises(); - - expect(findCopyableField().props('value')).toBe(mockReferenceValue); - }); - - describe('when error occurs', () => { - it('calls createFlash with correct parameters', async () => { - const mockError = new Error('mayday'); - - createComponent({ - issuableType, - referenceQuery, - referenceQueryHandler: jest.fn().mockRejectedValue(mockError), - }); - - await waitForPromises(); - - const [ - [ - { - message, - error: { networkError }, - }, - ], - ] = wrapper.emitted('fetch-error'); - expect(message).toBe('An error occurred while fetching reference'); - expect(networkError).toEqual(mockError); - }); - }); - }); -}); diff --git a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js index bdea33371d8..948f7c82956 100644 --- a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js +++ b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js @@ -4,7 +4,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/flash'; import { INCIDENT_SEVERITY, ISSUABLE_TYPES } from '~/sidebar/components/severity/constants'; -import updateIssuableSeverity from '~/sidebar/components/severity/graphql/mutations/update_issuable_severity.mutation.graphql'; +import updateIssuableSeverity from '~/sidebar/queries/update_issuable_severity.mutation.graphql'; import SeverityToken from '~/sidebar/components/severity/severity.vue'; import SidebarSeverity from '~/sidebar/components/severity/sidebar_severity.vue'; diff --git a/spec/frontend/sidebar/components/time_tracking/report_spec.js b/spec/frontend/sidebar/components/time_tracking/report_spec.js index af72122052f..0259aee48f0 100644 --- a/spec/frontend/sidebar/components/time_tracking/report_spec.js +++ b/spec/frontend/sidebar/components/time_tracking/report_spec.js @@ -8,9 +8,9 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/flash'; import Report from '~/sidebar/components/time_tracking/report.vue'; -import getIssueTimelogsQuery from '~/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql'; -import getMrTimelogsQuery from '~/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql'; -import deleteTimelogMutation from '~/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql'; +import getIssueTimelogsQuery from '~/sidebar/queries/get_issue_timelogs.query.graphql'; +import getMrTimelogsQuery from '~/sidebar/queries/get_mr_timelogs.query.graphql'; +import deleteTimelogMutation from '~/sidebar/queries/delete_timelog.mutation.graphql'; import { getIssueTimelogsQueryResponse, getMrTimelogsQueryResponse, diff --git a/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js b/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js index f73491ca95f..5bfe3b59eb3 100644 --- a/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js +++ b/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js @@ -7,7 +7,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/flash'; import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue'; import epicTodoQuery from '~/sidebar/queries/epic_todo.query.graphql'; -import TodoButton from '~/vue_shared/components/sidebar/todo_toggle/todo_button.vue'; +import TodoButton from '~/sidebar/components/todo_toggle/todo_button.vue'; import { todosResponse, noTodosResponse } from '../../mock_data'; jest.mock('~/flash'); diff --git a/spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js b/spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js new file mode 100644 index 00000000000..fb07029a249 --- /dev/null +++ b/spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js @@ -0,0 +1,68 @@ +import { GlButton } from '@gitlab/ui'; +import { shallowMount, mount } from '@vue/test-utils'; +import TodoButton from '~/sidebar/components/todo_toggle/todo_button.vue'; + +describe('Todo Button', () => { + let wrapper; + let dispatchEventSpy; + + const createComponent = (props = {}, mountFn = shallowMount) => { + wrapper = mountFn(TodoButton, { + propsData: { + ...props, + }, + }); + }; + + beforeEach(() => { + dispatchEventSpy = jest.spyOn(document, 'dispatchEvent'); + jest.spyOn(document, 'querySelector').mockReturnValue({ + innerText: 2, + }); + }); + + afterEach(() => { + wrapper.destroy(); + dispatchEventSpy = null; + jest.clearAllMocks(); + }); + + it('renders GlButton', () => { + createComponent(); + + expect(wrapper.findComponent(GlButton).exists()).toBe(true); + }); + + it('emits click event when clicked', () => { + createComponent({}, mount); + wrapper.findComponent(GlButton).trigger('click'); + + expect(wrapper.emitted().click).toHaveLength(1); + }); + + it('calls dispatchDocumentEvent to update global To-Do counter correctly', () => { + createComponent({}, mount); + wrapper.findComponent(GlButton).trigger('click'); + const dispatchedEvent = dispatchEventSpy.mock.calls[0][0]; + + expect(dispatchEventSpy).toHaveBeenCalledTimes(1); + expect(dispatchedEvent.detail).toEqual({ count: 1 }); + expect(dispatchedEvent.type).toBe('todo:toggle'); + }); + + it.each` + label | isTodo + ${'Mark as done'} | ${true} + ${'Add a to do'} | ${false} + `('sets correct label when isTodo is $isTodo', ({ label, isTodo }) => { + createComponent({ isTodo }); + + expect(wrapper.findComponent(GlButton).text()).toBe(label); + }); + + it('binds additional props to GlButton', () => { + createComponent({ loading: true }); + + expect(wrapper.findComponent(GlButton).props('loading')).toBe(true); + }); +}); diff --git a/spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js b/spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js new file mode 100644 index 00000000000..cf9b2828dde --- /dev/null +++ b/spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js @@ -0,0 +1,46 @@ +import { GlButton } from '@gitlab/ui'; +import { mount, shallowMount } from '@vue/test-utils'; + +import { nextTick } from 'vue'; +import ToggleSidebar from '~/sidebar/components/toggle/toggle_sidebar.vue'; + +describe('ToggleSidebar', () => { + let wrapper; + + const defaultProps = { + collapsed: true, + }; + + const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => { + wrapper = mountFn(ToggleSidebar, { + propsData: { ...defaultProps, ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findGlButton = () => wrapper.findComponent(GlButton); + + it('should render the "chevron-double-lg-left" icon when collapsed', () => { + createComponent(); + + expect(findGlButton().props('icon')).toBe('chevron-double-lg-left'); + }); + + it('should render the "chevron-double-lg-right" icon when expanded', async () => { + createComponent({ props: { collapsed: false } }); + + expect(findGlButton().props('icon')).toBe('chevron-double-lg-right'); + }); + + it('should emit toggle event when button clicked', async () => { + createComponent({ mountFn: mount }); + + findGlButton().trigger('click'); + await nextTick(); + + expect(wrapper.emitted('toggle')[0]).toBeDefined(); + }); +}); -- cgit v1.2.1