Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create mock for kolibri.resources that automocks every exported Resource class #8082

Merged
merged 4 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions kolibri/core/assets/src/api-resources/__mocks__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const mockedResources = jest.genMockFromModule('../index.js');

module.exports = mockedResources;
2 changes: 1 addition & 1 deletion kolibri/core/assets/src/core-app/__mocks__/client.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
let returnedPayload = {};

const client = () => Promise.resolve({ data: returnedPayload });
const client = jest.fn().mockResolvedValue({ data: returnedPayload });

client.__setPayload = payload => {
returnedPayload = payload;
Expand Down
27 changes: 13 additions & 14 deletions kolibri/plugins/coach/assets/test/showPageActions.spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { jestMockResource } from 'testUtils'; // eslint-disable-line
import { ClassroomResource, ContentNodeResource, ExamResource } from 'kolibri.resources';
import { ClassroomResource, ExamResource } from 'kolibri.resources';
import { showExamsPage } from '../src/modules/examsRoot/handlers';
import makeStore from './makeStore';

jestMockResource(ClassroomResource);
jestMockResource(ContentNodeResource);
jestMockResource(ExamResource);
jest.mock('kolibri.resources');

// fakes for data, since they have similar shape
const fakeItems = [
Expand Down Expand Up @@ -328,23 +325,25 @@ fakeExamState.forEach(fakeExam => {

describe('showPage actions for coach exams section', () => {
let store;

beforeEach(() => {
store = makeStore();
ClassroomResource.__resetMocks();
ContentNodeResource.__resetMocks();
ExamResource.__resetMocks();
ClassroomResource.fetchCollection.mockReset();
ExamResource.fetchCollection.mockReset();
});

describe('showExamsPage', () => {
it('store is properly set up when there are no problems', async () => {
ClassroomResource.__getCollectionFetchReturns(fakeItems);
ExamResource.__getCollectionFetchReturns(fakeExams);
ExamResource.__getCollectionFetchReturns(fakeExams);
ClassroomResource.fetchCollection.mockResolvedValue(fakeItems);
ExamResource.fetchCollection.mockResolvedValue(fakeExams);

// Using the weird naming from fakeItems
const classId = 'item_1';
await showExamsPage(store, classId)._promise;
expect(ExamResource.getCollection).toHaveBeenCalledWith({ collection: classId });
expect(ExamResource.fetchCollection).toHaveBeenCalledWith({
getParams: { collection: classId },
force: true,
});
expect(store.state.examsRoot).toMatchObject({
exams: fakeExamState,
examsModalSet: false,
Expand All @@ -353,8 +352,8 @@ describe('showPage actions for coach exams section', () => {
});

it('store is properly set up when there are errors', async () => {
ClassroomResource.__getCollectionFetchReturns(fakeItems);
ExamResource.__getCollectionFetchReturns('channel error', true);
ClassroomResource.fetchCollection.mockResolvedValue(fakeItems);
ExamResource.fetchCollection.mockRejectedValue('channel error');
try {
await showExamsPage(store, 'class_1')._promise;
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
//
import omit from 'lodash/fp/omit';
import { jestMockResource } from 'testUtils'; // eslint-disable-line
import { ContentNodeGranularResource, TaskResource } from 'kolibri.resources';
import { ContentNodeGranularResource } from 'kolibri.resources';
import client from 'kolibri.client';
import { makeNode, contentNodeGranularPayload } from '../utils/data';
import { updateTreeViewTopic } from '../../src/modules/wizard/handlers';
import ChannelResource from '../../src/apiResources/deviceChannel';
import { makeSelectContentPageStore } from '../utils/makeStore';

const simplePath = (...ids) => ids.map(id => ({ id, title: `node_${id}` }));

jest.mock('kolibri.urls');
jest.mock('kolibri.client');

jestMockResource(ChannelResource);
jestMockResource(ContentNodeGranularResource);
jestMockResource(TaskResource);
jest.mock('kolibri.resources');

const ADD_NODE_ACTION = 'manageContent/wizard/addNodeForTransfer';
const REMOVE_NODE_ACTION = 'manageContent/wizard/removeNodeForTransfer';
Expand Down Expand Up @@ -325,9 +319,12 @@ describe('updateTreeViewTopic action', () => {
topic_3.path = [...topic_2.path, topic_2];
topic_4.path = [...topic_3.path, topic_3];

beforeAll(() => {
ContentNodeGranularResource.fetchModel.mockResolvedValue(cngPayload);
});

beforeEach(() => {
store = makeSelectContentPageStore();
ContentNodeGranularResource.__getModelFetchReturns(cngPayload);
});

function assertPathEquals(expected) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
//
import { ContentNodeGranularResource, TaskResource } from 'kolibri.resources';
import { TaskResource } from 'kolibri.resources';
import { loadChannelMetadata } from '../../src/modules/wizard/actions/selectContentActions';
import { jestMockResource } from 'testUtils'; // eslint-disable-line
import ChannelResource from '../../src/apiResources/deviceChannel';
import { defaultChannel } from '../utils/data';
import { makeSelectContentPageStore } from '../utils/makeStore';

jestMockResource(ChannelResource);
jestMockResource(ContentNodeGranularResource);
jestMockResource(TaskResource);
jest.mock('kolibri.resources');
jest.genMockFromModule('../../src/apiResources/deviceChannel');

// Have store suddenly add a Task to the store so the task waiting step
// resolves successfully
Expand All @@ -21,10 +18,8 @@ function hackStoreWatcher(store) {
describe('loadChannelMetadata action', () => {
let store;

beforeEach(() => {
// Add mock methods not in generic mock Resource
TaskResource.startRemoteChannelImport = jest.fn();
TaskResource.startDiskChannelImport = jest.fn();
beforeAll(() => {
ChannelResource.fetchModel = jest.fn();
});

beforeEach(() => {
Expand All @@ -35,26 +30,18 @@ describe('loadChannelMetadata action', () => {
]);
hackStoreWatcher(store);
const taskEntity = { data: { id: 'task_1' } };
TaskResource.cancelTask = jest.fn().mockResolvedValue();
TaskResource.startDiskChannelImport.mockResolvedValue(taskEntity);
TaskResource.startRemoteChannelImport.mockResolvedValue(taskEntity);
ChannelResource.getModel.mockReturnValue({
fetch: () => ({
_promise: Promise.resolve({
name: 'Channel One',
root: 'channel_1_root',
}),
}),
ChannelResource.fetchModel.mockResolvedValue({
name: 'Channel One',
root: 'channel_1_root',
});
});

afterEach(() => {
ChannelResource.__resetMocks();
ContentNodeGranularResource.__resetMocks();
TaskResource.__resetMocks();
TaskResource.startRemoteChannelImport.mockReset();
ChannelResource.fetchModel.mockReset();
TaskResource.startDiskChannelImport.mockReset();
TaskResource.cancelTask.mockReset();
TaskResource.startRemoteChannelImport.mockReset();
});

function setUpStateForTransferType(transferType) {
Expand Down
66 changes: 34 additions & 32 deletions kolibri/plugins/facility/assets/test/state/facilityConfig.spec.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { FacilityResource, FacilityDatasetResource } from 'kolibri.resources';
import client from 'kolibri.client';
import { showFacilityConfigPage } from '../../src/modules/facilityConfig/handlers';
import { jestMockResource } from 'testUtils'; // eslint-disable-line
import makeStore from '../makeStore';

jest.mock('kolibri.client', () => jest.fn());
jest.mock('kolibri.urls', () => ({
'kolibri:core:facilitydataset-resetsettings': () => {},
}));

const FacilityStub = jestMockResource(FacilityResource);
const DatasetStub = jestMockResource(FacilityDatasetResource);
jest.mock('kolibri.client');
jest.mock('kolibri.urls');
jest.mock('kolibri.resources');

const fakeFacility = {
name: 'Nalanda Maths',
Expand Down Expand Up @@ -40,23 +35,28 @@ describe('facility config page actions', () => {

beforeEach(() => {
store = makeStore();
store.state.route = { params: {} };
commitStub = jest.spyOn(store, 'commit');
store.state.route = { params: {} };
Object.assign(store.state.core, {
pageId: '123',
session: {
facility_id: 1,
},
});
FacilityResource.__resetMocks();
FacilityDatasetResource.__resetMocks();
});

afterEach(() => {
FacilityResource.fetchModel.mockReset();
FacilityResource.fetchCollection.mockReset();
FacilityDatasetResource.fetchCollection.mockReset();
FacilityDatasetResource.saveModel.mockReset();
});

describe('showFacilityConfigPage action', () => {
it('when resources load successfully', () => {
FacilityStub.__getModelFetchReturns(fakeFacility);
DatasetStub.__getCollectionFetchReturns(fakeDatasets);
FacilityStub.__getCollectionFetchReturns(fakeFacilities);
FacilityResource.fetchModel.mockResolvedValue(fakeFacility);
FacilityResource.fetchCollection.mockResolvedValue(fakeFacilities);
FacilityDatasetResource.fetchCollection.mockResolvedValue(fakeDatasets);
const expectedState = {
facilityDatasetId: 'dataset_2',
facilityName: 'Nalanda Maths',
Expand All @@ -77,7 +77,9 @@ describe('facility config page actions', () => {
};

return showFacilityConfigPage(store, toRoute).then(() => {
expect(DatasetStub.getCollection).toHaveBeenCalledWith({ facility_id: 1 });
expect(FacilityDatasetResource.fetchCollection).toHaveBeenCalledWith({
getParams: { facility_id: 1 },
});
expect(commitStub).toHaveBeenCalledWith(
'facilityConfig/SET_STATE',
expect.objectContaining(expectedState)
Expand All @@ -91,9 +93,9 @@ describe('facility config page actions', () => {
settings: null,
};
it('when fetching Facility fails', () => {
FacilityStub.__getModelFetchReturns('incomprehensible error', true);
DatasetStub.__getCollectionFetchReturns(fakeDatasets);
FacilityStub.__getCollectionFetchReturns(fakeFacilities);
FacilityResource.fetchModel.mockRejectedValue('incomprehensible error');
FacilityResource.fetchCollection.mockResolvedValue(fakeFacilities);
FacilityDatasetResource.fetchCollection.mockResolvedValue(fakeDatasets);
return showFacilityConfigPage(store, toRoute).then(() => {
expect(commitStub).toHaveBeenCalledWith(
'facilityConfig/SET_STATE',
Expand All @@ -102,16 +104,15 @@ describe('facility config page actions', () => {
});
});

it('when fetching FacilityDataset fails', () => {
FacilityStub.__getModelFetchReturns(fakeFacility);
DatasetStub.__getCollectionFetchReturns('incomprehensible error', true);
FacilityStub.__getCollectionFetchReturns(fakeFacilities);
return showFacilityConfigPage(store, toRoute).then(() => {
expect(commitStub).toHaveBeenCalledWith(
'facilityConfig/SET_STATE',
expect.objectContaining(expectedState)
);
});
it('when fetching FacilityDataset fails', async () => {
FacilityResource.fetchModel.mockResolvedValue(fakeFacility);
FacilityResource.fetchCollection.mockResolvedValue(fakeFacilities);
FacilityDatasetResource.fetchCollection.mockRejectedValue('incoprehensible error');
await showFacilityConfigPage(store, toRoute);
expect(commitStub).toHaveBeenCalledWith(
'facilityConfig/SET_STATE',
expect.objectContaining(expectedState)
);
});
});
});
Expand Down Expand Up @@ -139,16 +140,17 @@ describe('facility config page actions', () => {
learner_can_sign_up: false,
};
// IRL returns the updated Model
const saveStub = DatasetStub.__getModelSaveReturns('ok');
const saveStub = FacilityDatasetResource.saveModel.mockResolvedValue('ok');

return store.dispatch('facilityConfig/saveFacilityConfig').then(() => {
expect(DatasetStub.getModel).toHaveBeenCalledWith(1000, {});
expect(saveStub).toHaveBeenCalledWith(expect.objectContaining(expectedRequest), false);
expect(saveStub).toHaveBeenCalledWith(
expect.objectContaining({ id: 1000, data: expectedRequest })
);
});
});

it('when save fails', () => {
const saveStub = DatasetStub.__getModelSaveReturns('heck no', true);
const saveStub = FacilityDatasetResource.saveModel.mockRejectedValue('heck no', true);
return store.dispatch('facilityConfig/saveFacilityConfig').catch(() => {
expect(saveStub).toHaveBeenCalled();
expect(store.state.facilityConfig.settings).toEqual({
Expand Down
17 changes: 9 additions & 8 deletions kolibri/plugins/learn/assets/test/state/prepareLearnApp.spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { jestMockResource } from 'testUtils'; // eslint-disable-line import/no-unresolved
import { MembershipResource } from 'kolibri.resources';
import { prepareLearnApp } from '../../src/modules/coreLearn/actions';
import makeStore from '../makeStore';

jestMockResource(MembershipResource);
jest.mock('kolibri.resources');

describe('prepareLearnApp action', () => {
let store;
Expand All @@ -15,34 +14,36 @@ describe('prepareLearnApp action', () => {

beforeEach(() => {
store = makeStore();
MembershipResource.__resetMocks();
MembershipResource.fetchCollection.mockReset();
});

it('does not modify state for guest user', () => {
setSessionUserId(null);

return prepareLearnApp(store).then(() => {
expect(MembershipResource.getCollection).not.toHaveBeenCalled();
expect(MembershipResource.fetchCollection).not.toHaveBeenCalled();
expect(getMemberships(store)).toEqual([]);
});
});

it('adds memberships to state for logged-in user', () => {
setSessionUserId(101);
const fakeMemberships = [{ id: 'membership_1' }, { id: 'membership_2' }];
MembershipResource.__getCollectionFetchReturns(fakeMemberships);
MembershipResource.fetchCollection.mockResolvedValue(fakeMemberships);

return prepareLearnApp(store).then(() => {
expect(MembershipResource.getCollection).toHaveBeenCalledWith({
user: 101,
expect(MembershipResource.fetchCollection).toHaveBeenCalledWith({
getParams: {
user: 101,
},
});
expect(getMemberships(store)).toEqual(fakeMemberships);
});
});

it('handles errors', () => {
setSessionUserId(102);
MembershipResource.__getCollectionFetchReturns('fetch error', true);
MembershipResource.fetchCollection.mockRejectedValue('fetch error', true);

return prepareLearnApp(store).catch(() => {
expect(MembershipResource.getCollection).toHaveBeenCalledWith({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,8 @@ import Vuex from 'vuex';
import client from 'kolibri.client';
import pluginModule from '../pluginModule';

jest.mock('kolibri.client', () =>
jest.fn().mockResolvedValue({
data: {
facility: {},
},
})
);

jest.mock('kolibri.urls', () => ({
'kolibri:core:deviceprovision': () => '/deviceprovision',
}));
jest.mock('kolibri.client');
jest.mock('kolibri.urls');

// Since kolibriLogin is the only core action used, we just
// add this mock to the setup wizard module, instead of using
Expand All @@ -30,6 +21,10 @@ function makeStore() {
}

describe('Setup Wizard Vuex module', () => {
beforeAll(() => {
client.mockResolvedValue({ data: { facility: {} } });
});

it('sets the correct default facility name', async () => {
const store = makeStore();
store.commit('SET_FACILITY_PRESET', 'informal');
Expand Down
6 changes: 6 additions & 0 deletions packages/kolibri-tools/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ module.exports = {
jest: true,
},
},
{
files: ['**/__mocks__/*.*'],
env: {
jest: true,
}
},
{
files: ['*.int.js'],
env: {
Expand Down
Loading