diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-20 23:50:22 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-20 23:50:22 +0000 |
commit | 9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch) | |
tree | 70467ae3692a0e35e5ea56bcb803eb512a10bedb /spec/frontend/boards/components | |
parent | 4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff) | |
download | gitlab-ce-9dc93a4519d9d5d7be48ff274127136236a3adb3.tar.gz |
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'spec/frontend/boards/components')
15 files changed, 569 insertions, 140 deletions
diff --git a/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap b/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap new file mode 100644 index 00000000000..c000f300e4d --- /dev/null +++ b/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BoardBlockedIcon on mouseenter on blocked icon with more than three blocking issues matches the snapshot 1`] = ` +"<div class=\\"gl-display-inline\\"><svg data-testid=\\"issue-blocked-icon\\" aria-hidden=\\"true\\" class=\\"issue-blocked-icon gl-mr-2 gl-cursor-pointer gl-icon s16\\" id=\\"blocked-icon-uniqueId\\"> + <use href=\\"#issue-block\\"></use> + </svg> + <div class=\\"gl-popover\\"> + <ul class=\\"gl-list-style-none gl-p-0\\"> + <li><a href=\\"http://gdk.test:3000/gitlab-org/my-project-1/-/issues/6\\" class=\\"gl-link gl-text-blue-500! gl-font-sm\\">my-project-1#6</a> + <p data-testid=\\"issuable-title\\" class=\\"gl-mb-3 gl-display-block!\\"> + blocking issue title 1 + </p> + </li> + <li><a href=\\"http://gdk.test:3000/gitlab-org/my-project-1/-/issues/5\\" class=\\"gl-link gl-text-blue-500! gl-font-sm\\">my-project-1#5</a> + <p data-testid=\\"issuable-title\\" class=\\"gl-mb-3 gl-display-block!\\"> + blocking issue title 2 + blocking issue title 2 + blocking issue title 2 + bloc… + </p> + </li> + <li><a href=\\"http://gdk.test:3000/gitlab-org/my-project-1/-/issues/4\\" class=\\"gl-link gl-text-blue-500! gl-font-sm\\">my-project-1#4</a> + <p data-testid=\\"issuable-title\\" class=\\"gl-mb-3 gl-display-block!\\"> + blocking issue title 3 + </p> + </li> + </ul> + <div class=\\"gl-mt-4\\"> + <p data-testid=\\"hidden-blocking-count\\" class=\\"gl-mb-3\\">+ 1 more issue</p> <a data-testid=\\"view-all-issues\\" href=\\"http://gdk.test:3000/gitlab-org/my-project-1/-/issues/0#related-issues\\" class=\\"gl-link gl-text-blue-500! gl-font-sm\\">View all blocking issues</a> + </div><span data-testid=\\"popover-title\\">Blocked by 4 issues</span> + </div> +</div>" +`; diff --git a/spec/frontend/boards/components/board_add_new_column_form_spec.js b/spec/frontend/boards/components/board_add_new_column_form_spec.js index 3702f55f17b..3b26ca57d6f 100644 --- a/spec/frontend/boards/components/board_add_new_column_form_spec.js +++ b/spec/frontend/boards/components/board_add_new_column_form_spec.js @@ -1,6 +1,6 @@ -import { GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui'; +import { GlDropdown, GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; +import Vue from 'vue'; import Vuex from 'vuex'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue'; @@ -25,7 +25,7 @@ describe('Board card layout', () => { const mountComponent = ({ loading = false, - formDescription = '', + noneSelected = '', searchLabel = '', searchPlaceholder = '', selectedId, @@ -34,12 +34,9 @@ describe('Board card layout', () => { } = {}) => { wrapper = extendedWrapper( shallowMount(BoardAddNewColumnForm, { - stubs: { - GlFormGroup: true, - }, propsData: { loading, - formDescription, + noneSelected, searchLabel, searchPlaceholder, selectedId, @@ -51,13 +48,15 @@ describe('Board card layout', () => { ...actions, }, }), + stubs: { + GlDropdown, + }, }), ); }; afterEach(() => { wrapper.destroy(); - wrapper = null; }); const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text(); @@ -65,10 +64,13 @@ describe('Board card layout', () => { const findSearchLabel = () => wrapper.find(GlFormGroup); const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn'); const submitButton = () => wrapper.findByTestId('addNewColumnButton'); + const findDropdown = () => wrapper.findComponent(GlDropdown); it('shows form title & search input', () => { mountComponent(); + findDropdown().vm.$emit('show'); + expect(formTitle()).toEqual(BoardAddNewColumnForm.i18n.newList); expect(findSearchInput().exists()).toBe(true); }); @@ -86,16 +88,6 @@ describe('Board card layout', () => { expect(setAddColumnFormVisibility).toHaveBeenCalledWith(expect.anything(), false); }); - it('sets placeholder and description from props', () => { - const props = { - formDescription: 'Some description of a list', - }; - - mountComponent(props); - - expect(wrapper.html()).toHaveText(props.formDescription); - }); - describe('items', () => { const mountWithItems = (loading) => mountComponent({ @@ -151,13 +143,11 @@ describe('Board card layout', () => { expect(submitButton().props('disabled')).toBe(true); }); - it('emits add-list event on click', async () => { + it('emits add-list event on click', () => { mountComponent({ selectedId: mockLabelList.label.id, }); - await nextTick(); - submitButton().vm.$emit('click'); expect(wrapper.emitted('add-list')).toEqual([[]]); diff --git a/spec/frontend/boards/components/board_add_new_column_spec.js b/spec/frontend/boards/components/board_add_new_column_spec.js index 60584eaf6cf..61f210f566b 100644 --- a/spec/frontend/boards/components/board_add_new_column_spec.js +++ b/spec/frontend/boards/components/board_add_new_column_spec.js @@ -1,3 +1,4 @@ +import { GlFormRadioGroup } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; @@ -12,6 +13,10 @@ Vue.use(Vuex); describe('Board card layout', () => { let wrapper; + const selectLabel = (id) => { + wrapper.findComponent(GlFormRadioGroup).vm.$emit('change', id); + }; + const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => { return new Vuex.Store({ state: { @@ -57,6 +62,11 @@ describe('Board card layout', () => { }, }), ); + + // trigger change event + if (selectedId) { + selectLabel(selectedId); + } }; afterEach(() => { diff --git a/spec/frontend/boards/components/board_blocked_icon_spec.js b/spec/frontend/boards/components/board_blocked_icon_spec.js new file mode 100644 index 00000000000..7b04942f056 --- /dev/null +++ b/spec/frontend/boards/components/board_blocked_icon_spec.js @@ -0,0 +1,226 @@ +import { GlIcon, GlLink, GlPopover, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount, mount } from '@vue/test-utils'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue'; +import { blockingIssuablesQueries, issuableTypes } from '~/boards/constants'; +import { truncate } from '~/lib/utils/text_utility'; +import { + mockIssue, + mockBlockingIssue1, + mockBlockingIssue2, + mockBlockingIssuablesResponse1, + mockBlockingIssuablesResponse2, + mockBlockingIssuablesResponse3, + mockBlockedIssue1, + mockBlockedIssue2, +} from '../mock_data'; + +describe('BoardBlockedIcon', () => { + let wrapper; + let mockApollo; + + const findGlIcon = () => wrapper.find(GlIcon); + const findGlPopover = () => wrapper.find(GlPopover); + const findGlLink = () => wrapper.find(GlLink); + const findPopoverTitle = () => wrapper.findByTestId('popover-title'); + const findIssuableTitle = () => wrapper.findByTestId('issuable-title'); + const findHiddenBlockingCount = () => wrapper.findByTestId('hidden-blocking-count'); + const findViewAllIssuableLink = () => wrapper.findByTestId('view-all-issues'); + + const waitForApollo = async () => { + jest.runOnlyPendingTimers(); + await waitForPromises(); + }; + + const mouseenter = async () => { + findGlIcon().vm.$emit('mouseenter'); + + await wrapper.vm.$nextTick(); + await waitForApollo(); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const createWrapperWithApollo = ({ + item = mockBlockedIssue1, + blockingIssuablesSpy = jest.fn().mockResolvedValue(mockBlockingIssuablesResponse1), + } = {}) => { + mockApollo = createMockApollo([ + [blockingIssuablesQueries[issuableTypes.issue].query, blockingIssuablesSpy], + ]); + + Vue.use(VueApollo); + wrapper = extendedWrapper( + mount(BoardBlockedIcon, { + apolloProvider: mockApollo, + propsData: { + item: { + ...mockIssue, + ...item, + }, + uniqueId: 'uniqueId', + issuableType: issuableTypes.issue, + }, + attachTo: document.body, + }), + ); + }; + + const createWrapper = ({ item = {}, queries = {}, data = {}, loading = false } = {}) => { + wrapper = extendedWrapper( + shallowMount(BoardBlockedIcon, { + propsData: { + item: { + ...mockIssue, + ...item, + }, + uniqueId: 'uniqueid', + issuableType: issuableTypes.issue, + }, + data() { + return { + ...data, + }; + }, + mocks: { + $apollo: { + queries: { + blockingIssuables: { loading }, + ...queries, + }, + }, + }, + stubs: { + GlPopover, + }, + attachTo: document.body, + }), + ); + }; + + it('should render blocked icon', () => { + createWrapper(); + + expect(findGlIcon().exists()).toBe(true); + }); + + it('should display a loading spinner while loading', () => { + createWrapper({ loading: true }); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + + it('should not query for blocking issuables by default', async () => { + createWrapperWithApollo(); + + expect(findGlPopover().text()).not.toContain(mockBlockingIssue1.title); + }); + + describe('on mouseenter on blocked icon', () => { + it('should query for blocking issuables and render the result', async () => { + createWrapperWithApollo(); + + expect(findGlPopover().text()).not.toContain(mockBlockingIssue1.title); + + await mouseenter(); + + expect(findGlPopover().exists()).toBe(true); + expect(findIssuableTitle().text()).toContain(mockBlockingIssue1.title); + expect(wrapper.vm.skip).toBe(true); + }); + + it('should emit "blocking-issuables-error" event on query error', async () => { + const mockError = new Error('mayday'); + createWrapperWithApollo({ blockingIssuablesSpy: jest.fn().mockRejectedValue(mockError) }); + + await mouseenter(); + + const [ + [ + { + message, + error: { networkError }, + }, + ], + ] = wrapper.emitted('blocking-issuables-error'); + expect(message).toBe('Failed to fetch blocking issues'); + expect(networkError).toBe(mockError); + }); + + describe('with a single blocking issue', () => { + beforeEach(async () => { + createWrapperWithApollo(); + + await mouseenter(); + }); + + it('should render a title of the issuable', async () => { + expect(findIssuableTitle().text()).toBe(mockBlockingIssue1.title); + }); + + it('should render issuable reference and link to the issuable', async () => { + const formattedRef = mockBlockingIssue1.reference.split('/')[1]; + + expect(findGlLink().text()).toBe(formattedRef); + expect(findGlLink().attributes('href')).toBe(mockBlockingIssue1.webUrl); + }); + + it('should render popover title with correct blocking issuable count', async () => { + expect(findPopoverTitle().text()).toBe('Blocked by 1 issue'); + }); + }); + + describe('when issue has a long title', () => { + it('should render a truncated title', async () => { + createWrapperWithApollo({ + blockingIssuablesSpy: jest.fn().mockResolvedValue(mockBlockingIssuablesResponse2), + }); + + await mouseenter(); + + const truncatedTitle = truncate( + mockBlockingIssue2.title, + wrapper.vm.$options.textTruncateWidth, + ); + expect(findIssuableTitle().text()).toBe(truncatedTitle); + }); + }); + + describe('with more than three blocking issues', () => { + beforeEach(async () => { + createWrapperWithApollo({ + item: mockBlockedIssue2, + blockingIssuablesSpy: jest.fn().mockResolvedValue(mockBlockingIssuablesResponse3), + }); + + await mouseenter(); + }); + + it('matches the snapshot', () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should render popover title with correct blocking issuable count', async () => { + expect(findPopoverTitle().text()).toBe('Blocked by 4 issues'); + }); + + it('should render the number of hidden blocking issuables', () => { + expect(findHiddenBlockingCount().text()).toBe('+ 1 more issue'); + }); + + it('should link to the blocked issue page at the related issue anchor', async () => { + expect(findViewAllIssuableLink().text()).toBe('View all blocking issues'); + expect(findViewAllIssuableLink().attributes('href')).toBe( + `${mockBlockedIssue2.webUrl}#related-issues`, + ); + }); + }); + }); +}); diff --git a/spec/frontend/boards/components/board_content_sidebar_spec.js b/spec/frontend/boards/components/board_content_sidebar_spec.js new file mode 100644 index 00000000000..7f949739891 --- /dev/null +++ b/spec/frontend/boards/components/board_content_sidebar_spec.js @@ -0,0 +1,140 @@ +import { GlDrawer } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { stubComponent } from 'helpers/stub_component'; +import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue'; +import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue'; +import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue'; +import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue'; +import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue'; +import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue'; +import { ISSUABLE } from '~/boards/constants'; +import { mockIssue, mockIssueGroupPath, mockIssueProjectPath } from '../mock_data'; + +describe('BoardContentSidebar', () => { + let wrapper; + let store; + + const createStore = ({ mockGetters = {}, mockActions = {} } = {}) => { + store = new Vuex.Store({ + state: { + sidebarType: ISSUABLE, + issues: { [mockIssue.id]: { ...mockIssue, epic: null } }, + activeId: mockIssue.id, + issuableType: 'issue', + }, + getters: { + activeBoardItem: () => { + return { ...mockIssue, epic: null }; + }, + groupPathForActiveIssue: () => mockIssueGroupPath, + projectPathForActiveIssue: () => mockIssueProjectPath, + isSidebarOpen: () => true, + ...mockGetters, + }, + actions: mockActions, + }); + }; + + const createComponent = () => { + /* + Dynamically imported components (in our case ee imports) + aren't stubbed automatically in VTU v1: + https://github.com/vuejs/vue-test-utils/issues/1279. + + This requires us to additionally mock apollo or vuex stores. + */ + wrapper = shallowMount(BoardContentSidebar, { + provide: { + canUpdate: true, + rootPath: '/', + groupId: 1, + }, + store, + stubs: { + GlDrawer: stubComponent(GlDrawer, { + template: '<div><slot name="header"></slot><slot></slot></div>', + }), + }, + mocks: { + $apollo: { + queries: { + participants: { + loading: false, + }, + currentIteration: { + loading: false, + }, + iterations: { + loading: false, + }, + }, + }, + }, + }); + }; + + beforeEach(() => { + createStore(); + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('confirms we render GlDrawer', () => { + expect(wrapper.find(GlDrawer).exists()).toBe(true); + }); + + it('does not render GlDrawer when isSidebarOpen is false', () => { + createStore({ mockGetters: { isSidebarOpen: () => false } }); + createComponent(); + + expect(wrapper.find(GlDrawer).exists()).toBe(false); + }); + + it('applies an open attribute', () => { + expect(wrapper.find(GlDrawer).props('open')).toBe(true); + }); + + it('renders BoardSidebarLabelsSelect', () => { + expect(wrapper.find(BoardSidebarLabelsSelect).exists()).toBe(true); + }); + + it('renders BoardSidebarTitle', () => { + expect(wrapper.find(BoardSidebarTitle).exists()).toBe(true); + }); + + it('renders BoardSidebarDueDate', () => { + expect(wrapper.find(BoardSidebarDueDate).exists()).toBe(true); + }); + + it('renders BoardSidebarSubscription', () => { + expect(wrapper.find(BoardSidebarSubscription).exists()).toBe(true); + }); + + it('renders BoardSidebarMilestoneSelect', () => { + expect(wrapper.find(BoardSidebarMilestoneSelect).exists()).toBe(true); + }); + + describe('when we emit close', () => { + let toggleBoardItem; + + beforeEach(() => { + toggleBoardItem = jest.fn(); + createStore({ mockActions: { toggleBoardItem } }); + createComponent(); + }); + + it('calls toggleBoardItem with correct parameters', async () => { + wrapper.find(GlDrawer).vm.$emit('close'); + + expect(toggleBoardItem).toHaveBeenCalledTimes(1); + expect(toggleBoardItem).toHaveBeenCalledWith(expect.any(Object), { + boardItem: { ...mockIssue, epic: null }, + sidebarType: ISSUABLE, + }); + }); + }); +}); diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js index 159b67ccc67..8c1a7bd3947 100644 --- a/spec/frontend/boards/components/board_content_spec.js +++ b/spec/frontend/boards/components/board_content_spec.js @@ -33,7 +33,12 @@ describe('BoardContent', () => { }); }; - const createComponent = ({ state, props = {}, graphqlBoardListsEnabled = false } = {}) => { + const createComponent = ({ + state, + props = {}, + graphqlBoardListsEnabled = false, + canAdminList = true, + } = {}) => { const store = createStore({ ...defaultState, ...state, @@ -42,11 +47,11 @@ describe('BoardContent', () => { localVue, propsData: { lists: mockListsWithModel, - canAdminList: true, disabled: false, ...props, }, provide: { + canAdminList, glFeatures: { graphqlBoardLists: graphqlBoardListsEnabled }, }, store, @@ -82,7 +87,7 @@ describe('BoardContent', () => { describe('can admin list', () => { beforeEach(() => { - createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: true } }); + createComponent({ graphqlBoardListsEnabled: true, canAdminList: true }); }); it('renders draggable component', () => { @@ -92,7 +97,7 @@ describe('BoardContent', () => { describe('can not admin list', () => { beforeEach(() => { - createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: false } }); + createComponent({ graphqlBoardListsEnabled: true, canAdminList: false }); }); it('does not render draggable component', () => { diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js index 32499bd5480..24fcdd528d5 100644 --- a/spec/frontend/boards/components/board_form_spec.js +++ b/spec/frontend/boards/components/board_form_spec.js @@ -226,7 +226,7 @@ describe('BoardForm', () => { it('passes correct primary action text and variant', () => { expect(findModalActionPrimary().text).toBe('Save changes'); - expect(findModalActionPrimary().attributes[0].variant).toBe('info'); + expect(findModalActionPrimary().attributes[0].variant).toBe('confirm'); }); it('does not render delete confirmation message', () => { diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js index 737a18294bc..e6405bbcff3 100644 --- a/spec/frontend/boards/components/board_new_issue_spec.js +++ b/spec/frontend/boards/components/board_new_issue_spec.js @@ -86,7 +86,7 @@ describe('Issue boards new issue form', () => { describe('submit success', () => { it('creates new issue', async () => { - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); await vm.$nextTick(); await submitIssue(); @@ -95,7 +95,7 @@ describe('Issue boards new issue form', () => { it('enables button after submit', async () => { jest.spyOn(wrapper.vm, 'submit').mockImplementation(); - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); await vm.$nextTick(); await submitIssue(); @@ -103,7 +103,7 @@ describe('Issue boards new issue form', () => { }); it('clears title after submit', async () => { - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); await vm.$nextTick(); await submitIssue(); diff --git a/spec/frontend/boards/components/board_settings_sidebar_spec.js b/spec/frontend/boards/components/board_settings_sidebar_spec.js index 52b4d71f7b9..464331b6e30 100644 --- a/spec/frontend/boards/components/board_settings_sidebar_spec.js +++ b/spec/frontend/boards/components/board_settings_sidebar_spec.js @@ -4,6 +4,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import Vuex from 'vuex'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue'; import { inactiveId, LIST } from '~/boards/constants'; import { createStore } from '~/boards/stores'; @@ -22,11 +23,18 @@ describe('BoardSettingsSidebar', () => { const labelColor = '#FFFF'; const listId = 1; - const createComponent = () => { - wrapper = shallowMount(BoardSettingsSidebar, { - store, - localVue, - }); + const findRemoveButton = () => wrapper.findByTestId('remove-list'); + + const createComponent = ({ canAdminList = false } = {}) => { + wrapper = extendedWrapper( + shallowMount(BoardSettingsSidebar, { + store, + localVue, + provide: { + canAdminList, + }, + }), + ); }; const findLabel = () => wrapper.find(GlLabel); const findDrawer = () => wrapper.find(GlDrawer); @@ -164,4 +172,29 @@ describe('BoardSettingsSidebar', () => { expect(findDrawer().exists()).toBe(false); }); }); + + it('does not render "Remove list" when user cannot admin the boards list', () => { + createComponent(); + + expect(findRemoveButton().exists()).toBe(false); + }); + + describe('when user can admin the boards list', () => { + beforeEach(() => { + store.state.activeId = listId; + store.state.sidebarType = LIST; + + boardsStore.addList({ + id: listId, + label: { title: labelTitle, color: labelColor }, + list_type: 'label', + }); + + createComponent({ canAdminList: true }); + }); + + it('renders "Remove list" button', () => { + expect(findRemoveButton().exists()).toBe(true); + }); + }); }); diff --git a/spec/frontend/boards/components/filtered_search_spec.js b/spec/frontend/boards/components/filtered_search_spec.js deleted file mode 100644 index 7f238aa671f..00000000000 --- a/spec/frontend/boards/components/filtered_search_spec.js +++ /dev/null @@ -1,65 +0,0 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import Vuex from 'vuex'; -import FilteredSearch from '~/boards/components/filtered_search.vue'; -import { createStore } from '~/boards/stores'; -import * as commonUtils from '~/lib/utils/common_utils'; -import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('FilteredSearch', () => { - let wrapper; - let store; - - const createComponent = () => { - wrapper = shallowMount(FilteredSearch, { - localVue, - propsData: { search: '' }, - store, - attachTo: document.body, - }); - }; - - beforeEach(() => { - // this needed for actions call for performSearch - window.gon = { features: {} }; - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('default', () => { - beforeEach(() => { - store = createStore(); - - jest.spyOn(store, 'dispatch'); - - createComponent(); - }); - - it('finds FilteredSearch', () => { - expect(wrapper.find(FilteredSearchBarRoot).exists()).toBe(true); - }); - - describe('when onFilter is emitted', () => { - it('calls performSearch', () => { - wrapper.find(FilteredSearchBarRoot).vm.$emit('onFilter', [{ value: { data: '' } }]); - - expect(store.dispatch).toHaveBeenCalledWith('performSearch'); - }); - - it('calls historyPushState', () => { - commonUtils.historyPushState = jest.fn(); - wrapper - .find(FilteredSearchBarRoot) - .vm.$emit('onFilter', [{ value: { data: 'searchQuery' } }]); - - expect(commonUtils.historyPushState).toHaveBeenCalledWith( - 'http://test.host/?search=searchQuery', - ); - }); - }); - }); -}); diff --git a/spec/frontend/boards/components/issue_time_estimate_spec.js b/spec/frontend/boards/components/issue_time_estimate_spec.js index 2e253d24125..635964b6b4a 100644 --- a/spec/frontend/boards/components/issue_time_estimate_spec.js +++ b/spec/frontend/boards/components/issue_time_estimate_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { config as vueConfig } from 'vue'; +import Vue from 'vue'; import IssueTimeEstimate from '~/boards/components/issue_time_estimate.vue'; describe('Issue Time Estimate component', () => { @@ -34,10 +34,10 @@ describe('Issue Time Estimate component', () => { try { // This will raise props validating warning by Vue, silencing it - vueConfig.silent = true; + Vue.config.silent = true; await wrapper.setProps({ estimate: 'Foo <script>alert("XSS")</script>' }); } finally { - vueConfig.silent = false; + Vue.config.silent = false; } expect(alertSpy).not.toHaveBeenCalled(); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js index 98ac211238c..153d0640b23 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js @@ -64,7 +64,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => { beforeEach(async () => { createWrapper(); - jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => TEST_LABELS); + jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => TEST_LABELS); findLabelsSelect().vm.$emit('updateSelectedLabels', TEST_LABELS_PAYLOAD); store.state.boardItems[TEST_ISSUE.id].labels = TEST_LABELS; await wrapper.vm.$nextTick(); @@ -76,7 +76,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => { }); it('commits change to the server', () => { - expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({ + expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({ addLabelIds: TEST_LABELS.map((label) => label.id), projectPath: 'gitlab-org/test-subgroup/gitlab-test', removeLabelIds: [], @@ -94,13 +94,13 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => { beforeEach(async () => { createWrapper({ labels: TEST_LABELS }); - jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => expectedLabels); + jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => expectedLabels); findLabelsSelect().vm.$emit('updateSelectedLabels', testLabelsPayload); await wrapper.vm.$nextTick(); }); it('commits change to the server', () => { - expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({ + expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({ addLabelIds: [5, 7], removeLabelIds: [6], projectPath: 'gitlab-org/test-subgroup/gitlab-test', @@ -114,13 +114,13 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => { beforeEach(async () => { createWrapper({ labels: [testLabel] }); - jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => {}); + jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => {}); }); it('commits change to the server', () => { wrapper.find(GlLabel).vm.$emit('close', testLabel); - expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({ + expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({ removeLabelIds: [getIdFromGraphQLId(testLabel.id)], projectPath: 'gitlab-org/test-subgroup/gitlab-test', }); @@ -131,7 +131,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => { beforeEach(async () => { createWrapper({ labels: TEST_LABELS }); - jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => { + jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => { throw new Error(['failed mutation']); }); findLabelsSelect().vm.$emit('updateSelectedLabels', [{ id: '?' }]); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js index cfd7f32b2cc..7976e73ff2f 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js @@ -1,5 +1,6 @@ import { GlToggle, GlLoadingIcon } from '@gitlab/ui'; -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue'; import { createStore } from '~/boards/stores'; @@ -9,8 +10,7 @@ import { mockActiveIssue } from '../../mock_data'; jest.mock('~/flash.js'); -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () => { let wrapper; @@ -20,14 +20,16 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = const findToggle = () => wrapper.find(GlToggle); const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon); - const createComponent = (activeIssue = { ...mockActiveIssue }) => { + const createComponent = (activeBoardItem = { ...mockActiveIssue }) => { store = createStore(); - store.state.boardItems = { [activeIssue.id]: activeIssue }; - store.state.activeId = activeIssue.id; + store.state.boardItems = { [activeBoardItem.id]: activeBoardItem }; + store.state.activeId = activeBoardItem.id; wrapper = mount(BoardSidebarSubscription, { - localVue, store, + provide: { + emailsDisabled: false, + }, }); }; @@ -90,9 +92,9 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = describe('Board sidebar subscription component `behavior`', () => { const mockSetActiveIssueSubscribed = (subscribedState) => { - jest.spyOn(wrapper.vm, 'setActiveIssueSubscribed').mockImplementation(async () => { - store.commit(types.UPDATE_ISSUE_BY_ID, { - issueId: mockActiveIssue.id, + jest.spyOn(wrapper.vm, 'setActiveItemSubscribed').mockImplementation(async () => { + store.commit(types.UPDATE_BOARD_ITEM_BY_ID, { + itemId: mockActiveIssue.id, prop: 'subscribed', value: subscribedState, }); @@ -110,7 +112,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = await wrapper.vm.$nextTick(); expect(findGlLoadingIcon().exists()).toBe(true); - expect(wrapper.vm.setActiveIssueSubscribed).toHaveBeenCalledWith({ + expect(wrapper.vm.setActiveItemSubscribed).toHaveBeenCalledWith({ subscribed: true, projectPath: 'gitlab-org/test-subgroup/gitlab-test', }); @@ -134,7 +136,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = await wrapper.vm.$nextTick(); - expect(wrapper.vm.setActiveIssueSubscribed).toHaveBeenCalledWith({ + expect(wrapper.vm.setActiveItemSubscribed).toHaveBeenCalledWith({ subscribed: false, projectPath: 'gitlab-org/test-subgroup/gitlab-test', }); @@ -148,7 +150,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = it('flashes an error message when setting the subscribed state fails', async () => { createComponent(); - jest.spyOn(wrapper.vm, 'setActiveIssueSubscribed').mockImplementation(async () => { + jest.spyOn(wrapper.vm, 'setActiveItemSubscribed').mockImplementation(async () => { throw new Error(); }); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js new file mode 100644 index 00000000000..03924bfa8d3 --- /dev/null +++ b/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js @@ -0,0 +1,58 @@ +/* + To avoid duplicating tests in time_tracker.spec, + this spec only contains a simple test to check rendering. + + A detailed feature spec is used to test time tracking feature + in swimlanes sidebar. +*/ + +import { shallowMount } from '@vue/test-utils'; +import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue'; +import { createStore } from '~/boards/stores'; +import IssuableTimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue'; + +describe('BoardSidebarTimeTracker', () => { + let wrapper; + let store; + + const createComponent = (options) => { + wrapper = shallowMount(BoardSidebarTimeTracker, { + store, + ...options, + }); + }; + + beforeEach(() => { + store = createStore(); + store.state.boardItems = { + 1: { + timeEstimate: 3600, + totalTimeSpent: 1800, + humanTimeEstimate: '1h', + humanTotalTimeSpent: '30min', + }, + }; + store.state.activeId = '1'; + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it.each([[true], [false]])( + 'renders IssuableTimeTracker with correct spent and estimated time (timeTrackingLimitToHours=%s)', + (timeTrackingLimitToHours) => { + createComponent({ provide: { timeTrackingLimitToHours } }); + + expect(wrapper.find(IssuableTimeTracker).props()).toEqual({ + timeEstimate: 3600, + timeSpent: 1800, + humanTimeEstimate: '1h', + humanTimeSpent: '30min', + limitToHours: timeTrackingLimitToHours, + showCollapsed: false, + }); + }, + ); +}); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js index 723d0345f76..c8ccd4c88a5 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js @@ -1,11 +1,11 @@ import { GlAlert, GlFormInput, GlForm } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; -import BoardSidebarIssueTitle from '~/boards/components/sidebar/board_sidebar_issue_title.vue'; +import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue'; import { createStore } from '~/boards/stores'; import createFlash from '~/flash'; -const TEST_TITLE = 'New issue title'; +const TEST_TITLE = 'New item title'; const TEST_ISSUE_A = { id: 'gid://gitlab/Issue/1', iid: 8, @@ -21,7 +21,7 @@ const TEST_ISSUE_B = { jest.mock('~/flash'); -describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { +describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { let wrapper; let store; @@ -32,12 +32,12 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { wrapper = null; }); - const createWrapper = (issue = TEST_ISSUE_A) => { + const createWrapper = (item = TEST_ISSUE_A) => { store = createStore(); - store.state.boardItems = { [issue.id]: { ...issue } }; - store.dispatch('setActiveId', { id: issue.id }); + store.state.boardItems = { [item.id]: { ...item } }; + store.dispatch('setActiveId', { id: item.id }); - wrapper = shallowMount(BoardSidebarIssueTitle, { + wrapper = shallowMount(BoardSidebarTitle, { store, provide: { canUpdate: true, @@ -53,7 +53,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { const findFormInput = () => wrapper.find(GlFormInput); const findEditableItem = () => wrapper.find(BoardEditableItem); const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]'); - const findTitle = () => wrapper.find('[data-testid="issue-title"]'); + const findTitle = () => wrapper.find('[data-testid="item-title"]'); const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]'); it('renders title and reference', () => { @@ -73,7 +73,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { beforeEach(async () => { createWrapper(); - jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => { + jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => { store.state.boardItems[TEST_ISSUE_A.id].title = TEST_TITLE; }); findFormInput().vm.$emit('input', TEST_TITLE); @@ -87,7 +87,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { }); it('commits change to the server', () => { - expect(wrapper.vm.setActiveIssueTitle).toHaveBeenCalledWith({ + expect(wrapper.vm.setActiveItemTitle).toHaveBeenCalledWith({ title: TEST_TITLE, projectPath: 'h/b', }); @@ -98,14 +98,14 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { beforeEach(async () => { createWrapper(); - jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => {}); + jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => {}); findFormInput().vm.$emit('input', ''); findForm().vm.$emit('submit', { preventDefault: () => {} }); await wrapper.vm.$nextTick(); }); it('commits change to the server', () => { - expect(wrapper.vm.setActiveIssueTitle).not.toHaveBeenCalled(); + expect(wrapper.vm.setActiveItemTitle).not.toHaveBeenCalled(); }); }); @@ -122,7 +122,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { it('does not collapses sidebar and shows alert', () => { expect(findCollapsed().isVisible()).toBe(false); expect(findAlert().exists()).toBe(true); - expect(localStorage.getItem(`${TEST_ISSUE_A.id}/issue-title-pending-changes`)).toBe( + expect(localStorage.getItem(`${TEST_ISSUE_A.id}/item-title-pending-changes`)).toBe( TEST_TITLE, ); }); @@ -130,7 +130,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { describe('when accessing the form with pending changes', () => { beforeAll(() => { - localStorage.setItem(`${TEST_ISSUE_A.id}/issue-title-pending-changes`, TEST_TITLE); + localStorage.setItem(`${TEST_ISSUE_A.id}/item-title-pending-changes`, TEST_TITLE); createWrapper(); }); @@ -146,7 +146,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { beforeEach(async () => { createWrapper(TEST_ISSUE_B); - jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => { + jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => { store.state.boardItems[TEST_ISSUE_B.id].title = TEST_TITLE; }); findFormInput().vm.$emit('input', TEST_TITLE); @@ -155,7 +155,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { }); it('collapses sidebar and render former title', () => { - expect(wrapper.vm.setActiveIssueTitle).not.toHaveBeenCalled(); + expect(wrapper.vm.setActiveItemTitle).not.toHaveBeenCalled(); expect(findCollapsed().isVisible()).toBe(true); expect(findTitle().text()).toBe(TEST_ISSUE_B.title); }); @@ -165,7 +165,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { beforeEach(async () => { createWrapper(TEST_ISSUE_B); - jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => { + jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => { throw new Error(['failed mutation']); }); findFormInput().vm.$emit('input', 'Invalid title'); @@ -173,7 +173,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => { await wrapper.vm.$nextTick(); }); - it('collapses sidebar and renders former issue title', () => { + it('collapses sidebar and renders former item title', () => { expect(findCollapsed().isVisible()).toBe(true); expect(findTitle().text()).toContain(TEST_ISSUE_B.title); expect(createFlash).toHaveBeenCalled(); |