diff --git a/packages/adapter-nextjs/package.json b/packages/adapter-nextjs/package.json index 0e18414b403..e222528f89e 100644 --- a/packages/adapter-nextjs/package.json +++ b/packages/adapter-nextjs/package.json @@ -69,7 +69,7 @@ "clean:size": "rimraf dual-publish-tmp tmp*", "format": "echo \"Not implemented\"", "lint": "tslint 'src/**/*.ts' && npm run ts-coverage", - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "ts-coverage": "typescript-coverage-report -p ./tsconfig.build.json -t 90.31" } } diff --git a/packages/analytics/package.json b/packages/analytics/package.json index e544d7d93ed..3cdaaa11770 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -11,7 +11,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:watch": "tslint 'src/**/*.ts' && jest -w 1 --watch", "build-with-test": "npm run clean && npm run test && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json index 8febfc6b226..6a8a8c2edd4 100644 --- a/packages/api-graphql/package.json +++ b/packages/api-graphql/package.json @@ -14,7 +14,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:watch": "tslint 'src/**/*.ts' && jest -w 1 --watch", "build-with-test": "npm test && npm build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json index d8daf52b400..932a897a438 100644 --- a/packages/api-rest/package.json +++ b/packages/api-rest/package.json @@ -11,7 +11,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:watch": "tslint 'src/**/*.ts' && jest -w 1 --watch", "build-with-test": "npm test && npm build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/api/package.json b/packages/api/package.json index 841cfeafa8e..16d50330537 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -14,7 +14,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "build-with-test": "npm test && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", "build:esm-cjs": "rollup -c rollup.config.mjs", diff --git a/packages/auth/package.json b/packages/auth/package.json index 06fd013a061..7b6c5e563bf 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -14,7 +14,7 @@ "access": "public" }, "scripts": { - "test": "yarn lint --fix && jest -w 1 --coverage", + "test": "yarn lint --fix && jest -w 1 --coverage --logHeapUsage", "build-with-test": "npm test && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", "build:esm-cjs": "rollup -c rollup.config.mjs", diff --git a/packages/aws-amplify/package.json b/packages/aws-amplify/package.json index 38b41d0be3e..377099dc354 100644 --- a/packages/aws-amplify/package.json +++ b/packages/aws-amplify/package.json @@ -224,7 +224,7 @@ }, "sideEffects": false, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:size": "size-limit", "build-with-test": "npm run clean && npm test && tsc && webpack -p", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/core/package.json b/packages/core/package.json index e25287ad3f4..8778b2824be 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -16,7 +16,7 @@ "./dist/esm/Cache/index.mjs" ], "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:size": "size-limit", "build-with-test": "npm test && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/datastore-storage-adapter/package.json b/packages/datastore-storage-adapter/package.json index df7d2a356c5..4c50a452229 100644 --- a/packages/datastore-storage-adapter/package.json +++ b/packages/datastore-storage-adapter/package.json @@ -10,7 +10,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "build-with-test": "npm test && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", "build:esm-cjs": "rollup -c rollup.config.mjs", diff --git a/packages/datastore/__tests__/DataStore/DataStore.BasicOperations.test.ts b/packages/datastore/__tests__/DataStore/DataStore.BasicOperations.test.ts new file mode 100644 index 00000000000..6439f7d9648 --- /dev/null +++ b/packages/datastore/__tests__/DataStore/DataStore.BasicOperations.test.ts @@ -0,0 +1,1109 @@ +import { from, of } from 'rxjs'; +import { + DataStore as DataStoreType, + initSchema as initSchemaType, +} from '../../src/datastore/datastore'; +import { + NonModelTypeConstructor, + PersistentModelConstructor, +} from '../../src/types'; +import { Metadata, Model, testSchema, getDataStore } from '../helpers'; + +let initSchema: typeof initSchemaType; + +let { DataStore } = getDataStore() as { + DataStore: typeof DataStoreType; +}; + +describe('Basic operations', () => { + let Model: PersistentModelConstructor; + let Metadata: NonModelTypeConstructor; + + beforeEach(() => { + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + runExclusive: jest.fn(() => []), + query: jest.fn(() => []), + observe: jest.fn(() => from([])), + clear: jest.fn(), + })); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + ({ Model, Metadata } = classes as { + Model: PersistentModelConstructor; + Metadata: NonModelTypeConstructor; + }); + }); + + test('Save returns the saved model', async () => { + let model: Model; + const save = jest.fn(() => [model]); + const query = jest.fn(() => [model]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { Model } = classes as { + Model: PersistentModelConstructor; + }; + + model = new Model({ + field1: 'Some value', + dateCreated: new Date().toISOString(), + }); + + const result = await DataStore.save(model); + + const [settingsSave, modelCall] = save.mock.calls; + const [_model, _condition, _mutator, patches] = modelCall; + + const expectedPatchedFields = [ + 'field1', + 'dateCreated', + 'id', + '_version', + '_lastChangedAt', + '_deleted', + ]; + + expect(result).toMatchObject(model); + expect(patches[0].map(p => p.path.join(''))).toEqual(expectedPatchedFields); + }); + + test('Save returns the updated model and patches', async () => { + let model: Model; + const save = jest.fn(() => [model]); + const query = jest.fn(() => [model]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { Model } = classes as { + Model: PersistentModelConstructor; + }; + + model = new Model({ + field1: 'something', + dateCreated: new Date().toISOString(), + }); + + await DataStore.save(model); + + model = Model.copyOf(model, draft => { + draft.field1 = 'edited'; + }); + + const result = await DataStore.save(model); + + const [settingsSave, modelSave, modelUpdate] = save.mock.calls; + const [_model, _condition, _mutator, [patches]] = modelUpdate; + + const expectedPatches = [ + { op: 'replace', path: ['field1'], value: 'edited' }, + ]; + + expect(result).toMatchObject(model); + expect(patches).toMatchObject(expectedPatches); + }); + + test('Save returns the updated model and patches - list field', async () => { + let model: Model; + const save = jest.fn(() => [model]); + const query = jest.fn(() => [model]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { Model } = classes as { + Model: PersistentModelConstructor; + }; + + model = new Model({ + field1: 'something', + dateCreated: new Date().toISOString(), + emails: ['john@doe.com', 'jane@doe.com'], + }); + + await DataStore.save(model); + + model = Model.copyOf(model, draft => { + draft.emails = [...model.emails!, 'joe@doe.com']; + }); + + let result = await DataStore.save(model); + + expect(result).toMatchObject(model); + + model = Model.copyOf(model, draft => { + draft.emails?.push('joe@doe.com'); + }); + + result = await DataStore.save(model); + + expect(result).toMatchObject(model); + + const [settingsSave, modelSave, modelUpdate, modelUpdate2] = ( + save.mock.calls + ); + + const [_model, _condition, _mutator, [patches]] = modelUpdate; + const [_model2, _condition2, _mutator2, [patches2]] = modelUpdate2; + + const expectedPatches = [ + { + op: 'replace', + path: ['emails'], + value: ['john@doe.com', 'jane@doe.com', 'joe@doe.com'], + }, + ]; + + const expectedPatches2 = [ + { + op: 'replace', + path: ['emails'], + value: ['john@doe.com', 'jane@doe.com', 'joe@doe.com', 'joe@doe.com'], + }, + ]; + + expect(patches).toMatchObject(expectedPatches); + expect(patches2).toMatchObject(expectedPatches2); + }); + + test('Read-only fields cannot be overwritten', async () => { + let model: Model; + const save = jest.fn(() => [model]); + const query = jest.fn(() => [model]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { Model } = classes as { + Model: PersistentModelConstructor; + }; + + expect(() => { + new Model({ + field1: 'something', + dateCreated: new Date().toISOString(), + createdAt: '2021-06-03T20:56:23.201Z', + } as any); + }).toThrow('createdAt is read-only.'); + + model = new Model({ + field1: 'something', + dateCreated: new Date().toISOString(), + }); + + expect(() => { + Model.copyOf(model, draft => { + (draft as any).createdAt = '2021-06-03T20:56:23.201Z'; + }); + }).toThrow('createdAt is read-only.'); + + expect(() => { + Model.copyOf(model, draft => { + (draft as any).updatedAt = '2021-06-03T20:56:23.201Z'; + }); + }).toThrow('updatedAt is read-only.'); + }); + + describe('Instantiation validations', () => { + test('required field (undefined)', () => { + expect(() => { + new Model({ + field1: undefined!, + dateCreated: new Date().toISOString(), + }); + }).toThrow('Field field1 is required'); + }); + + test('required field (null)', () => { + expect(() => { + new Model({ + field1: null!, + dateCreated: new Date().toISOString(), + }); + }).toThrow('Field field1 is required'); + }); + + test('wrong type (number -> string)', () => { + expect(() => { + new Model({ + field1: 1234, + dateCreated: new Date().toISOString(), + }); + }).toThrow( + 'Field field1 should be of type string, number received. 1234' + ); + }); + + test('wrong type (string -> date)', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: 'not-a-date', + }); + }).toThrow( + 'Field dateCreated should be of type AWSDateTime, validation failed. not-a-date' + ); + }); + + test('set nested non model field as undefined', () => { + expect( + // @ts-ignore + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + tags: undefined, + rewards: [], + penNames: [], + nominations: [], + }), + }).metadata.tags + ).toBeUndefined(); + }); + + test('pass null to nested non model array field (constructor)', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + tags: undefined, + rewards: [null!], + penNames: [], + nominations: [], + }), + }); + }).toThrow( + 'All elements in the rewards array should be of type string, [null] received. ' + ); + }); + + // without non model constructor + test('pass null to nested non model non nullable array field (no constructor)', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + tags: undefined, + rewards: [null!], + penNames: [], + nominations: [], + }, + }); + }).toThrow( + 'All elements in the rewards array should be of type string, [null] received. ' + ); + }); + + test('valid model with null optional fields', () => { + const m = new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + optionalField1: null, + }); + expect(m.optionalField1).toBe(null); + }); + + test('valid model with `undefined` optional fields', () => { + const m = new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + optionalField1: undefined, + }); + expect(m.optionalField1).toBe(null); + }); + + test('valid model with omitted optional fields', () => { + const m = new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + /** + * Omitting this: + * + * optionalField: undefined + */ + }); + expect(m.optionalField1).toBe(null); + }); + + test('copyOf() setting optional field to null', () => { + const emailsVal = ['test@test.test']; + const original = new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + optionalField1: 'defined value', + emails: emailsVal, + }); + const copied = Model.copyOf(original, d => (d.optionalField1 = null)); + expect(copied.optionalField1).toBe(null); + expect(copied.emails).toEqual(emailsVal); + }); + + test('copyOf() setting optional field to undefined', () => { + const original = new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + optionalField1: 'defined value', + }); + const copied = Model.copyOf( + original, + d => (d.optionalField1 = undefined) + ); + expect(copied.optionalField1).toBe(null); + }); + + test('pass null to non nullable array field', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + emails: [null!], + }); + }).toThrow( + 'All elements in the emails array should be of type string, [null] received. ' + ); + }); + + test('pass null to nullable array field', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + ips: [null], + }); + }).not.toThrow(); + }); + + test('valid model array of AWSIPAdress', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + ips: ['1.1.1.1'], + }); + }).not.toThrow(); + }); + + test('invalid AWSIPAddress', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + ips: ['not.an.ip'], + }); + }).toThrow( + `All elements in the ips array should be of type AWSIPAddress, validation failed for one or more elements. not.an.ip` + ); + }); + + test('invalid AWSIPAddress in one index', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + ips: ['1.1.1.1', 'not.an.ip'], + }); + }).toThrow( + `All elements in the ips array should be of type AWSIPAddress, validation failed for one or more elements. 1.1.1.1,not.an.ip` + ); + }); + + test('valid AWSEmail', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + emails: ['test@example.com'], + }); + }).not.toThrow(); + }); + + test('valid empty array of AWSEmail', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + emails: [], + ips: [], + }); + }).not.toThrow(); + }); + + test('invalid AWSEmail', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + emails: ['not-an-email'], + }); + }).toThrow( + 'All elements in the emails array should be of type AWSEmail, validation failed for one or more elements. not-an-email' + ); + }); + + test('required sub non model field with constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + tags: undefined, + rewards: [], + penNames: [], + nominations: null!, + }), + }); + }).toThrow('Field nominations is required'); + }); + + test('required sub non model field without constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + tags: undefined, + rewards: [], + penNames: [], + nominations: null!, + }, + }); + }).toThrow('Field nominations is required'); + }); + + test('sub non model non nullable array field with constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + tags: undefined, + rewards: [], + penNames: [undefined!], + nominations: [], + }), + }); + }).toThrow( + 'All elements in the penNames array should be of type string, [undefined] received. ' + ); + }); + + // without non model constructor + test('sub non model non nullable array field without constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + tags: undefined, + rewards: [], + penNames: [undefined!], + nominations: [], + }, + }); + }).toThrow( + 'All elements in the penNames array should be of type string, [undefined] received. ' + ); + }); + + test('sub non model array field invalid type with constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + tags: [1234], + rewards: [], + penNames: [], + nominations: [], + }), + }); + }).toThrow( + 'All elements in the tags array should be of type string | null | undefined, [number] received. 1234' + ); + }); + + // without non model constructor + test('sub non model array field invalid type without constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + tags: [1234], + rewards: [], + penNames: [], + nominations: [], + }, + }); + }).toThrow( + 'All elements in the tags array should be of type string | null | undefined, [number] received. 1234' + ); + }); + + test('valid sub non model nullable array field (null) with constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [null], + }), + }); + }).not.toThrow(); + }); + + test('valid sub non model nullable array field (null) without constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [null], + }, + }); + }).not.toThrow(); + }); + + test('valid sub non model nullable array field (undefined) with constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [undefined!], + }), + }); + }).not.toThrow(); + }); + + test('valid sub non model nullable array field (undefined) without constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [undefined!], + }, + }); + }).not.toThrow(); + }); + + test('valid sub non model nullable array field (undefined and null) with constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [undefined!, null], + }), + }); + }).not.toThrow(); + }); + + test('valid sub non model nullable array field (undefined and null) without constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [undefined!, null], + }, + }); + }).not.toThrow(); + }); + + test('valid sub non model nullable array field (null and string) with constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [null, 'ok'], + }), + }); + }).not.toThrow(); + }); + + test('valid sub non model nullable array field (null and string) without constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [null, 'ok'], + }, + }); + }).not.toThrow(); + }); + + test('wrong type sub non model nullable array field (null and number) with constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [null, 123], + }), + }); + }).toThrow( + 'All elements in the misc array should be of type string | null | undefined, [null,number] received. ,123' + ); + }); + + test('wrong type sub non model nullable array field (null and number) without constructor', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [null, 123], + }, + }); + }).toThrow( + 'All elements in the misc array should be of type string | null | undefined, [null,number] received. ,123' + ); + }); + + test('allow extra attribute', () => { + expect( + new Model({ extraAttribute: 'some value', field1: 'some value' }) + ).toHaveProperty('extraAttribute'); + }); + + test('throw on invalid constructor', () => { + expect(() => { + Model.copyOf(undefined, d => d); + }).toThrow('The source object is not a valid model'); + }); + + test('invalid type on copyOf', () => { + expect(() => { + const source = new Model({ + field1: 'something', + dateCreated: new Date().toISOString(), + }); + Model.copyOf(source, d => (d.field1 = 1234)); + }).toThrow( + 'Field field1 should be of type string, number received. 1234' + ); + }); + + test('invalid sub non model type', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + // @ts-ignore + metadata: 'invalid', + }); + }).toThrow( + 'Field metadata should be of type Metadata, string recieved. invalid' + ); + }); + + test('sub non model null', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: null, + }); + }).not.toThrow( + 'Field metadata should be of type Metadata, string recieved. invalid' + ); + }); + + test('invalid nested sub non model type', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: { + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + // @ts-ignore + login: 'login', + }, + }); + }).toThrow('Field login should be of type Login, string recieved. login'); + }); + + test('invalid array sub non model type', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + // @ts-ignore + logins: ['bad type', 'another bad type'], + }); + }).toThrow( + 'All elements in the logins array should be of type Login, [string] received. bad type' + ); + }); + + test('invalid array sub non model field type', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + // @ts-ignore + logins: [{ username: 4 }], + }); + }).toThrow('Field username should be of type string, number received. 4'); + }); + + test('nullable array sub non model', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + logins: [null!, { username: 'user' }], + }); + }).not.toThrow(); + }); + + test('array sub non model wrong type', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + // @ts-ignore + logins: 'my login', + }); + }).toThrow( + 'Field logins should be of type [Login | null | undefined], string received. my login' + ); + }); + + test('array sub non model null', () => { + expect(() => { + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + logins: null, + }); + }).not.toThrow(); + }); + }); + + test('Delete params', async () => { + await expect(DataStore.delete(undefined)).rejects.toThrow( + 'Model or Model Constructor required' + ); + + await expect(DataStore.delete(Model)).rejects.toThrow( + 'Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL' + ); + + await expect(DataStore.delete(Model, (() => {}))).rejects.toThrow( + "Invalid predicate. Terminate your predicate with a valid condition (e.g., `p => p.field.eq('value')`) or pass `Predicates.ALL`." + ); + + await expect(DataStore.delete({})).rejects.toThrow( + 'Object is not an instance of a valid model' + ); + + await expect( + DataStore.delete( + new Model({ + field1: 'somevalue', + dateCreated: new Date().toISOString(), + }), + {} + ) + ).rejects.toThrow('Invalid criteria'); + }); + + test('Delete many returns many', async () => { + const models: Model[] = []; + const save = jest.fn(model => { + model instanceof Model && models.push(model); + }); + const query = jest.fn(() => models); + const _delete = jest.fn(() => [models, models]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + delete: _delete, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { Model } = classes as { + Model: PersistentModelConstructor; + }; + + for (let i = 0; i < 10; i++) { + await DataStore.save( + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author ' + i, + rewards: [], + penNames: [], + nominations: [], + misc: [null, 'ok'], + }), + }) + ); + } + + const deleted = await DataStore.delete(Model, m => + m.field1.eq('someField') + ); + + expect(deleted.length).toEqual(10); + deleted.forEach(deletedItem => { + expect(deletedItem.field1).toEqual('someField'); + }); + }); + + test('Delete one returns one', async () => { + let model: Model | undefined; + const save = jest.fn(saved => (model = saved)); + const query = jest.fn(() => [model]); + const _delete = jest.fn(() => [[model], [model]]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + delete: _delete, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { Model } = classes as { + Model: PersistentModelConstructor; + }; + + const saved = await DataStore.save( + new Model({ + field1: 'someField', + dateCreated: new Date().toISOString(), + metadata: new Metadata({ + author: 'Some author', + rewards: [], + penNames: [], + nominations: [], + misc: [null, 'ok'], + }), + }) + ); + + const deleted: Model[] = await DataStore.delete(Model, saved.id); + + expect(deleted.length).toEqual(1); + expect(deleted[0]).toEqual(model); + }); + + test('Query params', async () => { + await expect(DataStore.query(undefined)).rejects.toThrow( + 'Constructor is not for a valid model' + ); + + await expect(DataStore.query(undefined)).rejects.toThrow( + 'Constructor is not for a valid model' + ); + + await expect( + DataStore.query(Model, 'someid', { page: 0 }) + ).rejects.toThrow('Limit is required when requesting a page'); + + await expect( + DataStore.query(Model, 'someid', { page: 'a', limit: 10 }) + ).rejects.toThrow('Page should be a number'); + + await expect( + DataStore.query(Model, 'someid', { page: -1, limit: 10 }) + ).rejects.toThrow("Page can't be negative"); + + await expect( + DataStore.query(Model, 'someid', { + page: 0, + limit: 'avalue', + }) + ).rejects.toThrow('Limit should be a number'); + + await expect( + DataStore.query(Model, 'someid', { page: 0, limit: -1 }) + ).rejects.toThrow("Limit can't be negative"); + }); +}); diff --git a/packages/datastore/__tests__/DataStore/DataStore.CustomPrimaryKey.test.ts b/packages/datastore/__tests__/DataStore/DataStore.CustomPrimaryKey.test.ts new file mode 100644 index 00000000000..53040e76780 --- /dev/null +++ b/packages/datastore/__tests__/DataStore/DataStore.CustomPrimaryKey.test.ts @@ -0,0 +1,1044 @@ +import { from, of } from 'rxjs'; + +import { Predicates } from '../../src/predicates'; +import { + NonModelTypeConstructor, + PersistentModelConstructor, +} from '../../src/types'; +import { + DataStore as DataStoreType, + initSchema as initSchemaType, +} from '../../src/datastore/datastore'; +import { ExclusiveStorage as StorageType } from '../../src/storage/storage'; +import { + Metadata, + getDataStore, + PostCustomPK as PostCustomPKType, + testSchema, +} from '../helpers'; + +let initSchema: typeof initSchemaType; + +let { DataStore } = getDataStore() as { + DataStore: typeof DataStoreType; +}; +const nameOf = (name: keyof T) => name; +/** + * Does nothing intentionally, we care only about type checking + */ +const expectType: (param: T) => void = () => {}; + +describe('DataStore Custom PK tests', () => { + // `console` methods have been mocked in jest.setup.js + const consoleError = console.error as jest.Mock; + const consoleWarn = console.warn as jest.Mock; + const consoleDebug = console.debug as jest.Mock; + const consoleInfo = console.info as jest.Mock; + + beforeEach(() => { + jest.resetModules(); + + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + runExclusive: jest.fn(), + query: jest.fn(() => []), + save: jest.fn(() => []), + observe: jest.fn(() => of()), + clear: jest.fn(), + })); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + }); + + afterEach(async () => { + consoleError.mockClear(); + consoleWarn.mockClear(); + consoleDebug.mockClear(); + consoleInfo.mockClear(); + try { + await DataStore.clear(); + } catch (error) { + // we expect some tests to leave DataStore in a state where this + // error will be thrown on clear. + if (!/not initialized/.test(error.message)) { + throw error; + } + } + }); + + describe('initSchema tests', () => { + test('PostCustomPK class is created', () => { + const classes = initSchema(testSchema()); + + expect(classes).toHaveProperty('PostCustomPK'); + + const { PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }; + + expect(PostCustomPK).toHaveProperty( + nameOf>('copyOf') + ); + + expect(typeof PostCustomPK.copyOf).toBe('function'); + }); + + test('PostCustomPK class can be instantiated', () => { + const { PostCustomPK } = initSchema(testSchema()) as { + PostCustomPK: PersistentModelConstructor; + }; + + const model = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + }); + + expect(model).toBeInstanceOf(PostCustomPK); + + expect(model.postId).toBeDefined(); + }); + }); + + describe('Immutability', () => { + test('Title cannot be changed', () => { + const { PostCustomPK } = initSchema(testSchema()) as { + PostCustomPK: PersistentModelConstructor; + }; + + const model = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + }); + + expect(() => { + (model).title = 'edit'; + }).toThrow("Cannot assign to read only property 'title' of object"); + }); + + test('PostCustomPK can be copied+edited by creating an edited copy', () => { + const { PostCustomPK } = initSchema(testSchema()) as { + PostCustomPK: PersistentModelConstructor; + }; + + const model1 = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + }); + + const model2 = PostCustomPK.copyOf(model1, draft => { + draft.title = 'edited'; + }); + + expect(model1).not.toBe(model2); + + // postId should be kept the same + expect(model1.postId).toBe(model2.postId); + + expect(model1.title).toBe('something'); + expect(model2.title).toBe('edited'); + }); + + test('postId cannot be changed inside copyOf', () => { + const { PostCustomPK } = initSchema(testSchema()) as { + PostCustomPK: PersistentModelConstructor; + }; + + const model1 = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + }); + + const model2 = PostCustomPK.copyOf(model1, draft => { + (draft).postId = 'a-new-postId'; + }); + + // postId should be kept the same + expect(model1.postId).toBe(model2.postId); + + // we should always be told *in some way* when an "update" will not actually + // be applied. for now, this is a warning, because throwing an error, updating + // the record's PK, or creating a new record are all breaking changes. + expect(consoleWarn).toHaveBeenCalledWith( + expect.stringContaining( + "copyOf() does not update PK fields. The 'postId' update is being ignored." + ), + expect.objectContaining({ source: model1 }) + ); + }); + + test('Optional field can be initialized with undefined', () => { + const { PostCustomPK } = initSchema(testSchema()) as { + PostCustomPK: PersistentModelConstructor; + }; + + const model1 = new PostCustomPK({ + postId: '12345', + title: 'something', + description: undefined, + dateCreated: new Date().toISOString(), + }); + + expect(model1.description).toBeNull(); + }); + + test('Optional field can be initialized with null', () => { + const { PostCustomPK } = initSchema(testSchema()) as { + PostCustomPK: PersistentModelConstructor; + }; + + const model1 = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + description: null, + }); + + expect(model1.description).toBeNull(); + }); + + test('Optional field can be changed to undefined inside copyOf', () => { + const { PostCustomPK } = initSchema(testSchema()) as { + PostCustomPK: PersistentModelConstructor; + }; + + const model1 = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + description: 'something-else', + }); + + const model2 = PostCustomPK.copyOf(model1, draft => { + (draft).description = undefined; + }); + + // postId should be kept the same + expect(model1.postId).toBe(model2.postId); + + expect(model1.description).toBe('something-else'); + expect(model2.description).toBeNull(); + }); + + test('Optional field can be set to null inside copyOf', () => { + const { PostCustomPK } = initSchema(testSchema()) as { + PostCustomPK: PersistentModelConstructor; + }; + + const model1 = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + }); + + const model2 = PostCustomPK.copyOf(model1, draft => { + (draft).description = null; + }); + + // postId should be kept the same + expect(model1.postId).toBe(model2.postId); + + expect(model1.description).toBeNull(); + expect(model2.description).toBeNull(); + }); + + test('Non @model - Field cannot be changed', () => { + const { Metadata } = initSchema(testSchema()) as { + Metadata: NonModelTypeConstructor; + }; + + const nonPostCustomPK = new Metadata({ + author: 'something', + rewards: [], + penNames: [], + nominations: [], + }); + + expect(() => { + (nonPostCustomPK).author = 'edit'; + }).toThrow("Cannot assign to read only property 'author' of object"); + }); + }); + + describe('Initialization', () => { + let PostCustomPK; + test('start is called only once', async () => { + const storage: StorageType = + require('../../src/storage/storage').ExclusiveStorage; + + const classes = initSchema(testSchema()); + + ({ PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }); + + const promises = [ + DataStore.query(PostCustomPK), + DataStore.query(PostCustomPK), + DataStore.query(PostCustomPK), + DataStore.query(PostCustomPK), + ]; + + await Promise.all(promises); + + expect(storage).toHaveBeenCalledTimes(1); + }); + + test('It is initialized when observing (no query)', async () => { + const storage: StorageType = + require('../../src/storage/storage').ExclusiveStorage; + + const classes = initSchema(testSchema()); + + ({ PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }); + + DataStore.observe(PostCustomPK).subscribe(jest.fn()); + + expect(storage).toHaveBeenCalledTimes(1); + }); + }); + + describe('Basic operations', () => { + let PostCustomPK: PersistentModelConstructor; + + beforeEach(() => { + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + runExclusive: jest.fn(() => []), + query: jest.fn(() => []), + observe: jest.fn(() => from([])), + clear: jest.fn(), + })); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + ({ PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }); + }); + + test('Save returns the saved model', async () => { + let model: PostCustomPKType; + const save = jest.fn(() => [model]); + const query = jest.fn(() => [model]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }; + + model = new PostCustomPK({ + postId: '12345', + title: 'Some value', + dateCreated: new Date().toISOString(), + }); + + const result = await DataStore.save(model); + + const [settingsSave, modelCall] = save.mock.calls; + const [_model, _condition, _mutator, patches] = modelCall; + + const expectedPatchedFields = [ + 'postId', + 'title', + 'dateCreated', + '_version', + '_lastChangedAt', + '_deleted', + ]; + + expect(result).toMatchObject(model); + expect(patches[0].map(p => p.path.join(''))).toEqual( + expectedPatchedFields + ); + }); + + test('Save returns the updated model and patches', async () => { + let model: PostCustomPKType; + const save = jest.fn(() => [model]); + const query = jest.fn(() => [model]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }; + + model = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + }); + + await DataStore.save(model); + + model = PostCustomPK.copyOf(model, draft => { + draft.title = 'edited'; + }); + + const result = await DataStore.save(model); + + const [settingsSave, modelSave, modelUpdate] = save.mock.calls; + const [_model, _condition, _mutator, [patches]] = modelUpdate; + + const expectedPatches = [ + { op: 'replace', path: ['title'], value: 'edited' }, + ]; + + expect(result).toMatchObject(model); + expect(patches).toMatchObject(expectedPatches); + }); + + test('Save returns the updated model and patches - list field', async () => { + let model: PostCustomPKType; + const save = jest.fn(() => [model]); + const query = jest.fn(() => [model]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }; + + model = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + emails: ['john@doe.com', 'jane@doe.com'], + }); + + await DataStore.save(model); + + model = PostCustomPK.copyOf(model, draft => { + draft.emails = [...draft.emails!, 'joe@doe.com']; + }); + + let result = await DataStore.save(model); + + expect(result).toMatchObject(model); + + model = PostCustomPK.copyOf(model, draft => { + draft.emails!.push('joe@doe.com'); + }); + + result = await DataStore.save(model); + + expect(result).toMatchObject(model); + + const [settingsSave, modelSave, modelUpdate, modelUpdate2] = ( + save.mock.calls + ); + + const [_model, _condition, _mutator, [patches]] = modelUpdate; + const [_model2, _condition2, _mutator2, [patches2]] = modelUpdate2; + + const expectedPatches = [ + { + op: 'replace', + path: ['emails'], + value: ['john@doe.com', 'jane@doe.com', 'joe@doe.com'], + }, + ]; + + const expectedPatches2 = [ + { + op: 'replace', + path: ['emails'], + value: ['john@doe.com', 'jane@doe.com', 'joe@doe.com', 'joe@doe.com'], + }, + ]; + + expect(patches).toMatchObject(expectedPatches); + expect(patches2).toMatchObject(expectedPatches2); + }); + + test('Read-only fields cannot be overwritten', async () => { + let model: PostCustomPKType; + const save = jest.fn(() => [model]); + const query = jest.fn(() => [model]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }; + + expect(() => { + new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + createdAt: '2021-06-03T20:56:23.201Z', + }) as any; + }).toThrow('createdAt is read-only.'); + + model = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + }); + + expect(() => { + PostCustomPK.copyOf(model, draft => { + (draft as any).createdAt = '2021-06-03T20:56:23.201Z'; + }); + }).toThrow('createdAt is read-only.'); + + expect(() => { + PostCustomPK.copyOf(model, draft => { + (draft as any).updatedAt = '2021-06-03T20:56:23.201Z'; + }); + }).toThrow('updatedAt is read-only.'); + }); + + test('Instantiation validations custom pk', async () => { + expect(() => { + new PostCustomPK({ + postId: '12345', + title: undefined as any, // because we're trying to trigger JS error + dateCreated: new Date().toISOString(), + }); + }).toThrow('Field title is required'); + + expect(() => { + new PostCustomPK({ + postId: '12345', + title: null as any, // because we're trying to trigger JS error + dateCreated: new Date().toISOString(), + }); + }).toThrow('Field title is required'); + + expect(() => { + new PostCustomPK({ + postId: '12345', + title: 1234, + dateCreated: new Date().toISOString(), + }); + }).toThrow('Field title should be of type string, number received. 1234'); + + expect(() => { + new PostCustomPK({ + postId: '12345', + title: 'someField', + dateCreated: 'not-a-date', + }); + }).toThrow( + 'Field dateCreated should be of type AWSDateTime, validation failed. not-a-date' + ); + + expect(() => { + new PostCustomPK({ + postId: '12345', + title: 'someField', + dateCreated: new Date().toISOString(), + emails: [null as any], // because we're trying to trigger JS error + }); + }).toThrow( + 'All elements in the emails array should be of type string, [null] received. ' + ); + + expect(() => { + new PostCustomPK({ + postId: '12345', + title: 'someField', + dateCreated: new Date().toISOString(), + emails: ['test@example.com'], + }); + }).not.toThrow(); + + expect(() => { + new PostCustomPK({ + postId: '12345', + title: 'someField', + dateCreated: new Date().toISOString(), + emails: ['not-an-email'], + }); + }).toThrow( + 'All elements in the emails array should be of type AWSEmail, validation failed for one or more elements. not-an-email' + ); + + expect({ + extraAttribute: 'some value', + title: 'some value', + }).toHaveProperty('extraAttribute'); + + expect(() => { + PostCustomPK.copyOf(undefined, d => d); + }).toThrow('The source object is not a valid model'); + expect(() => { + const source = new PostCustomPK({ + postId: '12345', + title: 'something', + dateCreated: new Date().toISOString(), + }); + PostCustomPK.copyOf(source, d => (d.title = 1234)); + }).toThrow('Field title should be of type string, number received. 1234'); + }); + + test('Delete params', async () => { + await expect(DataStore.delete(undefined)).rejects.toThrow( + 'Model or Model Constructor required' + ); + + await expect(DataStore.delete(PostCustomPK)).rejects.toThrow( + 'Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL' + ); + + await expect( + DataStore.delete(PostCustomPK, (() => {})) + ).rejects.toThrow( + "Invalid predicate. Terminate your predicate with a valid condition (e.g., `p => p.field.eq('value')`) or pass `Predicates.ALL`." + ); + + await expect(DataStore.delete({})).rejects.toThrow( + 'Object is not an instance of a valid model' + ); + + await expect( + DataStore.delete( + new PostCustomPK({ + postId: '12345', + title: 'somevalue', + dateCreated: new Date().toISOString(), + }), + {} + ) + ).rejects.toThrow('Invalid criteria'); + }); + + test('Delete many returns many', async () => { + const models: PostCustomPKType[] = []; + const save = jest.fn(model => { + model instanceof PostCustomPK && models.push(model); + }); + const query = jest.fn(() => models); + const _delete = jest.fn(() => [models, models]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock: jest.Mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + delete: _delete, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }; + + Promise.all( + [...Array(10).keys()].map(async i => { + await DataStore.save( + new PostCustomPK({ + postId: `${i}`, + title: 'someField', + dateCreated: new Date().toISOString(), + }) + ); + }) + ); + + const deleted = await DataStore.delete(PostCustomPK, m => + m.title.eq('someField') + ); + + const sortedRecords = deleted.sort((a, b) => + a.postId < b.postId ? -1 : 1 + ); + + expect(sortedRecords.length).toEqual(10); + sortedRecords.forEach((deletedItem, idx) => { + expect(deletedItem.postId).toEqual(`${idx}`); + expect(deletedItem.title).toEqual('someField'); + }); + }); + + test('Delete one by Custom PK returns one', async () => { + let model: PostCustomPKType; + const save = jest.fn(saved => (model = saved)); + const query = jest.fn(() => [model]); + const _delete = jest.fn(() => [[model], [model]]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + delete: _delete, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }; + + const saved = await DataStore.save( + new PostCustomPK({ + postId: '12345', + title: 'someField', + dateCreated: new Date().toISOString(), + }) + ); + + const deleted: PostCustomPKType[] = await DataStore.delete( + PostCustomPK, + saved.postId + ); + + expect(deleted.length).toEqual(1); + expect(deleted[0]).toEqual(model!); + }); + + test('Delete one by Custom PK with predicate returns one', async () => { + let model: PostCustomPKType; + const save = jest.fn(saved => (model = saved)); + const query = jest.fn(() => [model]); + const _delete = jest.fn(() => [[model], [model]]); + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => { + const _mock = { + init: jest.fn(), + save, + query, + delete: _delete, + runExclusive: jest.fn(fn => fn.bind(this, _mock)()), + clear: jest.fn(), + }; + return _mock; + }); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + const { PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }; + + const saved = await DataStore.save( + new PostCustomPK({ + postId: '12345', + title: 'someField', + dateCreated: new Date().toISOString(), + }) + ); + + const deleted: PostCustomPKType[] = await DataStore.delete( + PostCustomPK, + + m => m.postId.eq(saved.postId) + ); + + expect(deleted.length).toEqual(1); + expect(deleted[0]).toEqual(model!); + }); + + test('Query params', async () => { + await expect(DataStore.query(undefined)).rejects.toThrow( + 'Constructor is not for a valid model' + ); + + await expect(DataStore.query(undefined)).rejects.toThrow( + 'Constructor is not for a valid model' + ); + + await expect( + DataStore.query(PostCustomPK, 'someid', { page: 0 }) + ).rejects.toThrow('Limit is required when requesting a page'); + + await expect( + DataStore.query(PostCustomPK, 'someid', { + page: 'a', + limit: 10, + }) + ).rejects.toThrow('Page should be a number'); + + await expect( + DataStore.query(PostCustomPK, 'someid', { page: -1, limit: 10 }) + ).rejects.toThrow("Page can't be negative"); + + await expect( + DataStore.query(PostCustomPK, 'someid', { + page: 0, + limit: 'avalue', + }) + ).rejects.toThrow('Limit should be a number'); + + await expect( + DataStore.query(PostCustomPK, 'someid', { + page: 0, + limit: -1, + }) + ).rejects.toThrow("Limit can't be negative"); + }); + + describe('Type definitions', () => { + let PostCustomPK: PersistentModelConstructor; + + beforeEach(() => { + let model: PostCustomPKType; + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + runExclusive: jest.fn(() => [model]), + query: jest.fn(() => [model]), + observe: jest.fn(() => from([])), + clear: jest.fn(), + })); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + ({ PostCustomPK } = classes as { + PostCustomPK: PersistentModelConstructor; + }); + + model = new PostCustomPK({ + postId: '12345', + title: 'Some value', + dateCreated: new Date().toISOString(), + }); + }); + + describe('Query', () => { + test('all', async () => { + const allPostCustomPKs = await DataStore.query(PostCustomPK); + + expectType(allPostCustomPKs); + + const [one] = allPostCustomPKs; + expect(one.title).toBeDefined(); + expect(one).toBeInstanceOf(PostCustomPK); + }); + test('one by custom PK', async () => { + const onePostCustomPKById = await DataStore.query( + PostCustomPK, + 'someid' + ); + + expectType(onePostCustomPKById!); + expect(onePostCustomPKById!.title).toBeDefined(); + expect(onePostCustomPKById).toBeInstanceOf(PostCustomPK); + }); + test('with criteria', async () => { + const multiPostCustomPKWithCriteria = await DataStore.query( + PostCustomPK, + c => c.title.contains('something') + ); + + expectType(multiPostCustomPKWithCriteria); + + const [one] = multiPostCustomPKWithCriteria; + expect(one.title).toBeDefined(); + expect(one).toBeInstanceOf(PostCustomPK); + }); + test('with pagination', async () => { + const allPostCustomPKsPaginated = await DataStore.query( + PostCustomPK, + Predicates.ALL, + { page: 0, limit: 20 } + ); + + expectType(allPostCustomPKsPaginated); + const [one] = allPostCustomPKsPaginated; + expect(one.title).toBeDefined(); + expect(one).toBeInstanceOf(PostCustomPK); + }); + }); + + describe('Query with generic type', () => { + test('all', async () => { + const allPostCustomPKs = + await DataStore.query(PostCustomPK); + + expectType(allPostCustomPKs); + + const [one] = allPostCustomPKs; + expect(one.title).toBeDefined(); + expect(one).toBeInstanceOf(PostCustomPK); + }); + test('one by postId', async () => { + const onePostCustomPKById = await DataStore.query( + PostCustomPK, + 'someid' + ); + expectType(onePostCustomPKById!); + expect(onePostCustomPKById!.title).toBeDefined(); + expect(onePostCustomPKById).toBeInstanceOf(PostCustomPK); + }); + test('with criteria', async () => { + const multiPostCustomPKWithCriteria = + await DataStore.query(PostCustomPK, c => + c.title.contains('something') + ); + + expectType(multiPostCustomPKWithCriteria); + + const [one] = multiPostCustomPKWithCriteria; + expect(one.title).toBeDefined(); + expect(one).toBeInstanceOf(PostCustomPK); + }); + test('with pagination', async () => { + const allPostCustomPKsPaginated = + await DataStore.query( + PostCustomPK, + Predicates.ALL, + { page: 0, limit: 20 } + ); + + expectType(allPostCustomPKsPaginated); + const [one] = allPostCustomPKsPaginated; + expect(one.title).toBeDefined(); + expect(one).toBeInstanceOf(PostCustomPK); + }); + }); + }); + }); +}); diff --git a/packages/datastore/__tests__/DataStore/DataStore.TypeDefinitions.test.ts b/packages/datastore/__tests__/DataStore/DataStore.TypeDefinitions.test.ts new file mode 100644 index 00000000000..246317eb65a --- /dev/null +++ b/packages/datastore/__tests__/DataStore/DataStore.TypeDefinitions.test.ts @@ -0,0 +1,207 @@ +import { from } from 'rxjs'; + +import { Predicates } from '../../src/predicates'; +import { + DataStore as DataStoreType, + initSchema as initSchemaType, +} from '../../src/datastore/datastore'; +import { PersistentModel, PersistentModelConstructor } from '../../src/types'; +import { Model, testSchema, getDataStore } from '../helpers'; + +let initSchema: typeof initSchemaType; + +let { DataStore } = getDataStore() as { + DataStore: typeof DataStoreType; +}; + +/** + * Does nothing intentionally, we care only about type checking + */ +const expectType: (param: T) => void = () => {}; + +describe('Type definitions', () => { + let Model: PersistentModelConstructor; + + beforeEach(() => { + let model: Model; + + jest.resetModules(); + jest.doMock('../../src/storage/storage', () => { + const mock = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + runExclusive: jest.fn(() => [model]), + query: jest.fn(() => [model]), + observe: jest.fn(() => from([])), + clear: jest.fn(), + })); + + (mock).getNamespace = () => ({ models: {} }); + + return { ExclusiveStorage: mock }; + }); + ({ initSchema, DataStore } = require('../../src/datastore/datastore')); + + const classes = initSchema(testSchema()); + + ({ Model } = classes as { Model: PersistentModelConstructor }); + + model = new Model({ + field1: 'Some value', + dateCreated: new Date().toISOString(), + }); + }); + + describe('Query', () => { + test('all', async () => { + const allModels = await DataStore.query(Model); + expectType(allModels); + const [one] = allModels; + expect(one.field1).toBeDefined(); + expect(one).toBeInstanceOf(Model); + }); + test('one by id', async () => { + const oneModelById = await DataStore.query(Model, 'someid'); + expectType(oneModelById); + expect(oneModelById?.field1).toBeDefined(); + expect(oneModelById).toBeInstanceOf(Model); + }); + test('with criteria', async () => { + const multiModelWithCriteria = await DataStore.query(Model, c => + c.field1.contains('something') + ); + expectType(multiModelWithCriteria); + const [one] = multiModelWithCriteria; + expect(one.field1).toBeDefined(); + expect(one).toBeInstanceOf(Model); + }); + test('with identity function criteria', async () => { + const multiModelWithCriteria = await DataStore.query(Model, c => c); + expectType(multiModelWithCriteria); + const [one] = multiModelWithCriteria; + expect(one.field1).toBeDefined(); + expect(one).toBeInstanceOf(Model); + }); + test('with pagination', async () => { + const allModelsPaginated = await DataStore.query(Model, Predicates.ALL, { + page: 0, + limit: 20, + }); + expectType(allModelsPaginated); + const [one] = allModelsPaginated; + expect(one.field1).toBeDefined(); + expect(one).toBeInstanceOf(Model); + }); + }); + + describe('Query with generic type', () => { + test('all', async () => { + const allModels = await DataStore.query(Model); + expectType(allModels); + const [one] = allModels; + expect(one.field1).toBeDefined(); + expect(one).toBeInstanceOf(Model); + }); + test('one by id', async () => { + const oneModelById = await DataStore.query(Model, 'someid'); + expectType(oneModelById); + expect(oneModelById?.field1).toBeDefined(); + expect(oneModelById).toBeInstanceOf(Model); + }); + test('with criteria', async () => { + const multiModelWithCriteria = await DataStore.query(Model, c => + c.field1.contains('something') + ); + expectType(multiModelWithCriteria); + const [one] = multiModelWithCriteria; + expect(one.field1).toBeDefined(); + expect(one).toBeInstanceOf(Model); + }); + test('with pagination', async () => { + const allModelsPaginated = await DataStore.query( + Model, + Predicates.ALL, + { page: 0, limit: 20 } + ); + expectType(allModelsPaginated); + const [one] = allModelsPaginated; + expect(one.field1).toBeDefined(); + expect(one).toBeInstanceOf(Model); + }); + }); + + describe('Observe', () => { + test('subscribe to all models', async () => { + DataStore.observe().subscribe(({ element, model }) => { + expectType>(model); + expectType(element); + }); + }); + test('subscribe to model instance', async () => { + const model = new Model({ + field1: 'somevalue', + dateCreated: new Date().toISOString(), + }); + + DataStore.observe(model).subscribe(({ element, model }) => { + expectType>(model); + expectType(element); + }); + }); + test('subscribe to model', async () => { + DataStore.observe(Model).subscribe(({ element, model }) => { + expectType>(model); + expectType(element); + }); + }); + test('subscribe to model instance by id', async () => { + DataStore.observe(Model, 'some id').subscribe(({ element, model }) => { + expectType>(model); + expectType(element); + }); + }); + test('subscribe to model with criteria', async () => { + DataStore.observe(Model, c => c.field1.ne('somevalue')).subscribe( + ({ element, model }) => { + expectType>(model); + expectType(element); + } + ); + }); + }); + + describe('Observe with generic type', () => { + test('subscribe to model instance', async () => { + const model = new Model({ + field1: 'somevalue', + dateCreated: new Date().toISOString(), + }); + + DataStore.observe(model).subscribe(({ element, model }) => { + expectType>(model); + expectType(element); + }); + }); + test('subscribe to model', async () => { + DataStore.observe(Model).subscribe(({ element, model }) => { + expectType>(model); + expectType(element); + }); + }); + test('subscribe to model instance by id', async () => { + DataStore.observe(Model, 'some id').subscribe( + ({ element, model }) => { + expectType>(model); + expectType(element); + } + ); + }); + test('subscribe to model with criteria', async () => { + DataStore.observe(Model, c => c.field1.ne('somevalue')).subscribe( + ({ element, model }) => { + expectType>(model); + expectType(element); + } + ); + }); + }); +}); diff --git a/packages/datastore/__tests__/DataStore/DataStore.test.ts b/packages/datastore/__tests__/DataStore/DataStore.test.ts index b56b1c2f404..2cbcc66ff9d 100644 --- a/packages/datastore/__tests__/DataStore/DataStore.test.ts +++ b/packages/datastore/__tests__/DataStore/DataStore.test.ts @@ -9,7 +9,6 @@ import { Predicates } from '../../src/predicates'; import { ExclusiveStorage as StorageType } from '../../src/storage/storage'; import { NonModelTypeConstructor, - PersistentModel, PersistentModelConstructor, } from '../../src/types'; import { @@ -41,6 +40,12 @@ process.on('unhandledRejection', reason => { }); describe('DataStore tests', () => { + // `console` methods have been mocked in jest.setup.js + const consoleError = console.error as jest.Mock; + const consoleWarn = console.warn as jest.Mock; + const consoleDebug = console.debug as jest.Mock; + const consoleInfo = console.info as jest.Mock; + beforeEach(() => { jest.resetModules(); @@ -62,12 +67,16 @@ describe('DataStore tests', () => { }); afterEach(async () => { + consoleError.mockClear(); + consoleWarn.mockClear(); + consoleDebug.mockClear(); + consoleInfo.mockClear(); try { await DataStore.clear(); } catch (error) { // we expect some tests to leave DataStore in a state where this // error will be thrown on clear. - if (!error.message.match(/not initialized/)) { + if (!/not initialized/.test(error.message)) { throw error; } } @@ -80,19 +89,21 @@ describe('DataStore tests', () => { */ test('error on schema not initialized on start', async () => { - const errorLog = jest.spyOn(console, 'error'); const errorRegex = /Schema is not initialized/; await expect(DataStore.start()).rejects.toThrow(errorRegex); - expect(errorLog).toHaveBeenCalledWith(expect.stringMatching(errorRegex)); + expect(consoleError).toHaveBeenCalledWith( + expect.stringMatching(errorRegex) + ); }); test('error on schema not initialized on clear', async () => { - const errorLog = jest.spyOn(console, 'error'); const errorRegex = /Schema is not initialized/; await expect(DataStore.clear()).rejects.toThrow(errorRegex); - expect(errorLog).toHaveBeenCalledWith(expect.stringMatching(errorRegex)); + expect(consoleError).toHaveBeenCalledWith( + expect.stringMatching(errorRegex) + ); }); test("non-@models can't be saved", async () => { @@ -174,13 +185,11 @@ describe('DataStore tests', () => { test('initSchema is executed only once', () => { initSchema(testSchema()); - const spy = jest.spyOn(console, 'warn'); - expect(() => { initSchema(testSchema()); }).not.toThrow(); - expect(spy).toHaveBeenCalledWith( + expect(consoleWarn).toHaveBeenCalledWith( 'The schema has already been initialized' ); }); @@ -303,8 +312,6 @@ describe('DataStore tests', () => { }); test('Id cannot be changed inside copyOf', () => { - const consoleWarn = jest.spyOn(console, 'warn'); - const { Model } = initSchema(testSchema()) as { Model: PersistentModelConstructor; }; @@ -566,2278 +573,4 @@ describe('DataStore tests', () => { expect(storage).toHaveBeenCalledTimes(1); }); }); - - describe('Basic operations', () => { - let Model: PersistentModelConstructor; - let Metadata: NonModelTypeConstructor; - - beforeEach(() => { - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => ({ - init: jest.fn(), - runExclusive: jest.fn(() => []), - query: jest.fn(() => []), - observe: jest.fn(() => from([])), - clear: jest.fn(), - })); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - ({ Model, Metadata } = classes as { - Model: PersistentModelConstructor; - Metadata: NonModelTypeConstructor; - }); - }); - - test('Save returns the saved model', async () => { - let model: Model; - const save = jest.fn(() => [model]); - const query = jest.fn(() => [model]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { Model } = classes as { - Model: PersistentModelConstructor; - }; - - model = new Model({ - field1: 'Some value', - dateCreated: new Date().toISOString(), - }); - - const result = await DataStore.save(model); - - const [settingsSave, modelCall] = save.mock.calls; - const [_model, _condition, _mutator, patches] = modelCall; - - const expectedPatchedFields = [ - 'field1', - 'dateCreated', - 'id', - '_version', - '_lastChangedAt', - '_deleted', - ]; - - expect(result).toMatchObject(model); - expect(patches[0].map(p => p.path.join(''))).toEqual( - expectedPatchedFields - ); - }); - - test('Save returns the updated model and patches', async () => { - let model: Model; - const save = jest.fn(() => [model]); - const query = jest.fn(() => [model]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { Model } = classes as { - Model: PersistentModelConstructor; - }; - - model = new Model({ - field1: 'something', - dateCreated: new Date().toISOString(), - }); - - await DataStore.save(model); - - model = Model.copyOf(model, draft => { - draft.field1 = 'edited'; - }); - - const result = await DataStore.save(model); - - const [settingsSave, modelSave, modelUpdate] = save.mock.calls; - const [_model, _condition, _mutator, [patches]] = modelUpdate; - - const expectedPatches = [ - { op: 'replace', path: ['field1'], value: 'edited' }, - ]; - - expect(result).toMatchObject(model); - expect(patches).toMatchObject(expectedPatches); - }); - - test('Save returns the updated model and patches - list field', async () => { - let model: Model; - const save = jest.fn(() => [model]); - const query = jest.fn(() => [model]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { Model } = classes as { - Model: PersistentModelConstructor; - }; - - model = new Model({ - field1: 'something', - dateCreated: new Date().toISOString(), - emails: ['john@doe.com', 'jane@doe.com'], - }); - - await DataStore.save(model); - - model = Model.copyOf(model, draft => { - draft.emails = [...model.emails!, 'joe@doe.com']; - }); - - let result = await DataStore.save(model); - - expect(result).toMatchObject(model); - - model = Model.copyOf(model, draft => { - draft.emails?.push('joe@doe.com'); - }); - - result = await DataStore.save(model); - - expect(result).toMatchObject(model); - - const [settingsSave, modelSave, modelUpdate, modelUpdate2] = ( - save.mock.calls - ); - - const [_model, _condition, _mutator, [patches]] = modelUpdate; - const [_model2, _condition2, _mutator2, [patches2]] = modelUpdate2; - - const expectedPatches = [ - { - op: 'replace', - path: ['emails'], - value: ['john@doe.com', 'jane@doe.com', 'joe@doe.com'], - }, - ]; - - const expectedPatches2 = [ - { - op: 'replace', - path: ['emails'], - value: ['john@doe.com', 'jane@doe.com', 'joe@doe.com', 'joe@doe.com'], - }, - ]; - - expect(patches).toMatchObject(expectedPatches); - expect(patches2).toMatchObject(expectedPatches2); - }); - - test('Read-only fields cannot be overwritten', async () => { - let model: Model; - const save = jest.fn(() => [model]); - const query = jest.fn(() => [model]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { Model } = classes as { - Model: PersistentModelConstructor; - }; - - expect(() => { - new Model({ - field1: 'something', - dateCreated: new Date().toISOString(), - createdAt: '2021-06-03T20:56:23.201Z', - } as any); - }).toThrow('createdAt is read-only.'); - - model = new Model({ - field1: 'something', - dateCreated: new Date().toISOString(), - }); - - expect(() => { - Model.copyOf(model, draft => { - (draft as any).createdAt = '2021-06-03T20:56:23.201Z'; - }); - }).toThrow('createdAt is read-only.'); - - expect(() => { - Model.copyOf(model, draft => { - (draft as any).updatedAt = '2021-06-03T20:56:23.201Z'; - }); - }).toThrow('updatedAt is read-only.'); - }); - - describe('Instantiation validations', () => { - test('required field (undefined)', () => { - expect(() => { - new Model({ - field1: undefined!, - dateCreated: new Date().toISOString(), - }); - }).toThrow('Field field1 is required'); - }); - - test('required field (null)', () => { - expect(() => { - new Model({ - field1: null!, - dateCreated: new Date().toISOString(), - }); - }).toThrow('Field field1 is required'); - }); - - test('wrong type (number -> string)', () => { - expect(() => { - new Model({ - field1: 1234, - dateCreated: new Date().toISOString(), - }); - }).toThrow( - 'Field field1 should be of type string, number received. 1234' - ); - }); - - test('wrong type (string -> date)', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: 'not-a-date', - }); - }).toThrow( - 'Field dateCreated should be of type AWSDateTime, validation failed. not-a-date' - ); - }); - - test('set nested non model field as undefined', () => { - expect( - // @ts-ignore - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - tags: undefined, - rewards: [], - penNames: [], - nominations: [], - }), - }).metadata.tags - ).toBeUndefined(); - }); - - test('pass null to nested non model array field (constructor)', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - tags: undefined, - rewards: [null!], - penNames: [], - nominations: [], - }), - }); - }).toThrow( - 'All elements in the rewards array should be of type string, [null] received. ' - ); - }); - - // without non model constructor - test('pass null to nested non model non nullable array field (no constructor)', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - tags: undefined, - rewards: [null!], - penNames: [], - nominations: [], - }, - }); - }).toThrow( - 'All elements in the rewards array should be of type string, [null] received. ' - ); - }); - - test('valid model with null optional fields', () => { - const m = new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - optionalField1: null, - }); - expect(m.optionalField1).toBe(null); - }); - - test('valid model with `undefined` optional fields', () => { - const m = new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - optionalField1: undefined, - }); - expect(m.optionalField1).toBe(null); - }); - - test('valid model with omitted optional fields', () => { - const m = new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - /** - * Omitting this: - * - * optionalField: undefined - */ - }); - expect(m.optionalField1).toBe(null); - }); - - test('copyOf() setting optional field to null', () => { - const emailsVal = ['test@test.test']; - const original = new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - optionalField1: 'defined value', - emails: emailsVal, - }); - const copied = Model.copyOf(original, d => (d.optionalField1 = null)); - expect(copied.optionalField1).toBe(null); - expect(copied.emails).toEqual(emailsVal); - }); - - test('copyOf() setting optional field to undefined', () => { - const original = new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - optionalField1: 'defined value', - }); - const copied = Model.copyOf( - original, - d => (d.optionalField1 = undefined) - ); - expect(copied.optionalField1).toBe(null); - }); - - test('pass null to non nullable array field', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - emails: [null!], - }); - }).toThrow( - 'All elements in the emails array should be of type string, [null] received. ' - ); - }); - - test('pass null to nullable array field', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - ips: [null], - }); - }).not.toThrow(); - }); - - test('valid model array of AWSIPAdress', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - ips: ['1.1.1.1'], - }); - }).not.toThrow(); - }); - - test('invalid AWSIPAddress', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - ips: ['not.an.ip'], - }); - }).toThrow( - `All elements in the ips array should be of type AWSIPAddress, validation failed for one or more elements. not.an.ip` - ); - }); - - test('invalid AWSIPAddress in one index', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - ips: ['1.1.1.1', 'not.an.ip'], - }); - }).toThrow( - `All elements in the ips array should be of type AWSIPAddress, validation failed for one or more elements. 1.1.1.1,not.an.ip` - ); - }); - - test('valid AWSEmail', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - emails: ['test@example.com'], - }); - }).not.toThrow(); - }); - - test('valid empty array of AWSEmail', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - emails: [], - ips: [], - }); - }).not.toThrow(); - }); - - test('invalid AWSEmail', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - emails: ['not-an-email'], - }); - }).toThrow( - 'All elements in the emails array should be of type AWSEmail, validation failed for one or more elements. not-an-email' - ); - }); - - test('required sub non model field with constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - tags: undefined, - rewards: [], - penNames: [], - nominations: null!, - }), - }); - }).toThrow('Field nominations is required'); - }); - - test('required sub non model field without constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - tags: undefined, - rewards: [], - penNames: [], - nominations: null!, - }, - }); - }).toThrow('Field nominations is required'); - }); - - test('sub non model non nullable array field with constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - tags: undefined, - rewards: [], - penNames: [undefined!], - nominations: [], - }), - }); - }).toThrow( - 'All elements in the penNames array should be of type string, [undefined] received. ' - ); - }); - - // without non model constructor - test('sub non model non nullable array field without constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - tags: undefined, - rewards: [], - penNames: [undefined!], - nominations: [], - }, - }); - }).toThrow( - 'All elements in the penNames array should be of type string, [undefined] received. ' - ); - }); - - test('sub non model array field invalid type with constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - tags: [1234], - rewards: [], - penNames: [], - nominations: [], - }), - }); - }).toThrow( - 'All elements in the tags array should be of type string | null | undefined, [number] received. 1234' - ); - }); - - // without non model constructor - test('sub non model array field invalid type without constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - tags: [1234], - rewards: [], - penNames: [], - nominations: [], - }, - }); - }).toThrow( - 'All elements in the tags array should be of type string | null | undefined, [number] received. 1234' - ); - }); - - test('valid sub non model nullable array field (null) with constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [null], - }), - }); - }).not.toThrow(); - }); - - test('valid sub non model nullable array field (null) without constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [null], - }, - }); - }).not.toThrow(); - }); - - test('valid sub non model nullable array field (undefined) with constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [undefined!], - }), - }); - }).not.toThrow(); - }); - - test('valid sub non model nullable array field (undefined) without constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [undefined!], - }, - }); - }).not.toThrow(); - }); - - test('valid sub non model nullable array field (undefined and null) with constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [undefined!, null], - }), - }); - }).not.toThrow(); - }); - - test('valid sub non model nullable array field (undefined and null) without constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [undefined!, null], - }, - }); - }).not.toThrow(); - }); - - test('valid sub non model nullable array field (null and string) with constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [null, 'ok'], - }), - }); - }).not.toThrow(); - }); - - test('valid sub non model nullable array field (null and string) without constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [null, 'ok'], - }, - }); - }).not.toThrow(); - }); - - test('wrong type sub non model nullable array field (null and number) with constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [null, 123], - }), - }); - }).toThrow( - 'All elements in the misc array should be of type string | null | undefined, [null,number] received. ,123' - ); - }); - - test('wrong type sub non model nullable array field (null and number) without constructor', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [null, 123], - }, - }); - }).toThrow( - 'All elements in the misc array should be of type string | null | undefined, [null,number] received. ,123' - ); - }); - - test('allow extra attribute', () => { - expect( - new Model({ extraAttribute: 'some value', field1: 'some value' }) - ).toHaveProperty('extraAttribute'); - }); - - test('throw on invalid constructor', () => { - expect(() => { - Model.copyOf(undefined, d => d); - }).toThrow('The source object is not a valid model'); - }); - - test('invalid type on copyOf', () => { - expect(() => { - const source = new Model({ - field1: 'something', - dateCreated: new Date().toISOString(), - }); - Model.copyOf(source, d => (d.field1 = 1234)); - }).toThrow( - 'Field field1 should be of type string, number received. 1234' - ); - }); - - test('invalid sub non model type', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - // @ts-ignore - metadata: 'invalid', - }); - }).toThrow( - 'Field metadata should be of type Metadata, string recieved. invalid' - ); - }); - - test('sub non model null', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: null, - }); - }).not.toThrow( - 'Field metadata should be of type Metadata, string recieved. invalid' - ); - }); - - test('invalid nested sub non model type', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: { - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - // @ts-ignore - login: 'login', - }, - }); - }).toThrow( - 'Field login should be of type Login, string recieved. login' - ); - }); - - test('invalid array sub non model type', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - // @ts-ignore - logins: ['bad type', 'another bad type'], - }); - }).toThrow( - 'All elements in the logins array should be of type Login, [string] received. bad type' - ); - }); - - test('invalid array sub non model field type', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - // @ts-ignore - logins: [{ username: 4 }], - }); - }).toThrow( - 'Field username should be of type string, number received. 4' - ); - }); - - test('nullable array sub non model', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - logins: [null!, { username: 'user' }], - }); - }).not.toThrow(); - }); - - test('array sub non model wrong type', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - // @ts-ignore - logins: 'my login', - }); - }).toThrow( - 'Field logins should be of type [Login | null | undefined], string received. my login' - ); - }); - - test('array sub non model null', () => { - expect(() => { - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - logins: null, - }); - }).not.toThrow(); - }); - }); - - test('Delete params', async () => { - await expect(DataStore.delete(undefined)).rejects.toThrow( - 'Model or Model Constructor required' - ); - - await expect(DataStore.delete(Model)).rejects.toThrow( - 'Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL' - ); - - await expect(DataStore.delete(Model, (() => {}))).rejects.toThrow( - "Invalid predicate. Terminate your predicate with a valid condition (e.g., `p => p.field.eq('value')`) or pass `Predicates.ALL`." - ); - - await expect(DataStore.delete({})).rejects.toThrow( - 'Object is not an instance of a valid model' - ); - - await expect( - DataStore.delete( - new Model({ - field1: 'somevalue', - dateCreated: new Date().toISOString(), - }), - {} - ) - ).rejects.toThrow('Invalid criteria'); - }); - - test('Delete many returns many', async () => { - const models: Model[] = []; - const save = jest.fn(model => { - model instanceof Model && models.push(model); - }); - const query = jest.fn(() => models); - const _delete = jest.fn(() => [models, models]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - delete: _delete, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { Model } = classes as { - Model: PersistentModelConstructor; - }; - - for (let i = 0; i < 10; i++) { - await DataStore.save( - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author ' + i, - rewards: [], - penNames: [], - nominations: [], - misc: [null, 'ok'], - }), - }) - ); - } - - const deleted = await DataStore.delete(Model, m => - m.field1.eq('someField') - ); - - expect(deleted.length).toEqual(10); - deleted.forEach(deletedItem => { - expect(deletedItem.field1).toEqual('someField'); - }); - }); - - test('Delete one returns one', async () => { - let model: Model | undefined; - const save = jest.fn(saved => (model = saved)); - const query = jest.fn(() => [model]); - const _delete = jest.fn(() => [[model], [model]]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - delete: _delete, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { Model } = classes as { - Model: PersistentModelConstructor; - }; - - const saved = await DataStore.save( - new Model({ - field1: 'someField', - dateCreated: new Date().toISOString(), - metadata: new Metadata({ - author: 'Some author', - rewards: [], - penNames: [], - nominations: [], - misc: [null, 'ok'], - }), - }) - ); - - const deleted: Model[] = await DataStore.delete(Model, saved.id); - - expect(deleted.length).toEqual(1); - expect(deleted[0]).toEqual(model); - }); - - test('Query params', async () => { - await expect(DataStore.query(undefined)).rejects.toThrow( - 'Constructor is not for a valid model' - ); - - await expect(DataStore.query(undefined)).rejects.toThrow( - 'Constructor is not for a valid model' - ); - - await expect( - DataStore.query(Model, 'someid', { page: 0 }) - ).rejects.toThrow('Limit is required when requesting a page'); - - await expect( - DataStore.query(Model, 'someid', { page: 'a', limit: 10 }) - ).rejects.toThrow('Page should be a number'); - - await expect( - DataStore.query(Model, 'someid', { page: -1, limit: 10 }) - ).rejects.toThrow("Page can't be negative"); - - await expect( - DataStore.query(Model, 'someid', { - page: 0, - limit: 'avalue', - }) - ).rejects.toThrow('Limit should be a number'); - - await expect( - DataStore.query(Model, 'someid', { page: 0, limit: -1 }) - ).rejects.toThrow("Limit can't be negative"); - }); - }); - - describe('Type definitions', () => { - let Model: PersistentModelConstructor; - - beforeEach(() => { - let model: Model; - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => ({ - init: jest.fn(), - runExclusive: jest.fn(() => [model]), - query: jest.fn(() => [model]), - observe: jest.fn(() => from([])), - clear: jest.fn(), - })); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - ({ Model } = classes as { Model: PersistentModelConstructor }); - - model = new Model({ - field1: 'Some value', - dateCreated: new Date().toISOString(), - }); - }); - - describe('Query', () => { - test('all', async () => { - const allModels = await DataStore.query(Model); - expectType(allModels); - const [one] = allModels; - expect(one.field1).toBeDefined(); - expect(one).toBeInstanceOf(Model); - }); - test('one by id', async () => { - const oneModelById = await DataStore.query(Model, 'someid'); - expectType(oneModelById); - expect(oneModelById?.field1).toBeDefined(); - expect(oneModelById).toBeInstanceOf(Model); - }); - test('with criteria', async () => { - const multiModelWithCriteria = await DataStore.query(Model, c => - c.field1.contains('something') - ); - expectType(multiModelWithCriteria); - const [one] = multiModelWithCriteria; - expect(one.field1).toBeDefined(); - expect(one).toBeInstanceOf(Model); - }); - test('with identity function criteria', async () => { - const multiModelWithCriteria = await DataStore.query(Model, c => c); - expectType(multiModelWithCriteria); - const [one] = multiModelWithCriteria; - expect(one.field1).toBeDefined(); - expect(one).toBeInstanceOf(Model); - }); - test('with pagination', async () => { - const allModelsPaginated = await DataStore.query( - Model, - Predicates.ALL, - { page: 0, limit: 20 } - ); - expectType(allModelsPaginated); - const [one] = allModelsPaginated; - expect(one.field1).toBeDefined(); - expect(one).toBeInstanceOf(Model); - }); - }); - - describe('Query with generic type', () => { - test('all', async () => { - const allModels = await DataStore.query(Model); - expectType(allModels); - const [one] = allModels; - expect(one.field1).toBeDefined(); - expect(one).toBeInstanceOf(Model); - }); - test('one by id', async () => { - const oneModelById = await DataStore.query(Model, 'someid'); - expectType(oneModelById); - expect(oneModelById?.field1).toBeDefined(); - expect(oneModelById).toBeInstanceOf(Model); - }); - test('with criteria', async () => { - const multiModelWithCriteria = await DataStore.query(Model, c => - c.field1.contains('something') - ); - expectType(multiModelWithCriteria); - const [one] = multiModelWithCriteria; - expect(one.field1).toBeDefined(); - expect(one).toBeInstanceOf(Model); - }); - test('with pagination', async () => { - const allModelsPaginated = await DataStore.query( - Model, - Predicates.ALL, - { page: 0, limit: 20 } - ); - expectType(allModelsPaginated); - const [one] = allModelsPaginated; - expect(one.field1).toBeDefined(); - expect(one).toBeInstanceOf(Model); - }); - }); - - describe('Observe', () => { - test('subscribe to all models', async () => { - DataStore.observe().subscribe(({ element, model }) => { - expectType>(model); - expectType(element); - }); - }); - test('subscribe to model instance', async () => { - const model = new Model({ - field1: 'somevalue', - dateCreated: new Date().toISOString(), - }); - - DataStore.observe(model).subscribe(({ element, model }) => { - expectType>(model); - expectType(element); - }); - }); - test('subscribe to model', async () => { - DataStore.observe(Model).subscribe(({ element, model }) => { - expectType>(model); - expectType(element); - }); - }); - test('subscribe to model instance by id', async () => { - DataStore.observe(Model, 'some id').subscribe(({ element, model }) => { - expectType>(model); - expectType(element); - }); - }); - test('subscribe to model with criteria', async () => { - DataStore.observe(Model, c => c.field1.ne('somevalue')).subscribe( - ({ element, model }) => { - expectType>(model); - expectType(element); - } - ); - }); - }); - - describe('Observe with generic type', () => { - test('subscribe to model instance', async () => { - const model = new Model({ - field1: 'somevalue', - dateCreated: new Date().toISOString(), - }); - - DataStore.observe(model).subscribe(({ element, model }) => { - expectType>(model); - expectType(element); - }); - }); - test('subscribe to model', async () => { - DataStore.observe(Model).subscribe(({ element, model }) => { - expectType>(model); - expectType(element); - }); - }); - test('subscribe to model instance by id', async () => { - DataStore.observe(Model, 'some id').subscribe( - ({ element, model }) => { - expectType>(model); - expectType(element); - } - ); - }); - test('subscribe to model with criteria', async () => { - DataStore.observe(Model, c => - c.field1.ne('somevalue') - ).subscribe(({ element, model }) => { - expectType>(model); - expectType(element); - }); - }); - }); - }); - - describe('DataStore Custom PK tests', () => { - describe('initSchema tests', () => { - test('PostCustomPK class is created', () => { - const classes = initSchema(testSchema()); - - expect(classes).toHaveProperty('PostCustomPK'); - - const { PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }; - - expect(PostCustomPK).toHaveProperty( - nameOf>('copyOf') - ); - - expect(typeof PostCustomPK.copyOf).toBe('function'); - }); - - test('PostCustomPK class can be instantiated', () => { - const { PostCustomPK } = initSchema(testSchema()) as { - PostCustomPK: PersistentModelConstructor; - }; - - const model = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - }); - - expect(model).toBeInstanceOf(PostCustomPK); - - expect(model.postId).toBeDefined(); - }); - }); - - describe('Immutability', () => { - test('Title cannot be changed', () => { - const { PostCustomPK } = initSchema(testSchema()) as { - PostCustomPK: PersistentModelConstructor; - }; - - const model = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - }); - - expect(() => { - (model).title = 'edit'; - }).toThrow("Cannot assign to read only property 'title' of object"); - }); - - test('PostCustomPK can be copied+edited by creating an edited copy', () => { - const { PostCustomPK } = initSchema(testSchema()) as { - PostCustomPK: PersistentModelConstructor; - }; - - const model1 = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - }); - - const model2 = PostCustomPK.copyOf(model1, draft => { - draft.title = 'edited'; - }); - - expect(model1).not.toBe(model2); - - // postId should be kept the same - expect(model1.postId).toBe(model2.postId); - - expect(model1.title).toBe('something'); - expect(model2.title).toBe('edited'); - }); - - test('postId cannot be changed inside copyOf', () => { - const consoleWarn = jest.spyOn(console, 'warn'); - - const { PostCustomPK } = initSchema(testSchema()) as { - PostCustomPK: PersistentModelConstructor; - }; - - const model1 = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - }); - - const model2 = PostCustomPK.copyOf(model1, draft => { - (draft).postId = 'a-new-postId'; - }); - - // postId should be kept the same - expect(model1.postId).toBe(model2.postId); - - // we should always be told *in some way* when an "update" will not actually - // be applied. for now, this is a warning, because throwing an error, updating - // the record's PK, or creating a new record are all breaking changes. - expect(consoleWarn).toHaveBeenCalledWith( - expect.stringContaining( - "copyOf() does not update PK fields. The 'postId' update is being ignored." - ), - expect.objectContaining({ source: model1 }) - ); - }); - - test('Optional field can be initialized with undefined', () => { - const { PostCustomPK } = initSchema(testSchema()) as { - PostCustomPK: PersistentModelConstructor; - }; - - const model1 = new PostCustomPK({ - postId: '12345', - title: 'something', - description: undefined, - dateCreated: new Date().toISOString(), - }); - - expect(model1.description).toBeNull(); - }); - - test('Optional field can be initialized with null', () => { - const { PostCustomPK } = initSchema(testSchema()) as { - PostCustomPK: PersistentModelConstructor; - }; - - const model1 = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - description: null, - }); - - expect(model1.description).toBeNull(); - }); - - test('Optional field can be changed to undefined inside copyOf', () => { - const { PostCustomPK } = initSchema(testSchema()) as { - PostCustomPK: PersistentModelConstructor; - }; - - const model1 = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - description: 'something-else', - }); - - const model2 = PostCustomPK.copyOf(model1, draft => { - (draft).description = undefined; - }); - - // postId should be kept the same - expect(model1.postId).toBe(model2.postId); - - expect(model1.description).toBe('something-else'); - expect(model2.description).toBeNull(); - }); - - test('Optional field can be set to null inside copyOf', () => { - const { PostCustomPK } = initSchema(testSchema()) as { - PostCustomPK: PersistentModelConstructor; - }; - - const model1 = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - }); - - const model2 = PostCustomPK.copyOf(model1, draft => { - (draft).description = null; - }); - - // postId should be kept the same - expect(model1.postId).toBe(model2.postId); - - expect(model1.description).toBeNull(); - expect(model2.description).toBeNull(); - }); - - test('Non @model - Field cannot be changed', () => { - const { Metadata } = initSchema(testSchema()) as { - Metadata: NonModelTypeConstructor; - }; - - const nonPostCustomPK = new Metadata({ - author: 'something', - rewards: [], - penNames: [], - nominations: [], - }); - - expect(() => { - (nonPostCustomPK).author = 'edit'; - }).toThrow("Cannot assign to read only property 'author' of object"); - }); - }); - - describe('Initialization', () => { - let PostCustomPK; - test('start is called only once', async () => { - const storage: StorageType = - require('../../src/storage/storage').ExclusiveStorage; - - const classes = initSchema(testSchema()); - - ({ PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }); - - const promises = [ - DataStore.query(PostCustomPK), - DataStore.query(PostCustomPK), - DataStore.query(PostCustomPK), - DataStore.query(PostCustomPK), - ]; - - await Promise.all(promises); - - expect(storage).toHaveBeenCalledTimes(1); - }); - - test('It is initialized when observing (no query)', async () => { - const storage: StorageType = - require('../../src/storage/storage').ExclusiveStorage; - - const classes = initSchema(testSchema()); - - ({ PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }); - - DataStore.observe(PostCustomPK).subscribe(jest.fn()); - - expect(storage).toHaveBeenCalledTimes(1); - }); - }); - - describe('Basic operations', () => { - let PostCustomPK: PersistentModelConstructor; - - beforeEach(() => { - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => ({ - init: jest.fn(), - runExclusive: jest.fn(() => []), - query: jest.fn(() => []), - observe: jest.fn(() => from([])), - clear: jest.fn(), - })); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - ({ PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }); - }); - - test('Save returns the saved model', async () => { - let model: PostCustomPKType; - const save = jest.fn(() => [model]); - const query = jest.fn(() => [model]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }; - - model = new PostCustomPK({ - postId: '12345', - title: 'Some value', - dateCreated: new Date().toISOString(), - }); - - const result = await DataStore.save(model); - - const [settingsSave, modelCall] = save.mock.calls; - const [_model, _condition, _mutator, patches] = modelCall; - - const expectedPatchedFields = [ - 'postId', - 'title', - 'dateCreated', - '_version', - '_lastChangedAt', - '_deleted', - ]; - - expect(result).toMatchObject(model); - expect(patches[0].map(p => p.path.join(''))).toEqual( - expectedPatchedFields - ); - }); - - test('Save returns the updated model and patches', async () => { - let model: PostCustomPKType; - const save = jest.fn(() => [model]); - const query = jest.fn(() => [model]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }; - - model = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - }); - - await DataStore.save(model); - - model = PostCustomPK.copyOf(model, draft => { - draft.title = 'edited'; - }); - - const result = await DataStore.save(model); - - const [settingsSave, modelSave, modelUpdate] = save.mock.calls; - const [_model, _condition, _mutator, [patches]] = modelUpdate; - - const expectedPatches = [ - { op: 'replace', path: ['title'], value: 'edited' }, - ]; - - expect(result).toMatchObject(model); - expect(patches).toMatchObject(expectedPatches); - }); - - test('Save returns the updated model and patches - list field', async () => { - let model: PostCustomPKType; - const save = jest.fn(() => [model]); - const query = jest.fn(() => [model]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }; - - model = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - emails: ['john@doe.com', 'jane@doe.com'], - }); - - await DataStore.save(model); - - model = PostCustomPK.copyOf(model, draft => { - draft.emails = [...draft.emails!, 'joe@doe.com']; - }); - - let result = await DataStore.save(model); - - expect(result).toMatchObject(model); - - model = PostCustomPK.copyOf(model, draft => { - draft.emails!.push('joe@doe.com'); - }); - - result = await DataStore.save(model); - - expect(result).toMatchObject(model); - - const [settingsSave, modelSave, modelUpdate, modelUpdate2] = ( - save.mock.calls - ); - - const [_model, _condition, _mutator, [patches]] = modelUpdate; - const [_model2, _condition2, _mutator2, [patches2]] = modelUpdate2; - - const expectedPatches = [ - { - op: 'replace', - path: ['emails'], - value: ['john@doe.com', 'jane@doe.com', 'joe@doe.com'], - }, - ]; - - const expectedPatches2 = [ - { - op: 'replace', - path: ['emails'], - value: [ - 'john@doe.com', - 'jane@doe.com', - 'joe@doe.com', - 'joe@doe.com', - ], - }, - ]; - - expect(patches).toMatchObject(expectedPatches); - expect(patches2).toMatchObject(expectedPatches2); - }); - - test('Read-only fields cannot be overwritten', async () => { - let model: PostCustomPKType; - const save = jest.fn(() => [model]); - const query = jest.fn(() => [model]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }; - - expect(() => { - new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - createdAt: '2021-06-03T20:56:23.201Z', - }) as any; - }).toThrow('createdAt is read-only.'); - - model = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - }); - - expect(() => { - PostCustomPK.copyOf(model, draft => { - (draft as any).createdAt = '2021-06-03T20:56:23.201Z'; - }); - }).toThrow('createdAt is read-only.'); - - expect(() => { - PostCustomPK.copyOf(model, draft => { - (draft as any).updatedAt = '2021-06-03T20:56:23.201Z'; - }); - }).toThrow('updatedAt is read-only.'); - }); - - test('Instantiation validations custom pk', async () => { - expect(() => { - new PostCustomPK({ - postId: '12345', - title: undefined as any, // because we're trying to trigger JS error - dateCreated: new Date().toISOString(), - }); - }).toThrow('Field title is required'); - - expect(() => { - new PostCustomPK({ - postId: '12345', - title: null as any, // because we're trying to trigger JS error - dateCreated: new Date().toISOString(), - }); - }).toThrow('Field title is required'); - - expect(() => { - new PostCustomPK({ - postId: '12345', - title: 1234, - dateCreated: new Date().toISOString(), - }); - }).toThrow( - 'Field title should be of type string, number received. 1234' - ); - - expect(() => { - new PostCustomPK({ - postId: '12345', - title: 'someField', - dateCreated: 'not-a-date', - }); - }).toThrow( - 'Field dateCreated should be of type AWSDateTime, validation failed. not-a-date' - ); - - expect(() => { - new PostCustomPK({ - postId: '12345', - title: 'someField', - dateCreated: new Date().toISOString(), - emails: [null as any], // because we're trying to trigger JS error - }); - }).toThrow( - 'All elements in the emails array should be of type string, [null] received. ' - ); - - expect(() => { - new PostCustomPK({ - postId: '12345', - title: 'someField', - dateCreated: new Date().toISOString(), - emails: ['test@example.com'], - }); - }).not.toThrow(); - - expect(() => { - new PostCustomPK({ - postId: '12345', - title: 'someField', - dateCreated: new Date().toISOString(), - emails: ['not-an-email'], - }); - }).toThrow( - 'All elements in the emails array should be of type AWSEmail, validation failed for one or more elements. not-an-email' - ); - - expect({ - extraAttribute: 'some value', - title: 'some value', - }).toHaveProperty('extraAttribute'); - - expect(() => { - PostCustomPK.copyOf(undefined, d => d); - }).toThrow('The source object is not a valid model'); - expect(() => { - const source = new PostCustomPK({ - postId: '12345', - title: 'something', - dateCreated: new Date().toISOString(), - }); - PostCustomPK.copyOf(source, d => (d.title = 1234)); - }).toThrow( - 'Field title should be of type string, number received. 1234' - ); - }); - - test('Delete params', async () => { - await expect(DataStore.delete(undefined)).rejects.toThrow( - 'Model or Model Constructor required' - ); - - await expect(DataStore.delete(PostCustomPK)).rejects.toThrow( - 'Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL' - ); - - await expect( - DataStore.delete(PostCustomPK, (() => {})) - ).rejects.toThrow( - "Invalid predicate. Terminate your predicate with a valid condition (e.g., `p => p.field.eq('value')`) or pass `Predicates.ALL`." - ); - - await expect(DataStore.delete({})).rejects.toThrow( - 'Object is not an instance of a valid model' - ); - - await expect( - DataStore.delete( - new PostCustomPK({ - postId: '12345', - title: 'somevalue', - dateCreated: new Date().toISOString(), - }), - {} - ) - ).rejects.toThrow('Invalid criteria'); - }); - - test('Delete many returns many', async () => { - const models: PostCustomPKType[] = []; - const save = jest.fn(model => { - model instanceof PostCustomPK && models.push(model); - }); - const query = jest.fn(() => models); - const _delete = jest.fn(() => [models, models]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock: jest.Mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - delete: _delete, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }; - - Promise.all( - [...Array(10).keys()].map(async i => { - await DataStore.save( - new PostCustomPK({ - postId: `${i}`, - title: 'someField', - dateCreated: new Date().toISOString(), - }) - ); - }) - ); - - const deleted = await DataStore.delete(PostCustomPK, m => - m.title.eq('someField') - ); - - const sortedRecords = deleted.sort((a, b) => - a.postId < b.postId ? -1 : 1 - ); - - expect(sortedRecords.length).toEqual(10); - sortedRecords.forEach((deletedItem, idx) => { - expect(deletedItem.postId).toEqual(`${idx}`); - expect(deletedItem.title).toEqual('someField'); - }); - }); - - test('Delete one by Custom PK returns one', async () => { - let model: PostCustomPKType; - const save = jest.fn(saved => (model = saved)); - const query = jest.fn(() => [model]); - const _delete = jest.fn(() => [[model], [model]]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - delete: _delete, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }; - - const saved = await DataStore.save( - new PostCustomPK({ - postId: '12345', - title: 'someField', - dateCreated: new Date().toISOString(), - }) - ); - - const deleted: PostCustomPKType[] = await DataStore.delete( - PostCustomPK, - saved.postId - ); - - expect(deleted.length).toEqual(1); - expect(deleted[0]).toEqual(model!); - }); - - test('Delete one by Custom PK with predicate returns one', async () => { - let model: PostCustomPKType; - const save = jest.fn(saved => (model = saved)); - const query = jest.fn(() => [model]); - const _delete = jest.fn(() => [[model], [model]]); - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => { - const _mock = { - init: jest.fn(), - save, - query, - delete: _delete, - runExclusive: jest.fn(fn => fn.bind(this, _mock)()), - clear: jest.fn(), - }; - return _mock; - }); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - - ({ initSchema, DataStore } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - const { PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }; - - const saved = await DataStore.save( - new PostCustomPK({ - postId: '12345', - title: 'someField', - dateCreated: new Date().toISOString(), - }) - ); - - const deleted: PostCustomPKType[] = await DataStore.delete( - PostCustomPK, - - m => m.postId.eq(saved.postId) - ); - - expect(deleted.length).toEqual(1); - expect(deleted[0]).toEqual(model!); - }); - - test('Query params', async () => { - await expect(DataStore.query(undefined)).rejects.toThrow( - 'Constructor is not for a valid model' - ); - - await expect(DataStore.query(undefined)).rejects.toThrow( - 'Constructor is not for a valid model' - ); - - await expect( - DataStore.query(PostCustomPK, 'someid', { page: 0 }) - ).rejects.toThrow('Limit is required when requesting a page'); - - await expect( - DataStore.query(PostCustomPK, 'someid', { - page: 'a', - limit: 10, - }) - ).rejects.toThrow('Page should be a number'); - - await expect( - DataStore.query(PostCustomPK, 'someid', { page: -1, limit: 10 }) - ).rejects.toThrow("Page can't be negative"); - - await expect( - DataStore.query(PostCustomPK, 'someid', { - page: 0, - limit: 'avalue', - }) - ).rejects.toThrow('Limit should be a number'); - - await expect( - DataStore.query(PostCustomPK, 'someid', { - page: 0, - limit: -1, - }) - ).rejects.toThrow("Limit can't be negative"); - }); - - describe('Type definitions', () => { - let PostCustomPK: PersistentModelConstructor; - - beforeEach(() => { - let model: PostCustomPKType; - - jest.resetModules(); - jest.doMock('../../src/storage/storage', () => { - const mock = jest.fn().mockImplementation(() => ({ - init: jest.fn(), - runExclusive: jest.fn(() => [model]), - query: jest.fn(() => [model]), - observe: jest.fn(() => from([])), - clear: jest.fn(), - })); - - (mock).getNamespace = () => ({ models: {} }); - - return { ExclusiveStorage: mock }; - }); - ({ - initSchema, - DataStore, - } = require('../../src/datastore/datastore')); - - const classes = initSchema(testSchema()); - - ({ PostCustomPK } = classes as { - PostCustomPK: PersistentModelConstructor; - }); - - model = new PostCustomPK({ - postId: '12345', - title: 'Some value', - dateCreated: new Date().toISOString(), - }); - }); - - describe('Query', () => { - test('all', async () => { - const allPostCustomPKs = await DataStore.query(PostCustomPK); - - expectType(allPostCustomPKs); - - const [one] = allPostCustomPKs; - expect(one.title).toBeDefined(); - expect(one).toBeInstanceOf(PostCustomPK); - }); - test('one by custom PK', async () => { - const onePostCustomPKById = await DataStore.query( - PostCustomPK, - 'someid' - ); - - expectType(onePostCustomPKById!); - expect(onePostCustomPKById!.title).toBeDefined(); - expect(onePostCustomPKById).toBeInstanceOf(PostCustomPK); - }); - test('with criteria', async () => { - const multiPostCustomPKWithCriteria = await DataStore.query( - PostCustomPK, - c => c.title.contains('something') - ); - - expectType(multiPostCustomPKWithCriteria); - - const [one] = multiPostCustomPKWithCriteria; - expect(one.title).toBeDefined(); - expect(one).toBeInstanceOf(PostCustomPK); - }); - test('with pagination', async () => { - const allPostCustomPKsPaginated = await DataStore.query( - PostCustomPK, - Predicates.ALL, - { page: 0, limit: 20 } - ); - - expectType(allPostCustomPKsPaginated); - const [one] = allPostCustomPKsPaginated; - expect(one.title).toBeDefined(); - expect(one).toBeInstanceOf(PostCustomPK); - }); - }); - - describe('Query with generic type', () => { - test('all', async () => { - const allPostCustomPKs = - await DataStore.query(PostCustomPK); - - expectType(allPostCustomPKs); - - const [one] = allPostCustomPKs; - expect(one.title).toBeDefined(); - expect(one).toBeInstanceOf(PostCustomPK); - }); - test('one by postId', async () => { - const onePostCustomPKById = await DataStore.query( - PostCustomPK, - 'someid' - ); - expectType(onePostCustomPKById!); - expect(onePostCustomPKById!.title).toBeDefined(); - expect(onePostCustomPKById).toBeInstanceOf(PostCustomPK); - }); - test('with criteria', async () => { - const multiPostCustomPKWithCriteria = - await DataStore.query(PostCustomPK, c => - c.title.contains('something') - ); - - expectType(multiPostCustomPKWithCriteria); - - const [one] = multiPostCustomPKWithCriteria; - expect(one.title).toBeDefined(); - expect(one).toBeInstanceOf(PostCustomPK); - }); - test('with pagination', async () => { - const allPostCustomPKsPaginated = - await DataStore.query( - PostCustomPK, - Predicates.ALL, - { page: 0, limit: 20 } - ); - - expectType(allPostCustomPKsPaginated); - const [one] = allPostCustomPKsPaginated; - expect(one.title).toBeDefined(); - expect(one).toBeInstanceOf(PostCustomPK); - }); - }); - }); - }); - }); }); diff --git a/packages/datastore/package.json b/packages/datastore/package.json index c0ac5813e48..8e657043b7e 100644 --- a/packages/datastore/package.json +++ b/packages/datastore/package.json @@ -14,7 +14,7 @@ "./dist/esm/datastore/datastore.mjs" ], "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:size": "size-limit", "build-with-test": "npm test && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/geo/package.json b/packages/geo/package.json index a731288f8d6..26e311b54e0 100644 --- a/packages/geo/package.json +++ b/packages/geo/package.json @@ -14,7 +14,7 @@ "./dist/esm/geo/geo.mjs" ], "scripts": { - "test": "yarn run lint && jest -w 1 --coverage", + "test": "yarn run lint && jest -w 1 --coverage --logHeapUsage", "test:size": "size-limit", "build-with-test": "npm test && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/interactions/package.json b/packages/interactions/package.json index 851c60ea1ef..6ba368c2b1c 100644 --- a/packages/interactions/package.json +++ b/packages/interactions/package.json @@ -11,7 +11,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:size": "size-limit", "build-with-test": "npm run clean && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/notifications/package.json b/packages/notifications/package.json index 800dffb7e79..b48af3d5bf6 100644 --- a/packages/notifications/package.json +++ b/packages/notifications/package.json @@ -11,7 +11,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:watch": "tslint 'src/**/*.ts' && jest -w 1 --watch", "build-with-test": "npm run clean && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/predictions/package.json b/packages/predictions/package.json index f6afcf0c2b8..8ff0747f864 100644 --- a/packages/predictions/package.json +++ b/packages/predictions/package.json @@ -11,7 +11,7 @@ "./dist/esm/Predictions.mjs" ], "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:size": "size-limit", "build-with-test": "npm run clean && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json index 91bfc635493..520bac887eb 100644 --- a/packages/pubsub/package.json +++ b/packages/pubsub/package.json @@ -11,7 +11,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:size": "size-limit", "build-with-test": "npm run clean && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", diff --git a/packages/rtn-push-notification/package.json b/packages/rtn-push-notification/package.json index 50aa2367c79..628b61b7a4e 100644 --- a/packages/rtn-push-notification/package.json +++ b/packages/rtn-push-notification/package.json @@ -11,7 +11,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:android": "./android/gradlew test -p ./android", "build-with-test": "npm run clean && npm test && tsc", "build:esm-cjs": "rollup -c rollup.config.mjs", diff --git a/packages/storage/package.json b/packages/storage/package.json index f0dc0189615..fe7c7b6a296 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -22,7 +22,7 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest -w 1 --coverage", + "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "build-with-test": "npm test && npm run build", "build:umd": "webpack && webpack --config ./webpack.config.dev.js", "build:esm-cjs": "rollup -c rollup.config.mjs",