Skip to content

Commit

Permalink
fix(@aws-amplify/datastore): Improve query and observe typings (aws-a…
Browse files Browse the repository at this point in the history
…mplify#5468)

* Handle indexeddb cursor when store is empty

* Validate tests with tslint

* Improve query and observe types and tests

* Allow observing a model instance

* Fix for observe() test case
  • Loading branch information
manueliglesias authored Apr 21, 2020
1 parent 307628f commit 84286be
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 39 deletions.
204 changes: 195 additions & 9 deletions packages/datastore/__tests__/DataStore.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import 'fake-indexeddb/auto';
import uuidValidate from 'uuid-validate';
import Observable from 'zen-observable-ts';
import {
initSchema as initSchemaType,
DataStore as DataStoreType,
initSchema as initSchemaType,
} from '../src/datastore/datastore';
import { Predicates } from '../src/predicates';
import { ExclusiveStorage as StorageType } from '../src/storage/storage';
import {
ModelInit,
MutableModel,
NonModelTypeConstructor,
PersistentModel,
PersistentModelConstructor,
Schema,
NonModelTypeConstructor,
} from '../src/types';
import { ExclusiveStorage as StorageType } from '../src/storage/storage';
import Observable from 'zen-observable-ts';

let initSchema: typeof initSchemaType;
let DataStore: typeof DataStoreType;
Expand All @@ -34,6 +36,13 @@ beforeEach(() => {
({ initSchema, DataStore } = require('../src/datastore/datastore'));
});

const nameOf = <T>(name: keyof T) => name;

/**
* Does nothing intentionally, we care only about type checking
*/
const expectType: <T>(param: T) => void = () => {};

describe('DataStore tests', () => {
describe('initSchema tests', () => {
test('Model class is created', () => {
Expand All @@ -43,8 +52,9 @@ describe('DataStore tests', () => {

const { Model } = classes as { Model: PersistentModelConstructor<Model> };

let property: keyof PersistentModelConstructor<any> = 'copyOf';
expect(Model).toHaveProperty(property);
expect(Model).toHaveProperty(
nameOf<PersistentModelConstructor<any>>('copyOf')
);

expect(typeof Model.copyOf).toBe('function');
});
Expand Down Expand Up @@ -79,7 +89,10 @@ describe('DataStore tests', () => {

expect(model.id).toBeDefined();

// local models use something like a uuid v1, see https://github.com/kelektiv/node-uuid/issues/75#issuecomment-483756623
/**
* local models use something like a uuid v1
* see https://github.com/kelektiv/node-uuid/issues/75#issuecomment-483756623
*/
expect(
uuidValidate(model.id.replace(/^(.{4})-(.{4})-(.{8})/, '$3-$2-$1'), 1)
).toBe(true);
Expand All @@ -100,8 +113,9 @@ describe('DataStore tests', () => {

const { Metadata } = classes;

let property: keyof PersistentModelConstructor<any> = 'copyOf';
expect(Metadata).not.toHaveProperty(property);
expect(Metadata).not.toHaveProperty(
nameOf<PersistentModelConstructor<any>>('copyOf')
);
});

test('Non @model class can be instantiated', () => {
Expand Down Expand Up @@ -268,6 +282,178 @@ describe('DataStore tests', () => {
'Object is not an instance of a valid model'
);
});

describe('Type definitions', () => {
let Model: PersistentModelConstructor<Model>;

beforeEach(() => {
let model: Model;

jest.resetModules();
jest.doMock('../src/storage/storage', () => {
const mock = jest.fn().mockImplementation(() => ({
runExclusive: jest.fn(() => [model]),
query: jest.fn(() => [model]),
observe: jest.fn(() => Observable.from([])),
}));

(<any>mock).getNamespace = () => ({ models: {} });

return { ExclusiveStorage: mock };
});
({ initSchema, DataStore } = require('../src/datastore/datastore'));

const classes = initSchema(testSchema());

({ Model } = classes as { Model: PersistentModelConstructor<Model> });

model = new Model({
field1: 'Some value',
});
});

describe('Query', () => {
test('all', async () => {
const allModels = await DataStore.query(Model);
expectType<Model[]>(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<Model>(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<Model[]>(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<Model[]>(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>(Model);
expectType<Model[]>(allModels);
const [one] = allModels;
expect(one.field1).toBeDefined();
expect(one).toBeInstanceOf(Model);
});
test('one by id', async () => {
const oneModelById = await DataStore.query<Model>(Model, 'someid');
expectType<Model>(oneModelById);
expect(oneModelById.field1).toBeDefined();
expect(oneModelById).toBeInstanceOf(Model);
});
test('with criteria', async () => {
const multiModelWithCriteria = await DataStore.query<Model>(Model, c =>
c.field1('contains', 'something')
);
expectType<Model[]>(multiModelWithCriteria);
const [one] = multiModelWithCriteria;
expect(one.field1).toBeDefined();
expect(one).toBeInstanceOf(Model);
});
test('with pagination', async () => {
const allModelsPaginated = await DataStore.query<Model>(
Model,
Predicates.ALL,
{ page: 0, limit: 20 }
);
expectType<Model[]>(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<PersistentModelConstructor<PersistentModel>>(model);
expectType<PersistentModel>(element);
});
});
test('subscribe to model instance', async () => {
const model = new Model({ field1: 'somevalue' });

DataStore.observe(model).subscribe(({ element, model }) => {
expectType<PersistentModelConstructor<Model>>(model);
expectType<Model>(element);
});
});
test('subscribe to model', async () => {
DataStore.observe(Model).subscribe(({ element, model }) => {
expectType<PersistentModelConstructor<Model>>(model);
expectType<Model>(element);
});
});
test('subscribe to model instance by id', async () => {
DataStore.observe(Model, 'some id').subscribe(({ element, model }) => {
expectType<PersistentModelConstructor<Model>>(model);
expectType<Model>(element);
});
});
test('subscribe to model with criteria', async () => {
DataStore.observe(Model, c => c.field1('ne', 'somevalue')).subscribe(
({ element, model }) => {
expectType<PersistentModelConstructor<Model>>(model);
expectType<Model>(element);
}
);
});
});

describe('Observe with generic type', () => {
test('subscribe to model instance', async () => {
const model = new Model({ field1: 'somevalue' });

DataStore.observe<Model>(model).subscribe(({ element, model }) => {
expectType<PersistentModelConstructor<Model>>(model);
expectType<Model>(element);
});
});
test('subscribe to model', async () => {
DataStore.observe<Model>(Model).subscribe(({ element, model }) => {
expectType<PersistentModelConstructor<Model>>(model);
expectType<Model>(element);
});
});
test('subscribe to model instance by id', async () => {
DataStore.observe<Model>(Model, 'some id').subscribe(
({ element, model }) => {
expectType<PersistentModelConstructor<Model>>(model);
expectType<Model>(element);
}
);
});
test('subscribe to model with criteria', async () => {
DataStore.observe<Model>(Model, c =>
c.field1('ne', 'somevalue')
).subscribe(({ element, model }) => {
expectType<PersistentModelConstructor<Model>>(model);
expectType<Model>(element);
});
});
});
});
});

//#region Test helpers
Expand Down
19 changes: 13 additions & 6 deletions packages/datastore/__tests__/subscription.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import {
USER_CREDENTIALS,
} from '../src/sync/processors/subscription';
import { TransformerMutationType } from '../src/sync/utils';
import { SchemaModel } from '../src/types';

describe('sync engine subscription module', () => {
test('owner authorization', () => {
const model = {
const model: SchemaModel = {
syncable: true,
name: 'Post',
pluralName: 'Posts',
Expand Down Expand Up @@ -75,6 +76,7 @@ describe('sync engine subscription module', () => {
};

expect(
// @ts-ignore
SubscriptionProcessor.prototype.getAuthorizationInfo(
model,
TransformerMutationType.CREATE,
Expand All @@ -84,7 +86,7 @@ describe('sync engine subscription module', () => {
).toEqual(authInfo);
});
test('group authorization', () => {
const model = {
const model: SchemaModel = {
syncable: true,
name: 'Post',
pluralName: 'Posts',
Expand Down Expand Up @@ -152,6 +154,7 @@ describe('sync engine subscription module', () => {
};

expect(
// @ts-ignore
SubscriptionProcessor.prototype.getAuthorizationInfo(
model,
TransformerMutationType.CREATE,
Expand All @@ -161,7 +164,7 @@ describe('sync engine subscription module', () => {
).toEqual(authInfo);
});
test('public iam authorization for unauth user', () => {
const model = {
const model: SchemaModel = {
syncable: true,
name: 'Post',
pluralName: 'Posts',
Expand Down Expand Up @@ -210,6 +213,7 @@ describe('sync engine subscription module', () => {
};

expect(
// @ts-ignore
SubscriptionProcessor.prototype.getAuthorizationInfo(
model,
TransformerMutationType.CREATE,
Expand All @@ -218,7 +222,7 @@ describe('sync engine subscription module', () => {
).toEqual(authInfo);
});
test('private iam authorization for unauth user', () => {
const model = {
const model: SchemaModel = {
syncable: true,
name: 'Post',
pluralName: 'Posts',
Expand Down Expand Up @@ -267,6 +271,7 @@ describe('sync engine subscription module', () => {
};

expect(
// @ts-ignore
SubscriptionProcessor.prototype.getAuthorizationInfo(
model,
TransformerMutationType.CREATE,
Expand All @@ -275,7 +280,7 @@ describe('sync engine subscription module', () => {
).toEqual(null);
});
test('private iam authorization for auth user', () => {
const model = {
const model: SchemaModel = {
syncable: true,
name: 'Post',
pluralName: 'Posts',
Expand Down Expand Up @@ -324,6 +329,7 @@ describe('sync engine subscription module', () => {
};

expect(
// @ts-ignore
SubscriptionProcessor.prototype.getAuthorizationInfo(
model,
TransformerMutationType.CREATE,
Expand All @@ -332,7 +338,7 @@ describe('sync engine subscription module', () => {
).toEqual(authInfo);
});
test('public apiKey authorization without credentials', () => {
const model = {
const model: SchemaModel = {
syncable: true,
name: 'Post',
pluralName: 'Posts',
Expand Down Expand Up @@ -381,6 +387,7 @@ describe('sync engine subscription module', () => {
};

expect(
// @ts-ignore
SubscriptionProcessor.prototype.getAuthorizationInfo(
model,
TransformerMutationType.CREATE,
Expand Down
8 changes: 4 additions & 4 deletions packages/datastore/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"sideEffects": false,
"scripts": {
"test": "tslint 'src/**/*.ts' && jest -w 1 --coverage",
"test": "npm run lint && jest -w 1 --coverage",
"build-with-test": "npm test && npm run build",
"build:cjs": "node ./build es5 && webpack && webpack --config ./webpack.config.dev.js",
"build:esm": "node ./build es6",
Expand All @@ -22,7 +22,7 @@
"build": "npm run clean && npm run build:esm && npm run build:cjs",
"clean": "rimraf lib-esm lib dist",
"format": "echo \"Not implemented\"",
"lint": "tslint 'src/**/*.ts'"
"lint": "tslint '{__tests__,src}/**/*.ts'"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -51,7 +51,7 @@
"jest": {
"globals": {
"ts-jest": {
"diagnostics": false,
"diagnostics": true,
"tsConfig": {
"lib": [
"es5",
Expand Down Expand Up @@ -94,4 +94,4 @@
"/node_modules/"
]
}
}
}
Loading

0 comments on commit 84286be

Please sign in to comment.