gitlab-org--gitlab-foss/spec/frontend/boards/stores/actions_spec.js

1012 lines
23 KiB
JavaScript

import testAction from 'helpers/vuex_action_helper';
import {
mockLists,
mockListsById,
mockIssue,
mockIssue2,
rawIssue,
mockIssues,
mockMilestone,
labels,
mockActiveIssue,
} from '../mock_data';
import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types';
import { inactiveId } from '~/boards/constants';
import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql';
import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql';
import updateAssignees from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql';
import { fullBoardId, formatListIssues, formatBoardLists } from '~/boards/boards_util';
import createFlash from '~/flash';
jest.mock('~/flash');
const expectNotImplemented = (action) => {
it('is not implemented', () => {
expect(action).toThrow(new Error('Not implemented!'));
});
};
// We need this helper to make sure projectPath is including
// subgroups when the movIssue action is called.
const getProjectPath = (path) => path.split('#')[0];
beforeEach(() => {
window.gon = { features: {} };
});
describe('setInitialBoardData', () => {
it('sets data object', () => {
const mockData = {
foo: 'bar',
bar: 'baz',
};
return testAction(
actions.setInitialBoardData,
mockData,
{},
[{ type: types.SET_INITIAL_BOARD_DATA, payload: mockData }],
[],
);
});
});
describe('setFilters', () => {
it('should commit mutation SET_FILTERS', (done) => {
const state = {
filters: {},
};
const filters = { labelName: 'label' };
testAction(
actions.setFilters,
filters,
state,
[{ type: types.SET_FILTERS, payload: filters }],
[],
done,
);
});
});
describe('performSearch', () => {
it('should dispatch setFilters action', (done) => {
testAction(actions.performSearch, {}, {}, [], [{ type: 'setFilters', payload: {} }], done);
});
it('should dispatch setFilters, fetchLists and resetIssues action when graphqlBoardLists FF is on', (done) => {
window.gon = { features: { graphqlBoardLists: true } };
testAction(
actions.performSearch,
{},
{},
[],
[{ type: 'setFilters', payload: {} }, { type: 'fetchLists' }, { type: 'resetIssues' }],
done,
);
});
});
describe('setActiveId', () => {
it('should commit mutation SET_ACTIVE_ID', (done) => {
const state = {
activeId: inactiveId,
};
testAction(
actions.setActiveId,
{ id: 1, sidebarType: 'something' },
state,
[{ type: types.SET_ACTIVE_ID, payload: { id: 1, sidebarType: 'something' } }],
[],
done,
);
});
});
describe('fetchLists', () => {
const state = {
fullPath: 'gitlab-org',
boardId: '1',
filterParams: {},
boardType: 'group',
};
let queryResponse = {
data: {
group: {
board: {
hideBacklogList: true,
lists: {
nodes: [mockLists[1]],
},
},
},
},
};
const formattedLists = formatBoardLists(queryResponse.data.group.board.lists);
it('should commit mutations RECEIVE_BOARD_LISTS_SUCCESS on success', (done) => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
testAction(
actions.fetchLists,
{},
state,
[
{
type: types.RECEIVE_BOARD_LISTS_SUCCESS,
payload: formattedLists,
},
],
[],
done,
);
});
it('dispatch createList action when backlog list does not exist and is not hidden', (done) => {
queryResponse = {
data: {
group: {
board: {
hideBacklogList: false,
lists: {
nodes: [mockLists[1]],
},
},
},
},
};
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
testAction(
actions.fetchLists,
{},
state,
[
{
type: types.RECEIVE_BOARD_LISTS_SUCCESS,
payload: formattedLists,
},
],
[{ type: 'createList', payload: { backlog: true } }],
done,
);
});
});
describe('createList', () => {
it('should dispatch addList action when creating backlog list', (done) => {
const backlogList = {
id: 'gid://gitlab/List/1',
listType: 'backlog',
title: 'Open',
position: 0,
};
jest.spyOn(gqlClient, 'mutate').mockReturnValue(
Promise.resolve({
data: {
boardListCreate: {
list: backlogList,
errors: [],
},
},
}),
);
const state = {
fullPath: 'gitlab-org',
boardId: '1',
boardType: 'group',
disabled: false,
boardLists: [{ type: 'closed' }],
};
testAction(
actions.createList,
{ backlog: true },
state,
[],
[{ type: 'addList', payload: backlogList }],
done,
);
});
it('should commit CREATE_LIST_FAILURE mutation when API returns an error', (done) => {
jest.spyOn(gqlClient, 'mutate').mockReturnValue(
Promise.resolve({
data: {
boardListCreate: {
list: {},
errors: [{ foo: 'bar' }],
},
},
}),
);
const state = {
fullPath: 'gitlab-org',
boardId: '1',
boardType: 'group',
disabled: false,
boardLists: [{ type: 'closed' }],
};
testAction(
actions.createList,
{ backlog: true },
state,
[{ type: types.CREATE_LIST_FAILURE }],
[],
done,
);
});
});
describe('moveList', () => {
it('should commit MOVE_LIST mutation and dispatch updateList action', (done) => {
const initialBoardListsState = {
'gid://gitlab/List/1': mockLists[0],
'gid://gitlab/List/2': mockLists[1],
};
const state = {
fullPath: 'gitlab-org',
boardId: '1',
boardType: 'group',
disabled: false,
boardLists: initialBoardListsState,
};
testAction(
actions.moveList,
{
listId: 'gid://gitlab/List/1',
replacedListId: 'gid://gitlab/List/2',
newIndex: 1,
adjustmentValue: 1,
},
state,
[
{
type: types.MOVE_LIST,
payload: { movedList: mockLists[0], listAtNewIndex: mockLists[1] },
},
],
[
{
type: 'updateList',
payload: {
listId: 'gid://gitlab/List/1',
position: 0,
backupList: initialBoardListsState,
},
},
],
done,
);
});
it('should not commit MOVE_LIST or dispatch updateList if listId and replacedListId are the same', () => {
const initialBoardListsState = {
'gid://gitlab/List/1': mockLists[0],
'gid://gitlab/List/2': mockLists[1],
};
const state = {
fullPath: 'gitlab-org',
boardId: '1',
boardType: 'group',
disabled: false,
boardLists: initialBoardListsState,
};
testAction(
actions.moveList,
{
listId: 'gid://gitlab/List/1',
replacedListId: 'gid://gitlab/List/1',
newIndex: 1,
adjustmentValue: 1,
},
state,
[],
[],
);
});
});
describe('updateList', () => {
it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', (done) => {
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
updateBoardList: {
list: {},
errors: [{ foo: 'bar' }],
},
},
});
const state = {
fullPath: 'gitlab-org',
boardId: '1',
boardType: 'group',
disabled: false,
boardLists: [{ type: 'closed' }],
};
testAction(
actions.updateList,
{ listId: 'gid://gitlab/List/1', position: 1 },
state,
[{ type: types.UPDATE_LIST_FAILURE }],
[],
done,
);
});
});
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', () => {
const listId = mockLists[0].id;
const state = {
fullPath: 'gitlab-org',
boardId: '1',
filterParams: {},
boardType: 'group',
};
const mockIssuesNodes = mockIssues.map((issue) => ({ node: issue }));
const pageInfo = {
endCursor: '',
hasNextPage: false,
};
const queryResponse = {
data: {
group: {
board: {
lists: {
nodes: [
{
id: listId,
issues: {
edges: mockIssuesNodes,
pageInfo,
},
},
],
},
},
},
},
};
const formattedIssues = formatListIssues(queryResponse.data.group.board.lists);
const listPageInfo = {
[listId]: pageInfo,
};
it('should commit mutations REQUEST_ISSUES_FOR_LIST and RECEIVE_ISSUES_FOR_LIST_SUCCESS on success', (done) => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
testAction(
actions.fetchIssuesForList,
{ listId },
state,
[
{
type: types.REQUEST_ISSUES_FOR_LIST,
payload: { listId, fetchNext: false },
},
{
type: types.RECEIVE_ISSUES_FOR_LIST_SUCCESS,
payload: { listIssues: formattedIssues, listPageInfo, listId },
},
],
[],
done,
);
});
it('should commit mutations REQUEST_ISSUES_FOR_LIST and RECEIVE_ISSUES_FOR_LIST_FAILURE on failure', (done) => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(Promise.reject());
testAction(
actions.fetchIssuesForList,
{ listId },
state,
[
{
type: types.REQUEST_ISSUES_FOR_LIST,
payload: { listId, fetchNext: false },
},
{ type: types.RECEIVE_ISSUES_FOR_LIST_FAILURE, payload: listId },
],
[],
done,
);
});
});
describe('resetIssues', () => {
it('commits RESET_ISSUES mutation', () => {
return testAction(actions.resetIssues, {}, {}, [{ type: types.RESET_ISSUES }], []);
});
});
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,
issuesByListId: listIssues,
issues,
};
it('should commit MOVE_ISSUE mutation and MOVE_ISSUE_SUCCESS mutation when successful', (done) => {
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
issueMoveList: {
issue: rawIssue,
errors: [],
},
},
});
testAction(
actions.moveIssue,
{
issueId: '436',
issueIid: mockIssue.iid,
issuePath: mockIssue.referencePath,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
},
state,
[
{
type: types.MOVE_ISSUE,
payload: {
originalIssue: mockIssue,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
},
},
{
type: types.MOVE_ISSUE_SUCCESS,
payload: { issue: rawIssue },
},
],
[],
done,
);
});
it('calls mutate with the correct variables', () => {
const mutationVariables = {
mutation: issueMoveListMutation,
variables: {
projectPath: getProjectPath(mockIssue.referencePath),
boardId: fullBoardId(state.boardId),
iid: mockIssue.iid,
fromListId: 1,
toListId: 2,
moveBeforeId: undefined,
moveAfterId: undefined,
},
};
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
issueMoveList: {
issue: rawIssue,
errors: [],
},
},
});
actions.moveIssue(
{ state, commit: () => {} },
{
issueId: mockIssue.id,
issueIid: mockIssue.iid,
issuePath: mockIssue.referencePath,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
},
);
expect(gqlClient.mutate).toHaveBeenCalledWith(mutationVariables);
});
it('should commit MOVE_ISSUE mutation and MOVE_ISSUE_FAILURE mutation when unsuccessful', (done) => {
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
issueMoveList: {
issue: {},
errors: [{ foo: 'bar' }],
},
},
});
testAction(
actions.moveIssue,
{
issueId: '436',
issueIid: mockIssue.iid,
issuePath: mockIssue.referencePath,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
},
state,
[
{
type: types.MOVE_ISSUE,
payload: {
originalIssue: mockIssue,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
},
},
{
type: types.MOVE_ISSUE_FAILURE,
payload: {
originalIssue: mockIssue,
fromListId: 'gid://gitlab/List/1',
toListId: 'gid://gitlab/List/2',
originalIndex: 0,
},
},
],
[],
done,
);
});
});
describe('setAssignees', () => {
const node = { username: 'name' };
const name = 'username';
const projectPath = 'h/h';
const refPath = `${projectPath}#3`;
const iid = '1';
describe('when succeeds', () => {
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: types.SET_ASSIGNEE_LOADING, payload: true },
{
type: 'UPDATE_ISSUE_BY_ID',
payload: { prop: 'assignees', issueId: undefined, value: [node] },
},
{ type: types.SET_ASSIGNEE_LOADING, payload: false },
],
[],
done,
);
});
});
describe('when fails', () => {
beforeEach(() => {
jest.spyOn(gqlClient, 'mutate').mockRejectedValue();
});
it('calls createFlash', async () => {
await actions.setAssignees({
commit: () => {},
getters: { activeIssue: { iid, referencePath: refPath } },
});
expect(createFlash).toHaveBeenCalledWith({
message: 'An error occurred while updating assignees.',
});
});
});
});
describe('createNewIssue', () => {
const state = {
boardType: 'group',
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.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('setActiveIssueLabels', () => {
const state = { issues: { [mockIssue.id]: mockIssue } };
const getters = { activeIssue: mockIssue };
const testLabelIds = labels.map((label) => label.id);
const input = {
addLabelIds: testLabelIds,
removeLabelIds: [],
projectPath: 'h/b',
};
it('should assign labels on success', (done) => {
jest
.spyOn(gqlClient, 'mutate')
.mockResolvedValue({ data: { updateIssue: { issue: { labels: { nodes: labels } } } } });
const payload = {
issueId: getters.activeIssue.id,
prop: 'labels',
value: labels,
};
testAction(
actions.setActiveIssueLabels,
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.setActiveIssueLabels({ getters }, input)).rejects.toThrow(Error);
});
});
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('setActiveIssueMilestone', () => {
const state = { issues: { [mockIssue.id]: mockIssue } };
const getters = { activeIssue: 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 = {
issueId: getters.activeIssue.id,
prop: 'milestone',
value: testMilestone,
};
testAction(
actions.setActiveIssueMilestone,
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.setActiveIssueMilestone({ getters }, input)).rejects.toThrow(Error);
});
});
describe('fetchBacklog', () => {
expectNotImplemented(actions.fetchBacklog);
});
describe('bulkUpdateIssues', () => {
expectNotImplemented(actions.bulkUpdateIssues);
});
describe('fetchIssue', () => {
expectNotImplemented(actions.fetchIssue);
});
describe('toggleIssueSubscription', () => {
expectNotImplemented(actions.toggleIssueSubscription);
});
describe('showPage', () => {
expectNotImplemented(actions.showPage);
});
describe('toggleEmptyState', () => {
expectNotImplemented(actions.toggleEmptyState);
});