diff options
Diffstat (limited to 'spec/frontend/boards/stores')
-rw-r--r-- | spec/frontend/boards/stores/actions_spec.js | 323 | ||||
-rw-r--r-- | spec/frontend/boards/stores/getters_spec.js | 30 | ||||
-rw-r--r-- | spec/frontend/boards/stores/mutations_spec.js | 114 |
3 files changed, 407 insertions, 60 deletions
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 78e70161121..4d529580a7a 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -2,17 +2,21 @@ import testAction from 'helpers/vuex_action_helper'; import { mockListsWithModel, mockLists, + mockListsById, mockIssue, mockIssueWithModel, mockIssue2WithModel, rawIssue, mockIssues, labels, + mockActiveIssue, } from '../mock_data'; import actions, { gqlClient } from '~/boards/stores/actions'; import * as types from '~/boards/stores/mutation_types'; -import { inactiveId, ListType } from '~/boards/constants'; +import { inactiveId } from '~/boards/constants'; import issueMoveListMutation from '~/boards/queries/issue_move_list.mutation.graphql'; +import destroyBoardListMutation from '~/boards/queries/board_list_destroy.mutation.graphql'; +import updateAssignees from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql'; import { fullBoardId, formatListIssues, formatBoardLists } from '~/boards/boards_util'; const expectNotImplemented = action => { @@ -116,7 +120,7 @@ describe('fetchLists', () => { payload: formattedLists, }, ], - [{ type: 'showWelcomeList' }], + [{ type: 'generateDefaultLists' }], done, ); }); @@ -146,14 +150,15 @@ describe('fetchLists', () => { payload: formattedLists, }, ], - [{ type: 'createList', payload: { backlog: true } }, { type: 'showWelcomeList' }], + [{ type: 'createList', payload: { backlog: true } }, { type: 'generateDefaultLists' }], done, ); }); }); -describe('showWelcomeList', () => { - it('should dispatch addList action', done => { +describe('generateDefaultLists', () => { + let store; + beforeEach(() => { const state = { endpoints: { fullPath: 'gitlab-org', boardId: '1' }, boardType: 'group', @@ -161,26 +166,19 @@ describe('showWelcomeList', () => { boardLists: [{ type: 'backlog' }, { type: 'closed' }], }; - const blankList = { - id: 'blank', - listType: ListType.blank, - title: 'Welcome to your issue board!', - position: 0, - }; - - testAction( - actions.showWelcomeList, - {}, + store = { + commit: jest.fn(), + dispatch: jest.fn(() => Promise.resolve()), state, - [], - [{ type: 'addList', payload: blankList }], - done, - ); + }; }); -}); -describe('generateDefaultLists', () => { - expectNotImplemented(actions.generateDefaultLists); + it('should dispatch fetchLabels', () => { + return actions.generateDefaultLists(store).then(() => { + expect(store.dispatch.mock.calls[0]).toEqual(['fetchLabels', 'to do']); + expect(store.dispatch.mock.calls[1]).toEqual(['fetchLabels', 'doing']); + }); + }); }); describe('createList', () => { @@ -323,8 +321,82 @@ describe('updateList', () => { }); }); -describe('deleteList', () => { - expectNotImplemented(actions.deleteList); +describe('removeList', () => { + let state; + const list = mockLists[0]; + const listId = list.id; + const mutationVariables = { + mutation: destroyBoardListMutation, + variables: { + listId, + }, + }; + + beforeEach(() => { + state = { + boardLists: mockListsById, + }; + }); + + afterEach(() => { + state = null; + }); + + it('optimistically deletes the list', () => { + const commit = jest.fn(); + + actions.removeList({ commit, state }, listId); + + expect(commit.mock.calls).toEqual([[types.REMOVE_LIST, listId]]); + }); + + it('keeps the updated list if remove succeeds', async () => { + const commit = jest.fn(); + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { + destroyBoardList: { + errors: [], + }, + }, + }); + + await actions.removeList({ commit, state }, listId); + + expect(gqlClient.mutate).toHaveBeenCalledWith(mutationVariables); + expect(commit.mock.calls).toEqual([[types.REMOVE_LIST, listId]]); + }); + + 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); + + expect(gqlClient.mutate).toHaveBeenCalledWith(mutationVariables); + expect(commit.mock.calls).toEqual([ + [types.REMOVE_LIST, listId], + [types.REMOVE_LIST_FAILURE, mockListsById], + ]); + }); + + it('restores the list if update response has errors', async () => { + const commit = jest.fn(); + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { + destroyBoardList: { + errors: ['update failed, ID invalid'], + }, + }, + }); + + await actions.removeList({ commit, state }, listId); + + expect(gqlClient.mutate).toHaveBeenCalledWith(mutationVariables); + expect(commit.mock.calls).toEqual([ + [types.REMOVE_LIST, listId], + [types.REMOVE_LIST_FAILURE, mockListsById], + ]); + }); }); describe('fetchIssuesForList', () => { @@ -560,41 +632,106 @@ describe('moveIssue', () => { }); }); -describe('createNewIssue', () => { - expectNotImplemented(actions.createNewIssue); +describe('setAssignees', () => { + const node = { username: 'name' }; + const name = 'username'; + const projectPath = 'h/h'; + const refPath = `${projectPath}#3`; + const iid = '1'; + + beforeEach(() => { + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { issueSetAssignees: { issue: { assignees: { nodes: [{ ...node }] } } } }, + }); + }); + + it('calls mutate with the correct values', async () => { + await actions.setAssignees( + { commit: () => {}, getters: { activeIssue: { iid, referencePath: refPath } } }, + [name], + ); + + expect(gqlClient.mutate).toHaveBeenCalledWith({ + mutation: updateAssignees, + variables: { iid, assigneeUsernames: [name], projectPath }, + }); + }); + + it('calls the correct mutation with the correct values', done => { + testAction( + actions.setAssignees, + {}, + { activeIssue: { iid, referencePath: refPath }, commit: () => {} }, + [ + { + type: 'UPDATE_ISSUE_BY_ID', + payload: { prop: 'assignees', issueId: undefined, value: [node] }, + }, + ], + [], + done, + ); + }); }); -describe('addListIssue', () => { - it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', done => { - const payload = { - list: mockLists[0], - issue: mockIssue, - position: 0, - }; +describe('createNewIssue', () => { + const state = { + boardType: 'group', + endpoints: { + fullPath: 'gitlab-org/gitlab', + }, + }; + + 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); + }); + + it('should commit CREATE_ISSUE_FAILURE mutation when API returns an error', done => { + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { + createIssue: { + issue: {}, + errors: [{ foo: 'bar' }], + }, + }, + }); + + const payload = mockIssue; testAction( - actions.addListIssue, + actions.createNewIssue, payload, - {}, - [{ type: types.ADD_ISSUE_TO_LIST, payload }], + state, + [{ type: types.CREATE_ISSUE_FAILURE }], [], done, ); }); }); -describe('addListIssueFailure', () => { - it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', done => { +describe('addListIssue', () => { + it('should commit ADD_ISSUE_TO_LIST mutation', done => { const payload = { list: mockLists[0], issue: mockIssue, + position: 0, }; testAction( - actions.addListIssueFailure, + actions.addListIssue, payload, {}, - [{ type: types.ADD_ISSUE_TO_LIST_FAILURE, payload }], + [{ type: types.ADD_ISSUE_TO_LIST, payload }], [], done, ); @@ -603,7 +740,7 @@ describe('addListIssueFailure', () => { describe('setActiveIssueLabels', () => { const state = { issues: { [mockIssue.id]: mockIssue } }; - const getters = { getActiveIssue: mockIssue }; + const getters = { activeIssue: mockIssue }; const testLabelIds = labels.map(label => label.id); const input = { addLabelIds: testLabelIds, @@ -617,7 +754,7 @@ describe('setActiveIssueLabels', () => { .mockResolvedValue({ data: { updateIssue: { issue: { labels: { nodes: labels } } } } }); const payload = { - issueId: getters.getActiveIssue.id, + issueId: getters.activeIssue.id, prop: 'labels', value: labels, }; @@ -646,6 +783,108 @@ describe('setActiveIssueLabels', () => { }); }); +describe('setActiveIssueDueDate', () => { + const state = { issues: { [mockIssue.id]: mockIssue } }; + const getters = { activeIssue: 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 = { + issueId: getters.activeIssue.id, + prop: 'dueDate', + value: testDueDate, + }; + + testAction( + actions.setActiveIssueDueDate, + input, + { ...state, ...getters }, + [ + { + type: types.UPDATE_ISSUE_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('setActiveIssueSubscribed', () => { + const state = { issues: { [mockActiveIssue.id]: mockActiveIssue } }; + const getters = { activeIssue: mockActiveIssue }; + const subscribedState = true; + const input = { + subscribedState, + projectPath: 'gitlab-org/gitlab-test', + }; + + it('should commit subscribed status', done => { + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { + issueSetSubscription: { + issue: { + subscribed: subscribedState, + }, + errors: [], + }, + }, + }); + + const payload = { + issueId: getters.activeIssue.id, + prop: 'subscribed', + value: subscribedState, + }; + + testAction( + actions.setActiveIssueSubscribed, + input, + { ...state, ...getters }, + [ + { + type: types.UPDATE_ISSUE_BY_ID, + payload, + }, + ], + [], + done, + ); + }); + + it('throws error if fails', async () => { + jest + .spyOn(gqlClient, 'mutate') + .mockResolvedValue({ data: { issueSetSubscription: { errors: ['failed mutation'] } } }); + + await expect(actions.setActiveIssueSubscribed({ getters }, input)).rejects.toThrow(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 b987080abab..64025726dd1 100644 --- a/spec/frontend/boards/stores/getters_spec.js +++ b/spec/frontend/boards/stores/getters_spec.js @@ -10,13 +10,13 @@ import { } from '../mock_data'; describe('Boards - Getters', () => { - describe('getLabelToggleState', () => { + describe('labelToggleState', () => { it('should return "on" when isShowingLabels is true', () => { const state = { isShowingLabels: true, }; - expect(getters.getLabelToggleState(state)).toBe('on'); + expect(getters.labelToggleState(state)).toBe('on'); }); it('should return "off" when isShowingLabels is false', () => { @@ -24,7 +24,7 @@ describe('Boards - Getters', () => { isShowingLabels: false, }; - expect(getters.getLabelToggleState(state)).toBe('off'); + expect(getters.labelToggleState(state)).toBe('off'); }); }); @@ -112,7 +112,7 @@ describe('Boards - Getters', () => { }); }); - describe('getActiveIssue', () => { + describe('activeIssue', () => { it.each` id | expected ${'1'} | ${'issue'} @@ -120,11 +120,27 @@ describe('Boards - Getters', () => { `('returns $expected when $id is passed to state', ({ id, expected }) => { const state = { issues: { '1': 'issue' }, activeId: id }; - expect(getters.getActiveIssue(state)).toEqual(expected); + expect(getters.activeIssue(state)).toEqual(expected); }); }); - describe('getIssues', () => { + describe('projectPathByIssueId', () => { + it('returns project path for the active issue', () => { + const mockActiveIssue = { + referencePath: 'gitlab-org/gitlab-test#1', + }; + expect(getters.projectPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual( + 'gitlab-org/gitlab-test', + ); + }); + + it('returns empty string as project when active issue is an empty object', () => { + const mockActiveIssue = {}; + expect(getters.projectPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual(''); + }); + }); + + describe('getIssuesByList', () => { const boardsState = { issuesByListId: mockIssuesByListId, issues, @@ -132,7 +148,7 @@ describe('Boards - Getters', () => { it('returns issues for a given listId', () => { const getIssueById = issueId => [mockIssue, mockIssue2].find(({ id }) => id === issueId); - expect(getters.getIssues(boardsState, { getIssueById })('gid://gitlab/List/2')).toEqual( + expect(getters.getIssuesByList(boardsState, { getIssueById })('gid://gitlab/List/2')).toEqual( mockIssues, ); }); diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js index 6e53f184bb3..e1e57a8fd43 100644 --- a/spec/frontend/boards/stores/mutations_spec.js +++ b/spec/frontend/boards/stores/mutations_spec.js @@ -82,7 +82,7 @@ describe('Board Store Mutations', () => { mutations.SET_ACTIVE_ID(state, expected); }); - it('updates aciveListId to be the value that is passed', () => { + it('updates activeListId to be the value that is passed', () => { expect(state.activeId).toBe(expected.id); }); @@ -101,6 +101,34 @@ describe('Board Store Mutations', () => { }); }); + describe('CREATE_LIST_FAILURE', () => { + it('sets error message', () => { + mutations.CREATE_LIST_FAILURE(state); + + expect(state.error).toEqual('An error occurred while creating the list. Please try again.'); + }); + }); + + describe('RECEIVE_LABELS_FAILURE', () => { + it('sets error message', () => { + mutations.RECEIVE_LABELS_FAILURE(state); + + expect(state.error).toEqual( + 'An error occurred while fetching labels. Please reload the page.', + ); + }); + }); + + describe('GENERATE_DEFAULT_LISTS_FAILURE', () => { + it('sets error message', () => { + mutations.GENERATE_DEFAULT_LISTS_FAILURE(state); + + expect(state.error).toEqual( + 'An error occurred while generating lists. Please reload the page.', + ); + }); + }); + describe('REQUEST_ADD_LIST', () => { expectNotImplemented(mutations.REQUEST_ADD_LIST); }); @@ -156,16 +184,43 @@ describe('Board Store Mutations', () => { }); }); - describe('REQUEST_REMOVE_LIST', () => { - expectNotImplemented(mutations.REQUEST_REMOVE_LIST); - }); + describe('REMOVE_LIST', () => { + it('removes list from boardLists', () => { + const [list, secondList] = mockListsWithModel; + const expected = { + [secondList.id]: secondList, + }; + state = { + ...state, + boardLists: { ...initialBoardListsState }, + }; - describe('RECEIVE_REMOVE_LIST_SUCCESS', () => { - expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_SUCCESS); + mutations[types.REMOVE_LIST](state, list.id); + + expect(state.boardLists).toEqual(expected); + }); }); - describe('RECEIVE_REMOVE_LIST_ERROR', () => { - expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR); + describe('REMOVE_LIST_FAILURE', () => { + it('restores lists from backup', () => { + const backupLists = { ...initialBoardListsState }; + + mutations[types.REMOVE_LIST_FAILURE](state, backupLists); + + expect(state.boardLists).toEqual(backupLists); + }); + + it('sets error state', () => { + const backupLists = { ...initialBoardListsState }; + state = { + ...state, + error: undefined, + }; + + mutations[types.REMOVE_LIST_FAILURE](state, backupLists); + + expect(state.error).toEqual('An error occurred while removing the list. Please try again.'); + }); }); describe('RESET_ISSUES', () => { @@ -387,6 +442,14 @@ 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); + + expect(state.error).toBe('An error occurred while creating the issue. Please try again.'); + }); + }); + describe('ADD_ISSUE_TO_LIST', () => { it('adds issue to issues state and issue id in list in issuesByListId', () => { const listIssues = { @@ -400,17 +463,45 @@ describe('Board Store Mutations', () => { ...state, issuesByListId: listIssues, issues, + boardLists: initialBoardListsState, }; - mutations.ADD_ISSUE_TO_LIST(state, { list: mockLists[0], issue: mockIssue2 }); + expect(state.boardLists['gid://gitlab/List/1'].issuesSize).toBe(1); + + mutations.ADD_ISSUE_TO_LIST(state, { list: mockListsWithModel[0], issue: mockIssue2 }); expect(state.issuesByListId['gid://gitlab/List/1']).toContain(mockIssue2.id); expect(state.issues[mockIssue2.id]).toEqual(mockIssue2); + expect(state.boardLists['gid://gitlab/List/1'].issuesSize).toBe(2); }); }); describe('ADD_ISSUE_TO_LIST_FAILURE', () => { - it('removes issue id from list in issuesByListId', () => { + it('removes issue id from list in issuesByListId and sets error message', () => { + const listIssues = { + 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id], + }; + const issues = { + '1': mockIssue, + '2': mockIssue2, + }; + + state = { + ...state, + issuesByListId: listIssues, + issues, + boardLists: initialBoardListsState, + }; + + mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issueId: mockIssue2.id }); + + expect(state.issuesByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id); + expect(state.error).toBe('An error occurred while creating the issue. Please try again.'); + }); + }); + + describe('REMOVE_ISSUE_FROM_LIST', () => { + it('removes issue id from list in issuesByListId and deletes issue from state', () => { const listIssues = { 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id], }; @@ -426,9 +517,10 @@ describe('Board Store Mutations', () => { boardLists: initialBoardListsState, }; - mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issue: mockIssue2 }); + mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issueId: mockIssue2.id }); expect(state.issuesByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id); + expect(state.issues).not.toContain(mockIssue2); }); }); |