Commit 6a03f079 authored by Eulyeon Ko's avatar Eulyeon Ko Committed by Eulyeon Ko

Add spec with mock apollo client

Use custom cache identifier for Requirement type
when intializing mock apollo client.
parent 8afb27b4
......@@ -725,6 +725,7 @@ export default {
@drawer-close="handleNewRequirementCancel"
/>
<requirement-edit-form
data-testid="edit-form"
:drawer-open="showRequirementViewDrawer"
:requirement="editedRequirement"
:enable-requirement-edit="enableRequirementEdit"
......
import { GlPagination } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlPagination, GlIcon } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import VueApollo from 'vue-apollo';
import RequirementItem from 'ee/requirements/components/requirement_item.vue';
import RequirementStatusBadge from 'ee/requirements/components/requirement_status_badge.vue';
import RequirementsEmptyState from 'ee/requirements/components/requirements_empty_state.vue';
import RequirementsLoading from 'ee/requirements/components/requirements_loading.vue';
import RequirementsRoot from 'ee/requirements/components/requirements_root.vue';
import RequirementsTabs from 'ee/requirements/components/requirements_tabs.vue';
import { TestReportStatus } from 'ee/requirements/constants';
import createRequirement from 'ee/requirements/queries/createRequirement.mutation.graphql';
import exportRequirement from 'ee/requirements/queries/exportRequirements.mutation.graphql';
import projectRequirements from 'ee/requirements/queries/projectRequirements.query.graphql';
import projectRequirementsCount from 'ee/requirements/queries/projectRequirementsCount.query.graphql';
import updateRequirement from 'ee/requirements/queries/updateRequirement.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { TEST_HOST } from 'helpers/test_constants';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
......@@ -24,12 +33,19 @@ import {
mockFilters,
mockAuthorToken,
mockStatusToken,
mockInitialRequirementCounts,
mockProjectRequirementCounts,
mockProjectRequirements,
mockUpdateRequirementTitle,
mockUpdateRequirementToFailed,
mockProjectRequirementPassed,
} from '../mock_data';
jest.mock('ee/requirements/constants', () => ({
DEFAULT_PAGE_SIZE: 2,
FilterState: jest.requireActual('ee/requirements/constants').FilterState,
AvailableSortOptions: jest.requireActual('ee/requirements/constants').AvailableSortOptions,
TestReportStatus: jest.requireActual('ee/requirements/constants').TestReportStatus,
}));
jest.mock('~/flash');
......@@ -38,29 +54,25 @@ const $toast = {
show: jest.fn(),
};
const createComponent = ({
projectPath = 'gitlab-org/gitlab-shell',
initialFilterBy = FilterState.opened,
initialRequirementsCount = mockRequirementsCount,
showCreateRequirement = false,
emptyStatePath = '/assets/illustrations/empty-state/requirements.svg',
loading = false,
canCreateRequirement = true,
requirementsWebUrl = '/gitlab-org/gitlab-shell/-/requirements',
importCsvPath = '/gitlab-org/gitlab-shell/-/requirements/import_csv',
currentUserEmail = 'admin@example.com',
} = {}) =>
const localVue = createLocalVue();
const defaultProps = {
projectPath: 'gitlab-org/gitlab-shell',
initialFilterBy: FilterState.opened,
initialRequirementsCount: mockRequirementsCount,
showCreateRequirement: false,
emptyStatePath: '/assets/illustrations/empty-state/requirements.svg',
canCreateRequirement: true,
requirementsWebUrl: '/gitlab-org/gitlab-shell/-/requirements',
importCsvPath: '/gitlab-org/gitlab-shell/-/requirements/import_csv',
currentUserEmail: 'admin@example.com',
};
const createComponent = ({ props = {}, loading = false } = {}) =>
shallowMount(RequirementsRoot, {
propsData: {
projectPath,
initialFilterBy,
initialRequirementsCount,
showCreateRequirement,
emptyStatePath,
canCreateRequirement,
requirementsWebUrl,
importCsvPath,
currentUserEmail,
...defaultProps,
...props,
},
mocks: {
$apollo: {
......@@ -72,7 +84,7 @@ const createComponent = ({
refetch: jest.fn(),
},
requirementsCount: {
...initialRequirementsCount,
...defaultProps.initialRequirementsCount,
refetch: jest.fn(),
},
},
......@@ -82,10 +94,44 @@ const createComponent = ({
},
});
const createComponentWithApollo = ({ props = {}, requestHandlers } = {}) => {
localVue.use(VueApollo);
const mockApollo = createMockApollo(
requestHandlers,
{},
{
dataIdFromObject: (object) =>
// eslint-disable-next-line no-underscore-dangle
object.__typename === 'Requirement' ? object.iid : defaultDataIdFromObject(object),
},
);
return shallowMount(RequirementsRoot, {
localVue,
apolloProvider: mockApollo,
propsData: {
...defaultProps,
...props,
},
mocks: {
$toast,
},
stubs: {
RequirementItem,
RequirementStatusBadge,
GlIcon,
},
});
};
describe('RequirementsRoot', () => {
let wrapper;
let trackingSpy;
const findRequirementEditForm = () => wrapper.find("[data-testid='edit-form']");
const findFailedStatusIcon = () => wrapper.find("[data-testid='status_failed-icon']");
beforeEach(() => {
wrapper = createComponent();
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
......@@ -427,51 +473,6 @@ describe('RequirementsRoot', () => {
);
});
describe('when `lastTestReportState` is included in object param', () => {
beforeEach(() => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdateMutationResult);
});
it('calls `$apollo.mutate` with `lastTestReportState` when it is not null', () => {
wrapper.vm.updateRequirement({
iid: '1',
lastTestReportState: 'PASSED',
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith(
expect.objectContaining({
mutation: updateRequirement,
variables: {
updateRequirementInput: {
projectPath: 'gitlab-org/gitlab-shell',
iid: '1',
lastTestReportState: 'PASSED',
},
},
}),
);
});
it('calls `$apollo.mutate` without `lastTestReportState` when it is null', () => {
wrapper.vm.updateRequirement({
iid: '1',
lastTestReportState: null,
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith(
expect.objectContaining({
mutation: updateRequirement,
variables: {
updateRequirementInput: {
projectPath: 'gitlab-org/gitlab-shell',
iid: '1',
},
},
}),
);
});
});
it('calls `createFlash` with provided `errorFlashMessage` param when request fails', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(new Error({}));
......@@ -1025,4 +1026,103 @@ describe('RequirementsRoot', () => {
});
});
});
describe('with apollo mock', () => {
describe('when requirement is edited', () => {
let updateRequirementSpy;
describe('when user changes the requirement\'s status to "FAILED" from "SUCCESS"', () => {
const editRequirementToFailed = () => {
findRequirementEditForm().vm.$emit('save', {
description: mockProjectRequirementPassed.description,
iid: mockProjectRequirementPassed.iid,
title: mockProjectRequirementPassed.title,
lastTestReportState: TestReportStatus.Failed,
});
};
beforeEach(() => {
updateRequirementSpy = jest.fn().mockResolvedValue(mockUpdateRequirementToFailed);
const requestHandlers = [
[projectRequirements, jest.fn().mockResolvedValue(mockProjectRequirements)],
[projectRequirementsCount, jest.fn().mockResolvedValue(mockProjectRequirementCounts)],
[updateRequirement, updateRequirementSpy],
];
wrapper = createComponentWithApollo({
props: { initialRequirementsCount: mockInitialRequirementCounts },
requestHandlers,
});
});
it('calls `updateRequirement` mutation with correct parameters', () => {
editRequirementToFailed();
expect(updateRequirementSpy).toHaveBeenCalledWith({
updateRequirementInput: {
projectPath: 'gitlab-org/gitlab-shell',
iid: mockProjectRequirementPassed.iid,
lastTestReportState: TestReportStatus.Failed,
title: mockProjectRequirementPassed.title,
},
});
});
it('renders a failed badge after the update', async () => {
expect(findFailedStatusIcon().exists()).toBe(false);
editRequirementToFailed();
await waitForPromises();
expect(findFailedStatusIcon().exists()).toBe(true);
});
});
describe('when user changes the title of a requirement', () => {
const editRequirementTitle = () => {
findRequirementEditForm().vm.$emit('save', {
description: mockProjectRequirementPassed.description,
iid: mockProjectRequirementPassed.iid,
title: 'edited title',
lastTestReportState: null,
});
};
beforeEach(async () => {
updateRequirementSpy = jest.fn().mockResolvedValue(mockUpdateRequirementTitle);
const requestHandlers = [
[projectRequirements, jest.fn().mockResolvedValueOnce(mockProjectRequirements)],
[projectRequirementsCount, jest.fn().mockResolvedValue(mockProjectRequirementCounts)],
[updateRequirement, updateRequirementSpy],
];
wrapper = createComponentWithApollo({
props: { initialRequirementsCount: mockInitialRequirementCounts },
requestHandlers,
});
});
it('calls `updateRequirement` mutation with correct parameters without `lastTestReport`', () => {
editRequirementTitle();
expect(updateRequirementSpy).toHaveBeenCalledWith({
updateRequirementInput: {
projectPath: 'gitlab-org/gitlab-shell',
iid: mockProjectRequirementPassed.iid,
title: 'edited title',
},
});
});
it('renders the edited title', async () => {
editRequirementTitle();
await waitForPromises();
expect(wrapper.find('.issue-title-text').text()).toContain('edited title');
});
});
});
});
});
......@@ -174,3 +174,136 @@ export const mockStatusToken = {
token: StatusToken,
operators: [{ value: '=', description: 'is', default: 'true' }],
};
/*
Mock data used for testing with mock apollo client
*/
export const mockInitialRequirementCounts = {
ARCHIVED: 0,
OPENED: 1,
ALL: 1,
};
export const mockProjectRequirementCounts = {
data: {
project: {
requirementStatesCount: {
opened: mockInitialRequirementCounts.OPENED,
archived: mockInitialRequirementCounts.ARCHIVED,
__typename: 'RequirementStatesCount',
},
__typename: 'Project',
},
},
};
const mockUser = {
...mockAuthor,
id: 'gid://gitlab/User/1',
__typename: 'User',
};
export const mockTestReportConnectionPassed = {
nodes: [mockTestReport],
__typename: 'TestReportConnection',
};
export const mockTestReportConnectionFailed = {
nodes: [mockTestReportFailed],
__typename: 'TestReportConnection',
};
export const mockEmptyTestReportConnection = {
nodes: [],
__typename: 'TestReportConnection',
};
const projectRequirementBase = {
__typename: 'Requirement',
iid: '1',
title: 'Requirement 1',
titleHtml: 'Requirement 1',
description: '',
descriptionHtml: '',
createdAt: '2021-03-15T05:24:32Z',
updatedAt: '2021-03-15T05:24:32Z',
state: 'OPENED',
userPermissions: {
updateRequirement: true,
adminRequirement: true,
__typename: 'RequirementPermissions',
},
author: {
...mockUser,
},
};
export const mockProjectRequirementFailed = {
...projectRequirementBase,
lastTestReportState: 'FAILED',
lastTestReportManuallyCreated: true,
testReports: {
...mockTestReportConnectionFailed,
},
};
export const mockProjectRequirementPassed = {
...projectRequirementBase,
lastTestReportState: 'PASSED',
lastTestReportManuallyCreated: true,
testReports: {
...mockTestReportConnectionPassed,
},
};
export const mockUpdateRequirementTitle = {
data: {
updateRequirement: {
clientMutationId: null,
errors: [],
requirement: {
...mockProjectRequirementPassed,
title: 'edited title',
},
__typename: 'UpdateRequirementPayload',
},
},
};
export const mockUpdateRequirementToFailed = {
data: {
updateRequirement: {
clientMutationId: null,
errors: [],
requirement: {
...mockProjectRequirementFailed,
},
__typename: 'UpdateRequirementPayload',
},
},
};
const mockRequirementConnection = {
nodes: [],
pageInfo: {
__typename: 'PageInfo',
hasNextPage: false,
hasPreviousPage: false,
startCursor: 'eyJpZCI6',
endCursor: 'eyJpZCI6I',
},
__typename: 'RequirementConnection',
};
export const mockProjectRequirements = {
data: {
project: {
requirements: {
...mockRequirementConnection,
nodes: [{ ...mockProjectRequirementPassed }],
},
__typename: 'Project',
},
},
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment