Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/sidebar/components/sidebar_dropdown_spec.js')
-rw-r--r--spec/frontend/sidebar/components/sidebar_dropdown_spec.js285
1 files changed, 285 insertions, 0 deletions
diff --git a/spec/frontend/sidebar/components/sidebar_dropdown_spec.js b/spec/frontend/sidebar/components/sidebar_dropdown_spec.js
new file mode 100644
index 00000000000..83bc8cf7002
--- /dev/null
+++ b/spec/frontend/sidebar/components/sidebar_dropdown_spec.js
@@ -0,0 +1,285 @@
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownText,
+ GlFormInput,
+ GlSearchBoxByType,
+} from '@gitlab/ui';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/flash';
+import { IssuableType } from '~/issues/constants';
+import SidebarDropdown from '~/sidebar/components/sidebar_dropdown.vue';
+import { IssuableAttributeType } from '~/sidebar/constants';
+import projectIssueMilestoneQuery from '~/sidebar/queries/project_issue_milestone.query.graphql';
+import projectMilestonesQuery from '~/sidebar/queries/project_milestones.query.graphql';
+import {
+ emptyProjectMilestonesResponse,
+ mockIssue,
+ mockProjectMilestonesResponse,
+ noCurrentMilestoneResponse,
+} from '../mock_data';
+
+jest.mock('~/flash');
+
+describe('SidebarDropdown component', () => {
+ let wrapper;
+
+ const promiseData = { issuableSetAttribute: { issue: { attribute: { id: '123' } } } };
+ const mutationSuccess = () => jest.fn().mockResolvedValue({ data: promiseData });
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownText = () => wrapper.findComponent(GlDropdownText);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findDropdownItemWithText = (text) =>
+ findAllDropdownItems().wrappers.find((x) => x.text() === text);
+ const findAttributeItems = () => wrapper.findByTestId('milestone-items');
+ const findNoAttributeItem = () => wrapper.findByTestId('no-milestone-item');
+ const findLoadingIconDropdown = () => wrapper.findByTestId('loading-icon-dropdown');
+
+ const toggleDropdown = async () => {
+ wrapper.vm.$refs.dropdown.show();
+ findDropdown().vm.$emit('show');
+
+ await nextTick();
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
+ };
+
+ const createComponentWithApollo = ({
+ requestHandlers = [],
+ projectMilestonesSpy = jest.fn().mockResolvedValue(mockProjectMilestonesResponse),
+ currentMilestoneSpy = jest.fn().mockResolvedValue(noCurrentMilestoneResponse),
+ } = {}) => {
+ Vue.use(VueApollo);
+
+ wrapper = mountExtended(SidebarDropdown, {
+ apolloProvider: createMockApollo([
+ [projectMilestonesQuery, projectMilestonesSpy],
+ [projectIssueMilestoneQuery, currentMilestoneSpy],
+ ...requestHandlers,
+ ]),
+ propsData: {
+ attrWorkspacePath: mockIssue.projectPath,
+ currentAttribute: {},
+ issuableType: IssuableType.Issue,
+ issuableAttribute: IssuableAttributeType.Milestone,
+ },
+ attachTo: document.body,
+ });
+ };
+
+ const createComponent = ({
+ props = {},
+ data = {},
+ mutationPromise = mutationSuccess,
+ queries = {},
+ } = {}) => {
+ wrapper = mountExtended(SidebarDropdown, {
+ propsData: {
+ attrWorkspacePath: mockIssue.projectPath,
+ currentAttribute: {},
+ issuableType: IssuableType.Issue,
+ issuableAttribute: IssuableAttributeType.Milestone,
+ ...props,
+ },
+ data() {
+ return data;
+ },
+ mocks: {
+ $apollo: {
+ mutate: mutationPromise(),
+ queries: {
+ currentAttribute: { loading: false },
+ attributesList: { loading: false },
+ ...queries,
+ },
+ },
+ },
+ });
+ };
+
+ describe('when a user can edit', () => {
+ describe('when user is editing', () => {
+ describe('when rendering the dropdown', () => {
+ it('shows a loading spinner while fetching a list of attributes', async () => {
+ createComponent({
+ queries: {
+ attributesList: { loading: true },
+ },
+ });
+
+ await toggleDropdown();
+
+ expect(findLoadingIconDropdown().exists()).toBe(true);
+ });
+
+ describe('GlDropdownItem with the right title and id', () => {
+ const id = 'id';
+ const title = 'title';
+
+ beforeEach(async () => {
+ createComponent({
+ props: { currentAttribute: { id, title } },
+ data: { attributesList: [{ id, title }] },
+ });
+
+ await toggleDropdown();
+ });
+
+ it('does not show a loading spinner', () => {
+ expect(findLoadingIconDropdown().exists()).toBe(false);
+ });
+
+ it('renders title $title', () => {
+ expect(findDropdownItemWithText(title).exists()).toBe(true);
+ });
+
+ it('checks the correct dropdown item', () => {
+ expect(
+ findAllDropdownItems()
+ .filter((w) => w.props('isChecked') === true)
+ .at(0)
+ .text(),
+ ).toBe(title);
+ });
+ });
+
+ describe('when no data is assigned', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ await toggleDropdown();
+ });
+
+ it('finds GlDropdownItem with "No milestone"', () => {
+ expect(findNoAttributeItem().text()).toBe('No milestone');
+ });
+
+ it('"No milestone" is checked', () => {
+ expect(findAllDropdownItems('No milestone').at(0).props('isChecked')).toBe(true);
+ });
+
+ it('does not render any dropdown item', () => {
+ expect(findAttributeItems().exists()).toBe(false);
+ });
+ });
+
+ describe('when clicking on dropdown item', () => {
+ describe('when currentAttribute is equal to attribute id', () => {
+ it('does not call setIssueAttribute mutation', async () => {
+ createComponent({
+ props: { currentAttribute: { id: 'id', title: 'title' } },
+ data: { attributesList: [{ id: 'id', title: 'title' }] },
+ });
+
+ await toggleDropdown();
+
+ findDropdownItemWithText('title').vm.$emit('click');
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(0);
+ });
+ });
+ });
+ });
+
+ describe('when a user is searching', () => {
+ describe('when search result is not found', () => {
+ describe('when milestone', () => {
+ it('renders "No milestone found"', async () => {
+ createComponent();
+
+ await toggleDropdown();
+
+ findSearchBox().vm.$emit('input', 'non existing milestones');
+ await nextTick();
+
+ expect(findDropdownText().text()).toBe('No milestone found');
+ });
+ });
+ });
+ });
+ });
+ });
+
+ describe('with mock apollo', () => {
+ describe("when issuable type is 'issue'", () => {
+ describe('when dropdown is expanded and user can edit', () => {
+ it('renders the dropdown on clicking edit', async () => {
+ createComponentWithApollo();
+
+ await toggleDropdown();
+
+ expect(findDropdown().isVisible()).toBe(true);
+ });
+
+ it('focuses on the input when dropdown is shown', async () => {
+ createComponentWithApollo();
+
+ await toggleDropdown();
+
+ expect(document.activeElement).toEqual(wrapper.findComponent(GlFormInput).element);
+ });
+
+ describe('milestones', () => {
+ it('should call createAlert if milestones query fails', async () => {
+ createComponentWithApollo({
+ projectMilestonesSpy: jest.fn().mockRejectedValue(new Error()),
+ });
+
+ await toggleDropdown();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: wrapper.vm.i18n.listFetchError,
+ captureError: true,
+ error: expect.any(Error),
+ });
+ });
+
+ it('only fetches attributes when dropdown is opened', async () => {
+ const projectMilestonesSpy = jest
+ .fn()
+ .mockResolvedValueOnce(emptyProjectMilestonesResponse);
+ createComponentWithApollo({ projectMilestonesSpy });
+
+ expect(projectMilestonesSpy).not.toHaveBeenCalled();
+
+ await toggleDropdown();
+
+ expect(projectMilestonesSpy).toHaveBeenNthCalledWith(1, {
+ fullPath: mockIssue.projectPath,
+ state: 'active',
+ title: '',
+ });
+ });
+
+ describe('when a user is searching', () => {
+ it('sends a projectMilestones query with the entered search term "foo"', async () => {
+ const mockSearchTerm = 'foobar';
+ const projectMilestonesSpy = jest
+ .fn()
+ .mockResolvedValueOnce(emptyProjectMilestonesResponse);
+ createComponentWithApollo({ projectMilestonesSpy });
+
+ await toggleDropdown();
+
+ findSearchBox().vm.$emit('input', mockSearchTerm);
+ await nextTick();
+ jest.runOnlyPendingTimers(); // Account for debouncing
+
+ expect(projectMilestonesSpy).toHaveBeenNthCalledWith(2, {
+ fullPath: mockIssue.projectPath,
+ state: 'active',
+ title: mockSearchTerm,
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+});