diff options
Diffstat (limited to 'spec/frontend/boards')
10 files changed, 437 insertions, 172 deletions
diff --git a/spec/frontend/boards/components/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js index 25ec568e48d..5742dfdc5d2 100644 --- a/spec/frontend/boards/components/board_card_spec.js +++ b/spec/frontend/boards/components/board_card_spec.js @@ -64,12 +64,12 @@ describe('Board card', () => { }; const selectCard = async () => { - wrapper.trigger('mouseup'); + wrapper.trigger('click'); await wrapper.vm.$nextTick(); }; const multiSelectCard = async () => { - wrapper.trigger('mouseup', { ctrlKey: true }); + wrapper.trigger('click', { ctrlKey: true }); await wrapper.vm.$nextTick(); }; diff --git a/spec/frontend/boards/components/board_filtered_search_spec.js b/spec/frontend/boards/components/board_filtered_search_spec.js index dc93890f27a..b858d6e95a0 100644 --- a/spec/frontend/boards/components/board_filtered_search_spec.js +++ b/spec/frontend/boards/components/board_filtered_search_spec.js @@ -7,6 +7,7 @@ import { __ } from '~/locale'; import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; +import { createStore } from '~/boards/stores'; Vue.use(Vuex); @@ -42,17 +43,13 @@ describe('BoardFilteredSearch', () => { }, ]; - const createComponent = ({ initialFilterParams = {} } = {}) => { - store = new Vuex.Store({ - actions: { - performSearch: jest.fn(), - }, - }); - + const createComponent = ({ initialFilterParams = {}, props = {} } = {}) => { + store = createStore(); wrapper = shallowMount(BoardFilteredSearch, { provide: { initialFilterParams, fullPath: '' }, store, propsData: { + ...props, tokens, }, }); @@ -68,11 +65,7 @@ describe('BoardFilteredSearch', () => { beforeEach(() => { createComponent(); - jest.spyOn(store, 'dispatch'); - }); - - it('renders FilteredSearch', () => { - expect(findFilteredSearch().exists()).toBe(true); + jest.spyOn(store, 'dispatch').mockImplementation(); }); it('passes the correct tokens to FilteredSearch', () => { @@ -99,6 +92,22 @@ describe('BoardFilteredSearch', () => { }); }); + describe('when eeFilters is not empty', () => { + it('passes the correct initialFilterValue to FitleredSearchBarRoot', () => { + createComponent({ props: { eeFilters: { labelName: ['label'] } } }); + + expect(findFilteredSearch().props('initialFilterValue')).toEqual([ + { type: 'label_name', value: { data: 'label', operator: '=' } }, + ]); + }); + }); + + it('renders FilteredSearch', () => { + createComponent(); + + expect(findFilteredSearch().exists()).toBe(true); + }); + describe('when searching', () => { beforeEach(() => { createComponent(); diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js index 52f1907654a..692fd3ec555 100644 --- a/spec/frontend/boards/components/board_form_spec.js +++ b/spec/frontend/boards/components/board_form_spec.js @@ -1,7 +1,6 @@ import { GlModal } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import setWindowLocation from 'helpers/set_window_location_helper'; -import { TEST_HOST } from 'helpers/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; import BoardForm from '~/boards/components/board_form.vue'; @@ -18,21 +17,18 @@ jest.mock('~/lib/utils/url_utility', () => ({ })); const currentBoard = { - id: 1, + id: 'gid://gitlab/Board/1', name: 'test', labels: [], - milestone_id: undefined, + milestone: {}, assignee: {}, - assignee_id: undefined, weight: null, - hide_backlog_list: false, - hide_closed_list: false, + hideBacklogList: false, + hideClosedList: false, }; const defaultProps = { canAdminBoard: false, - labelsPath: `${TEST_HOST}/labels/path`, - labelsWebUrl: `${TEST_HOST}/-/labels`, currentBoard, currentPage: '', }; @@ -252,7 +248,7 @@ describe('BoardForm', () => { mutation: updateBoardMutation, variables: { input: expect.objectContaining({ - id: `gid://gitlab/Board/${currentBoard.id}`, + id: currentBoard.id, }), }, }); @@ -278,7 +274,7 @@ describe('BoardForm', () => { mutation: updateBoardMutation, variables: { input: expect.objectContaining({ - id: `gid://gitlab/Board/${currentBoard.id}`, + id: currentBoard.id, }), }, }); @@ -326,7 +322,7 @@ describe('BoardForm', () => { expect(mutate).toHaveBeenCalledWith({ mutation: destroyBoardMutation, variables: { - id: 'gid://gitlab/Board/1', + id: currentBoard.id, }, }); diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js index bf317b51e83..c841c17a029 100644 --- a/spec/frontend/boards/components/boards_selector_spec.js +++ b/spec/frontend/boards/components/boards_selector_spec.js @@ -1,13 +1,22 @@ import { GlDropdown, GlLoadingIcon, GlDropdownSectionHeader } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import Vuex from 'vuex'; import { TEST_HOST } from 'spec/test_constants'; import BoardsSelector from '~/boards/components/boards_selector.vue'; +import groupBoardQuery from '~/boards/graphql/group_board.query.graphql'; +import projectBoardQuery from '~/boards/graphql/project_board.query.graphql'; +import defaultStore from '~/boards/stores'; import axios from '~/lib/utils/axios_utils'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { mockGroupBoardResponse, mockProjectBoardResponse } from '../mock_data'; const throttleDuration = 1; +Vue.use(VueApollo); + function boardGenerator(n) { return new Array(n).fill().map((board, index) => { const id = `${index}`; @@ -25,9 +34,27 @@ describe('BoardsSelector', () => { let allBoardsResponse; let recentBoardsResponse; let mock; + let fakeApollo; + let store; const boards = boardGenerator(20); const recentBoards = boardGenerator(5); + const createStore = ({ isGroupBoard = false, isProjectBoard = false } = {}) => { + store = new Vuex.Store({ + ...defaultStore, + actions: { + setError: jest.fn(), + }, + getters: { + isGroupBoard: () => isGroupBoard, + isProjectBoard: () => isProjectBoard, + }, + state: { + boardType: isGroupBoard ? 'group' : 'project', + }, + }); + }; + const fillSearchBox = (filterTerm) => { const searchBox = wrapper.find({ ref: 'searchBox' }); const searchBoxInput = searchBox.find('input'); @@ -40,52 +67,27 @@ describe('BoardsSelector', () => { const getLoadingIcon = () => wrapper.find(GlLoadingIcon); const findDropdown = () => wrapper.find(GlDropdown); - beforeEach(() => { - mock = new MockAdapter(axios); - const $apollo = { - queries: { - boards: { - loading: false, - }, - }, - }; + const projectBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockProjectBoardResponse); + const groupBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockGroupBoardResponse); - allBoardsResponse = Promise.resolve({ - data: { - group: { - boards: { - edges: boards.map((board) => ({ node: board })), - }, - }, - }, - }); - recentBoardsResponse = Promise.resolve({ - data: recentBoards, - }); + const createComponent = () => { + fakeApollo = createMockApollo([ + [projectBoardQuery, projectBoardQueryHandlerSuccess], + [groupBoardQuery, groupBoardQueryHandlerSuccess], + ]); wrapper = mount(BoardsSelector, { + store, + apolloProvider: fakeApollo, propsData: { throttleDuration, - currentBoard: { - id: 1, - name: 'Development', - milestone_id: null, - weight: null, - assignee_id: null, - labels: [], - }, boardBaseUrl: `${TEST_HOST}/board/base/url`, hasMissingBoards: false, canAdminBoard: true, multipleIssueBoardsAvailable: true, - labelsPath: `${TEST_HOST}/labels/path`, - labelsWebUrl: `${TEST_HOST}/labels`, - projectId: 42, - groupId: 19, scopedIssueBoardFeatureEnabled: true, weights: [], }, - mocks: { $apollo }, attachTo: document.body, provide: { fullPath: '', @@ -98,12 +100,7 @@ describe('BoardsSelector', () => { [options.loadingKey]: true, }); }); - - mock.onGet(`${TEST_HOST}/recent`).replyOnce(200, recentBoards); - - // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time - findDropdown().vm.$emit('show'); - }); + }; afterEach(() => { wrapper.destroy(); @@ -111,104 +108,158 @@ describe('BoardsSelector', () => { mock.restore(); }); - describe('loading', () => { - // we are testing loading state, so don't resolve responses until after the tests - afterEach(() => { - return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick()); - }); + describe('fetching all boards', () => { + beforeEach(() => { + mock = new MockAdapter(axios); - it('shows loading spinner', () => { - expect(getDropdownHeaders()).toHaveLength(0); - expect(getDropdownItems()).toHaveLength(0); - expect(getLoadingIcon().exists()).toBe(true); + allBoardsResponse = Promise.resolve({ + data: { + group: { + boards: { + edges: boards.map((board) => ({ node: board })), + }, + }, + }, + }); + recentBoardsResponse = Promise.resolve({ + data: recentBoards, + }); + + createStore(); + createComponent(); + + mock.onGet(`${TEST_HOST}/recent`).replyOnce(200, recentBoards); }); - }); - describe('loaded', () => { - beforeEach(async () => { - await wrapper.setData({ - loadingBoards: false, + describe('loading', () => { + beforeEach(async () => { + // Wait for current board to be loaded + await nextTick(); + + // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time + findDropdown().vm.$emit('show'); + }); + + // we are testing loading state, so don't resolve responses until after the tests + afterEach(async () => { + await Promise.all([allBoardsResponse, recentBoardsResponse]); + await nextTick(); }); - return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick()); - }); - it('hides loading spinner', async () => { - await wrapper.vm.$nextTick(); - expect(getLoadingIcon().exists()).toBe(false); + it('shows loading spinner', () => { + expect(getDropdownHeaders()).toHaveLength(0); + expect(getDropdownItems()).toHaveLength(0); + expect(getLoadingIcon().exists()).toBe(true); + }); }); - describe('filtering', () => { - beforeEach(() => { - wrapper.setData({ - boards, - }); + describe('loaded', () => { + beforeEach(async () => { + // Wait for current board to be loaded + await nextTick(); + + // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time + findDropdown().vm.$emit('show'); - return nextTick(); + await wrapper.setData({ + loadingBoards: false, + loadingRecentBoards: false, + }); + await Promise.all([allBoardsResponse, recentBoardsResponse]); + await nextTick(); }); - it('shows all boards without filtering', () => { - expect(getDropdownItems()).toHaveLength(boards.length + recentBoards.length); + it('hides loading spinner', async () => { + await nextTick(); + expect(getLoadingIcon().exists()).toBe(false); }); - it('shows only matching boards when filtering', () => { - const filterTerm = 'board1'; - const expectedCount = boards.filter((board) => board.name.includes(filterTerm)).length; + describe('filtering', () => { + beforeEach(async () => { + wrapper.setData({ + boards, + }); + + await nextTick(); + }); - fillSearchBox(filterTerm); + it('shows all boards without filtering', () => { + expect(getDropdownItems()).toHaveLength(boards.length + recentBoards.length); + }); - return nextTick().then(() => { + it('shows only matching boards when filtering', async () => { + const filterTerm = 'board1'; + const expectedCount = boards.filter((board) => board.name.includes(filterTerm)).length; + + fillSearchBox(filterTerm); + + await nextTick(); expect(getDropdownItems()).toHaveLength(expectedCount); }); - }); - it('shows message if there are no matching boards', () => { - fillSearchBox('does not exist'); + it('shows message if there are no matching boards', async () => { + fillSearchBox('does not exist'); - return nextTick().then(() => { + await nextTick(); expect(getDropdownItems()).toHaveLength(0); expect(wrapper.text().includes('No matching boards found')).toBe(true); }); }); - }); - describe('recent boards section', () => { - it('shows only when boards are greater than 10', () => { - wrapper.setData({ - boards, - }); + describe('recent boards section', () => { + it('shows only when boards are greater than 10', async () => { + wrapper.setData({ + boards, + }); - return nextTick().then(() => { + await nextTick(); expect(getDropdownHeaders()).toHaveLength(2); }); - }); - it('does not show when boards are less than 10', () => { - wrapper.setData({ - boards: boards.slice(0, 5), - }); + it('does not show when boards are less than 10', async () => { + wrapper.setData({ + boards: boards.slice(0, 5), + }); - return nextTick().then(() => { + await nextTick(); expect(getDropdownHeaders()).toHaveLength(0); }); - }); - it('does not show when recentBoards api returns empty array', () => { - wrapper.setData({ - recentBoards: [], - }); + it('does not show when recentBoards api returns empty array', async () => { + wrapper.setData({ + recentBoards: [], + }); - return nextTick().then(() => { + await nextTick(); expect(getDropdownHeaders()).toHaveLength(0); }); - }); - it('does not show when search is active', () => { - fillSearchBox('Random string'); + it('does not show when search is active', async () => { + fillSearchBox('Random string'); - return nextTick().then(() => { + await nextTick(); expect(getDropdownHeaders()).toHaveLength(0); }); }); }); }); + + describe('fetching current board', () => { + it.each` + boardType | queryHandler | notCalledHandler + ${'group'} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess} + ${'project'} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess} + `('fetches $boardType board', async ({ boardType, queryHandler, notCalledHandler }) => { + createStore({ + isProjectBoard: boardType === 'project', + isGroupBoard: boardType === 'group', + }); + createComponent(); + + await nextTick(); + + expect(queryHandler).toHaveBeenCalled(); + expect(notCalledHandler).not.toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/boards/components/issue_board_filtered_search_spec.js b/spec/frontend/boards/components/issue_board_filtered_search_spec.js index b6de46f8db8..45c5c87d800 100644 --- a/spec/frontend/boards/components/issue_board_filtered_search_spec.js +++ b/spec/frontend/boards/components/issue_board_filtered_search_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue'; +import BoardFilteredSearch from 'ee_else_ce/boards/components/board_filtered_search.vue'; import IssueBoardFilteredSpec from '~/boards/components/issue_board_filtered_search.vue'; import issueBoardFilters from '~/boards/issue_board_filters'; import { mockTokens } from '../mock_data'; @@ -9,39 +9,60 @@ jest.mock('~/boards/issue_board_filters'); describe('IssueBoardFilter', () => { let wrapper; - const createComponent = () => { + const findBoardsFilteredSearch = () => wrapper.findComponent(BoardFilteredSearch); + + const createComponent = ({ isSignedIn = false } = {}) => { wrapper = shallowMount(IssueBoardFilteredSpec, { - props: { fullPath: '', boardType: '' }, + propsData: { fullPath: 'gitlab-org', boardType: 'group' }, + provide: { + isSignedIn, + }, }); }; + let fetchAuthorsSpy; + let fetchLabelsSpy; + beforeEach(() => { + fetchAuthorsSpy = jest.fn(); + fetchLabelsSpy = jest.fn(); + + issueBoardFilters.mockReturnValue({ + fetchAuthors: fetchAuthorsSpy, + fetchLabels: fetchLabelsSpy, + }); + }); + afterEach(() => { wrapper.destroy(); }); describe('default', () => { - let fetchAuthorsSpy; - let fetchLabelsSpy; beforeEach(() => { - fetchAuthorsSpy = jest.fn(); - fetchLabelsSpy = jest.fn(); - - issueBoardFilters.mockReturnValue({ - fetchAuthors: fetchAuthorsSpy, - fetchLabels: fetchLabelsSpy, - }); - createComponent(); }); it('finds BoardFilteredSearch', () => { - expect(wrapper.find(BoardFilteredSearch).exists()).toBe(true); + expect(findBoardsFilteredSearch().exists()).toBe(true); }); - it('passes the correct tokens to BoardFilteredSearch', () => { - const tokens = mockTokens(fetchLabelsSpy, fetchAuthorsSpy, wrapper.vm.fetchMilestones); + it.each` + isSignedIn + ${true} + ${false} + `( + 'passes the correct tokens to BoardFilteredSearch when user sign in is $isSignedIn', + ({ isSignedIn }) => { + createComponent({ isSignedIn }); - expect(wrapper.find(BoardFilteredSearch).props('tokens')).toEqual(tokens); - }); + const tokens = mockTokens( + fetchLabelsSpy, + fetchAuthorsSpy, + wrapper.vm.fetchMilestones, + isSignedIn, + ); + + expect(findBoardsFilteredSearch().props('tokens')).toEqual(tokens); + }, + ); }); }); diff --git a/spec/frontend/boards/components/new_board_button_spec.js b/spec/frontend/boards/components/new_board_button_spec.js new file mode 100644 index 00000000000..075fe225ec2 --- /dev/null +++ b/spec/frontend/boards/components/new_board_button_spec.js @@ -0,0 +1,75 @@ +import { mount } from '@vue/test-utils'; +import { GlButton } from '@gitlab/ui'; +import NewBoardButton from '~/boards/components/new_board_button.vue'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { stubExperiments } from 'helpers/experimentation_helper'; +import eventHub from '~/boards/eventhub'; + +const FEATURE = 'prominent_create_board_btn'; + +describe('NewBoardButton', () => { + let wrapper; + + const createComponent = (args = {}) => + extendedWrapper( + mount(NewBoardButton, { + provide: { + canAdminBoard: true, + multipleIssueBoardsAvailable: true, + ...args, + }, + }), + ); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('control variant', () => { + beforeAll(() => { + stubExperiments({ [FEATURE]: 'control' }); + }); + + it('renders nothing', () => { + wrapper = createComponent(); + + expect(wrapper.text()).toBe(''); + }); + }); + + describe('candidate variant', () => { + beforeAll(() => { + stubExperiments({ [FEATURE]: 'candidate' }); + }); + + it('renders New board button when `candidate` variant', () => { + wrapper = createComponent(); + + expect(wrapper.text()).toBe('New board'); + }); + + it('renders nothing when `canAdminBoard` is `false`', () => { + wrapper = createComponent({ canAdminBoard: false }); + + expect(wrapper.find(GlButton).exists()).toBe(false); + }); + + it('renders nothing when `multipleIssueBoardsAvailable` is `false`', () => { + wrapper = createComponent({ multipleIssueBoardsAvailable: false }); + + expect(wrapper.find(GlButton).exists()).toBe(false); + }); + + it('emits `showBoardModal` when button is clicked', () => { + jest.spyOn(eventHub, '$emit').mockImplementation(); + + wrapper = createComponent(); + + wrapper.find(GlButton).vm.$emit('click', { preventDefault: () => {} }); + + expect(eventHub.$emit).toHaveBeenCalledWith('showBoardModal', 'new'); + }); + }); +}); 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 60474767f2d..fb9d823107e 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 @@ -105,6 +105,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => { describe('when labels are updated over existing labels', () => { const testLabelsPayload = [ { id: 5, set: true }, + { id: 6, set: false }, { id: 7, set: true }, ]; const expectedLabels = [{ id: 5 }, { id: 7 }]; 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 8847f626c1f..6e1b528babc 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js @@ -14,8 +14,8 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = let store; const findNotificationHeader = () => wrapper.find("[data-testid='notification-header-text']"); - const findToggle = () => wrapper.find(GlToggle); - const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findToggle = () => wrapper.findComponent(GlToggle); + const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const createComponent = (activeBoardItem = { ...mockActiveIssue }) => { store = createStore(); @@ -32,7 +32,6 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = afterEach(() => { wrapper.destroy(); - wrapper = null; store = null; jest.clearAllMocks(); }); @@ -104,7 +103,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = expect(findGlLoadingIcon().exists()).toBe(false); - findToggle().trigger('click'); + findToggle().vm.$emit('change'); await wrapper.vm.$nextTick(); @@ -129,7 +128,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = expect(findGlLoadingIcon().exists()).toBe(false); - findToggle().trigger('click'); + findToggle().vm.$emit('change'); await wrapper.vm.$nextTick(); @@ -152,7 +151,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () = }); jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {}); - findToggle().trigger('click'); + findToggle().vm.$emit('change'); await wrapper.vm.$nextTick(); expect(wrapper.vm.setError).toHaveBeenCalled(); diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 6a4f344bbfb..8fcad99f8a7 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -4,6 +4,7 @@ import { ListType } from '~/boards/constants'; import { __ } from '~/locale'; import { DEFAULT_MILESTONES_GRAPHQL } from '~/vue_shared/components/filtered_search_bar/constants'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; +import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue'; @@ -12,6 +13,7 @@ export const boardObj = { id: 1, name: 'test', milestone_id: null, + labels: [], }; export const listObj = { @@ -29,17 +31,27 @@ export const listObj = { }, }; -export const listObjDuplicate = { - id: listObj.id, - position: 1, - title: 'Test', - list_type: 'label', - weight: 3, - label: { - id: listObj.label.id, - title: 'Test', - color: '#ff0000', - description: 'testing;', +export const mockGroupBoardResponse = { + data: { + workspace: { + board: { + id: 'gid://gitlab/Board/1', + name: 'Development', + }, + __typename: 'Group', + }, + }, +}; + +export const mockProjectBoardResponse = { + data: { + workspace: { + board: { + id: 'gid://gitlab/Board/2', + name: 'Development', + }, + __typename: 'Project', + }, }, }; @@ -538,7 +550,16 @@ export const mockMoveData = { ...mockMoveIssueParams, }; -export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [ +export const mockEmojiToken = { + type: 'my_reaction_emoji', + icon: 'thumb-up', + title: 'My-Reaction', + unique: true, + token: EmojiToken, + fetchEmojis: expect.any(Function), +}; + +export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, hasEmoji) => [ { icon: 'user', title: __('Assignee'), @@ -579,6 +600,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [ symbol: '~', fetchLabels, }, + ...(hasEmoji ? [mockEmojiToken] : []), { icon: 'clock', title: __('Milestone'), @@ -593,7 +615,6 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [ icon: 'issues', title: __('Type'), type: 'types', - operators: [{ value: '=', description: 'is' }], token: GlFilteredSearchToken, unique: true, options: [ @@ -609,3 +630,43 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [ unique: true, }, ]; + +export const mockLabel1 = { + id: 'gid://gitlab/GroupLabel/121', + title: 'To Do', + color: '#F0AD4E', + textColor: '#FFFFFF', + description: null, +}; + +export const mockLabel2 = { + id: 'gid://gitlab/GroupLabel/122', + title: 'Doing', + color: '#F0AD4E', + textColor: '#FFFFFF', + description: null, +}; + +export const mockProjectLabelsResponse = { + data: { + workspace: { + id: 'gid://gitlab/Project/1', + labels: { + nodes: [mockLabel1, mockLabel2], + }, + __typename: 'Project', + }, + }, +}; + +export const mockGroupLabelsResponse = { + data: { + workspace: { + id: 'gid://gitlab/Group/1', + labels: { + nodes: [mockLabel1, mockLabel2], + }, + __typename: 'Group', + }, + }, +}; diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 0b90912a584..e245325b956 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -27,6 +27,7 @@ import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql' import actions from '~/boards/stores/actions'; import * as types from '~/boards/stores/mutation_types'; import mutations from '~/boards/stores/mutations'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { mockLists, @@ -1572,12 +1573,13 @@ describe('setActiveIssueLabels', () => { const getters = { activeBoardItem: mockIssue }; const testLabelIds = labels.map((label) => label.id); const input = { - addLabelIds: testLabelIds, + labelIds: testLabelIds, removeLabelIds: [], projectPath: 'h/b', + labels, }; - it('should assign labels on success, and sets loading state for labels', (done) => { + it('should assign labels on success', (done) => { jest .spyOn(gqlClient, 'mutate') .mockResolvedValue({ data: { updateIssue: { issue: { labels: { nodes: labels } } } } }); @@ -1594,14 +1596,6 @@ describe('setActiveIssueLabels', () => { { ...state, ...getters }, [ { - type: types.SET_LABELS_LOADING, - payload: true, - }, - { - type: types.SET_LABELS_LOADING, - payload: false, - }, - { type: types.UPDATE_BOARD_ITEM_BY_ID, payload, }, @@ -1618,6 +1612,64 @@ describe('setActiveIssueLabels', () => { await expect(actions.setActiveIssueLabels({ getters }, input)).rejects.toThrow(Error); }); + + describe('labels_widget FF on', () => { + beforeEach(() => { + window.gon = { + features: { labelsWidget: true }, + }; + + getters.activeBoardItem = { ...mockIssue, labels }; + }); + + afterEach(() => { + window.gon = { + features: {}, + }; + }); + + it('should assign labels', () => { + const payload = { + itemId: getters.activeBoardItem.id, + prop: 'labels', + value: labels, + }; + + testAction( + actions.setActiveIssueLabels, + input, + { ...state, ...getters }, + [ + { + type: types.UPDATE_BOARD_ITEM_BY_ID, + payload, + }, + ], + [], + ); + }); + + it('should remove label', () => { + const payload = { + itemId: getters.activeBoardItem.id, + prop: 'labels', + value: [labels[1]], + }; + + testAction( + actions.setActiveIssueLabels, + { ...input, removeLabelIds: [getIdFromGraphQLId(labels[0].id)] }, + { ...state, ...getters }, + [ + { + type: types.UPDATE_BOARD_ITEM_BY_ID, + payload, + }, + ], + [], + ); + }); + }); }); describe('setActiveItemSubscribed', () => { |