summaryrefslogtreecommitdiff
path: root/spec/frontend/sidebar
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-11-25 18:10:56 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-11-25 18:10:56 +0000
commitd612723c35d7fdaeb8b09e91232053e04850c2ae (patch)
tree0a55d7d5dcb3745f60b25aabe508c27a734a9e37 /spec/frontend/sidebar
parent35e5a7c8455f916bc969ec814c74cefd98d24f19 (diff)
downloadgitlab-ce-d612723c35d7fdaeb8b09e91232053e04850c2ae.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/sidebar')
-rw-r--r--spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js2
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js4
-rw-r--r--spec/frontend/sidebar/components/copy/copy_email_to_clipboard_spec.js (renamed from spec/frontend/sidebar/components/copy_email/copy_email_to_clipboard_spec.js)4
-rw-r--r--spec/frontend/sidebar/components/copy/copyable_field_spec.js77
-rw-r--r--spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js (renamed from spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js)4
-rw-r--r--spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js4
-rw-r--r--spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js388
-rw-r--r--spec/frontend/sidebar/components/move/move_issues_button_spec.js2
-rw-r--r--spec/frontend/sidebar/components/severity/sidebar_severity_spec.js2
-rw-r--r--spec/frontend/sidebar/components/time_tracking/report_spec.js6
-rw-r--r--spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js2
-rw-r--r--spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js68
-rw-r--r--spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js46
13 files changed, 594 insertions, 15 deletions
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_email/copy_email_to_clipboard_spec.js b/spec/frontend/sidebar/components/copy/copy_email_to_clipboard_spec.js
index 9f94a1b3f3a..5b6db43a366 100644
--- a/spec/frontend/sidebar/components/copy_email/copy_email_to_clipboard_spec.js
+++ b/spec/frontend/sidebar/components/copy/copy_email_to_clipboard_spec.js
@@ -1,6 +1,6 @@
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';
+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';
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/reference/sidebar_reference_widget_spec.js b/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js
index 69e35cd1d05..c5161a748a9 100644
--- a/spec/frontend/sidebar/components/reference/sidebar_reference_widget_spec.js
+++ b/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js
@@ -4,10 +4,10 @@ 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 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 '~/vue_shared/components/sidebar/copyable_field.vue';
+import CopyableField from '~/sidebar/components/copy/copyable_field.vue';
import { issueReferenceResponse } from '../../mock_data';
describe('Sidebar Reference Widget', () => {
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/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();
+ });
+});