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/work_items/components/work_item_milestone_spec.js')
-rw-r--r--spec/frontend/work_items/components/work_item_milestone_spec.js247
1 files changed, 247 insertions, 0 deletions
diff --git a/spec/frontend/work_items/components/work_item_milestone_spec.js b/spec/frontend/work_items/components/work_item_milestone_spec.js
new file mode 100644
index 00000000000..08cdf62ae52
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_milestone_spec.js
@@ -0,0 +1,247 @@
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlSearchBoxByType,
+ GlSkeletonLoader,
+ GlFormGroup,
+ GlDropdownText,
+} from '@gitlab/ui';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue';
+import { resolvers, temporaryConfig } from '~/graphql_shared/issuable_client';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { mockTracking } from 'helpers/tracking_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
+import projectMilestonesQuery from '~/sidebar/queries/project_milestones.query.graphql';
+import {
+ projectMilestonesResponse,
+ projectMilestonesResponseWithNoMilestones,
+ mockMilestoneWidgetResponse,
+ workItemResponseFactory,
+ updateWorkItemMutationErrorResponse,
+} from 'jest/work_items/mock_data';
+import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
+
+describe('WorkItemMilestone component', () => {
+ Vue.use(VueApollo);
+
+ let wrapper;
+
+ const workItemId = 'gid://gitlab/WorkItem/1';
+ const workItemType = 'Task';
+ const fullPath = 'full-path';
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
+ const findNoMilestoneDropdownItem = () => wrapper.findByTestId('no-milestone');
+ const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findFirstDropdownItem = () => findDropdownItems().at(0);
+ const findDropdownTexts = () => wrapper.findAllComponents(GlDropdownText);
+ const findDropdownItemAtIndex = (index) => findDropdownItems().at(index);
+ const findDisabledTextSpan = () => wrapper.findByTestId('disabled-text');
+ const findDropdownTextAtIndex = (index) => findDropdownTexts().at(index);
+ const findInputGroup = () => wrapper.findComponent(GlFormGroup);
+
+ const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true });
+
+ const networkResolvedValue = new Error();
+
+ const successSearchQueryHandler = jest.fn().mockResolvedValue(projectMilestonesResponse);
+ const successSearchWithNoMatchingMilestones = jest
+ .fn()
+ .mockResolvedValue(projectMilestonesResponseWithNoMilestones);
+
+ const showDropdown = () => {
+ findDropdown().vm.$emit('shown');
+ };
+
+ const hideDropdown = () => {
+ findDropdown().vm.$emit('hide');
+ };
+
+ const createComponent = ({
+ canUpdate = true,
+ milestone = mockMilestoneWidgetResponse,
+ searchQueryHandler = successSearchQueryHandler,
+ } = {}) => {
+ const apolloProvider = createMockApollo(
+ [[projectMilestonesQuery, searchQueryHandler]],
+ resolvers,
+ {
+ typePolicies: temporaryConfig.cacheConfig.typePolicies,
+ },
+ );
+
+ apolloProvider.clients.defaultClient.writeQuery({
+ query: workItemQuery,
+ variables: {
+ id: workItemId,
+ },
+ data: workItemQueryResponse.data,
+ });
+
+ wrapper = shallowMountExtended(WorkItemMilestone, {
+ apolloProvider,
+ propsData: {
+ canUpdate,
+ workItemMilestone: milestone,
+ workItemId,
+ workItemType,
+ fullPath,
+ },
+ stubs: {
+ GlDropdown,
+ GlSearchBoxByType,
+ },
+ });
+ };
+
+ it('has "Milestone" label', () => {
+ createComponent();
+
+ expect(findInputGroup().exists()).toBe(true);
+ expect(findInputGroup().attributes('label')).toBe(WorkItemMilestone.i18n.MILESTONE);
+ });
+
+ describe('Default text with canUpdate false and milestone value', () => {
+ describe.each`
+ description | milestone | value
+ ${'when no milestone'} | ${null} | ${WorkItemMilestone.i18n.NONE}
+ ${'when milestone set'} | ${mockMilestoneWidgetResponse} | ${mockMilestoneWidgetResponse.title}
+ `('$description', ({ milestone, value }) => {
+ it(`has a value of "${value}"`, () => {
+ createComponent({ canUpdate: false, milestone });
+
+ expect(findDisabledTextSpan().text()).toBe(value);
+ expect(findDropdown().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('Default text value when canUpdate true and no milestone set', () => {
+ it(`has a value of "Add to milestone"`, () => {
+ createComponent({ canUpdate: true, milestone: null });
+
+ expect(findDropdown().props('text')).toBe(WorkItemMilestone.i18n.MILESTONE_PLACEHOLDER);
+ });
+ });
+
+ describe('Dropdown search', () => {
+ it('has the search box', () => {
+ createComponent();
+
+ expect(findSearchBox().exists()).toBe(true);
+ });
+
+ it('shows no matching results when no items', () => {
+ createComponent({
+ searchQueryHandler: successSearchWithNoMatchingMilestones,
+ });
+
+ expect(findDropdownTextAtIndex(0).text()).toBe(WorkItemMilestone.i18n.NO_MATCHING_RESULTS);
+ expect(findDropdownItems()).toHaveLength(1);
+ expect(findDropdownTexts()).toHaveLength(1);
+ });
+ });
+
+ describe('Dropdown options', () => {
+ beforeEach(() => {
+ createComponent({ canUpdate: true });
+ });
+
+ it('shows the skeleton loader when the items are being fetched on click', async () => {
+ showDropdown();
+ await nextTick();
+
+ expect(findSkeletonLoader().exists()).toBe(true);
+ });
+
+ it('shows the milestones in dropdown when the items have finished fetching', async () => {
+ showDropdown();
+ await waitForPromises();
+
+ expect(findSkeletonLoader().exists()).toBe(false);
+ expect(findNoMilestoneDropdownItem().exists()).toBe(true);
+ expect(findDropdownItems()).toHaveLength(
+ projectMilestonesResponse.data.workspace.attributes.nodes.length + 1,
+ );
+ });
+
+ it('changes the milestone to null when clicked on no milestone', async () => {
+ showDropdown();
+ findFirstDropdownItem().vm.$emit('click');
+
+ hideDropdown();
+ await nextTick();
+ expect(findDropdown().props('loading')).toBe(true);
+
+ await waitForPromises();
+
+ expect(findDropdown().props('loading')).toBe(false);
+ expect(findDropdown().props('text')).toBe(WorkItemMilestone.i18n.MILESTONE_PLACEHOLDER);
+ });
+
+ it('changes the milestone to the selected milestone', async () => {
+ const milestoneIndex = 1;
+ /** the index is -1 since no matching results is also a dropdown item */
+ const milestoneAtIndex =
+ projectMilestonesResponse.data.workspace.attributes.nodes[milestoneIndex - 1];
+ showDropdown();
+
+ await waitForPromises();
+ findDropdownItemAtIndex(milestoneIndex).vm.$emit('click');
+
+ hideDropdown();
+ await waitForPromises();
+
+ expect(findDropdown().props('text')).toBe(milestoneAtIndex.title);
+ });
+ });
+
+ describe('Error handlers', () => {
+ it.each`
+ errorType | expectedErrorMessage | mockValue | resolveFunction
+ ${'graphql error'} | ${'Something went wrong while updating the task. Please try again.'} | ${updateWorkItemMutationErrorResponse} | ${'mockResolvedValue'}
+ ${'network error'} | ${'Something went wrong while updating the task. Please try again.'} | ${networkResolvedValue} | ${'mockRejectedValue'}
+ `(
+ 'emits an error when there is a $errorType',
+ async ({ mockValue, expectedErrorMessage, resolveFunction }) => {
+ createComponent({
+ mutationHandler: jest.fn()[resolveFunction](mockValue),
+ canUpdate: true,
+ });
+
+ showDropdown();
+ findFirstDropdownItem().vm.$emit('click');
+ hideDropdown();
+
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([[expectedErrorMessage]]);
+ },
+ );
+ });
+
+ describe('Tracking event', () => {
+ it('tracks updating the milestone', async () => {
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ createComponent({ canUpdate: true });
+
+ showDropdown();
+ findFirstDropdownItem().vm.$emit('click');
+ hideDropdown();
+
+ await waitForPromises();
+
+ expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_milestone', {
+ category: TRACKING_CATEGORY_SHOW,
+ label: 'item_milestone',
+ property: 'type_Task',
+ });
+ });
+ });
+});