diff options
Diffstat (limited to 'spec/frontend/work_items/components')
22 files changed, 427 insertions, 170 deletions
diff --git a/spec/frontend/work_items/components/notes/system_note_spec.js b/spec/frontend/work_items/components/notes/system_note_spec.js index 03f1aa356ad..69bc0961240 100644 --- a/spec/frontend/work_items/components/notes/system_note_spec.js +++ b/spec/frontend/work_items/components/notes/system_note_spec.js @@ -40,8 +40,14 @@ describe('Work Items system note component', () => { ); }); - it('should render svg icon', () => { - expect(findTimelineIcon().exists()).toBe(true); + it('should render svg icon only for allowed icons', () => { + expect(findTimelineIcon().exists()).toBe(false); + + const ALLOWED_ICONS = ['issue-close']; + ALLOWED_ICONS.forEach((icon) => { + createComponent({ note: { ...workItemSystemNoteWithMetadata, systemNoteIconName: icon } }); + expect(findTimelineIcon().exists()).toBe(true); + }); }); it('should not show compare previous version for FOSS', () => { diff --git a/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js b/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js index ee2b434bd75..fe89c525fea 100644 --- a/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js @@ -10,7 +10,7 @@ import { STATE_OPEN } from '~/work_items/constants'; import * as confirmViaGlModal from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import WorkItemCommentForm from '~/work_items/components/notes/work_item_comment_form.vue'; import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; -import WorkItemStateToggleButton from '~/work_items/components/work_item_state_toggle_button.vue'; +import WorkItemStateToggle from '~/work_items/components/work_item_state_toggle.vue'; Vue.use(VueApollo); @@ -37,7 +37,7 @@ describe('Work item comment form component', () => { const findConfirmButton = () => wrapper.find('[data-testid="confirm-button"]'); const findInternalNoteCheckbox = () => wrapper.findComponent(GlFormCheckbox); const findInternalNoteTooltipIcon = () => wrapper.findComponent(GlIcon); - const findWorkItemToggleStateButton = () => wrapper.findComponent(WorkItemStateToggleButton); + const findWorkItemToggleStateButton = () => wrapper.findComponent(WorkItemStateToggle); const createComponent = ({ isSubmitting = false, diff --git a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js index 6a24987b737..596283a9590 100644 --- a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js @@ -1,4 +1,4 @@ -import { GlDisclosureDropdown } from '@gitlab/ui'; +import { GlButton, GlDisclosureDropdown } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; @@ -17,7 +17,7 @@ describe('Work Item Note Actions', () => { const showSpy = jest.fn(); const findReplyButton = () => wrapper.findComponent(ReplyButton); - const findEditButton = () => wrapper.findByTestId('edit-work-item-note'); + const findEditButton = () => wrapper.findComponent(GlButton); const findEmojiButton = () => wrapper.findByTestId('note-emoji-button'); const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); const findDeleteNoteButton = () => wrapper.findByTestId('delete-note-action'); diff --git a/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js b/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js index 2e1a7983dec..a40e860d9fe 100644 --- a/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js +++ b/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js @@ -1,4 +1,4 @@ -import { GlLabel, GlIcon, GlLink } from '@gitlab/ui'; +import { GlLabel, GlIcon, GlLink, GlButton } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -9,7 +9,6 @@ import { createAlert } from '~/alert'; import RichTimestampTooltip from '~/vue_shared/components/rich_timestamp_tooltip.vue'; import WorkItemLinkChildContents from '~/work_items/components/shared/work_item_link_child_contents.vue'; -import WorkItemLinksMenu from '~/work_items/components/shared/work_item_links_menu.vue'; import { WORK_ITEM_TYPE_VALUE_OBJECTIVE } from '~/work_items/constants'; import { @@ -39,13 +38,18 @@ describe('WorkItemLinkChildContents', () => { const findAllLabels = () => wrapper.findAllComponents(GlLabel); const findRegularLabel = () => findAllLabels().at(0); const findScopedLabel = () => findAllLabels().at(1); - const findLinksMenuComponent = () => wrapper.findComponent(WorkItemLinksMenu); + const findRemoveButton = () => wrapper.findComponent(GlButton); - const createComponent = ({ canUpdate = true, childItem = workItemTask } = {}) => { + const createComponent = ({ + canUpdate = true, + childItem = workItemTask, + showLabels = true, + } = {}) => { wrapper = shallowMountExtended(WorkItemLinkChildContents, { propsData: { canUpdate, childItem, + showLabels, }, }); }; @@ -129,19 +133,6 @@ describe('WorkItemLinkChildContents', () => { expect(findMetadataComponent().exists()).toBe(false); }); - - it('renders labels', () => { - const mockLabel = mockLabels[0]; - - expect(findAllLabels()).toHaveLength(mockLabels.length); - expect(findRegularLabel().props()).toMatchObject({ - title: mockLabel.title, - backgroundColor: mockLabel.color, - description: mockLabel.description, - scoped: false, - }); - expect(findScopedLabel().props('scoped')).toBe(true); // Second label is scoped - }); }); describe('item menu', () => { @@ -149,20 +140,47 @@ describe('WorkItemLinkChildContents', () => { createComponent(); }); - it('renders work-item-links-menu', () => { - expect(findLinksMenuComponent().exists()).toBe(true); + it('renders remove button', () => { + expect(findRemoveButton().exists()).toBe(true); }); it('does not render work-item-links-menu when canUpdate is false', () => { createComponent({ canUpdate: false }); - expect(findLinksMenuComponent().exists()).toBe(false); + expect(findRemoveButton().exists()).toBe(false); }); it('removeChild event on menu triggers `click-remove-child` event', () => { - findLinksMenuComponent().vm.$emit('removeChild'); + findRemoveButton().vm.$emit('click'); expect(wrapper.emitted('removeChild')).toEqual([[workItemTask]]); }); }); + + describe('item labels', () => { + it('renders normal and scoped label', () => { + createComponent({ childItem: workItemObjectiveWithChild }); + + const mockLabel = mockLabels[0]; + + expect(findAllLabels()).toHaveLength(mockLabels.length); + expect(findRegularLabel().props()).toMatchObject({ + title: mockLabel.title, + backgroundColor: mockLabel.color, + description: mockLabel.description, + scoped: false, + }); + expect(findScopedLabel().props('scoped')).toBe(true); // Second label is scoped + }); + + it.each` + expectedAssertion | showLabels + ${'does not render labels'} | ${true} + ${'renders label'} | ${false} + `('$expectedAssertion when showLabels is $showLabels', ({ showLabels }) => { + createComponent({ showLabels, childItem: workItemObjectiveWithChild }); + + expect(findAllLabels().exists()).toBe(showLabels); + }); + }); }); diff --git a/spec/frontend/work_items/components/shared/work_item_links_menu_spec.js b/spec/frontend/work_items/components/shared/work_item_links_menu_spec.js deleted file mode 100644 index 338a70feae4..00000000000 --- a/spec/frontend/work_items/components/shared/work_item_links_menu_spec.js +++ /dev/null @@ -1,30 +0,0 @@ -import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui'; - -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import WorkItemLinksMenu from '~/work_items/components/shared/work_item_links_menu.vue'; - -describe('WorkItemLinksMenu', () => { - let wrapper; - - const createComponent = () => { - wrapper = shallowMountExtended(WorkItemLinksMenu); - }; - - const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); - const findRemoveDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem); - - beforeEach(() => { - createComponent(); - }); - - it('renders dropdown and dropdown items', () => { - expect(findDropdown().exists()).toBe(true); - expect(findRemoveDropdownItem().exists()).toBe(true); - }); - - it('emits removeChild event on click Remove', () => { - findRemoveDropdownItem().vm.$emit('action'); - - expect(wrapper.emitted('removeChild')).toHaveLength(1); - }); -}); diff --git a/spec/frontend/work_items/components/shared/work_item_token_input_spec.js b/spec/frontend/work_items/components/shared/work_item_token_input_spec.js index 075b69415cf..5726aaaa2d0 100644 --- a/spec/frontend/work_items/components/shared/work_item_token_input_spec.js +++ b/spec/frontend/work_items/components/shared/work_item_token_input_spec.js @@ -1,13 +1,19 @@ -import Vue from 'vue'; -import { GlTokenSelector } from '@gitlab/ui'; +import Vue, { nextTick } from 'vue'; +import { GlTokenSelector, GlAlert } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import WorkItemTokenInput from '~/work_items/components/shared/work_item_token_input.vue'; import { WORK_ITEM_TYPE_ENUM_TASK } from '~/work_items/constants'; +import groupWorkItemsQuery from '~/work_items/graphql/group_work_items.query.graphql'; import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql'; -import { availableWorkItemsResponse, searchedWorkItemsResponse } from '../../mock_data'; +import { + availableWorkItemsResponse, + searchWorkItemsTextResponse, + searchWorkItemsIidResponse, + searchWorkItemsTextIidResponse, +} from '../../mock_data'; Vue.use(VueApollo); @@ -15,17 +21,27 @@ describe('WorkItemTokenInput', () => { let wrapper; const availableWorkItemsResolver = jest.fn().mockResolvedValue(availableWorkItemsResponse); - const searchedWorkItemResolver = jest.fn().mockResolvedValue(searchedWorkItemsResponse); + const groupSearchedWorkItemResolver = jest.fn().mockResolvedValue(searchWorkItemsTextResponse); + const searchWorkItemTextResolver = jest.fn().mockResolvedValue(searchWorkItemsTextResponse); + const searchWorkItemIidResolver = jest.fn().mockResolvedValue(searchWorkItemsIidResponse); + const searchWorkItemTextIidResolver = jest.fn().mockResolvedValue(searchWorkItemsTextIidResponse); const createComponent = async ({ workItemsToAdd = [], parentConfidential = false, childrenType = WORK_ITEM_TYPE_ENUM_TASK, areWorkItemsToAddValid = true, - workItemsResolver = searchedWorkItemResolver, + workItemsResolver = searchWorkItemTextResolver, + isGroup = false, } = {}) => { wrapper = shallowMountExtended(WorkItemTokenInput, { - apolloProvider: createMockApollo([[projectWorkItemsQuery, workItemsResolver]]), + apolloProvider: createMockApollo([ + [projectWorkItemsQuery, workItemsResolver], + [groupWorkItemsQuery, groupSearchedWorkItemResolver], + ]), + provide: { + isGroup, + }, propsData: { value: workItemsToAdd, childrenType, @@ -41,6 +57,7 @@ describe('WorkItemTokenInput', () => { }; const findTokenSelector = () => wrapper.findComponent(GlTokenSelector); + const findGlAlert = () => wrapper.findComponent(GlAlert); it('searches for available work items on focus', async () => { createComponent({ workItemsResolver: availableWorkItemsResolver }); @@ -52,24 +69,34 @@ describe('WorkItemTokenInput', () => { searchTerm: '', types: [WORK_ITEM_TYPE_ENUM_TASK], in: undefined, + iid: null, + isNumber: false, }); expect(findTokenSelector().props('dropdownItems')).toHaveLength(3); }); - it('searches for available work items when typing in input', async () => { - createComponent({ workItemsResolver: searchedWorkItemResolver }); - findTokenSelector().vm.$emit('focus'); - findTokenSelector().vm.$emit('text-input', 'Task 2'); - await waitForPromises(); + it.each` + inputType | input | resolver | searchTerm | iid | isNumber | length + ${'iid'} | ${'101'} | ${searchWorkItemIidResolver} | ${'101'} | ${'101'} | ${true} | ${1} + ${'text'} | ${'Task 2'} | ${searchWorkItemTextResolver} | ${'Task 2'} | ${null} | ${false} | ${1} + ${'iid and text'} | ${'123'} | ${searchWorkItemTextIidResolver} | ${'123'} | ${'123'} | ${true} | ${2} + `( + 'searches by $inputType for available work items when typing in input', + async ({ input, resolver, searchTerm, iid, isNumber, length }) => { + createComponent({ workItemsResolver: resolver }); + findTokenSelector().vm.$emit('focus'); + findTokenSelector().vm.$emit('text-input', input); + await waitForPromises(); - expect(searchedWorkItemResolver).toHaveBeenCalledWith({ - fullPath: 'test-project-path', - searchTerm: 'Task 2', - types: [WORK_ITEM_TYPE_ENUM_TASK], - in: 'TITLE', - }); - expect(findTokenSelector().props('dropdownItems')).toHaveLength(1); - }); + expect(resolver).toHaveBeenCalledWith({ + searchTerm, + in: 'TITLE', + iid, + isNumber, + }); + expect(findTokenSelector().props('dropdownItems')).toHaveLength(length); + }, + ); it('renders red border around token selector input when work item is not valid', () => { createComponent({ @@ -78,4 +105,58 @@ describe('WorkItemTokenInput', () => { expect(findTokenSelector().props('containerClass')).toBe('gl-inset-border-1-red-500!'); }); + + describe('when project context', () => { + beforeEach(() => { + createComponent(); + findTokenSelector().vm.$emit('focus'); + }); + + it('calls the project work items query', () => { + expect(searchWorkItemTextResolver).toHaveBeenCalled(); + }); + + it('skips calling the group work items query', () => { + expect(groupSearchedWorkItemResolver).not.toHaveBeenCalled(); + }); + }); + + describe('when group context', () => { + beforeEach(() => { + createComponent({ isGroup: true }); + findTokenSelector().vm.$emit('focus'); + }); + + it('skips calling the project work items query', () => { + expect(searchWorkItemTextResolver).not.toHaveBeenCalled(); + }); + + it('calls the group work items query', () => { + expect(groupSearchedWorkItemResolver).toHaveBeenCalled(); + }); + }); + + describe('when project work items query fails', () => { + beforeEach(() => { + createComponent({ + workItemsResolver: jest + .fn() + .mockRejectedValue('Something went wrong while fetching the results'), + }); + findTokenSelector().vm.$emit('focus'); + }); + + it('shows error and allows error alert to be closed', async () => { + await waitForPromises(); + expect(findGlAlert().exists()).toBe(true); + expect(findGlAlert().text()).toBe( + 'Something went wrong while fetching the task. Please try again.', + ); + + findGlAlert().vm.$emit('dismiss'); + await nextTick(); + + expect(findGlAlert().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/work_items/components/work_item_actions_spec.js b/spec/frontend/work_items/components/work_item_actions_spec.js index 15c33bf5b1e..6cbb3c4384e 100644 --- a/spec/frontend/work_items/components/work_item_actions_spec.js +++ b/spec/frontend/work_items/components/work_item_actions_spec.js @@ -1,4 +1,10 @@ -import { GlDisclosureDropdown, GlDropdownDivider, GlModal, GlToggle } from '@gitlab/ui'; +import { + GlDisclosureDropdown, + GlDropdownDivider, + GlModal, + GlToggle, + GlDisclosureDropdownItem, +} from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; @@ -10,14 +16,16 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { isLoggedIn } from '~/lib/utils/common_utils'; import toast from '~/vue_shared/plugins/global_toast'; import WorkItemActions from '~/work_items/components/work_item_actions.vue'; +import WorkItemStateToggle from '~/work_items/components/work_item_state_toggle.vue'; import { + STATE_OPEN, TEST_ID_CONFIDENTIALITY_TOGGLE_ACTION, - TEST_ID_NOTIFICATIONS_TOGGLE_ACTION, TEST_ID_NOTIFICATIONS_TOGGLE_FORM, TEST_ID_DELETE_ACTION, TEST_ID_PROMOTE_ACTION, TEST_ID_COPY_REFERENCE_ACTION, TEST_ID_COPY_CREATE_NOTE_EMAIL_ACTION, + TEST_ID_TOGGLE_ACTION, } from '~/work_items/constants'; import updateWorkItemNotificationsMutation from '~/work_items/graphql/update_work_item_notifications.mutation.graphql'; import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql'; @@ -44,11 +52,10 @@ describe('WorkItemActions component', () => { const findModal = () => wrapper.findComponent(GlModal); const findConfidentialityToggleButton = () => wrapper.findByTestId(TEST_ID_CONFIDENTIALITY_TOGGLE_ACTION); - const findNotificationsToggleButton = () => - wrapper.findByTestId(TEST_ID_NOTIFICATIONS_TOGGLE_ACTION); const findDeleteButton = () => wrapper.findByTestId(TEST_ID_DELETE_ACTION); const findPromoteButton = () => wrapper.findByTestId(TEST_ID_PROMOTE_ACTION); const findCopyReferenceButton = () => wrapper.findByTestId(TEST_ID_COPY_REFERENCE_ACTION); + const findWorkItemToggleOption = () => wrapper.findComponent(WorkItemStateToggle); const findCopyCreateNoteEmailButton = () => wrapper.findByTestId(TEST_ID_COPY_CREATE_NOTE_EMAIL_ACTION); const findDropdownItems = () => wrapper.findAll('[data-testid="work-item-actions-dropdown"] > *'); @@ -108,6 +115,7 @@ describe('WorkItemActions component', () => { [updateWorkItemNotificationsMutation, notificationsMutationHandler], ]), propsData: { + workItemState: STATE_OPEN, fullPath: 'gitlab-org/gitlab-test', workItemId: 'gid://gitlab/WorkItem/1', canUpdate, @@ -132,6 +140,7 @@ describe('WorkItemActions component', () => { show: jest.fn(), }, }), + GlDisclosureDropdownItem, GlDisclosureDropdown: stubComponent(GlDisclosureDropdown, { methods: { close: modalShowSpy, @@ -167,6 +176,10 @@ describe('WorkItemActions component', () => { text: 'Turn on confidentiality', }, { + testId: TEST_ID_TOGGLE_ACTION, + text: '', + }, + { testId: TEST_ID_COPY_REFERENCE_ACTION, text: 'Copy reference', }, @@ -248,7 +261,7 @@ describe('WorkItemActions component', () => { }); it('renders toggle button', () => { - expect(findNotificationsToggleButton().exists()).toBe(true); + expect(findNotificationsToggle().exists()).toBe(true); }); it.each` @@ -366,4 +379,10 @@ describe('WorkItemActions component', () => { expect(toast).toHaveBeenCalledWith('Email address copied'); }); }); + + it('renders the toggle status button', () => { + createComponent(); + + expect(findWorkItemToggleOption().exists()).toBe(true); + }); }); diff --git a/spec/frontend/work_items/components/work_item_award_emoji_spec.js b/spec/frontend/work_items/components/work_item_award_emoji_spec.js index f8c5f8edc4c..a756bfa6889 100644 --- a/spec/frontend/work_items/components/work_item_award_emoji_spec.js +++ b/spec/frontend/work_items/components/work_item_award_emoji_spec.js @@ -9,7 +9,8 @@ import { isLoggedIn } from '~/lib/utils/common_utils'; import AwardList from '~/vue_shared/components/awards_list.vue'; import WorkItemAwardEmoji from '~/work_items/components/work_item_award_emoji.vue'; import updateAwardEmojiMutation from '~/work_items/graphql/update_award_emoji.mutation.graphql'; -import workItemAwardEmojiQuery from '~/work_items/graphql/award_emoji.query.graphql'; +import groupWorkItemAwardEmojiQuery from '~/work_items/graphql/group_award_emoji.query.graphql'; +import projectWorkItemAwardEmojiQuery from '~/work_items/graphql/award_emoji.query.graphql'; import { EMOJI_THUMBSUP, EMOJI_THUMBSDOWN, @@ -42,6 +43,7 @@ describe('WorkItemAwardEmoji component', () => { const workItemQueryResponse = workItemByIidResponseFactory(); const mockWorkItem = workItemQueryResponse.data.workspace.workItems.nodes[0]; + const groupAwardEmojiQuerySuccessHandler = jest.fn().mockResolvedValue(workItemQueryResponse); const awardEmojiQuerySuccessHandler = jest.fn().mockResolvedValue(workItemQueryResponse); const awardEmojiQueryEmptyHandler = jest.fn().mockResolvedValue( workItemByIidResponseFactory({ @@ -83,10 +85,12 @@ describe('WorkItemAwardEmoji component', () => { awardEmojiQueryHandler = awardEmojiQuerySuccessHandler, awardEmojiMutationHandler = awardEmojiAddSuccessHandler, workItemIid = '1', + isGroup = false, } = {}) => { mockApolloProvider = createMockApollo( [ - [workItemAwardEmojiQuery, awardEmojiQueryHandler], + [projectWorkItemAwardEmojiQuery, awardEmojiQueryHandler], + [groupWorkItemAwardEmojiQuery, groupAwardEmojiQuerySuccessHandler], [updateAwardEmojiMutation, awardEmojiMutationHandler], ], {}, @@ -108,6 +112,9 @@ describe('WorkItemAwardEmoji component', () => { wrapper = shallowMount(WorkItemAwardEmoji, { isLoggedIn: isLoggedIn(), apolloProvider: mockApolloProvider, + provide: { + isGroup, + }, propsData: { workItemId: 'gid://gitlab/WorkItem/1', workItemFullpath: 'test-project-path', @@ -270,7 +277,7 @@ describe('WorkItemAwardEmoji component', () => { }; }); - it('calls mutation succesfully and adds the award emoji with proper user details', async () => { + it('calls mutation successfully and adds the award emoji with proper user details', async () => { createComponent({ awardEmojiMutationHandler: awardEmojiAddSuccessHandler, }); @@ -345,4 +352,18 @@ describe('WorkItemAwardEmoji component', () => { }); }); }); + + describe('group award emoji query', () => { + it('is not called in a project context', () => { + createComponent(); + + expect(groupAwardEmojiQuerySuccessHandler).not.toHaveBeenCalled(); + }); + + it('is called in a group context', () => { + createComponent({ isGroup: true }); + + expect(groupAwardEmojiQuerySuccessHandler).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js index de2895591dd..1d25bb74986 100644 --- a/spec/frontend/work_items/components/work_item_description_spec.js +++ b/spec/frontend/work_items/components/work_item_description_spec.js @@ -92,7 +92,7 @@ describe('WorkItemDescription', () => { it('passes correct autocompletion data and preview markdown sources and enables quick actions', async () => { const { iid, - project: { fullPath }, + namespace: { fullPath }, } = workItemQueryResponse.data.workItem; await createComponent({ isEditing: true }); diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js index 28826748cb0..acfe4571cd2 100644 --- a/spec/frontend/work_items/components/work_item_detail_spec.js +++ b/spec/frontend/work_items/components/work_item_detail_spec.js @@ -23,8 +23,6 @@ import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree import WorkItemRelationships from '~/work_items/components/work_item_relationships/work_item_relationships.vue'; import WorkItemNotes from '~/work_items/components/work_item_notes.vue'; import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; -import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue'; -import WorkItemStateToggleButton from '~/work_items/components/work_item_state_toggle_button.vue'; import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; import WorkItemTodos from '~/work_items/components/work_item_todos.vue'; import { i18n } from '~/work_items/constants'; @@ -55,10 +53,6 @@ describe('WorkItemDetail component', () => { canUpdate: true, canDelete: true, }); - const workItemQueryResponseWithCannotUpdate = workItemByIidResponseFactory({ - canUpdate: false, - canDelete: false, - }); const workItemQueryResponseWithoutParent = workItemByIidResponseFactory({ parent: null, canUpdate: true, @@ -95,8 +89,6 @@ describe('WorkItemDetail component', () => { const findWorkItemTwoColumnViewContainer = () => wrapper.findByTestId('work-item-overview'); const findRightSidebar = () => wrapper.findByTestId('work-item-overview-right-sidebar'); const triggerPageScroll = () => findIntersectionObserver().vm.$emit('disappear'); - const findWorkItemStateToggleButton = () => wrapper.findComponent(WorkItemStateToggleButton); - const findWorkItemTypeIcon = () => wrapper.findComponent(WorkItemTypeIcon); const createComponent = ({ isGroup = false, @@ -212,25 +204,6 @@ describe('WorkItemDetail component', () => { }); }); - describe('work item state toggle button', () => { - describe.each` - description | canUpdate - ${'when user cannot update'} | ${false} - ${'when user can update'} | ${true} - `('$description', ({ canUpdate }) => { - it(`${canUpdate ? 'is rendered' : 'is not rendered'}`, async () => { - createComponent({ - handler: canUpdate - ? jest.fn().mockResolvedValue(workItemQueryResponse) - : jest.fn().mockResolvedValue(workItemQueryResponseWithCannotUpdate), - }); - await waitForPromises(); - - expect(findWorkItemStateToggleButton().exists()).toBe(canUpdate); - }); - }); - }); - describe('close button', () => { describe('when isModal prop is false', () => { it('does not render', async () => { @@ -408,12 +381,11 @@ describe('WorkItemDetail component', () => { expect(findParent().exists()).toBe(false); }); - it('shows work item type with reference when there is no a parent', async () => { + it('shows title in the header when there is no parent', async () => { createComponent({ handler: jest.fn().mockResolvedValue(workItemQueryResponseWithoutParent) }); await waitForPromises(); - expect(findWorkItemTypeIcon().props('showText')).toBe(true); - expect(findWorkItemType().text()).toBe('#1'); + expect(findWorkItemType().classes()).toEqual(['gl-w-full']); }); describe('with parent', () => { @@ -428,10 +400,6 @@ describe('WorkItemDetail component', () => { expect(findParent().exists()).toBe(true); }); - it('does not show work item type', () => { - expect(findWorkItemType().exists()).toBe(false); - }); - it('shows parent breadcrumb icon', () => { expect(findParentButton().props('icon')).toBe(mockParent.parent.workItemType.iconName); }); @@ -468,6 +436,10 @@ describe('WorkItemDetail component', () => { const { iid } = workItemQueryResponse.data.workspace.workItems.nodes[0]; expect(findParent().text()).toContain(`#${iid}`); }); + + it('does not show title in the header when parent exists', () => { + expect(findWorkItemType().classes()).toEqual(['gl-sm-display-none!']); + }); }); }); diff --git a/spec/frontend/work_items/components/work_item_labels_spec.js b/spec/frontend/work_items/components/work_item_labels_spec.js index 28aa7ffa1be..d7bebac6dbd 100644 --- a/spec/frontend/work_items/components/work_item_labels_spec.js +++ b/spec/frontend/work_items/components/work_item_labels_spec.js @@ -5,7 +5,8 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; -import labelSearchQuery from '~/sidebar/components/labels/labels_select_widget/graphql/project_labels.query.graphql'; +import groupLabelsQuery from '~/sidebar/components/labels/labels_select_widget/graphql/group_labels.query.graphql'; +import projectLabelsQuery from '~/sidebar/components/labels/labels_select_widget/graphql/project_labels.query.graphql'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql'; import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql'; @@ -37,7 +38,8 @@ describe('WorkItemLabels component', () => { const groupWorkItemQuerySuccess = jest .fn() .mockResolvedValue(groupWorkItemByIidResponseFactory({ labels: null })); - const successSearchQueryHandler = jest.fn().mockResolvedValue(projectLabelsResponse); + const projectLabelsQueryHandler = jest.fn().mockResolvedValue(projectLabelsResponse); + const groupLabelsQueryHandler = jest.fn().mockResolvedValue(projectLabelsResponse); const successUpdateWorkItemMutationHandler = jest .fn() .mockResolvedValue(updateWorkItemMutationResponse); @@ -47,7 +49,7 @@ describe('WorkItemLabels component', () => { canUpdate = true, isGroup = false, workItemQueryHandler = workItemQuerySuccess, - searchQueryHandler = successSearchQueryHandler, + searchQueryHandler = projectLabelsQueryHandler, updateWorkItemMutationHandler = successUpdateWorkItemMutationHandler, workItemIid = '1', } = {}) => { @@ -55,7 +57,8 @@ describe('WorkItemLabels component', () => { apolloProvider: createMockApollo([ [workItemByIidQuery, workItemQueryHandler], [groupWorkItemByIidQuery, groupWorkItemQuerySuccess], - [labelSearchQuery, searchQueryHandler], + [projectLabelsQuery, searchQueryHandler], + [groupLabelsQuery, groupLabelsQueryHandler], [updateWorkItemMutation, updateWorkItemMutationHandler], ]), provide: { @@ -179,7 +182,7 @@ describe('WorkItemLabels component', () => { findTokenSelector().vm.$emit('text-input', searchKey); await waitForPromises(); - expect(successSearchQueryHandler).toHaveBeenCalledWith( + expect(projectLabelsQueryHandler).toHaveBeenCalledWith( expect.objectContaining({ searchTerm: searchKey }), ); }); @@ -273,6 +276,16 @@ describe('WorkItemLabels component', () => { expect(workItemQuerySuccess).not.toHaveBeenCalled(); }); + + it('calls the project labels query on search', async () => { + createComponent(); + + findTokenSelector().vm.$emit('focus'); + findTokenSelector().vm.$emit('text-input', 'hello'); + await waitForPromises(); + + expect(projectLabelsQueryHandler).toHaveBeenCalled(); + }); }); describe('when group context', () => { @@ -296,5 +309,15 @@ describe('WorkItemLabels component', () => { expect(groupWorkItemQuerySuccess).not.toHaveBeenCalled(); }); + + it('calls the group labels query on search', async () => { + createComponent({ isGroup: true }); + + findTokenSelector().vm.$emit('focus'); + findTokenSelector().vm.$emit('text-input', 'hello'); + await waitForPromises(); + + expect(groupLabelsQueryHandler).toHaveBeenCalled(); + }); }); }); diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js index 9addf6c3450..36af0c5b3c8 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js @@ -91,6 +91,7 @@ describe('WorkItemLinkChild', () => { childItem: workItemObjectiveWithChild, canUpdate: true, showTaskIcon: false, + showLabels: true, }); }); }); diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js index 0b88b3ff5b4..f8b2736c0f8 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js @@ -1,5 +1,6 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; +import { GlToggle } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; @@ -93,6 +94,7 @@ describe('WorkItemLinks', () => { const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal); const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector); const findWorkItemLinkChildrenWrapper = () => wrapper.findComponent(WorkItemChildrenWrapper); + const findShowLabelsToggle = () => wrapper.findComponent(GlToggle); afterEach(() => { mockApollo = null; @@ -278,4 +280,21 @@ describe('WorkItemLinks', () => { expect(groupResponseWithAddChildPermission).toHaveBeenCalled(); }); }); + + it.each` + toggleValue + ${true} + ${false} + `( + 'passes showLabels as $toggleValue to child items when toggle is $toggleValue', + async ({ toggleValue }) => { + await createComponent(); + + findShowLabelsToggle().vm.$emit('change', toggleValue); + + await nextTick(); + + expect(findWorkItemLinkChildrenWrapper().props('showLabels')).toBe(toggleValue); + }, + ); }); diff --git a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js index f30fded0b45..6c1d1035c3d 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js @@ -1,4 +1,5 @@ import { nextTick } from 'vue'; +import { GlToggle } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import WidgetWrapper from '~/work_items/components/widget_wrapper.vue'; import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue'; @@ -20,6 +21,7 @@ describe('WorkItemTree', () => { const findForm = () => wrapper.findComponent(WorkItemLinksForm); const findWidgetWrapper = () => wrapper.findComponent(WidgetWrapper); const findWorkItemLinkChildrenWrapper = () => wrapper.findComponent(WorkItemChildrenWrapper); + const findShowLabelsToggle = () => wrapper.findComponent(GlToggle); const createComponent = ({ workItemType = 'Objective', @@ -126,4 +128,21 @@ describe('WorkItemTree', () => { expect(wrapper.emitted('addChild')).toEqual([[]]); }); + + it.each` + toggleValue + ${true} + ${false} + `( + 'passes showLabels as $toggleValue to child items when toggle is $toggleValue', + async ({ toggleValue }) => { + createComponent(); + + findShowLabelsToggle().vm.$emit('change', toggleValue); + + await nextTick(); + + expect(findWorkItemLinkChildrenWrapper().props('showLabels')).toBe(toggleValue); + }, + ); }); diff --git a/spec/frontend/work_items/components/work_item_milestone_spec.js b/spec/frontend/work_items/components/work_item_milestone_spec.js index e303ad4b481..fc2c5eb2af2 100644 --- a/spec/frontend/work_items/components/work_item_milestone_spec.js +++ b/spec/frontend/work_items/components/work_item_milestone_spec.js @@ -1,14 +1,8 @@ -import { - GlDropdown, - GlDropdownItem, - GlSearchBoxByType, - GlSkeletonLoader, - GlFormGroup, - GlDropdownText, -} from '@gitlab/ui'; +import { GlCollapsibleListbox, GlListboxItem, GlSkeletonLoader, GlFormGroup } from '@gitlab/ui'; + import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; -import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue'; +import WorkItemMilestone, { noMilestoneId } from '~/work_items/components/work_item_milestone.vue'; import createMockApollo from 'helpers/mock_apollo_helper'; import { mockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -32,17 +26,13 @@ describe('WorkItemMilestone component', () => { const workItemId = 'gid://gitlab/WorkItem/1'; const workItemType = 'Task'; - const findDropdown = () => wrapper.findComponent(GlDropdown); - const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); + const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox); 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 findNoMilestoneDropdownItem = () => wrapper.findByTestId('listbox-item-no-milestone-id'); + const findDropdownItems = () => wrapper.findAllComponents(GlListboxItem); const findDisabledTextSpan = () => wrapper.findByTestId('disabled-text'); - const findDropdownTextAtIndex = (index) => findDropdownTexts().at(index); const findInputGroup = () => wrapper.findComponent(GlFormGroup); + const findNoResultsText = () => wrapper.findByTestId('no-results-text'); const successSearchQueryHandler = jest.fn().mockResolvedValue(projectMilestonesResponse); const successSearchWithNoMatchingMilestones = jest @@ -74,8 +64,7 @@ describe('WorkItemMilestone component', () => { workItemType, }, stubs: { - GlDropdown, - GlSearchBoxByType, + GlCollapsibleListbox, }, }); }; @@ -106,7 +95,7 @@ describe('WorkItemMilestone component', () => { it(`has a value of "Add to milestone"`, () => { createComponent({ canUpdate: true, milestone: null }); - expect(findDropdown().props('text')).toBe(WorkItemMilestone.i18n.MILESTONE_PLACEHOLDER); + expect(findDropdown().props('toggleText')).toBe(WorkItemMilestone.i18n.MILESTONE_PLACEHOLDER); }); }); @@ -114,7 +103,7 @@ describe('WorkItemMilestone component', () => { it('has the search box', () => { createComponent(); - expect(findSearchBox().exists()).toBe(true); + expect(findDropdown().props('searchable')).toBe(true); }); it('shows no matching results when no items', () => { @@ -122,9 +111,8 @@ describe('WorkItemMilestone component', () => { searchQueryHandler: successSearchWithNoMatchingMilestones, }); - expect(findDropdownTextAtIndex(0).text()).toBe(WorkItemMilestone.i18n.NO_MATCHING_RESULTS); + expect(findNoResultsText().text()).toBe(WorkItemMilestone.i18n.NO_MATCHING_RESULTS); expect(findDropdownItems()).toHaveLength(1); - expect(findDropdownTexts()).toHaveLength(1); }); }); @@ -165,16 +153,18 @@ describe('WorkItemMilestone component', () => { it('changes the milestone to null when clicked on no milestone', async () => { showDropdown(); - findFirstDropdownItem().vm.$emit('click'); + findDropdown().vm.$emit('select', noMilestoneId); 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); + expect(findDropdown().props()).toMatchObject({ + loading: false, + toggleText: WorkItemMilestone.i18n.MILESTONE_PLACEHOLDER, + toggleClass: expect.arrayContaining(['gl-text-gray-500!']), + }); }); it('changes the milestone to the selected milestone', async () => { @@ -182,15 +172,16 @@ describe('WorkItemMilestone component', () => { /** 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'); + findDropdown().vm.$emit('select', milestoneAtIndex.id); hideDropdown(); await waitForPromises(); - expect(findDropdown().props('text')).toBe(milestoneAtIndex.title); + expect(findDropdown().props('toggleText')).toBe(milestoneAtIndex.title); }); }); @@ -208,7 +199,7 @@ describe('WorkItemMilestone component', () => { }); showDropdown(); - findFirstDropdownItem().vm.$emit('click'); + findDropdown().vm.$emit('select', noMilestoneId); hideDropdown(); await waitForPromises(); @@ -224,7 +215,7 @@ describe('WorkItemMilestone component', () => { createComponent({ canUpdate: true }); showDropdown(); - findFirstDropdownItem().vm.$emit('click'); + findDropdown().vm.$emit('select', noMilestoneId); hideDropdown(); await waitForPromises(); diff --git a/spec/frontend/work_items/components/work_item_parent_spec.js b/spec/frontend/work_items/components/work_item_parent_spec.js index a72eeabc43c..11fe6dffbfa 100644 --- a/spec/frontend/work_items/components/work_item_parent_spec.js +++ b/spec/frontend/work_items/components/work_item_parent_spec.js @@ -1,14 +1,15 @@ -import * as Sentry from '@sentry/browser'; import { GlCollapsibleListbox, GlFormGroup } from '@gitlab/ui'; - import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import waitForPromises from 'helpers/wait_for_promises'; import createMockApollo from 'helpers/mock_apollo_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import * as Sentry from '~/sentry/sentry_browser_wrapper'; import WorkItemParent from '~/work_items/components/work_item_parent.vue'; +import { removeHierarchyChild } from '~/work_items/graphql/cache_utils'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; +import groupWorkItemsQuery from '~/work_items/graphql/group_work_items.query.graphql'; import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql'; import { WORK_ITEM_TYPE_ENUM_OBJECTIVE } from '~/work_items/constants'; @@ -20,7 +21,10 @@ import { updateWorkItemMutationErrorResponse, } from '../mock_data'; -jest.mock('@sentry/browser'); +jest.mock('~/sentry/sentry_browser_wrapper'); +jest.mock('~/work_items/graphql/cache_utils', () => ({ + removeHierarchyChild: jest.fn(), +})); describe('WorkItemParent component', () => { Vue.use(VueApollo); @@ -29,7 +33,9 @@ describe('WorkItemParent component', () => { const workItemId = 'gid://gitlab/WorkItem/1'; const workItemType = 'Objective'; + const mockFullPath = 'full-path'; + const groupWorkItemsSuccessHandler = jest.fn().mockResolvedValue(availableObjectivesResponse); const availableWorkItemsSuccessHandler = jest.fn().mockResolvedValue(availableObjectivesResponse); const availableWorkItemsFailureHandler = jest.fn().mockRejectedValue(new Error()); @@ -42,14 +48,17 @@ describe('WorkItemParent component', () => { parent = null, searchQueryHandler = availableWorkItemsSuccessHandler, mutationHandler = successUpdateWorkItemMutationHandler, + isGroup = false, } = {}) => { wrapper = shallowMountExtended(WorkItemParent, { apolloProvider: createMockApollo([ [projectWorkItemsQuery, searchQueryHandler], + [groupWorkItemsQuery, groupWorkItemsSuccessHandler], [updateWorkItemMutation, mutationHandler], ]), provide: { - fullPath: 'full-path', + fullPath: mockFullPath, + isGroup, }, propsData: { canUpdate, @@ -81,7 +90,6 @@ describe('WorkItemParent component', () => { headerText: 'Assign parent', category: 'tertiary', loading: false, - noCaret: true, isCheckCentered: true, searchable: true, searching: false, @@ -90,7 +98,6 @@ describe('WorkItemParent component', () => { toggleText: 'None', searchPlaceholder: 'Search', resetButtonLabel: 'Unassign', - block: true, }); }); @@ -98,14 +105,12 @@ describe('WorkItemParent component', () => { createComponent({ canUpdate: false, parent: mockParentWidgetResponse }); expect(findCollapsibleListbox().exists()).toBe(false); - expect(findParentText().exists()).toBe(true); expect(findParentText().text()).toBe('Objective 101'); }); it('shows loading while searching', async () => { await findCollapsibleListbox().vm.$emit('shown'); expect(findCollapsibleListbox().props('searching')).toBe(true); - expect(findCollapsibleListbox().props('no-caret')).toBeUndefined(); }); }); @@ -143,15 +148,27 @@ describe('WorkItemParent component', () => { }); await findCollapsibleListbox().vm.$emit('shown'); - await findCollapsibleListbox().vm.$emit('search', 'Objective 101'); await waitForPromises(); expect(searchedItemQueryHandler).toHaveBeenCalledWith({ fullPath: 'full-path', + searchTerm: '', + types: [WORK_ITEM_TYPE_ENUM_OBJECTIVE], + in: undefined, + iid: null, + isNumber: false, + }); + + await findCollapsibleListbox().vm.$emit('search', 'Objective 101'); + + expect(searchedItemQueryHandler).toHaveBeenCalledWith({ + fullPath: 'full-path', searchTerm: 'Objective 101', types: [WORK_ITEM_TYPE_ENUM_OBJECTIVE], in: 'TITLE', + iid: null, + isNumber: false, }); await nextTick(); @@ -164,7 +181,6 @@ describe('WorkItemParent component', () => { describe('listbox', () => { const selectWorkItem = async (workItem) => { - await findCollapsibleListbox().vm.$emit('shown'); await findCollapsibleListbox().vm.$emit('select', workItem); }; @@ -181,6 +197,14 @@ describe('WorkItemParent component', () => { }, }, }); + + expect(removeHierarchyChild).toHaveBeenCalledWith({ + cache: expect.anything(Object), + fullPath: mockFullPath, + iid: undefined, + isGroup: false, + workItem: { id: 'gid://gitlab/WorkItem/1' }, + }); }); it('calls mutation when item is unassigned', async () => { @@ -188,6 +212,9 @@ describe('WorkItemParent component', () => { .fn() .mockResolvedValue(updateWorkItemMutationResponseFactory({ parent: null })); createComponent({ + parent: { + iid: '1', + }, mutationHandler: unAssignParentWorkItemMutationHandler, }); @@ -203,6 +230,13 @@ describe('WorkItemParent component', () => { }, }, }); + expect(removeHierarchyChild).toHaveBeenCalledWith({ + cache: expect.anything(Object), + fullPath: mockFullPath, + iid: '1', + isGroup: false, + workItem: { id: 'gid://gitlab/WorkItem/1' }, + }); }); it('emits error when mutation fails', async () => { @@ -233,4 +267,34 @@ describe('WorkItemParent component', () => { expect(Sentry.captureException).toHaveBeenCalledWith(error); }); }); + + describe('when project context', () => { + beforeEach(() => { + createComponent(); + findCollapsibleListbox().vm.$emit('shown'); + }); + + it('calls the project work items query', () => { + expect(availableWorkItemsSuccessHandler).toHaveBeenCalled(); + }); + + it('skips calling the group work items query', () => { + expect(groupWorkItemsSuccessHandler).not.toHaveBeenCalled(); + }); + }); + + describe('when group context', () => { + beforeEach(() => { + createComponent({ isGroup: true }); + findCollapsibleListbox().vm.$emit('shown'); + }); + + it('skips calling the project work items query', () => { + expect(availableWorkItemsSuccessHandler).not.toHaveBeenCalled(); + }); + + it('calls the group work items query', () => { + expect(groupWorkItemsSuccessHandler).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap b/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap index bbc19a011a5..20af8584e37 100644 --- a/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap +++ b/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap @@ -22,6 +22,7 @@ exports[`WorkItemRelationshipList renders linked item list 1`] = ` <work-item-link-child-contents-stub canupdate="true" childitem="[object Object]" + showlabels="true" showtaskicon="true" /> </li> diff --git a/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js b/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js index d7b3ced2ff9..520cf5f7ea4 100644 --- a/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js +++ b/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js @@ -34,6 +34,9 @@ describe('WorkItemAddRelationshipForm', () => { wrapper = shallowMountExtended(WorkItemAddRelationshipForm, { apolloProvider: mockApolloProvider, + provide: { + isGroup: false, + }, propsData: { workItemId, workItemIid, diff --git a/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js index 7178fa1aae7..0faea0e4862 100644 --- a/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js +++ b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js @@ -1,6 +1,6 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlToggle } from '@gitlab/ui'; import createMockApollo from 'helpers/mock_apollo_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -82,6 +82,7 @@ describe('WorkItemRelationships', () => { wrapper.findAllComponents(WorkItemRelationshipList); const findAddButton = () => wrapper.findByTestId('link-item-add-button'); const findWorkItemRelationshipForm = () => wrapper.findComponent(WorkItemAddRelationshipForm); + const findShowLabelsToggle = () => wrapper.findComponent(GlToggle); it('shows loading icon when query is not processed', () => { createComponent(); @@ -99,6 +100,11 @@ describe('WorkItemRelationships', () => { expect(findLinkedItemsHelpLink().attributes('href')).toBe( '/help/user/okrs.md#linked-items-in-okrs', ); + expect(findShowLabelsToggle().props()).toMatchObject({ + value: true, + labelPosition: 'left', + label: 'Show labels', + }); }); it('renders blocking linked item lists', async () => { @@ -153,6 +159,29 @@ describe('WorkItemRelationships', () => { expect(findWorkItemRelationshipForm().exists()).toBe(false); }); + it.each` + toggleValue + ${true} + ${false} + `( + 'passes showLabels as $toggleValue to child items when toggle is $toggleValue', + async ({ toggleValue }) => { + await createComponent({ + workItemQueryHandler: jest + .fn() + .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockLinkedItems })), + }); + + findShowLabelsToggle().vm.$emit('change', toggleValue); + + await nextTick(); + + expect(findAllWorkItemRelationshipListComponents().at(0).props('showLabels')).toBe( + toggleValue, + ); + }, + ); + describe('when project context', () => { it('calls the project work item query', () => { createComponent(); diff --git a/spec/frontend/work_items/components/work_item_state_toggle_button_spec.js b/spec/frontend/work_items/components/work_item_state_toggle_button_spec.js index c0b206e5da4..a210bd50422 100644 --- a/spec/frontend/work_items/components/work_item_state_toggle_button_spec.js +++ b/spec/frontend/work_items/components/work_item_state_toggle_button_spec.js @@ -5,7 +5,7 @@ import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import { mockTracking } from 'helpers/tracking_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import WorkItemStateToggleButton from '~/work_items/components/work_item_state_toggle_button.vue'; +import WorkItemStateToggle from '~/work_items/components/work_item_state_toggle.vue'; import { STATE_OPEN, STATE_CLOSED, @@ -33,7 +33,7 @@ describe('Work Item State toggle button component', () => { workItemState = STATE_OPEN, workItemType = 'Task', } = {}) => { - wrapper = shallowMount(WorkItemStateToggleButton, { + wrapper = shallowMount(WorkItemStateToggle, { apolloProvider: createMockApollo([[updateWorkItemMutation, mutationHandler]]), propsData: { workItemId: id, diff --git a/spec/frontend/work_items/components/work_item_title_spec.js b/spec/frontend/work_items/components/work_item_title_spec.js index 34391b74cf7..0f466bcf691 100644 --- a/spec/frontend/work_items/components/work_item_title_spec.js +++ b/spec/frontend/work_items/components/work_item_title_spec.js @@ -131,5 +131,25 @@ describe('WorkItemTitle component', () => { property: 'type_Task', }); }); + + describe('when title has more than 255 characters', () => { + const title = new Array(257).join('a'); + + it('does not call a mutation', () => { + createComponent(); + + findItemTitle().vm.$emit('title-changed', title); + + expect(mutationSuccessHandler).not.toHaveBeenCalled(); + }); + + it('emits an error message', () => { + createComponent(); + + findItemTitle().vm.$emit('title-changed', title); + + expect(wrapper.emitted('error')).toEqual([['Title cannot have more than 255 characters.']]); + }); + }); }); }); diff --git a/spec/frontend/work_items/components/work_item_todos_spec.js b/spec/frontend/work_items/components/work_item_todos_spec.js index c76cdbcee53..d67d84e75b5 100644 --- a/spec/frontend/work_items/components/work_item_todos_spec.js +++ b/spec/frontend/work_items/components/work_item_todos_spec.js @@ -34,7 +34,7 @@ describe('WorkItemTodo component', () => { const workItemQueryResponse = workItemResponseFactory({ canUpdate: true }); const mockWorkItemId = workItemQueryResponse.data.workItem.id; const mockWorkItemIid = workItemQueryResponse.data.workItem.iid; - const mockWorkItemFullpath = workItemQueryResponse.data.workItem.project.fullPath; + const mockWorkItemFullpath = workItemQueryResponse.data.workItem.namespace.fullPath; const createTodoSuccessHandler = jest .fn() |