UNPKG

d2

Version:

Javascript library for DHIS2

470 lines (395 loc) 15.4 kB
import fixtures from '../../__fixtures__/fixtures' import Api from '../../api/Api' import Model from '../Model' import ModelDefinition from '../ModelDefinition' import ModelCollectionProperty from '../ModelCollectionProperty' describe('ModelCollectionProperty', () => { // let ModelCollectionProperty; let mockParentModel let mockModelDefinition let mcp let testModels = [] beforeEach(() => { mockParentModel = { id: 'parentModelId', plural: 'notArealModel', href: 'my.dhis/instance', modelDefinition: { apiEndpoint: 'http://my.base.url/api/parentModelEndpoint', }, } mockModelDefinition = ModelDefinition.createFromSchema( fixtures.get('/api/schemas/dataElement') ) mcp = ModelCollectionProperty.create( mockParentModel, mockModelDefinition, 'dataElementGroups', [] ) testModels.push( mockModelDefinition.create({ id: 'dataEleme01', name: 'Test' }) ) testModels.push(mockModelDefinition.create({ id: 'dataEleme02' })) testModels.push(mockModelDefinition.create({ id: 'dataEleme03' })) }) afterEach(() => { testModels = [] }) it('Should be an object', () => { expect(ModelCollectionProperty).toBeInstanceOf(Object) }) it('Should not be callable as a function', () => { expect(() => ModelCollectionProperty()).toThrowError() }) describe('create()', () => { it('Supplies the default API implementation', () => { expect(mcp.api).toEqual(Api.getApi()) }) it('Sets the dirty flag to false', () => { expect(mcp.dirty).toBe(false) }) it('Creates empty Sets for added and removed elements', () => { expect(mcp.added).toBeInstanceOf(Set) expect(mcp.removed).toBeInstanceOf(Set) expect(mcp.added.size).toBe(0) expect(mcp.removed.size).toBe(0) }) it('Sets the correct parentModel', () => { expect(mcp.parentModel).toEqual(mockParentModel) }) }) describe('add()', () => { it('Registers added elements', () => { testModels.forEach(model => mcp.add(model)) expect(mcp.added.size).toBe(testModels.length) }) it('Only registers each added element once', () => { testModels.forEach(model => mcp.add(model)) testModels.forEach(model => mcp.add(model)) expect(mcp.added.size).toBe(testModels.length) }) it('Updates the dirty flag', () => { expect(mcp.dirty).toBe(false) mcp.add(testModels[0]) expect(mcp.dirty).toBe(true) }) it('Sets the dirty flag to false when an element is added and then removed', () => { expect(mcp.dirty).toBe(false) mcp.add(testModels[0]) expect(mcp.dirty).toBe(true) mcp.remove(testModels[0]) expect(mcp.dirty).toBe(false) }) }) describe('remove()', () => { beforeEach(() => { // Create a new ModelCollectionProperty with existing values mcp = ModelCollectionProperty.create( mockParentModel, mockModelDefinition, 'dataElementGroups', testModels ) }) it('Registers removed elements', () => { expect(mcp.removed.size).toBe(0) mcp.remove(testModels[0]) expect(mcp.removed.size).toBe(1) mcp.remove(testModels[1]) expect(mcp.removed.size).toBe(2) mcp.remove(testModels[2]) expect(mcp.removed.size).toBe(3) }) it('Only registers each removed element once', () => { expect(mcp.removed.size).toBe(0) mcp.remove(testModels[0]) expect(mcp.removed.size).toBe(1) mcp.remove(testModels[0]) expect(mcp.removed.size).toBe(1) }) it('Updates the dirty flag', () => { expect(mcp.dirty).toBe(false) mcp.remove(testModels[0]) expect(mcp.dirty).toBe(true) }) it('Sets the dirty flag to false when an element is removed and re-added', () => { expect(mcp.dirty).toBe(false) mcp.remove(testModels[0]) expect(mcp.dirty).toBe(true) mcp.add(testModels[0]) expect(mcp.dirty).toBe(false) }) }) describe('updateDirty()', () => { it('Updates the dirty flag correctly', () => { expect(mcp.updateDirty()).toBe(false) mcp.added.add({ id: 'not a real model' }) expect(mcp.updateDirty()).toBe(true) }) it('Returns the updated value of the dirty flag', () => { mcp.added.add({ id: 'not a real model' }) expect(mcp.updateDirty()).toBe(mcp.dirty) }) }) describe('resetDirtyState()', () => { it('Clears all added and removed elements', () => { mcp.added.add(testModels[0]) mcp.removed.add({ id: 'bah ' }) expect(mcp.added.size).toBe(1) expect(mcp.removed.size).toBe(1) mcp.resetDirtyState() expect(mcp.added.size).toBe(0) expect(mcp.removed.size).toBe(0) }) it('Sets the dirty flag to false', () => { expect(mcp.dirty).toBe(false) mcp.add(testModels[0]) mcp.removed.add({ id: 'bah ' }) expect(mcp.updateDirty()).toBe(true) mcp.resetDirtyState() expect(mcp.dirty).toBe(false) }) }) describe('isDirty()', () => { it('Returns the current value of the dirty flag', () => { expect(mcp.isDirty()).toBe(mcp.dirty) mcp.add(testModels[0]) expect(mcp.isDirty()).toBe(true) expect(mcp.isDirty()).toBe(mcp.dirty) }) it('Does not update the dirty flag', () => { expect(mcp.isDirty()).toBe(false) mcp.added.add(testModels[0]) expect(mcp.isDirty()).toBe(false) }) it('Should be dirty=true if any model has been edited by default', () => { expect(mcp.isDirty()).toBe(false) mcp.add(testModels[0]) expect(mcp.isDirty()).toBe(true) mcp.resetDirtyState() expect(mcp.isDirty()).toBe(false) testModels[0].name = 'Raccoon' expect(testModels[0].isDirty()).toBe(true) expect(mcp.isDirty()).toBe(true) }) it('Should be dirty=false if includeValues=false', () => { expect(mcp.isDirty()).toBe(false) mcp.add(testModels[0]) expect(mcp.isDirty()).toBe(true) mcp.resetDirtyState() expect(mcp.isDirty()).toBe(false) testModels[0].name = 'Raccoon' expect(testModels[0].isDirty()).toBe(true) expect(mcp.isDirty(false)).toBe(false) }) }) describe('save()', () => { const api = { get: jest.fn().mockReturnValue(Promise.resolve()), post: jest.fn().mockReturnValue(Promise.resolve()), } beforeEach(() => { mcp = new ModelCollectionProperty( mockParentModel, mockModelDefinition, 'dataElementGroups', [testModels[0]], api ) }) afterEach(() => { api.get.mockClear() api.post.mockClear() }) it('Does nothing when the collection not dirty', () => { expect.assertions(1) return mcp.save().then(() => { expect(api.post).toHaveBeenCalledTimes(0) }) }) it('Sends additions and removals in a single request', () => { mcp.remove(testModels[0]) mcp.add(testModels[1]) expect.assertions(2) return mcp.save().then(() => { expect(api.get).not.toHaveBeenCalled() expect(api.post).toHaveBeenCalledTimes(1) }) }) it('Sends an API requests with the correct additions and removals, using the correct URL', () => { mcp.remove(testModels[0]) mcp.add(testModels[1]) expect.assertions(3) return mcp.save().then(() => { expect(api.get).not.toHaveBeenCalled() expect(api.post).toHaveBeenCalledTimes(1) expect(api.post).toBeCalledWith( 'my.dhis/instance/dataElements', { additions: [{ id: 'dataEleme02' }], deletions: [{ id: 'dataEleme01' }], } ) }) }) it('Resets the dirty flag', () => { expect.assertions(2) mcp.remove(testModels[0]) mcp.add(testModels[1]) expect(mcp.dirty).toBe(true) return mcp.save().then(() => { expect(mcp.dirty).toBe(false) }) }) it('Rejects the promise when the API fails', () => { expect.assertions(2) api.post.mockReturnValue(Promise.reject()) mcp.add(testModels[1]) expect(mcp.dirty).toBe(true) return mcp.save().catch(() => { // TODO: useless assertion expect(true).toBe(true) }) }) }) describe('load()', () => { let loadedWithValues let loadedWithoutValues let unloadedWithValues let unloadedWithoutValues let excludedByFieldFilters const api = { get: jest.fn().mockReturnValue( Promise.resolve({ dataElementGroups: [ { id: 'groupNo0001' }, { id: 'groupNo0002' }, { id: 'groupNo0003' }, ], }) ), } const mockMcpPropName = 'dataElementGroups' beforeEach(() => { loadedWithValues = new ModelCollectionProperty( mockParentModel, mockModelDefinition, mockMcpPropName, [ // Loaded, actual values mockModelDefinition.create({ id: 'groupNo0001' }), mockModelDefinition.create({ id: 'groupNo0002' }), mockModelDefinition.create({ id: 'groupNo0003' }), ], api ) // A ModelCollectionProperty that has been fully loaded, but contains no values loadedWithoutValues = new ModelCollectionProperty( mockParentModel, mockModelDefinition, mockMcpPropName, [], // Loaded, no values api ) // A ModelCollectionProperty that has not yet been loaded, but contains values that can be lazy loaded unloadedWithValues = new ModelCollectionProperty( mockParentModel, mockModelDefinition, mockMcpPropName, true, // Not loaded, has values (meaning the field was loaded with the '::isNotEmpty' transformer) api ) unloadedWithoutValues = new ModelCollectionProperty( mockParentModel, mockModelDefinition, mockMcpPropName, false, // Not loaded, no values api ) excludedByFieldFilters = new ModelCollectionProperty( mockParentModel, mockModelDefinition, mockMcpPropName, undefined, // This field was not included in the API query api ) }) afterEach(() => { api.get.mockClear() }) it('Sets `hasUnloadedData` correctly', () => { expect(loadedWithValues.hasUnloadedData).toBe(false) expect(loadedWithoutValues.hasUnloadedData).toBe(false) expect(unloadedWithValues.hasUnloadedData).toBe(true) expect(unloadedWithoutValues.hasUnloadedData).toBe(false) expect(excludedByFieldFilters.hasUnloadedData).toBe(true) }) it('does not query the API when there are no unloaded values', () => { expect.assertions(1) return Promise.all([ loadedWithValues.load(), loadedWithoutValues.load(), unloadedWithoutValues.load(), ]).then(() => { expect(api.get).not.toHaveBeenCalled() }) }) it('performs the correct API call for lazy loading', () => { expect.assertions(1) return unloadedWithValues.load().then(() => { expect(api.get).toHaveBeenCalledWith( [ mockParentModel.modelDefinition.apiEndpoint, mockParentModel.id, ].join('/'), { fields: 'dataElementGroups[:all]', paging: false, } ) }) }) it('correctly merges request parameters when lazy loading', () => { expect.assertions(1) return unloadedWithValues .load({ paging: false, fields: 'id,displayName' }) .then(() => { expect(api.get).toHaveBeenCalledWith( [ mockParentModel.modelDefinition.apiEndpoint, mockParentModel.id, ].join('/'), { fields: 'dataElementGroups[id,displayName]', paging: false, } ) }) }) it('updates hasUnloadedData when data has been lazy loaded', () => { expect.assertions(2) expect(unloadedWithValues.hasUnloadedData).toBe(true) return unloadedWithValues.load().then(() => { expect(unloadedWithValues.hasUnloadedData).toBe(false) }) }) it('creates models for lazy loaded objects', () => { expect.assertions(4) return unloadedWithValues.load().then(() => { expect(unloadedWithValues.valuesContainerMap.size).toBe(3) unloadedWithValues .toArray() .forEach(value => expect(value).toBeInstanceOf(Model)) }) }) it('supports lazy loading collection fields that were not included in the original API query', () => { expect.assertions(3) expect(excludedByFieldFilters.hasUnloadedData).toBe(true) return excludedByFieldFilters.load().then(() => { expect(api.get).toHaveBeenCalled() expect(excludedByFieldFilters.hasUnloadedData).toBe(false) }) }) }) })