diff options
Diffstat (limited to 'spec/frontend/boards')
19 files changed, 325 insertions, 514 deletions
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js index 36043b09636..15ea5d4eec4 100644 --- a/spec/frontend/boards/board_card_inner_spec.js +++ b/spec/frontend/boards/board_card_inner_spec.js @@ -1,4 +1,4 @@ -import { GlLabel } from '@gitlab/ui'; +import { GlLabel, GlLoadingIcon } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { range } from 'lodash'; import Vuex from 'vuex'; @@ -63,6 +63,7 @@ describe('Board card component', () => { }, stubs: { GlLabel: true, + GlLoadingIcon: true, }, mocks: { $apollo: { @@ -121,6 +122,10 @@ describe('Board card component', () => { expect(wrapper.find('.board-card-assignee .avatar').exists()).toBe(false); }); + it('does not render loading icon', () => { + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); + }); + describe('blocked', () => { it('renders blocked icon if issue is blocked', async () => { createWrapper({ @@ -399,4 +404,17 @@ describe('Board card component', () => { }); }); }); + + describe('loading', () => { + it('renders loading icon', async () => { + createWrapper({ + item: { + ...issue, + isLoading: true, + }, + }); + + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + }); + }); }); diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js index bf39c3f3e42..76629c96f22 100644 --- a/spec/frontend/boards/board_list_spec.js +++ b/spec/frontend/boards/board_list_spec.js @@ -80,6 +80,7 @@ const createComponent = ({ rootPath: '/', weightFeatureAvailable: false, boardWeight: null, + canAdminList: true, }, stubs: { BoardCard, @@ -181,12 +182,6 @@ describe('Board list component', () => { }); }); - it('loads more issues after scrolling', () => { - wrapper.vm.listRef.dispatchEvent(new Event('scroll')); - - expect(actions.fetchItemsForList).toHaveBeenCalled(); - }); - it('does not load issues if already loading', () => { wrapper = createComponent({ state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } }, diff --git a/spec/frontend/boards/boards_util_spec.js b/spec/frontend/boards/boards_util_spec.js index 0feb1411003..289905a1948 100644 --- a/spec/frontend/boards/boards_util_spec.js +++ b/spec/frontend/boards/boards_util_spec.js @@ -1,17 +1,103 @@ -import { transformNotFilters } from '~/boards/boards_util'; +import { filterVariables } from '~/boards/boards_util'; -describe('transformNotFilters', () => { - const filters = { - 'not[labelName]': ['label'], - 'not[assigneeUsername]': 'assignee', - }; - - it('formats not filters, transforms epicId to fullEpicId', () => { - const result = transformNotFilters(filters); - - expect(result).toEqual({ - labelName: ['label'], - assigneeUsername: 'assignee', +describe('filterVariables', () => { + it.each([ + [ + 'correctly processes array filter values', + { + filters: { + 'not[filterA]': ['val1', 'val2'], + }, + expected: { + not: { + filterA: ['val1', 'val2'], + }, + }, + }, + ], + [ + "renames a filter if 'remap' method is available", + { + filters: { + filterD: 'some value', + }, + expected: { + filterA: 'some value', + not: {}, + }, + }, + ], + [ + 'correctly processes a negated filter that supports negation', + { + filters: { + 'not[filterA]': 'some value 1', + 'not[filterB]': 'some value 2', + }, + expected: { + not: { + filterA: 'some value 1', + }, + }, + }, + ], + [ + 'correctly removes an unsupported filter depending on issuableType', + { + issuableType: 'epic', + filters: { + filterA: 'some value 1', + filterE: 'some value 2', + }, + expected: { + filterE: 'some value 2', + not: {}, + }, + }, + ], + [ + 'applies a transform when the filter value needs to be modified', + { + filters: { + filterC: 'abc', + 'not[filterC]': 'def', + }, + expected: { + filterC: 'ABC', + not: { + filterC: 'DEF', + }, + }, + }, + ], + ])('%s', (_, { filters, issuableType = 'issue', expected }) => { + const result = filterVariables({ + filters, + issuableType, + filterInfo: { + filterA: { + negatedSupport: true, + }, + filterB: { + negatedSupport: false, + }, + filterC: { + negatedSupport: true, + transform: (val) => val.toUpperCase(), + }, + filterD: { + remap: () => 'filterA', + }, + filterE: { + negatedSupport: true, + }, + }, + filterFields: { + issue: ['filterA', 'filterB', 'filterC', 'filterD'], + epic: ['filterE'], + }, }); + + expect(result).toEqual(expected); }); }); 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 index c000f300e4d..3fb0706fd10 100644 --- a/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap +++ b/spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap @@ -1,7 +1,7 @@ // 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\\"> +"<div class=\\"gl-display-inline\\"><svg data-testid=\\"issue-blocked-icon\\" role=\\"img\\" 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\\"> diff --git a/spec/frontend/boards/components/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js index ceafa6ead94..9a9ce7b8dc1 100644 --- a/spec/frontend/boards/components/board_card_spec.js +++ b/spec/frontend/boards/components/board_card_spec.js @@ -1,5 +1,6 @@ import { GlLabel } from '@gitlab/ui'; -import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; +import { shallowMount, mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import BoardCard from '~/boards/components/board_card.vue'; @@ -12,8 +13,7 @@ describe('Board card', () => { let store; let mockActions; - const localVue = createLocalVue(); - localVue.use(Vuex); + Vue.use(Vuex); const createStore = ({ initialState = {} } = {}) => { mockActions = { @@ -41,14 +41,14 @@ describe('Board card', () => { provide = {}, mountFn = shallowMount, stubs = { BoardCardInner }, + item = mockIssue, } = {}) => { wrapper = mountFn(BoardCard, { - localVue, stubs, store, propsData: { list: mockLabelList, - item: mockIssue, + item, disabled: false, index: 0, ...propsData, @@ -72,6 +72,10 @@ describe('Board card', () => { await wrapper.vm.$nextTick(); }; + beforeEach(() => { + window.gon = { features: {} }; + }); + afterEach(() => { wrapper.destroy(); wrapper = null; @@ -140,6 +144,10 @@ describe('Board card', () => { }); describe('when using multi-select', () => { + beforeEach(() => { + window.gon = { features: { boardMultiSelect: true } }; + }); + it('should call vuex action "multiSelectBoardItem" with correct parameters', async () => { await multiSelectCard(); @@ -151,4 +159,24 @@ describe('Board card', () => { }); }); }); + + describe('when card is loading', () => { + it('card is disabled and user cannot drag', () => { + createStore(); + mountComponent({ item: { ...mockIssue, isLoading: true } }); + + expect(wrapper.classes()).toContain('is-disabled'); + expect(wrapper.classes()).not.toContain('user-can-drag'); + }); + }); + + describe('when card is not loading', () => { + it('user can drag', () => { + createStore(); + mountComponent(); + + expect(wrapper.classes()).not.toContain('is-disabled'); + expect(wrapper.classes()).toContain('user-can-drag'); + }); + }); }); diff --git a/spec/frontend/boards/components/board_content_sidebar_spec.js b/spec/frontend/boards/components/board_content_sidebar_spec.js index 01c99a02db2..10d739c65f5 100644 --- a/spec/frontend/boards/components/board_content_sidebar_spec.js +++ b/spec/frontend/boards/components/board_content_sidebar_spec.js @@ -1,13 +1,13 @@ import { GlDrawer } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; +import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue'; 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 BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue'; import { ISSUABLE } from '~/boards/constants'; +import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue'; import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue'; import { mockIssue, mockIssueGroupPath, mockIssueProjectPath } from '../mock_data'; @@ -68,6 +68,9 @@ describe('BoardContentSidebar', () => { iterations: { loading: false, }, + attributesList: { + loading: false, + }, }, }, }, @@ -84,38 +87,41 @@ describe('BoardContentSidebar', () => { }); it('confirms we render GlDrawer', () => { - expect(wrapper.find(GlDrawer).exists()).toBe(true); + expect(wrapper.findComponent(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); + expect(wrapper.findComponent(GlDrawer).exists()).toBe(false); }); it('applies an open attribute', () => { - expect(wrapper.find(GlDrawer).props('open')).toBe(true); + expect(wrapper.findComponent(GlDrawer).props('open')).toBe(true); }); it('renders BoardSidebarLabelsSelect', () => { - expect(wrapper.find(BoardSidebarLabelsSelect).exists()).toBe(true); + expect(wrapper.findComponent(BoardSidebarLabelsSelect).exists()).toBe(true); }); it('renders BoardSidebarTitle', () => { - expect(wrapper.find(BoardSidebarTitle).exists()).toBe(true); + expect(wrapper.findComponent(BoardSidebarTitle).exists()).toBe(true); }); - it('renders BoardSidebarDueDate', () => { - expect(wrapper.find(BoardSidebarDueDate).exists()).toBe(true); + it('renders SidebarDateWidget', () => { + expect(wrapper.findComponent(SidebarDateWidget).exists()).toBe(true); }); it('renders BoardSidebarSubscription', () => { - expect(wrapper.find(SidebarSubscriptionsWidget).exists()).toBe(true); + expect(wrapper.findComponent(SidebarSubscriptionsWidget).exists()).toBe(true); }); - it('renders BoardSidebarMilestoneSelect', () => { - expect(wrapper.find(BoardSidebarMilestoneSelect).exists()).toBe(true); + it('renders SidebarDropdownWidget for milestones', () => { + expect(wrapper.findComponent(SidebarDropdownWidget).exists()).toBe(true); + expect(wrapper.findComponent(SidebarDropdownWidget).props('issuableAttribute')).toEqual( + 'milestone', + ); }); describe('when we emit close', () => { @@ -128,7 +134,7 @@ describe('BoardContentSidebar', () => { }); it('calls toggleBoardItem with correct parameters', async () => { - wrapper.find(GlDrawer).vm.$emit('close'); + wrapper.findComponent(GlDrawer).vm.$emit('close'); expect(toggleBoardItem).toHaveBeenCalledTimes(1); expect(toggleBoardItem).toHaveBeenCalledWith(expect.any(Object), { diff --git a/spec/frontend/boards/components/board_filtered_search_spec.js b/spec/frontend/boards/components/board_filtered_search_spec.js index e27badca9de..6ac5d16e5a3 100644 --- a/spec/frontend/boards/components/board_filtered_search_spec.js +++ b/spec/frontend/boards/components/board_filtered_search_spec.js @@ -105,9 +105,9 @@ describe('BoardFilteredSearch', () => { beforeEach(() => { store = createStore(); - jest.spyOn(store, 'dispatch'); - createComponent(); + + jest.spyOn(wrapper.vm, 'performSearch').mockImplementation(); }); it('sets the url params to the correct results', async () => { diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js index 24fcdd528d5..80d740458dc 100644 --- a/spec/frontend/boards/components/board_form_spec.js +++ b/spec/frontend/boards/components/board_form_spec.js @@ -9,14 +9,12 @@ import createBoardMutation from '~/boards/graphql/board_create.mutation.graphql' import destroyBoardMutation from '~/boards/graphql/board_destroy.mutation.graphql'; import updateBoardMutation from '~/boards/graphql/board_update.mutation.graphql'; import { createStore } from '~/boards/stores'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; import { visitUrl } from '~/lib/utils/url_utility'; jest.mock('~/lib/utils/url_utility', () => ({ visitUrl: jest.fn().mockName('visitUrlMock'), stripFinalUrlSegment: jest.requireActual('~/lib/utils/url_utility').stripFinalUrlSegment, })); -jest.mock('~/flash'); const currentBoard = { id: 1, @@ -194,9 +192,11 @@ describe('BoardForm', () => { expect(visitUrl).toHaveBeenCalledWith('test-path'); }); - it('shows an error flash if GraphQL mutation fails', async () => { + it('shows a GlAlert if GraphQL mutation fails', async () => { mutate = jest.fn().mockRejectedValue('Houston, we have a problem'); createComponent({ canAdminBoard: true, currentPage: formType.new }); + jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {}); + fillForm(); await waitForPromises(); @@ -205,7 +205,7 @@ describe('BoardForm', () => { await waitForPromises(); expect(visitUrl).not.toHaveBeenCalled(); - expect(createFlash).toHaveBeenCalled(); + expect(wrapper.vm.setError).toHaveBeenCalled(); }); }); }); @@ -290,9 +290,11 @@ describe('BoardForm', () => { expect(visitUrl).toHaveBeenCalledWith('test-path?group_by=epic'); }); - it('shows an error flash if GraphQL mutation fails', async () => { + it('shows a GlAlert if GraphQL mutation fails', async () => { mutate = jest.fn().mockRejectedValue('Houston, we have a problem'); createComponent({ canAdminBoard: true, currentPage: formType.edit }); + jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {}); + findInput().trigger('keyup.enter', { metaKey: true }); await waitForPromises(); @@ -301,7 +303,7 @@ describe('BoardForm', () => { await waitForPromises(); expect(visitUrl).not.toHaveBeenCalled(); - expect(createFlash).toHaveBeenCalled(); + expect(wrapper.vm.setError).toHaveBeenCalled(); }); }); @@ -335,9 +337,11 @@ describe('BoardForm', () => { expect(visitUrl).toHaveBeenCalledWith('root'); }); - it('shows an error flash if GraphQL mutation fails', async () => { + it('dispatches `setError` action when GraphQL mutation fails', async () => { mutate = jest.fn().mockRejectedValue('Houston, we have a problem'); createComponent({ canAdminBoard: true, currentPage: formType.delete }); + jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {}); + findModal().vm.$emit('primary'); await waitForPromises(); @@ -346,7 +350,7 @@ describe('BoardForm', () => { await waitForPromises(); expect(visitUrl).not.toHaveBeenCalled(); - expect(createFlash).toHaveBeenCalled(); + expect(wrapper.vm.setError).toHaveBeenCalled(); }); }); }); diff --git a/spec/frontend/boards/components/board_list_header_deprecated_spec.js b/spec/frontend/boards/components/board_list_header_deprecated_spec.js index fdc7cd2b1d4..db79e67fe78 100644 --- a/spec/frontend/boards/components/board_list_header_deprecated_spec.js +++ b/spec/frontend/boards/components/board_list_header_deprecated_spec.js @@ -31,6 +31,7 @@ describe('Board List Header Component', () => { listType = ListType.backlog, collapsed = false, withLocalStorage = true, + currentUserId = 1, } = {}) => { const boardId = '1'; @@ -62,6 +63,7 @@ describe('Board List Header Component', () => { }, provide: { boardId, + currentUserId, }, }); }; @@ -100,10 +102,12 @@ describe('Board List Header Component', () => { }); }); - it('does render when logged out', () => { - createComponent(); + it('does not render when logged out', () => { + createComponent({ + currentUserId: null, + }); - expect(findAddIssueButton().exists()).toBe(true); + expect(findAddIssueButton().exists()).toBe(false); }); }); @@ -143,7 +147,6 @@ describe('Board List Header Component', () => { it("when logged in it calls list update and doesn't set localStorage", () => { jest.spyOn(List.prototype, 'update'); - window.gon.current_user_id = 1; createComponent({ withLocalStorage: false }); @@ -158,7 +161,7 @@ describe('Board List Header Component', () => { it("when logged out it doesn't call list update and sets localStorage", () => { jest.spyOn(List.prototype, 'update'); - createComponent(); + createComponent({ currentUserId: null }); findCaret().vm.$emit('click'); diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js index d2dfb4148b3..0abb00e0fa5 100644 --- a/spec/frontend/boards/components/board_list_header_spec.js +++ b/spec/frontend/boards/components/board_list_header_spec.js @@ -28,7 +28,7 @@ describe('Board List Header Component', () => { listType = ListType.backlog, collapsed = false, withLocalStorage = true, - currentUserId = null, + currentUserId = 1, } = {}) => { const boardId = '1'; @@ -109,10 +109,12 @@ describe('Board List Header Component', () => { }); }); - it('does render when logged out', () => { - createComponent(); + it('does not render when logged out', () => { + createComponent({ + currentUserId: null, + }); - expect(findAddIssueButton().exists()).toBe(true); + expect(findAddIssueButton().exists()).toBe(false); }); }); @@ -153,7 +155,9 @@ describe('Board List Header Component', () => { }); it("when logged out it doesn't call list update and sets localStorage", async () => { - createComponent(); + createComponent({ + currentUserId: null, + }); findCaret().vm.$emit('click'); await wrapper.vm.$nextTick(); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js deleted file mode 100644 index 8fd178a0856..00000000000 --- a/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js +++ /dev/null @@ -1,137 +0,0 @@ -import { GlDatepicker } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; -import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue'; -import { createStore } from '~/boards/stores'; -import createFlash from '~/flash'; - -const TEST_DUE_DATE = '2020-02-20'; -const TEST_FORMATTED_DUE_DATE = 'Feb 20, 2020'; -const TEST_PARSED_DATE = new Date(2020, 1, 20); -const TEST_ISSUE = { id: 'gid://gitlab/Issue/1', iid: 9, dueDate: null, referencePath: 'h/b#2' }; - -jest.mock('~/flash'); - -describe('~/boards/components/sidebar/board_sidebar_due_date.vue', () => { - let wrapper; - let store; - - afterEach(() => { - wrapper.destroy(); - store = null; - wrapper = null; - }); - - const createWrapper = ({ dueDate = null } = {}) => { - store = createStore(); - store.state.boardItems = { [TEST_ISSUE.id]: { ...TEST_ISSUE, dueDate } }; - store.state.activeId = TEST_ISSUE.id; - - wrapper = shallowMount(BoardSidebarDueDate, { - store, - provide: { - canUpdate: true, - }, - stubs: { - 'board-editable-item': BoardEditableItem, - }, - }); - }; - - const findDatePicker = () => wrapper.find(GlDatepicker); - const findResetButton = () => wrapper.find('[data-testid="reset-button"]'); - const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]'); - - it('renders "None" when no due date is set', () => { - createWrapper(); - - expect(findCollapsed().text()).toBe('None'); - expect(findResetButton().exists()).toBe(false); - }); - - it('renders formatted due date with reset button when set', () => { - createWrapper({ dueDate: TEST_DUE_DATE }); - - expect(findCollapsed().text()).toContain(TEST_FORMATTED_DUE_DATE); - expect(findResetButton().exists()).toBe(true); - }); - - describe('when due date is submitted', () => { - beforeEach(async () => { - createWrapper(); - - jest.spyOn(wrapper.vm, 'setActiveIssueDueDate').mockImplementation(() => { - store.state.boardItems[TEST_ISSUE.id].dueDate = TEST_DUE_DATE; - }); - findDatePicker().vm.$emit('input', TEST_PARSED_DATE); - await wrapper.vm.$nextTick(); - }); - - it('collapses sidebar and renders formatted due date with reset button', () => { - expect(findCollapsed().isVisible()).toBe(true); - expect(findCollapsed().text()).toContain(TEST_FORMATTED_DUE_DATE); - expect(findResetButton().exists()).toBe(true); - }); - - it('commits change to the server', () => { - expect(wrapper.vm.setActiveIssueDueDate).toHaveBeenCalledWith({ - dueDate: TEST_DUE_DATE, - projectPath: 'h/b', - }); - }); - }); - - describe('when due date is cleared', () => { - beforeEach(async () => { - createWrapper(); - - jest.spyOn(wrapper.vm, 'setActiveIssueDueDate').mockImplementation(() => { - store.state.boardItems[TEST_ISSUE.id].dueDate = null; - }); - findDatePicker().vm.$emit('clear'); - await wrapper.vm.$nextTick(); - }); - - it('collapses sidebar and renders "None"', () => { - expect(wrapper.vm.setActiveIssueDueDate).toHaveBeenCalled(); - expect(findCollapsed().isVisible()).toBe(true); - expect(findCollapsed().text()).toBe('None'); - }); - }); - - describe('when due date is resetted', () => { - beforeEach(async () => { - createWrapper({ dueDate: TEST_DUE_DATE }); - - jest.spyOn(wrapper.vm, 'setActiveIssueDueDate').mockImplementation(() => { - store.state.boardItems[TEST_ISSUE.id].dueDate = null; - }); - findResetButton().vm.$emit('click'); - await wrapper.vm.$nextTick(); - }); - - it('collapses sidebar and renders "None"', () => { - expect(wrapper.vm.setActiveIssueDueDate).toHaveBeenCalled(); - expect(findCollapsed().isVisible()).toBe(true); - expect(findCollapsed().text()).toBe('None'); - }); - }); - - describe('when the mutation fails', () => { - beforeEach(async () => { - createWrapper({ dueDate: TEST_DUE_DATE }); - - jest.spyOn(wrapper.vm, 'setActiveIssueDueDate').mockImplementation(() => { - throw new Error(['failed mutation']); - }); - findDatePicker().vm.$emit('input', 'Invalid date'); - await wrapper.vm.$nextTick(); - }); - - it('collapses sidebar and renders former issue due date', () => { - expect(findCollapsed().isVisible()).toBe(true); - expect(findCollapsed().text()).toContain(TEST_FORMATTED_DUE_DATE); - expect(createFlash).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 ad682774ee6..8992a5780f3 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 @@ -9,11 +9,8 @@ import { import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue'; import { createStore } from '~/boards/stores'; -import createFlash from '~/flash'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -jest.mock('~/flash'); - const TEST_LABELS_PAYLOAD = TEST_LABELS.map((label) => ({ ...label, set: true })); const TEST_LABELS_TITLES = TEST_LABELS.map((label) => label.title); @@ -154,6 +151,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => { jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => { throw new Error(['failed mutation']); }); + jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {}); findLabelsSelect().vm.$emit('updateSelectedLabels', [{ id: '?' }]); await wrapper.vm.$nextTick(); }); @@ -161,7 +159,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => { it('collapses sidebar and renders former issue weight', () => { expect(findCollapsed().isVisible()).toBe(true); expect(findLabelsTitles()).toEqual(TEST_LABELS_TITLES); - expect(createFlash).toHaveBeenCalled(); + expect(wrapper.vm.setError).toHaveBeenCalled(); }); }); }); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js deleted file mode 100644 index 8706424a296..00000000000 --- a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js +++ /dev/null @@ -1,178 +0,0 @@ -import { GlLoadingIcon, GlDropdown } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { mockMilestone as TEST_MILESTONE } from 'jest/boards/mock_data'; -import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; -import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue'; -import { createStore } from '~/boards/stores'; -import createFlash from '~/flash'; - -const TEST_ISSUE = { id: 'gid://gitlab/Issue/1', iid: 9, referencePath: 'h/b#2' }; - -jest.mock('~/flash'); - -describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () => { - let wrapper; - let store; - - afterEach(() => { - wrapper.destroy(); - store = null; - wrapper = null; - }); - - const createWrapper = ({ milestone = null, loading = false } = {}) => { - store = createStore(); - store.state.boardItems = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } }; - store.state.activeId = TEST_ISSUE.id; - - wrapper = shallowMount(BoardSidebarMilestoneSelect, { - store, - provide: { - canUpdate: true, - }, - data: () => ({ - milestones: [TEST_MILESTONE], - }), - stubs: { - 'board-editable-item': BoardEditableItem, - }, - mocks: { - $apollo: { - loading, - }, - }, - }); - }; - - const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]'); - const findLoader = () => wrapper.find(GlLoadingIcon); - const findDropdown = () => wrapper.find(GlDropdown); - const findBoardEditableItem = () => wrapper.find(BoardEditableItem); - const findDropdownItem = () => wrapper.find('[data-testid="milestone-item"]'); - const findUnsetMilestoneItem = () => wrapper.find('[data-testid="no-milestone-item"]'); - const findNoMilestonesFoundItem = () => wrapper.find('[data-testid="no-milestones-found"]'); - - describe('when not editing', () => { - it('opens the milestone dropdown on clicking edit', async () => { - createWrapper(); - wrapper.vm.$refs.dropdown.show = jest.fn(); - - await findBoardEditableItem().vm.$emit('open'); - - expect(wrapper.vm.$refs.dropdown.show).toHaveBeenCalledTimes(1); - }); - }); - - describe('when editing', () => { - beforeEach(() => { - createWrapper(); - jest.spyOn(wrapper.vm.$refs.sidebarItem, 'collapse'); - }); - - it('collapses BoardEditableItem on clicking edit', async () => { - await findBoardEditableItem().vm.$emit('close'); - - expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1); - }); - - it('collapses BoardEditableItem on hiding dropdown', async () => { - await findDropdown().vm.$emit('hide'); - - expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1); - }); - }); - - it('renders "None" when no milestone is selected', () => { - createWrapper(); - - expect(findCollapsed().text()).toBe('None'); - }); - - it('renders milestone title when set', () => { - createWrapper({ milestone: TEST_MILESTONE }); - - expect(findCollapsed().text()).toContain(TEST_MILESTONE.title); - }); - - it('shows loader while Apollo is loading', async () => { - createWrapper({ milestone: TEST_MILESTONE, loading: true }); - - expect(findLoader().exists()).toBe(true); - }); - - it('shows message when error or no milestones found', async () => { - createWrapper(); - - await wrapper.setData({ milestones: [] }); - - expect(findNoMilestonesFoundItem().text()).toBe('No milestones found'); - }); - - describe('when milestone is selected', () => { - beforeEach(async () => { - createWrapper(); - - jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => { - store.state.boardItems[TEST_ISSUE.id].milestone = TEST_MILESTONE; - }); - findDropdownItem().vm.$emit('click'); - await wrapper.vm.$nextTick(); - }); - - it('collapses sidebar and renders selected milestone', () => { - expect(findCollapsed().isVisible()).toBe(true); - expect(findCollapsed().text()).toContain(TEST_MILESTONE.title); - }); - - it('commits change to the server', () => { - expect(wrapper.vm.setActiveIssueMilestone).toHaveBeenCalledWith({ - milestoneId: TEST_MILESTONE.id, - projectPath: 'h/b', - }); - }); - }); - - describe('when milestone is set to "None"', () => { - beforeEach(async () => { - createWrapper({ milestone: TEST_MILESTONE }); - - jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => { - store.state.boardItems[TEST_ISSUE.id].milestone = null; - }); - findUnsetMilestoneItem().vm.$emit('click'); - await wrapper.vm.$nextTick(); - }); - - it('collapses sidebar and renders "None"', () => { - expect(findCollapsed().isVisible()).toBe(true); - expect(findCollapsed().text()).toBe('None'); - }); - - it('commits change to the server', () => { - expect(wrapper.vm.setActiveIssueMilestone).toHaveBeenCalledWith({ - milestoneId: null, - projectPath: 'h/b', - }); - }); - }); - - describe('when the mutation fails', () => { - const testMilestone = { id: '1', title: 'Former milestone' }; - - beforeEach(async () => { - createWrapper({ milestone: testMilestone }); - - jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => { - throw new Error(['failed mutation']); - }); - findDropdownItem().vm.$emit('click'); - await wrapper.vm.$nextTick(); - }); - - it('collapses sidebar and renders former milestone', () => { - expect(findCollapsed().isVisible()).toBe(true); - expect(findCollapsed().text()).toContain(testMilestone.title); - expect(createFlash).toHaveBeenCalled(); - }); - }); -}); 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 7976e73ff2f..8847f626c1f 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js @@ -5,11 +5,8 @@ import Vuex from 'vuex'; import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue'; import { createStore } from '~/boards/stores'; import * as types from '~/boards/stores/mutation_types'; -import createFlash from '~/flash'; import { mockActiveIssue } from '../../mock_data'; -jest.mock('~/flash.js'); - Vue.use(Vuex); describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () => { @@ -153,13 +150,15 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = jest.spyOn(wrapper.vm, 'setActiveItemSubscribed').mockImplementation(async () => { throw new Error(); }); + jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {}); findToggle().trigger('click'); await wrapper.vm.$nextTick(); - expect(createFlash).toHaveBeenNthCalledWith(1, { - message: wrapper.vm.$options.i18n.updateSubscribedErrorMessage, - }); + expect(wrapper.vm.setError).toHaveBeenCalled(); + expect(wrapper.vm.setError.mock.calls[0][0].message).toBe( + wrapper.vm.$options.i18n.updateSubscribedErrorMessage, + ); }); }); }); 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 index 03924bfa8d3..74441e147cf 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js @@ -26,6 +26,7 @@ describe('BoardSidebarTimeTracker', () => { store = createStore(); store.state.boardItems = { 1: { + iid: 1, timeEstimate: 3600, totalTimeSpent: 1800, humanTimeEstimate: '1h', @@ -46,12 +47,16 @@ describe('BoardSidebarTimeTracker', () => { createComponent({ provide: { timeTrackingLimitToHours } }); expect(wrapper.find(IssuableTimeTracker).props()).toEqual({ - timeEstimate: 3600, - timeSpent: 1800, - humanTimeEstimate: '1h', - humanTimeSpent: '30min', limitToHours: timeTrackingLimitToHours, showCollapsed: false, + issuableIid: '1', + fullPath: '', + initialTimeTracking: { + timeEstimate: 3600, + totalTimeSpent: 1800, + humanTimeEstimate: '1h', + humanTotalTimeSpent: '30min', + }, }); }, ); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js index c8ccd4c88a5..4a8eda298f2 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js @@ -3,7 +3,6 @@ import { shallowMount } from '@vue/test-utils'; import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue'; import { createStore } from '~/boards/stores'; -import createFlash from '~/flash'; const TEST_TITLE = 'New item title'; const TEST_ISSUE_A = { @@ -19,8 +18,6 @@ const TEST_ISSUE_B = { referencePath: 'h/b#2', }; -jest.mock('~/flash'); - describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { let wrapper; let store; @@ -168,6 +165,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => { throw new Error(['failed mutation']); }); + jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {}); findFormInput().vm.$emit('input', 'Invalid title'); findForm().vm.$emit('submit', { preventDefault: () => {} }); await wrapper.vm.$nextTick(); @@ -176,7 +174,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { it('collapses sidebar and renders former item title', () => { expect(findCollapsed().isVisible()).toBe(true); expect(findTitle().text()).toContain(TEST_ISSUE_B.title); - expect(createFlash).toHaveBeenCalled(); + expect(wrapper.vm.setError).toHaveBeenCalled(); }); }); }); diff --git a/spec/frontend/boards/project_select_deprecated_spec.js b/spec/frontend/boards/project_select_deprecated_spec.js index 37f519ef5b9..4494de43083 100644 --- a/spec/frontend/boards/project_select_deprecated_spec.js +++ b/spec/frontend/boards/project_select_deprecated_spec.js @@ -5,7 +5,7 @@ import AxiosMockAdapter from 'axios-mock-adapter'; import ProjectSelect from '~/boards/components/project_select_deprecated.vue'; import { ListType } from '~/boards/constants'; import eventHub from '~/boards/eventhub'; -import { deprecatedCreateFlash as flash } from '~/flash'; +import createFlash from '~/flash'; import httpStatus from '~/lib/utils/http_status'; import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; @@ -237,8 +237,10 @@ describe('ProjectSelect component', () => { await searchForProject('foobar'); - expect(flash).toHaveBeenCalledTimes(1); - expect(flash).toHaveBeenCalledWith('Something went wrong while fetching projects'); + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith({ + message: 'Something went wrong while fetching projects', + }); }); describe('with non-empty search result', () => { diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 09343b5704f..b28412f2127 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -15,6 +15,7 @@ import { formatIssueInput, formatIssue, getMoveData, + updateListPosition, } from '~/boards/boards_util'; import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql'; import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql'; @@ -29,13 +30,13 @@ import { mockIssue2, rawIssue, mockIssues, - mockMilestone, labels, mockActiveIssue, mockGroupProjects, mockMoveIssueParams, mockMoveState, mockMoveData, + mockList, } from '../mock_data'; jest.mock('~/flash'); @@ -70,27 +71,28 @@ describe('setFilters', () => { [ 'with correct filters as payload', { - filters: { labelName: 'label' }, - updatedFilters: { labelName: 'label', not: {} }, + filters: { labelName: 'label', foobar: 'not-a-filter', search: 'quick brown fox' }, + filterVariables: { labelName: 'label', search: 'quick brown fox', not: {} }, }, ], [ - 'and updates assigneeWildcardId', + "and use 'assigneeWildcardId' as filter variable for 'assigneId' param", { filters: { assigneeId: 'None' }, - updatedFilters: { assigneeWildcardId: 'NONE', not: {} }, + filterVariables: { assigneeWildcardId: 'NONE', not: {} }, }, ], - ])('should commit mutation SET_FILTERS %s', (_, { filters, updatedFilters }) => { + ])('should commit mutation SET_FILTERS %s', (_, { filters, filterVariables }) => { const state = { filters: {}, + issuableType: issuableTypes.issue, }; testAction( actions.setFilters, filters, state, - [{ type: types.SET_FILTERS, payload: updatedFilters }], + [{ type: types.SET_FILTERS, payload: filterVariables }], [], ); }); @@ -373,6 +375,24 @@ describe('createIssueList', () => { }); }); +describe('addList', () => { + const getters = { + getListByTitle: jest.fn().mockReturnValue(mockList), + }; + + it('should commit RECEIVE_ADD_LIST_SUCCESS mutation and dispatch fetchItemsForList action', () => { + testAction({ + action: actions.addList, + payload: mockLists[1], + state: { ...getters }, + expectedMutations: [ + { type: types.RECEIVE_ADD_LIST_SUCCESS, payload: updateListPosition(mockLists[1]) }, + ], + expectedActions: [{ type: 'fetchItemsForList', payload: { listId: mockList.id } }], + }); + }); +}); + describe('fetchLabels', () => { it('should commit mutation RECEIVE_LABELS_SUCCESS on success', async () => { const queryResponse = { @@ -520,7 +540,8 @@ describe('toggleListCollapsed', () => { describe('removeList', () => { let state; - const list = mockLists[0]; + let getters; + const list = mockLists[1]; const listId = list.id; const mutationVariables = { mutation: destroyBoardListMutation, @@ -534,6 +555,9 @@ describe('removeList', () => { boardLists: mockListsById, issuableType: issuableTypes.issue, }; + getters = { + getListByTitle: jest.fn().mockReturnValue(mockList), + }; }); afterEach(() => { @@ -543,13 +567,15 @@ describe('removeList', () => { it('optimistically deletes the list', () => { const commit = jest.fn(); - actions.removeList({ commit, state }, listId); + actions.removeList({ commit, state, getters, dispatch: () => {} }, listId); expect(commit.mock.calls).toEqual([[types.REMOVE_LIST, listId]]); }); it('keeps the updated list if remove succeeds', async () => { const commit = jest.fn(); + const dispatch = jest.fn(); + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ data: { destroyBoardList: { @@ -558,17 +584,18 @@ describe('removeList', () => { }, }); - await actions.removeList({ commit, state }, listId); + await actions.removeList({ commit, state, getters, dispatch }, listId); expect(gqlClient.mutate).toHaveBeenCalledWith(mutationVariables); expect(commit.mock.calls).toEqual([[types.REMOVE_LIST, listId]]); + expect(dispatch.mock.calls).toEqual([['fetchItemsForList', { listId: mockList.id }]]); }); it('restores the list if update fails', async () => { const commit = jest.fn(); jest.spyOn(gqlClient, 'mutate').mockResolvedValue(Promise.reject()); - await actions.removeList({ commit, state }, listId); + await actions.removeList({ commit, state, getters, dispatch: () => {} }, listId); expect(gqlClient.mutate).toHaveBeenCalledWith(mutationVariables); expect(commit.mock.calls).toEqual([ @@ -587,7 +614,7 @@ describe('removeList', () => { }, }); - await actions.removeList({ commit, state }, listId); + await actions.removeList({ commit, state, getters, dispatch: () => {} }, listId); expect(gqlClient.mutate).toHaveBeenCalledWith(mutationVariables); expect(commit.mock.calls).toEqual([ @@ -649,6 +676,10 @@ describe('fetchItemsForList', () => { state, [ { + type: types.RESET_ITEMS_FOR_LIST, + payload: listId, + }, + { type: types.REQUEST_ITEMS_FOR_LIST, payload: { listId, fetchNext: false }, }, @@ -671,6 +702,10 @@ describe('fetchItemsForList', () => { state, [ { + type: types.RESET_ITEMS_FOR_LIST, + payload: listId, + }, + { type: types.REQUEST_ITEMS_FOR_LIST, payload: { listId, fetchNext: false }, }, @@ -1114,6 +1149,7 @@ describe('addListItem', () => { listId: mockLists[0].id, itemId: mockIssue.id, atIndex: 0, + inProgress: false, }, }, { type: types.UPDATE_BOARD_ITEM, payload: mockIssue }, @@ -1244,8 +1280,9 @@ describe('addListNewIssue', () => { type: 'addListItem', payload: { list: fakeList, - item: formatIssue({ ...mockIssue, id: 'tmp' }), + item: formatIssue({ ...mockIssue, id: 'tmp', isLoading: true }), position: 0, + inProgress: true, }, }, { type: 'removeListItem', payload: { listId: fakeList.id, itemId: 'tmp' } }, @@ -1286,8 +1323,9 @@ describe('addListNewIssue', () => { type: 'addListItem', payload: { list: fakeList, - item: formatIssue({ ...mockIssue, id: 'tmp' }), + item: formatIssue({ ...mockIssue, id: 'tmp', isLoading: true }), position: 0, + inProgress: true, }, }, { type: 'removeListItem', payload: { listId: fakeList.id, itemId: 'tmp' } }, @@ -1348,57 +1386,6 @@ describe('setActiveIssueLabels', () => { }); }); -describe('setActiveIssueDueDate', () => { - const state = { boardItems: { [mockIssue.id]: mockIssue } }; - const getters = { activeBoardItem: mockIssue }; - const testDueDate = '2020-02-20'; - const input = { - dueDate: testDueDate, - projectPath: 'h/b', - }; - - it('should commit due date after setting the issue', (done) => { - jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ - data: { - updateIssue: { - issue: { - dueDate: testDueDate, - }, - errors: [], - }, - }, - }); - - const payload = { - itemId: getters.activeBoardItem.id, - prop: 'dueDate', - value: testDueDate, - }; - - testAction( - actions.setActiveIssueDueDate, - input, - { ...state, ...getters }, - [ - { - type: types.UPDATE_BOARD_ITEM_BY_ID, - payload, - }, - ], - [], - done, - ); - }); - - it('throws error if fails', async () => { - jest - .spyOn(gqlClient, 'mutate') - .mockResolvedValue({ data: { updateIssue: { errors: ['failed mutation'] } } }); - - await expect(actions.setActiveIssueDueDate({ getters }, input)).rejects.toThrow(Error); - }); -}); - describe('setActiveItemSubscribed', () => { const state = { boardItems: { @@ -1456,60 +1443,6 @@ describe('setActiveItemSubscribed', () => { }); }); -describe('setActiveIssueMilestone', () => { - const state = { boardItems: { [mockIssue.id]: mockIssue } }; - const getters = { activeBoardItem: mockIssue }; - const testMilestone = { - ...mockMilestone, - id: 'gid://gitlab/Milestone/1', - }; - const input = { - milestoneId: testMilestone.id, - projectPath: 'h/b', - }; - - it('should commit milestone after setting the issue', (done) => { - jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ - data: { - updateIssue: { - issue: { - milestone: testMilestone, - }, - errors: [], - }, - }, - }); - - const payload = { - itemId: getters.activeBoardItem.id, - prop: 'milestone', - value: testMilestone, - }; - - testAction( - actions.setActiveIssueMilestone, - input, - { ...state, ...getters }, - [ - { - type: types.UPDATE_BOARD_ITEM_BY_ID, - payload, - }, - ], - [], - done, - ); - }); - - it('throws error if fails', async () => { - jest - .spyOn(gqlClient, 'mutate') - .mockResolvedValue({ data: { updateIssue: { errors: ['failed mutation'] } } }); - - await expect(actions.setActiveIssueMilestone({ getters }, input)).rejects.toThrow(Error); - }); -}); - describe('setActiveItemTitle', () => { const state = { boardItems: { [mockIssue.id]: mockIssue }, diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js index d89abcc79ae..5b38f04e77b 100644 --- a/spec/frontend/boards/stores/mutations_spec.js +++ b/spec/frontend/boards/stores/mutations_spec.js @@ -273,6 +273,53 @@ describe('Board Store Mutations', () => { }); }); + describe('RESET_ITEMS_FOR_LIST', () => { + it('should remove issues from boardItemsByListId state', () => { + const listId = 'gid://gitlab/List/1'; + const boardItemsByListId = { + [listId]: [mockIssue.id], + }; + + state = { + ...state, + boardItemsByListId, + }; + + mutations[types.RESET_ITEMS_FOR_LIST](state, listId); + + expect(state.boardItemsByListId[listId]).toEqual([]); + }); + }); + + describe('REQUEST_ITEMS_FOR_LIST', () => { + const listId = 'gid://gitlab/List/1'; + const boardItemsByListId = { + [listId]: [mockIssue.id], + }; + + it.each` + fetchNext | isLoading | isLoadingMore + ${true} | ${undefined} | ${true} + ${false} | ${true} | ${undefined} + `( + 'sets isLoading to $isLoading and isLoadingMore to $isLoadingMore when fetchNext is $fetchNext', + ({ fetchNext, isLoading, isLoadingMore }) => { + state = { + ...state, + boardItemsByListId, + listsFlags: { + [listId]: {}, + }, + }; + + mutations[types.REQUEST_ITEMS_FOR_LIST](state, { listId, fetchNext }); + + expect(state.listsFlags[listId].isLoading).toBe(isLoading); + expect(state.listsFlags[listId].isLoadingMore).toBe(isLoadingMore); + }, + ); + }); + describe('RECEIVE_ITEMS_FOR_LIST_SUCCESS', () => { it('updates boardItemsByListId and issues on state', () => { const listIssues = { |