Skip to content

Commit

Permalink
Support non-@model types
Browse files Browse the repository at this point in the history
  • Loading branch information
manueliglesias committed Mar 17, 2020
1 parent db27ab3 commit 8c94bf4
Show file tree
Hide file tree
Showing 13 changed files with 714 additions and 165 deletions.
114 changes: 103 additions & 11 deletions packages/datastore/__tests__/DataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
MutableModel,
PersistentModelConstructor,
Schema,
NonModelTypeConstructor,
} from '../src/types';
import StorageType from '../src/storage/storage';
import Observable from 'zen-observable-ts';
Expand Down Expand Up @@ -36,20 +37,20 @@ beforeEach(() => {

describe('DataStore tests', () => {
describe('initSchema tests', () => {
test('Class is created', () => {
test('Model class is created', () => {
const classes = initSchema(testSchema());

expect(classes).toHaveProperty('Model');

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

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

expect(typeof Model.copyOf).toBe('function');
});

test('Class can be instantiated', () => {
test('Model class can be instantiated', () => {
const { Model } = initSchema(testSchema()) as {
Model: PersistentModelConstructor<Model>;
};
Expand Down Expand Up @@ -92,6 +93,32 @@ describe('DataStore tests', () => {
initSchema(testSchema());
}).toThrow('The schema has already been initialized');
});

test('Non @model class is created', () => {
const classes = initSchema(testSchema());

expect(classes).toHaveProperty('Metadata');

const { Metadata } = classes;

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

test('Non @model class can be instantiated', () => {
const { Metadata } = initSchema(testSchema()) as {
Metadata: NonModelTypeConstructor<Metadata>;
};

const metadata = new Metadata({
author: 'some author',
tags: [],
});

expect(metadata).toBeInstanceOf(Metadata);

expect(metadata).not.toHaveProperty('id');
});
});

describe('Immutability', () => {
Expand Down Expand Up @@ -147,6 +174,20 @@ describe('DataStore tests', () => {
// ID should be kept the same
expect(model1.id).toBe(model2.id);
});

test('Non @model - Field cannot be changed', () => {
const { Metadata } = initSchema(testSchema()) as {
Metadata: NonModelTypeConstructor<Metadata>;
};

const nonModel = new Metadata({
author: 'something',
});

expect(() => {
(<any>nonModel).author = 'edit';
}).toThrowError("Cannot assign to read only property 'author' of object");
});
});

describe('Initialization', () => {
Expand All @@ -155,7 +196,7 @@ describe('DataStore tests', () => {

const classes = initSchema(testSchema());

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

const promises = [
DataStore.query(Model),
Expand All @@ -168,18 +209,33 @@ describe('DataStore tests', () => {

expect(Storage).toHaveBeenCalledTimes(1);
});
});

test('It is initialized when observing (no query)', async () => {
Storage = require('../src/storage/storage').default;
test('It is initialized when observing (no query)', async () => {
Storage = require('../src/storage/storage').default;

const classes = initSchema(testSchema());
const classes = initSchema(testSchema());

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

DataStore.observe(Model).subscribe(jest.fn());
DataStore.observe(Model).subscribe(jest.fn());

expect(Storage).toHaveBeenCalledTimes(1);
expect(Storage).toHaveBeenCalledTimes(1);
});
});

test("non-@models can't be saved", async () => {
const { Metadata } = initSchema(testSchema()) as {
Metadata: NonModelTypeConstructor<Metadata>;
};

const metadata = new Metadata({
author: 'some author',
tags: [],
});

await expect(DataStore.save(<any>metadata)).rejects.toThrow(
'Object is not an instance of a valid model'
);
});
});

Expand All @@ -197,6 +253,12 @@ declare class Model {
): Model;
}

export declare class Metadata {
readonly author: string;
readonly tags?: string[];
constructor(init: Metadata);
}

function testSchema(): Schema {
return {
enums: {},
Expand All @@ -218,6 +280,15 @@ function testSchema(): Schema {
type: 'String',
isRequired: true,
},
metadata: {
name: 'metadata',
isArray: false,
type: {
type: 'Metadata',
},
isRequired: false,
attributes: [],
},
},
},
LocalModel: {
Expand All @@ -240,6 +311,27 @@ function testSchema(): Schema {
},
},
},
types: {
Metadata: {
name: 'Metadata',
fields: {
author: {
name: 'author',
isArray: false,
type: 'String',
isRequired: true,
attributes: [],
},
tags: {
name: 'tags',
isArray: true,
type: 'String',
isRequired: false,
attributes: [],
},
},
},
},
version: '1',
};
}
Expand Down
181 changes: 181 additions & 0 deletions packages/datastore/__tests__/graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { parse, print } from 'graphql';
import { SchemaNamespace } from '../src';
import {
buildGraphQLOperation,
buildSubscriptionGraphQLOperation,
TransformerMutationType,
} from '../src/sync/utils';
import { newSchema } from './schema';

const postSelectionSet = `
id
title
metadata {
rating
tags
nested {
aField
}
}
_version
_lastChangedAt
_deleted
reference {
id
_deleted
}
blog {
id
_deleted
}
`;

describe('DataStore GraphQL generation', () => {
test.each([
[
'LIST',
/* GraphQL */ `
query operation(
$limit: Int
$nextToken: String
$lastSync: AWSTimestamp
) {
syncPosts(limit: $limit, nextToken: $nextToken, lastSync: $lastSync) {
items {
${postSelectionSet}
}
nextToken
startedAt
}
}
`,
],
[
'CREATE',
/* GraphQL */ `
mutation operation($input: CreatePostInput!) {
createPost(input: $input) {
${postSelectionSet}
}
}
`,
],
[
'UPDATE',
/* GraphQL */ `
mutation operation(
$input: UpdatePostInput!
$condition: ModelPostConditionInput
) {
updatePost(input: $input, condition: $condition) {
${postSelectionSet}
}
}
`,
],
,
[
'DELETE',
/* GraphQL */ `
mutation operation(
$input: DeletePostInput!
$condition: ModelPostConditionInput
) {
deletePost(input: $input, condition: $condition) {
${postSelectionSet}
}
}
`,
],
,
[
'GET',
/* GraphQL */ `
query operation($id: ID!) {
getPost(id: $id) {
${postSelectionSet}
}
}
`,
],
])(
'%s - has full selection set including types, and inputs',
(graphQLOpType, expectedGraphQL) => {
const namespace = <SchemaNamespace>(<unknown>newSchema);

const {
models: { Post: postModelDefinition },
} = namespace;

const [[, , query]] = buildGraphQLOperation(
namespace,
postModelDefinition,
<any>graphQLOpType
);

expect(print(parse(query))).toStrictEqual(print(parse(expectedGraphQL)));
}
);

test.each([
[
TransformerMutationType.CREATE,
/* GraphQL */ `
subscription operation {
onCreatePost {
${postSelectionSet}
}
}
`,
],
[
TransformerMutationType.GET,
/* GraphQL */ `
subscription operation {
onGetPost {
${postSelectionSet}
}
}
`,
],
[
TransformerMutationType.UPDATE,
/* GraphQL */ `
subscription operation {
onUpdatePost {
${postSelectionSet}
}
}
`,
],
[
TransformerMutationType.DELETE,
/* GraphQL */ `
subscription operation {
onDeletePost {
${postSelectionSet}
}
}
`,
],
])(
'Subscription (%s) - has full selection set including types, and inputs',
(transformerMutationType, expectedGraphQL) => {
const namespace = <SchemaNamespace>(<unknown>newSchema);

const {
models: { Post: postModelDefinition },
} = namespace;

const [, , query] = buildSubscriptionGraphQLOperation(
namespace,
postModelDefinition,
<any>transformerMutationType,
false,
''
);

expect(print(parse(query))).toStrictEqual(print(parse(expectedGraphQL)));
}
);
});
Loading

0 comments on commit 8c94bf4

Please sign in to comment.