UNPKG

@data-client/endpoint

Version:

Declarative Network Interface Definitions

418 lines (383 loc) 11.5 kB
// eslint-env jest import { normalize, denormalize } from '@data-client/normalizr'; import { IDEntity } from '__tests__/new'; import { fromJS } from 'immutable'; import { schema } from '../../'; let dateSpy; beforeAll(() => { dateSpy = jest .spyOn(global.Date, 'now') .mockImplementation(() => new Date('2019-05-14T11:01:58.135Z').valueOf()); }); afterAll(() => { dateSpy.mockRestore(); }); test(`normalizes plain arrays as shorthand for ${schema.Array.name}`, () => { class User extends IDEntity {} expect(normalize([User], [{ id: '1' }, { id: '2' }])).toMatchSnapshot(); }); test('throws an error if created with more than one schema', () => { class User extends IDEntity {} class Cat extends IDEntity {} expect(() => normalize([Cat, User], [{ id: '1' }])).toThrow(); }); describe.each([ ['schema', sch => new schema.Array(sch)], ['plain', sch => [sch]], ])(`${schema.Array.name} normalization (%s)`, (_, createSchema) => { describe('Object', () => { test('should throw a custom error if data loads with string unexpected value', () => { class User extends IDEntity {} const sch = createSchema(User); function normalizeBad() { normalize(sch, 'abc'); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); test('should throw a custom error if data loads with json string unexpected value', () => { class User extends IDEntity {} const sch = createSchema(User); function normalizeBad() { normalize(sch, '[{"id":5}]'); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); test('passes its parent to its children when normalizing', () => { class Child extends IDEntity { content = ''; static fromJS(entity, parent, key) { return super.fromJS({ ...entity, parentId: parent.id, parentKey: key, }); } } class Parent extends IDEntity { content = ''; children = []; static schema = { children: createSchema(Child), }; } expect( normalize(Parent, { id: '1', content: 'parent', children: [{ id: 4, content: 'child' }], }), ).toMatchSnapshot(); }); test('normalizes Objects using their values', () => { class User extends IDEntity {} expect( normalize(createSchema(User), { foo: { id: '1' }, bar: { id: '2' } }), ).toMatchSnapshot(); }); }); describe('Class', () => { class Cats extends IDEntity {} test('normalizes a single entity', () => { const listSchema = createSchema(Cats); expect( normalize(listSchema, [{ id: '1' }, { id: '2' }]), ).toMatchSnapshot(); }); test('normalizes multiple entities', () => { const inferSchemaFn = jest.fn(input => input.type || 'dogs'); class Person extends IDEntity {} const listSchema = new schema.Array( { Cat: Cats, people: Person, }, inferSchemaFn, ); expect( normalize(listSchema, [ { type: 'Cat', id: '123' }, { type: 'people', id: '123' }, { id: '789', name: 'fido' }, { type: 'Cat', id: '456' }, ]), ).toMatchSnapshot(); expect(inferSchemaFn.mock.calls).toMatchSnapshot(); }); test('normalizes Objects using their values', () => { class User extends IDEntity {} const users = createSchema(User); expect( normalize(users, { foo: { id: '1' }, bar: { id: '2' } }), ).toMatchSnapshot(); }); test('does not filter out undefined and null normalized values', () => { class User extends IDEntity {} const users = createSchema(User); expect( normalize(users, [undefined, { id: '123' }, null]), ).toMatchSnapshot(); }); }); }); describe.each([ ['direct', data => data], ['immutable', fromJS], ])(`input (%s)`, (_, createInput) => { test('denormalizes plain arrays with nothing inside', () => { class User extends IDEntity {} const entities = { User: { 1: { id: '1', name: 'Jane' }, }, }; const sch = new schema.Object({ user: User, tacos: [] }); expect( denormalize(sch, { user: '1' }, createInput(entities)), ).toMatchSnapshot(); expect( denormalize(sch, createInput({ user: '1' }), createInput(entities)), ).toMatchSnapshot(); expect( denormalize(sch, { user: '1', tacos: [] }, createInput(entities)), ).toMatchSnapshot(); expect( denormalize( sch, createInput({ user: '1', tacos: [] }), createInput(entities), ), ).toMatchSnapshot(); }); describe.each([ ['class', sch => new schema.Array(sch)], ['object, direct', sch => [sch]], ])(`${schema.Array.name} denormalization (%s)`, (_, createSchema) => { test('denormalizes a single entity', () => { class Cat extends IDEntity {} const entities = { Cat: { 1: { id: '1', name: 'Milo' }, 2: { id: '2', name: 'Jake' }, }, }; expect( denormalize(createSchema(Cat), ['1', '2'], createInput(entities)), ).toMatchSnapshot(); }); test('denormalizes non-array as identity', () => { class Cat extends IDEntity {} const entities = { Cat: { 1: { id: '1', name: 'Milo' }, 2: { id: '2', name: 'Jake' }, }, }; expect( denormalize( createSchema(Cat), { a: '1', b: '2' }, createInput(entities), ), ).toMatchSnapshot(); }); test('denormalizes plain arrays with plain object inside', () => { class User extends IDEntity {} const entities = { User: { 1: { id: '1', name: 'Jane' }, }, }; const sch = new schema.Object({ user: User, tacos: createSchema({ next: '' }), }); expect( denormalize(sch, { user: '1' }, createInput(entities)), ).toMatchSnapshot(); expect( denormalize(sch, createInput({ user: '1' }), createInput(entities)), ).toMatchSnapshot(); expect( denormalize(sch, { user: '1', tacos: [] }, createInput(entities)), ).toMatchSnapshot(); expect( denormalize( sch, createInput({ user: '1', tacos: [] }), createInput(entities), ), ).toMatchSnapshot(); }); test('denormalizes nested in object', () => { class Cat extends IDEntity {} const catSchema = new schema.Object({ results: createSchema(Cat) }); const entities = { Cat: { 1: { id: '1', name: 'Milo' }, 2: { id: '2', name: 'Jake' }, }, }; expect( denormalize(catSchema, { results: ['1', '2'] }, createInput(entities)), ).toMatchSnapshot(); }); test('denormalizes nested in object with primitive', () => { class Cat extends IDEntity {} const catSchema = new schema.Object({ results: createSchema(Cat), nextPage: '', }); const entities = { Cat: { 1: { id: '1', name: 'Milo' }, 2: { id: '2', name: 'Jake' }, }, }; let value = denormalize( catSchema, { results: ['1', '2'] }, createInput(entities), ); expect(value).toMatchSnapshot(); value = denormalize( catSchema, createInput({ results: ['1', '2'] }), createInput(entities), ); expect(value).toMatchSnapshot(); }); test('denormalizes removes undefined but not null', () => { class Cat extends IDEntity {} const catSchema = new schema.Object({ results: createSchema(Cat), nextPage: '', }); const entities = { Cat: { 1: { id: '1', name: 'Milo' }, 2: { id: '2', name: 'Jake' }, }, }; let value = denormalize( catSchema, createInput({ results: ['1', undefined, '2', null] }), createInput(entities), ); expect(value).toMatchSnapshot(); value = denormalize( catSchema, { results: ['1', '2'] }, createInput(entities), ); expect(value).toMatchSnapshot(); }); test('denormalizes should not be found when result array is undefined', () => { class Cat extends IDEntity {} const catSchema = new schema.Object({ results: createSchema(Cat) }); const entities = { Cat: { 1: { id: '1', name: 'Milo' }, 2: { id: '2', name: 'Jake' }, }, }; let value = denormalize( catSchema, createInput({ results: undefined }), createInput(entities), ); expect(value).toMatchSnapshot(); }); test('denormalizes with missing entity should have true second value', () => { class Cat extends IDEntity {} const entities = { Cat: { 1: { id: '1', name: 'Milo' }, 2: { id: '2', name: 'Jake' }, }, }; let value = denormalize( createSchema(new schema.Object({ data: Cat })), createInput([{ data: '1' }, { data: '2' }, { data: '3' }]), createInput(entities), ); expect(value).toMatchSnapshot(); }); test('returns the input value if is not an array', () => { class Filling extends IDEntity {} class Taco extends IDEntity { static schema = { fillings: createSchema(Filling) }; } const entities = { Taco: { 123: { id: '123', fillings: null, }, }, }; expect(denormalize(Taco, '123', createInput(entities))).toMatchSnapshot(); }); test('denormalizes multiple entities', () => { class Cat extends IDEntity { type = 'Cat'; } class Person extends IDEntity { type = 'people'; } const listSchema = new schema.Array( { Cat: Cat, dogs: new schema.Object({}), people: Person, }, input => input.type || 'dogs', ); const entities = { Cat: { 123: { id: '123', type: 'Cat', }, 456: { id: '456', type: 'Cat', }, }, Person: { 123: { id: '123', type: 'people', }, }, }; const input = [ { id: '123', schema: 'Cat' }, { id: '123', schema: 'people' }, { id: { id: '789' }, schema: 'dogs' }, { id: '456', schema: 'Cat' }, ]; const value = denormalize( listSchema, createInput(input), createInput(entities), ); expect(value).toMatchSnapshot(); }); test('does not assume mapping of schema to attribute values when schemaAttribute is not set', () => { class Cat extends IDEntity {} const catRecord = new schema.Object({ cat: Cat, }); const catList = new schema.Array(catRecord); const input = [ { cat: { id: '1' }, id: '5' }, { cat: { id: '2' }, id: '6' }, ]; const output = normalize(catList, input); expect(output).toMatchSnapshot(); expect(denormalize(catList, output.result, output.entities)).toEqual( input, ); }); }); });