diff options
Diffstat (limited to 'spec/frontend/boards/stores')
-rw-r--r-- | spec/frontend/boards/stores/actions_spec.js | 708 | ||||
-rw-r--r-- | spec/frontend/boards/stores/getters_spec.js | 41 | ||||
-rw-r--r-- | spec/frontend/boards/stores/mutations_spec.js | 239 |
3 files changed, 685 insertions, 303 deletions
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 69d2c8977fb..460e77a3f03 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -1,16 +1,21 @@ +import * as Sentry from '@sentry/browser'; +import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql'; import testAction from 'helpers/vuex_action_helper'; import { fullBoardId, formatListIssues, formatBoardLists, formatIssueInput, + formatIssue, + getMoveData, } from '~/boards/boards_util'; -import { inactiveId, ISSUABLE } from '~/boards/constants'; +import { inactiveId, ISSUABLE, ListType } from '~/boards/constants'; import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql'; import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql'; -import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql'; import actions, { gqlClient } from '~/boards/stores/actions'; import * as types from '~/boards/stores/mutation_types'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; + import { mockLists, mockListsById, @@ -22,6 +27,9 @@ import { labels, mockActiveIssue, mockGroupProjects, + mockMoveIssueParams, + mockMoveState, + mockMoveData, } from '../mock_data'; jest.mock('~/flash'); @@ -638,73 +646,314 @@ describe('resetIssues', () => { }); describe('moveItem', () => { - it('should dispatch moveIssue action', () => { + it('should dispatch moveIssue action with payload', () => { + const payload = { mock: 'payload' }; + testAction({ action: actions.moveItem, - expectedActions: [{ type: 'moveIssue' }], + payload, + expectedActions: [{ type: 'moveIssue', payload }], }); }); }); describe('moveIssue', () => { - const listIssues = { - 'gid://gitlab/List/1': [436, 437], - 'gid://gitlab/List/2': [], - }; - - const issues = { - 436: mockIssue, - 437: mockIssue2, - }; - - const state = { - fullPath: 'gitlab-org', - boardId: '1', - boardType: 'group', - disabled: false, - boardLists: mockLists, - boardItemsByListId: listIssues, - boardItems: issues, - }; + it('should dispatch a correct set of actions', () => { + testAction({ + action: actions.moveIssue, + payload: mockMoveIssueParams, + state: mockMoveState, + expectedActions: [ + { type: 'moveIssueCard', payload: mockMoveData }, + { type: 'updateMovedIssue', payload: mockMoveData }, + { type: 'updateIssueOrder', payload: { moveData: mockMoveData } }, + ], + }); + }); +}); - it('should commit MOVE_ISSUE mutation and MOVE_ISSUE_SUCCESS mutation when successful', (done) => { - jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ - data: { - issueMoveList: { - issue: rawIssue, - errors: [], +describe('moveIssueCard and undoMoveIssueCard', () => { + describe('card should move without clonning', () => { + let state; + let params; + let moveMutations; + let undoMutations; + + describe('when re-ordering card', () => { + beforeEach( + ({ + itemId = 123, + fromListId = 'gid://gitlab/List/1', + toListId = 'gid://gitlab/List/1', + originalIssue = { foo: 'bar' }, + originalIndex = 0, + moveBeforeId = undefined, + moveAfterId = undefined, + } = {}) => { + state = { + boardLists: { + [toListId]: { listType: ListType.backlog }, + [fromListId]: { listType: ListType.backlog }, + }, + boardItems: { [itemId]: originalIssue }, + boardItemsByListId: { [fromListId]: [123] }, + }; + params = { itemId, fromListId, toListId, moveBeforeId, moveAfterId }; + moveMutations = [ + { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: fromListId } }, + { + type: types.ADD_BOARD_ITEM_TO_LIST, + payload: { itemId, listId: toListId, moveBeforeId, moveAfterId }, + }, + ]; + undoMutations = [ + { type: types.UPDATE_BOARD_ITEM, payload: originalIssue }, + { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: fromListId } }, + { + type: types.ADD_BOARD_ITEM_TO_LIST, + payload: { itemId, listId: fromListId, atIndex: originalIndex }, + }, + ]; }, - }, + ); + + it('moveIssueCard commits a correct set of actions', () => { + testAction({ + action: actions.moveIssueCard, + state, + payload: getMoveData(state, params), + expectedMutations: moveMutations, + }); + }); + + it('undoMoveIssueCard commits a correct set of actions', () => { + testAction({ + action: actions.undoMoveIssueCard, + state, + payload: getMoveData(state, params), + expectedMutations: undoMutations, + }); + }); }); - testAction( - actions.moveIssue, - { - itemId: '436', - itemIid: mockIssue.iid, - itemPath: mockIssue.referencePath, - fromListId: 'gid://gitlab/List/1', - toListId: 'gid://gitlab/List/2', - }, - state, + describe.each([ [ + 'issue moves out of backlog', { - type: types.MOVE_ISSUE, - payload: { - originalIssue: mockIssue, - fromListId: 'gid://gitlab/List/1', - toListId: 'gid://gitlab/List/2', - }, + fromListType: ListType.backlog, + toListType: ListType.label, }, + ], + [ + 'issue card moves to closed', { - type: types.MOVE_ISSUE_SUCCESS, - payload: { issue: rawIssue }, + fromListType: ListType.label, + toListType: ListType.closed, }, ], - [], - done, - ); + [ + 'issue card moves to non-closed, non-backlog list of the same type', + { + fromListType: ListType.label, + toListType: ListType.label, + }, + ], + ])('when %s', (_, { toListType, fromListType }) => { + beforeEach( + ({ + itemId = 123, + fromListId = 'gid://gitlab/List/1', + toListId = 'gid://gitlab/List/2', + originalIssue = { foo: 'bar' }, + originalIndex = 0, + moveBeforeId = undefined, + moveAfterId = undefined, + } = {}) => { + state = { + boardLists: { + [fromListId]: { listType: fromListType }, + [toListId]: { listType: toListType }, + }, + boardItems: { [itemId]: originalIssue }, + boardItemsByListId: { [fromListId]: [123], [toListId]: [] }, + }; + params = { itemId, fromListId, toListId, moveBeforeId, moveAfterId }; + moveMutations = [ + { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: fromListId } }, + { + type: types.ADD_BOARD_ITEM_TO_LIST, + payload: { itemId, listId: toListId, moveBeforeId, moveAfterId }, + }, + ]; + undoMutations = [ + { type: types.UPDATE_BOARD_ITEM, payload: originalIssue }, + { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: toListId } }, + { + type: types.ADD_BOARD_ITEM_TO_LIST, + payload: { itemId, listId: fromListId, atIndex: originalIndex }, + }, + ]; + }, + ); + + it('moveIssueCard commits a correct set of actions', () => { + testAction({ + action: actions.moveIssueCard, + state, + payload: getMoveData(state, params), + expectedMutations: moveMutations, + }); + }); + + it('undoMoveIssueCard commits a correct set of actions', () => { + testAction({ + action: actions.undoMoveIssueCard, + state, + payload: getMoveData(state, params), + expectedMutations: undoMutations, + }); + }); + }); + }); + + describe('card should clone on move', () => { + let state; + let params; + let moveMutations; + let undoMutations; + + describe.each([ + [ + 'issue card moves to non-closed, non-backlog list of a different type', + { + fromListType: ListType.label, + toListType: ListType.assignee, + }, + ], + ])('when %s', (_, { toListType, fromListType }) => { + beforeEach( + ({ + itemId = 123, + fromListId = 'gid://gitlab/List/1', + toListId = 'gid://gitlab/List/2', + originalIssue = { foo: 'bar' }, + originalIndex = 0, + moveBeforeId = undefined, + moveAfterId = undefined, + } = {}) => { + state = { + boardLists: { + [fromListId]: { listType: fromListType }, + [toListId]: { listType: toListType }, + }, + boardItems: { [itemId]: originalIssue }, + boardItemsByListId: { [fromListId]: [123], [toListId]: [] }, + }; + params = { itemId, fromListId, toListId, moveBeforeId, moveAfterId }; + moveMutations = [ + { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: fromListId } }, + { + type: types.ADD_BOARD_ITEM_TO_LIST, + payload: { itemId, listId: toListId, moveBeforeId, moveAfterId }, + }, + { + type: types.ADD_BOARD_ITEM_TO_LIST, + payload: { itemId, listId: fromListId, atIndex: originalIndex }, + }, + ]; + undoMutations = [ + { type: types.UPDATE_BOARD_ITEM, payload: originalIssue }, + { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: fromListId } }, + { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload: { itemId, listId: toListId } }, + { + type: types.ADD_BOARD_ITEM_TO_LIST, + payload: { itemId, listId: fromListId, atIndex: originalIndex }, + }, + ]; + }, + ); + + it('moveIssueCard commits a correct set of actions', () => { + testAction({ + action: actions.moveIssueCard, + state, + payload: getMoveData(state, params), + expectedMutations: moveMutations, + }); + }); + + it('undoMoveIssueCard commits a correct set of actions', () => { + testAction({ + action: actions.undoMoveIssueCard, + state, + payload: getMoveData(state, params), + expectedMutations: undoMutations, + }); + }); + }); }); +}); + +describe('updateMovedIssueCard', () => { + const label1 = { + id: 'label1', + }; + + it.each([ + [ + 'issue without a label is moved to a label list', + { + state: { + boardLists: { + from: {}, + to: { + listType: ListType.label, + label: label1, + }, + }, + boardItems: { + 1: { + labels: [], + }, + }, + }, + moveData: { + itemId: 1, + fromListId: 'from', + toListId: 'to', + }, + updatedIssue: { labels: [label1] }, + }, + ], + ])( + 'should commit UPDATE_BOARD_ITEM with a correctly updated issue data when %s', + (_, { state, moveData, updatedIssue }) => { + testAction({ + action: actions.updateMovedIssue, + payload: moveData, + state, + expectedMutations: [{ type: types.UPDATE_BOARD_ITEM, payload: updatedIssue }], + }); + }, + ); +}); + +describe('updateIssueOrder', () => { + const issues = { + 436: mockIssue, + 437: mockIssue2, + }; + + const state = { + boardItems: issues, + boardId: 'gid://gitlab/Board/1', + }; + + const moveData = { + itemId: 436, + fromListId: 'gid://gitlab/List/1', + toListId: 'gid://gitlab/List/2', + }; it('calls mutate with the correct variables', () => { const mutationVariables = { @@ -728,61 +977,56 @@ describe('moveIssue', () => { }, }); - actions.moveIssue( - { state, commit: () => {} }, - { - itemId: mockIssue.id, - itemIid: mockIssue.iid, - itemPath: mockIssue.referencePath, - fromListId: 'gid://gitlab/List/1', - toListId: 'gid://gitlab/List/2', - }, - ); + actions.updateIssueOrder({ state, commit: () => {}, dispatch: () => {} }, { moveData }); expect(gqlClient.mutate).toHaveBeenCalledWith(mutationVariables); }); - it('should commit MOVE_ISSUE mutation and MOVE_ISSUE_FAILURE mutation when unsuccessful', (done) => { + it('should commit MUTATE_ISSUE_SUCCESS mutation when successful', () => { jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ data: { issueMoveList: { - issue: {}, - errors: [{ foo: 'bar' }], + issue: rawIssue, + errors: [], }, }, }); testAction( - actions.moveIssue, - { - itemId: '436', - itemIid: mockIssue.iid, - itemPath: mockIssue.referencePath, - fromListId: 'gid://gitlab/List/1', - toListId: 'gid://gitlab/List/2', - }, + actions.updateIssueOrder, + { moveData }, state, [ { - type: types.MOVE_ISSUE, - payload: { - originalIssue: mockIssue, - fromListId: 'gid://gitlab/List/1', - toListId: 'gid://gitlab/List/2', - }, + type: types.MUTATE_ISSUE_SUCCESS, + payload: { issue: rawIssue }, }, + ], + [], + ); + }); + + it('should commit SET_ERROR and dispatch undoMoveIssueCard', () => { + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { + issueMoveList: { + issue: {}, + errors: [{ foo: 'bar' }], + }, + }, + }); + + testAction( + actions.updateIssueOrder, + { moveData }, + state, + [ { - type: types.MOVE_ISSUE_FAILURE, - payload: { - originalIssue: mockIssue, - fromListId: 'gid://gitlab/List/1', - toListId: 'gid://gitlab/List/2', - originalIndex: 0, - }, + type: types.SET_ERROR, + payload: 'An error occurred while moving the issue. Please try again.', }, ], - [], - done, + [{ type: 'undoMoveIssueCard', payload: moveData }], ); }); }); @@ -798,11 +1042,11 @@ describe('setAssignees', () => { testAction( actions.setAssignees, [node], - { activeIssue: { iid, referencePath: refPath }, commit: () => {} }, + { activeBoardItem: { iid, referencePath: refPath }, commit: () => {} }, [ { - type: 'UPDATE_ISSUE_BY_ID', - payload: { prop: 'assignees', issueId: undefined, value: [node] }, + type: 'UPDATE_BOARD_ITEM_BY_ID', + payload: { prop: 'assignees', itemId: undefined, value: [node] }, }, ], [], @@ -812,7 +1056,43 @@ describe('setAssignees', () => { }); }); -describe('createNewIssue', () => { +describe('addListItem', () => { + it('should commit ADD_BOARD_ITEM_TO_LIST and UPDATE_BOARD_ITEM mutations', () => { + const payload = { + list: mockLists[0], + item: mockIssue, + position: 0, + }; + + testAction(actions.addListItem, payload, {}, [ + { + type: types.ADD_BOARD_ITEM_TO_LIST, + payload: { + listId: mockLists[0].id, + itemId: mockIssue.id, + atIndex: 0, + }, + }, + { type: types.UPDATE_BOARD_ITEM, payload: mockIssue }, + ]); + }); +}); + +describe('removeListItem', () => { + it('should commit REMOVE_BOARD_ITEM_FROM_LIST and REMOVE_BOARD_ITEM mutations', () => { + const payload = { + listId: mockLists[0].id, + itemId: mockIssue.id, + }; + + testAction(actions.removeListItem, payload, {}, [ + { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload }, + { type: types.REMOVE_BOARD_ITEM, payload: mockIssue.id }, + ]); + }); +}); + +describe('addListNewIssue', () => { const state = { boardType: 'group', fullPath: 'gitlab-org/gitlab', @@ -839,19 +1119,7 @@ describe('createNewIssue', () => { }, }; - it('should return issue from API on success', async () => { - jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ - data: { - createIssue: { - issue: mockIssue, - errors: [], - }, - }, - }); - - const result = await actions.createNewIssue({ state }, mockIssue); - expect(result).toEqual(mockIssue); - }); + const fakeList = { id: 'gid://gitlab/List/123' }; it('should add board scope to the issue being created', async () => { jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ @@ -863,7 +1131,11 @@ describe('createNewIssue', () => { }, }); - await actions.createNewIssue({ state: stateWithBoardConfig }, mockIssue); + await actions.addListNewIssue( + { dispatch: jest.fn(), commit: jest.fn(), state: stateWithBoardConfig }, + { issueInput: mockIssue, list: fakeList }, + ); + expect(gqlClient.mutate).toHaveBeenCalledWith({ mutation: issueCreateMutation, variables: { @@ -890,7 +1162,11 @@ describe('createNewIssue', () => { const payload = formatIssueInput(issue, stateWithBoardConfig.boardConfig); - await actions.createNewIssue({ state: stateWithBoardConfig }, issue); + await actions.addListNewIssue( + { dispatch: jest.fn(), commit: jest.fn(), state: stateWithBoardConfig }, + { issueInput: issue, list: fakeList }, + ); + expect(gqlClient.mutate).toHaveBeenCalledWith({ mutation: issueCreateMutation, variables: { @@ -901,51 +1177,92 @@ describe('createNewIssue', () => { expect(payload.assigneeIds).toEqual(['gid://gitlab/User/1', 'gid://gitlab/User/2']); }); - it('should commit CREATE_ISSUE_FAILURE mutation when API returns an error', (done) => { - jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ - data: { - createIssue: { - issue: mockIssue, - errors: [{ foo: 'bar' }], + describe('when issue creation mutation request succeeds', () => { + it('dispatches a correct set of mutations', () => { + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { + createIssue: { + issue: mockIssue, + errors: [], + }, }, - }, + }); + + testAction({ + action: actions.addListNewIssue, + payload: { + issueInput: mockIssue, + list: fakeList, + placeholderId: 'tmp', + }, + state, + expectedActions: [ + { + type: 'addListItem', + payload: { + list: fakeList, + item: formatIssue({ ...mockIssue, id: 'tmp' }), + position: 0, + }, + }, + { type: 'removeListItem', payload: { listId: fakeList.id, itemId: 'tmp' } }, + { + type: 'addListItem', + payload: { + list: fakeList, + item: formatIssue({ ...mockIssue, id: getIdFromGraphQLId(mockIssue.id) }), + position: 0, + }, + }, + ], + }); }); - - const payload = mockIssue; - - testAction( - actions.createNewIssue, - payload, - state, - [{ type: types.CREATE_ISSUE_FAILURE }], - [], - done, - ); }); -}); - -describe('addListIssue', () => { - it('should commit ADD_ISSUE_TO_LIST mutation', (done) => { - const payload = { - list: mockLists[0], - issue: mockIssue, - position: 0, - }; - testAction( - actions.addListIssue, - payload, - {}, - [{ type: types.ADD_ISSUE_TO_LIST, payload }], - [], - done, - ); + describe('when issue creation mutation request fails', () => { + it('dispatches a correct set of mutations', () => { + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { + createIssue: { + issue: mockIssue, + errors: [{ foo: 'bar' }], + }, + }, + }); + + testAction({ + action: actions.addListNewIssue, + payload: { + issueInput: mockIssue, + list: fakeList, + placeholderId: 'tmp', + }, + state, + expectedActions: [ + { + type: 'addListItem', + payload: { + list: fakeList, + item: formatIssue({ ...mockIssue, id: 'tmp' }), + position: 0, + }, + }, + { type: 'removeListItem', payload: { listId: fakeList.id, itemId: 'tmp' } }, + ], + expectedMutations: [ + { + type: types.SET_ERROR, + payload: 'An error occurred while creating the issue. Please try again.', + }, + ], + }); + }); }); }); describe('setActiveIssueLabels', () => { const state = { boardItems: { [mockIssue.id]: mockIssue } }; - const getters = { activeIssue: mockIssue }; + const getters = { activeBoardItem: mockIssue }; const testLabelIds = labels.map((label) => label.id); const input = { addLabelIds: testLabelIds, @@ -959,7 +1276,7 @@ describe('setActiveIssueLabels', () => { .mockResolvedValue({ data: { updateIssue: { issue: { labels: { nodes: labels } } } } }); const payload = { - issueId: getters.activeIssue.id, + itemId: getters.activeBoardItem.id, prop: 'labels', value: labels, }; @@ -970,7 +1287,7 @@ describe('setActiveIssueLabels', () => { { ...state, ...getters }, [ { - type: types.UPDATE_ISSUE_BY_ID, + type: types.UPDATE_BOARD_ITEM_BY_ID, payload, }, ], @@ -990,7 +1307,7 @@ describe('setActiveIssueLabels', () => { describe('setActiveIssueDueDate', () => { const state = { boardItems: { [mockIssue.id]: mockIssue } }; - const getters = { activeIssue: mockIssue }; + const getters = { activeBoardItem: mockIssue }; const testDueDate = '2020-02-20'; const input = { dueDate: testDueDate, @@ -1010,7 +1327,7 @@ describe('setActiveIssueDueDate', () => { }); const payload = { - issueId: getters.activeIssue.id, + itemId: getters.activeBoardItem.id, prop: 'dueDate', value: testDueDate, }; @@ -1021,7 +1338,7 @@ describe('setActiveIssueDueDate', () => { { ...state, ...getters }, [ { - type: types.UPDATE_ISSUE_BY_ID, + type: types.UPDATE_BOARD_ITEM_BY_ID, payload, }, ], @@ -1039,9 +1356,15 @@ describe('setActiveIssueDueDate', () => { }); }); -describe('setActiveIssueSubscribed', () => { - const state = { boardItems: { [mockActiveIssue.id]: mockActiveIssue } }; - const getters = { activeIssue: mockActiveIssue }; +describe('setActiveItemSubscribed', () => { + const state = { + boardItems: { + [mockActiveIssue.id]: mockActiveIssue, + }, + fullPath: 'gitlab-org', + issuableType: 'issue', + }; + const getters = { activeBoardItem: mockActiveIssue, isEpicBoard: false }; const subscribedState = true; const input = { subscribedState, @@ -1051,7 +1374,7 @@ describe('setActiveIssueSubscribed', () => { it('should commit subscribed status', (done) => { jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ data: { - issueSetSubscription: { + updateIssuableSubscription: { issue: { subscribed: subscribedState, }, @@ -1061,18 +1384,18 @@ describe('setActiveIssueSubscribed', () => { }); const payload = { - issueId: getters.activeIssue.id, + itemId: getters.activeBoardItem.id, prop: 'subscribed', value: subscribedState, }; testAction( - actions.setActiveIssueSubscribed, + actions.setActiveItemSubscribed, input, { ...state, ...getters }, [ { - type: types.UPDATE_ISSUE_BY_ID, + type: types.UPDATE_BOARD_ITEM_BY_ID, payload, }, ], @@ -1084,15 +1407,15 @@ describe('setActiveIssueSubscribed', () => { it('throws error if fails', async () => { jest .spyOn(gqlClient, 'mutate') - .mockResolvedValue({ data: { issueSetSubscription: { errors: ['failed mutation'] } } }); + .mockResolvedValue({ data: { updateIssuableSubscription: { errors: ['failed mutation'] } } }); - await expect(actions.setActiveIssueSubscribed({ getters }, input)).rejects.toThrow(Error); + await expect(actions.setActiveItemSubscribed({ getters }, input)).rejects.toThrow(Error); }); }); describe('setActiveIssueMilestone', () => { const state = { boardItems: { [mockIssue.id]: mockIssue } }; - const getters = { activeIssue: mockIssue }; + const getters = { activeBoardItem: mockIssue }; const testMilestone = { ...mockMilestone, id: 'gid://gitlab/Milestone/1', @@ -1115,7 +1438,7 @@ describe('setActiveIssueMilestone', () => { }); const payload = { - issueId: getters.activeIssue.id, + itemId: getters.activeBoardItem.id, prop: 'milestone', value: testMilestone, }; @@ -1126,7 +1449,7 @@ describe('setActiveIssueMilestone', () => { { ...state, ...getters }, [ { - type: types.UPDATE_ISSUE_BY_ID, + type: types.UPDATE_BOARD_ITEM_BY_ID, payload, }, ], @@ -1144,9 +1467,13 @@ describe('setActiveIssueMilestone', () => { }); }); -describe('setActiveIssueTitle', () => { - const state = { boardItems: { [mockIssue.id]: mockIssue } }; - const getters = { activeIssue: mockIssue }; +describe('setActiveItemTitle', () => { + const state = { + boardItems: { [mockIssue.id]: mockIssue }, + issuableType: 'issue', + fullPath: 'path/f', + }; + const getters = { activeBoardItem: mockIssue, isEpicBoard: false }; const testTitle = 'Test Title'; const input = { title: testTitle, @@ -1156,7 +1483,7 @@ describe('setActiveIssueTitle', () => { it('should commit title after setting the issue', (done) => { jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ data: { - updateIssue: { + updateIssuableTitle: { issue: { title: testTitle, }, @@ -1166,18 +1493,18 @@ describe('setActiveIssueTitle', () => { }); const payload = { - issueId: getters.activeIssue.id, + itemId: getters.activeBoardItem.id, prop: 'title', value: testTitle, }; testAction( - actions.setActiveIssueTitle, + actions.setActiveItemTitle, input, { ...state, ...getters }, [ { - type: types.UPDATE_ISSUE_BY_ID, + type: types.UPDATE_BOARD_ITEM_BY_ID, payload, }, ], @@ -1191,7 +1518,7 @@ describe('setActiveIssueTitle', () => { .spyOn(gqlClient, 'mutate') .mockResolvedValue({ data: { updateIssue: { errors: ['failed mutation'] } } }); - await expect(actions.setActiveIssueTitle({ getters }, input)).rejects.toThrow(Error); + await expect(actions.setActiveItemTitle({ getters }, input)).rejects.toThrow(Error); }); }); @@ -1321,7 +1648,7 @@ describe('toggleBoardItemMultiSelection', () => { testAction( actions.toggleBoardItemMultiSelection, boardItem2, - { activeId: mockActiveIssue.id, activeIssue: mockActiveIssue, selectedBoardItems: [] }, + { activeId: mockActiveIssue.id, activeBoardItem: mockActiveIssue, selectedBoardItems: [] }, [ { type: types.ADD_BOARD_ITEM_TO_SELECTION, @@ -1378,6 +1705,51 @@ describe('toggleBoardItem', () => { }); }); +describe('setError', () => { + it('should commit mutation SET_ERROR', () => { + testAction({ + action: actions.setError, + payload: { message: 'mayday' }, + expectedMutations: [ + { + payload: 'mayday', + type: types.SET_ERROR, + }, + ], + }); + }); + + it('should capture error using Sentry when captureError is true', () => { + jest.spyOn(Sentry, 'captureException'); + + const mockError = new Error(); + actions.setError( + { commit: () => {} }, + { + message: 'mayday', + error: mockError, + captureError: true, + }, + ); + + expect(Sentry.captureException).toHaveBeenNthCalledWith(1, mockError); + }); +}); + +describe('unsetError', () => { + it('should commit mutation SET_ERROR with undefined as payload', () => { + testAction({ + action: actions.unsetError, + expectedMutations: [ + { + payload: undefined, + type: types.SET_ERROR, + }, + ], + }); + }); +}); + describe('fetchBacklog', () => { expectNotImplemented(actions.fetchBacklog); }); diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js index 32d73d861bc..6114ba0af5f 100644 --- a/spec/frontend/boards/stores/getters_spec.js +++ b/spec/frontend/boards/stores/getters_spec.js @@ -88,7 +88,7 @@ describe('Boards - Getters', () => { }); }); - describe('activeIssue', () => { + describe('activeBoardItem', () => { it.each` id | expected ${'1'} | ${'issue'} @@ -96,7 +96,7 @@ describe('Boards - Getters', () => { `('returns $expected when $id is passed to state', ({ id, expected }) => { const state = { boardItems: { 1: 'issue' }, activeId: id }; - expect(getters.activeIssue(state)).toEqual(expected); + expect(getters.activeBoardItem(state)).toEqual(expected); }); }); @@ -105,14 +105,14 @@ describe('Boards - Getters', () => { const mockActiveIssue = { referencePath: 'gitlab-org/gitlab-test#1', }; - expect(getters.groupPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual( + expect(getters.groupPathForActiveIssue({}, { activeBoardItem: mockActiveIssue })).toEqual( 'gitlab-org', ); }); it('returns empty string as group path when active issue is an empty object', () => { const mockActiveIssue = {}; - expect(getters.groupPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual(''); + expect(getters.groupPathForActiveIssue({}, { activeBoardItem: mockActiveIssue })).toEqual(''); }); }); @@ -121,14 +121,16 @@ describe('Boards - Getters', () => { const mockActiveIssue = { referencePath: 'gitlab-org/gitlab-test#1', }; - expect(getters.projectPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual( + expect(getters.projectPathForActiveIssue({}, { activeBoardItem: mockActiveIssue })).toEqual( 'gitlab-org/gitlab-test', ); }); it('returns empty string as project path when active issue is an empty object', () => { const mockActiveIssue = {}; - expect(getters.projectPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual(''); + expect(getters.projectPathForActiveIssue({}, { activeBoardItem: mockActiveIssue })).toEqual( + '', + ); }); }); @@ -177,4 +179,31 @@ describe('Boards - Getters', () => { expect(getters.activeGroupProjects(state)).toEqual([mockGroupProject1]); }); }); + + describe('isIssueBoard', () => { + it.each` + issuableType | expected + ${'issue'} | ${true} + ${'epic'} | ${false} + `( + 'returns $expected when issuableType on state is $issuableType', + ({ issuableType, expected }) => { + const state = { + issuableType, + }; + + expect(getters.isIssueBoard(state)).toBe(expected); + }, + ); + }); + + describe('isEpicBoard', () => { + afterEach(() => { + window.gon = { features: {} }; + }); + + it('returns false', () => { + expect(getters.isEpicBoard()).toBe(false); + }); + }); }); diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js index 33897cc0250..af6d439e294 100644 --- a/spec/frontend/boards/stores/mutations_spec.js +++ b/spec/frontend/boards/stores/mutations_spec.js @@ -1,3 +1,4 @@ +import { cloneDeep } from 'lodash'; import { issuableTypes } from '~/boards/constants'; import * as types from '~/boards/stores/mutation_types'; import mutations from '~/boards/stores/mutations'; @@ -9,6 +10,7 @@ import { mockIssue2, mockGroupProjects, labels, + mockList, } from '../mock_data'; const expectNotImplemented = (action) => { @@ -25,6 +27,14 @@ describe('Board Store Mutations', () => { 'gid://gitlab/List/2': mockLists[1], }; + const setBoardsListsState = () => { + state = cloneDeep({ + ...state, + boardItemsByListId: { 'gid://gitlab/List/1': [mockIssue.id] }, + boardLists: { 'gid://gitlab/List/1': mockList }, + }); + }; + beforeEach(() => { state = defaultState(); }); @@ -335,7 +345,7 @@ describe('Board Store Mutations', () => { expectNotImplemented(mutations.REQUEST_ADD_ISSUE); }); - describe('UPDATE_ISSUE_BY_ID', () => { + describe('UPDATE_BOARD_ITEM_BY_ID', () => { const issueId = '1'; const prop = 'id'; const value = '2'; @@ -353,8 +363,8 @@ describe('Board Store Mutations', () => { describe('when the issue is in state', () => { it('updates the property of the correct issue', () => { - mutations.UPDATE_ISSUE_BY_ID(state, { - issueId, + mutations.UPDATE_BOARD_ITEM_BY_ID(state, { + itemId: issueId, prop, value, }); @@ -366,8 +376,8 @@ describe('Board Store Mutations', () => { describe('when the issue is not in state', () => { it('throws an error', () => { expect(() => { - mutations.UPDATE_ISSUE_BY_ID(state, { - issueId: '3', + mutations.UPDATE_BOARD_ITEM_BY_ID(state, { + itemId: '3', prop, value, }); @@ -384,41 +394,7 @@ describe('Board Store Mutations', () => { expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_ERROR); }); - describe('MOVE_ISSUE', () => { - it('updates boardItemsByListId, moving issue between lists', () => { - const listIssues = { - 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id], - 'gid://gitlab/List/2': [], - }; - - const issues = { - 1: mockIssue, - 2: mockIssue2, - }; - - state = { - ...state, - boardItemsByListId: listIssues, - boardLists: initialBoardListsState, - boardItems: issues, - }; - - mutations.MOVE_ISSUE(state, { - originalIssue: mockIssue2, - fromListId: 'gid://gitlab/List/1', - toListId: 'gid://gitlab/List/2', - }); - - const updatedListIssues = { - 'gid://gitlab/List/1': [mockIssue.id], - 'gid://gitlab/List/2': [mockIssue2.id], - }; - - expect(state.boardItemsByListId).toEqual(updatedListIssues); - }); - }); - - describe('MOVE_ISSUE_SUCCESS', () => { + describe('MUTATE_ISSUE_SUCCESS', () => { it('updates issue in issues state', () => { const issues = { 436: { id: rawIssue.id }, @@ -429,7 +405,7 @@ describe('Board Store Mutations', () => { boardItems: issues, }; - mutations.MOVE_ISSUE_SUCCESS(state, { + mutations.MUTATE_ISSUE_SUCCESS(state, { issue: rawIssue, }); @@ -437,33 +413,24 @@ describe('Board Store Mutations', () => { }); }); - describe('MOVE_ISSUE_FAILURE', () => { - it('updates boardItemsByListId, reverting moving issue between lists, and sets error message', () => { - const listIssues = { - 'gid://gitlab/List/1': [mockIssue.id], - 'gid://gitlab/List/2': [mockIssue2.id], - }; + describe('UPDATE_BOARD_ITEM', () => { + it('updates the given issue in state.boardItems', () => { + const updatedIssue = { id: 'some_gid', foo: 'bar' }; + state = { boardItems: { some_gid: { id: 'some_gid' } } }; - state = { - ...state, - boardItemsByListId: listIssues, - boardLists: initialBoardListsState, - }; + mutations.UPDATE_BOARD_ITEM(state, updatedIssue); - mutations.MOVE_ISSUE_FAILURE(state, { - originalIssue: mockIssue2, - fromListId: 'gid://gitlab/List/1', - toListId: 'gid://gitlab/List/2', - originalIndex: 1, - }); + expect(state.boardItems.some_gid).toEqual(updatedIssue); + }); + }); - const updatedListIssues = { - 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id], - 'gid://gitlab/List/2': [], - }; + describe('REMOVE_BOARD_ITEM', () => { + it('removes the given issue from state.boardItems', () => { + state = { boardItems: { some_gid: {}, some_gid2: {} } }; + + mutations.REMOVE_BOARD_ITEM(state, 'some_gid'); - expect(state.boardItemsByListId).toEqual(updatedListIssues); - expect(state.error).toEqual('An error occurred while moving the issue. Please try again.'); + expect(state.boardItems).toEqual({ some_gid2: {} }); }); }); @@ -479,85 +446,89 @@ describe('Board Store Mutations', () => { expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_ERROR); }); - describe('CREATE_ISSUE_FAILURE', () => { - it('sets error message on state', () => { - mutations.CREATE_ISSUE_FAILURE(state); + describe('ADD_BOARD_ITEM_TO_LIST', () => { + beforeEach(() => { + setBoardsListsState(); + }); + + it.each([ + [ + 'at position 0 by default', + { + payload: { + itemId: mockIssue2.id, + listId: mockList.id, + }, + listState: [mockIssue2.id, mockIssue.id], + }, + ], + [ + 'at a given position', + { + payload: { + itemId: mockIssue2.id, + listId: mockList.id, + atIndex: 1, + }, + listState: [mockIssue.id, mockIssue2.id], + }, + ], + [ + "below the issue with id of 'moveBeforeId'", + { + payload: { + itemId: mockIssue2.id, + listId: mockList.id, + moveBeforeId: mockIssue.id, + }, + listState: [mockIssue.id, mockIssue2.id], + }, + ], + [ + "above the issue with id of 'moveAfterId'", + { + payload: { + itemId: mockIssue2.id, + listId: mockList.id, + moveAfterId: mockIssue.id, + }, + listState: [mockIssue2.id, mockIssue.id], + }, + ], + ])(`inserts an item into a list %s`, (_, { payload, listState }) => { + mutations.ADD_BOARD_ITEM_TO_LIST(state, payload); - expect(state.error).toBe('An error occurred while creating the issue. Please try again.'); + expect(state.boardItemsByListId[payload.listId]).toEqual(listState); }); - }); - - describe('ADD_ISSUE_TO_LIST', () => { - it('adds issue to issues state and issue id in list in boardItemsByListId', () => { - const listIssues = { - 'gid://gitlab/List/1': [mockIssue.id], - }; - const issues = { - 1: mockIssue, - }; - - state = { - ...state, - boardItemsByListId: listIssues, - boardItems: issues, - boardLists: initialBoardListsState, - }; + it("updates the list's items count", () => { expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1); - mutations.ADD_ISSUE_TO_LIST(state, { list: mockLists[0], issue: mockIssue2 }); + mutations.ADD_BOARD_ITEM_TO_LIST(state, { + itemId: mockIssue2.id, + listId: mockList.id, + }); - expect(state.boardItemsByListId['gid://gitlab/List/1']).toContain(mockIssue2.id); - expect(state.boardItems[mockIssue2.id]).toEqual(mockIssue2); expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(2); }); }); - describe('ADD_ISSUE_TO_LIST_FAILURE', () => { - it('removes issue id from list in boardItemsByListId and sets error message', () => { - const listIssues = { - 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id], - }; - const issues = { - 1: mockIssue, - 2: mockIssue2, - }; - - state = { - ...state, - boardItemsByListId: listIssues, - boardItems: issues, - boardLists: initialBoardListsState, - }; - - mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issueId: mockIssue2.id }); - - expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id); - expect(state.error).toBe('An error occurred while creating the issue. Please try again.'); + describe('REMOVE_BOARD_ITEM_FROM_LIST', () => { + beforeEach(() => { + setBoardsListsState(); }); - }); - describe('REMOVE_ISSUE_FROM_LIST', () => { - it('removes issue id from list in boardItemsByListId and deletes issue from state', () => { - const listIssues = { - 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id], - }; - const issues = { - 1: mockIssue, - 2: mockIssue2, - }; - - state = { - ...state, - boardItemsByListId: listIssues, - boardItems: issues, - boardLists: initialBoardListsState, - }; + it("removes an item from a list and updates the list's items count", () => { + expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1); + expect(state.boardItemsByListId['gid://gitlab/List/1']).toContain(mockIssue.id); - mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issueId: mockIssue2.id }); + mutations.REMOVE_BOARD_ITEM_FROM_LIST(state, { + itemId: mockIssue.id, + listId: mockList.id, + }); - expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id); - expect(state.boardItems).not.toContain(mockIssue2); + expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue.id); + expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(0); }); }); @@ -666,4 +637,14 @@ describe('Board Store Mutations', () => { expect(state.selectedBoardItems).toEqual([]); }); }); + + describe('SET_ERROR', () => { + it('Should set error state', () => { + state.error = undefined; + + mutations[types.SET_ERROR](state, 'mayday'); + + expect(state.error).toBe('mayday'); + }); + }); }); |