Skip to content

Commit

Permalink
fix(@aws-amplify/datastore): adds missing fields to items sent throug…
Browse files Browse the repository at this point in the history
…h observe/observeQuery (aws-amplify#9973)

* manual rebase: add missing fields to items sent through observe/observequery

* added testing to ensure outbox only contains expected fields

* removed cruft; removed explicit check for undef mutation field
  • Loading branch information
svidgen committed Jun 13, 2022
1 parent d5dd9cb commit ca2a11b
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,20 @@ describe('SQLiteAdapter', () => {
return await db.getAll('select * from MutationEvent', []);
}

async function clearOutbox() {
await pause(250);
const adapter = (DataStore as any).storageAdapter;
const db = (adapter as any).db;
return await db.executeStatements(['delete from MutationEvent']);
}

({ initSchema, DataStore } = require('@aws-amplify/datastore'));
addCommonQueryTests({
initSchema,
DataStore,
storageAdapter: SQLiteAdapter,
getMutations,
clearOutbox,
});

describe('something', () => {
Expand Down
7 changes: 7 additions & 0 deletions packages/datastore/__tests__/AsyncStorageAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import AsyncStorageAdapter from '../src/storage/adapter/AsyncStorageAdapter';
import {
DataStore as DataStoreType,
initSchema as initSchemaType,
syncClasses,
} from '../src/datastore/datastore';
import { PersistentModelConstructor, SortDirection } from '../src/types';
import { pause, Model, User, Profile, testSchema } from './helpers';
Expand All @@ -23,12 +24,18 @@ describe('AsyncStorageAdapter tests', () => {
return await adapter.getAll('sync_MutationEvent');
}

async function clearOutbox(adapter) {
await pause(250);
return await adapter.delete(syncClasses['MutationEvent']);
}

({ initSchema, DataStore } = require('../src/datastore/datastore'));
addCommonQueryTests({
initSchema,
DataStore,
storageAdapter: AsyncStorageAdapter,
getMutations,
clearOutbox,
});

describe('Query', () => {
Expand Down
260 changes: 258 additions & 2 deletions packages/datastore/__tests__/DataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@ import {
PersistentModel,
PersistentModelConstructor,
} from '../src/types';
import { Comment, Model, Post, Metadata, testSchema, pause } from './helpers';
import {
Comment,
Model,
Post,
Profile,
Metadata,
User,
testSchema,
pause,
} from './helpers';

let initSchema: typeof initSchemaType;
let DataStore: typeof DataStoreType;
Expand Down Expand Up @@ -265,13 +274,17 @@ describe('DataStore observeQuery, with fake-indexeddb and fake sync', () => {

let Comment: PersistentModelConstructor<Comment>;
let Post: PersistentModelConstructor<Post>;
let User: PersistentModelConstructor<User>;
let Profile: PersistentModelConstructor<Profile>;

beforeEach(async () => {
({ initSchema, DataStore } = require('../src/datastore/datastore'));
const classes = initSchema(testSchema());
({ Comment, Post } = classes as {
({ Comment, Post, User, Profile } = classes as {
Comment: PersistentModelConstructor<Comment>;
Post: PersistentModelConstructor<Post>;
User: PersistentModelConstructor<User>;
Profile: PersistentModelConstructor<Profile>;
});

// This prevents pollution between tests. DataStore may have processes in
Expand Down Expand Up @@ -598,6 +611,249 @@ describe('DataStore observeQuery, with fake-indexeddb and fake sync', () => {
}
})();
});

test('attaches related belongsTo properties consistently with query() on INSERT', async done => {
try {
const expecteds = [5, 15];

for (let i = 0; i < 5; i++) {
await DataStore.save(
new Comment({
content: `comment content ${i}`,
post: await DataStore.save(
new Post({
title: `new post ${i}`,
})
),
})
);
}

const sub = DataStore.observeQuery(Comment).subscribe(
({ items, isSynced }) => {
const expected = expecteds.shift() || 0;
expect(items.length).toBe(expected);

for (let i = 0; i < expected; i++) {
expect(items[i].content).toEqual(`comment content ${i}`);
expect(items[i].post.title).toEqual(`new post ${i}`);
}

if (expecteds.length === 0) {
sub.unsubscribe();
done();
}
}
);

setTimeout(async () => {
for (let i = 5; i < 15; i++) {
await DataStore.save(
new Comment({
content: `comment content ${i}`,
post: await DataStore.save(
new Post({
title: `new post ${i}`,
})
),
})
);
}
}, 1);
} catch (error) {
done(error);
}
});

test('attaches related hasOne properties consistently with query() on INSERT', async done => {
try {
const expecteds = [5, 15];

for (let i = 0; i < 5; i++) {
await DataStore.save(
new User({
name: `user ${i}`,
profile: await DataStore.save(
new Profile({
firstName: `firstName ${i}`,
lastName: `lastName ${i}`,
})
),
})
);
}

const sub = DataStore.observeQuery(User).subscribe(
({ items, isSynced }) => {
const expected = expecteds.shift() || 0;
expect(items.length).toBe(expected);

for (let i = 0; i < expected; i++) {
expect(items[i].name).toEqual(`user ${i}`);
expect(items[i].profile.firstName).toEqual(`firstName ${i}`);
expect(items[i].profile.lastName).toEqual(`lastName ${i}`);
}

if (expecteds.length === 0) {
sub.unsubscribe();
done();
}
}
);

setTimeout(async () => {
for (let i = 5; i < 15; i++) {
await DataStore.save(
new User({
name: `user ${i}`,
profile: await DataStore.save(
new Profile({
firstName: `firstName ${i}`,
lastName: `lastName ${i}`,
})
),
})
);
}
}, 1);
} catch (error) {
done(error);
}
});

test('attaches related belongsTo properties consistently with query() on UPDATE', async done => {
try {
const expecteds = [
['old post 0', 'old post 1', 'old post 2', 'old post 3', 'old post 4'],
['new post 0', 'new post 1', 'new post 2', 'new post 3', 'new post 4'],
];

for (let i = 0; i < 5; i++) {
await DataStore.save(
new Comment({
content: `comment content ${i}`,
post: await DataStore.save(
new Post({
title: `old post ${i}`,
})
),
})
);
}

const sub = DataStore.observeQuery(Comment).subscribe(
({ items, isSynced }) => {
const expected = expecteds.shift() || [];
expect(items.length).toBe(expected.length);

for (let i = 0; i < expected.length; i++) {
expect(items[i].content).toContain(`comment content ${i}`);
expect(items[i].post.title).toEqual(expected[i]);
}

if (expecteds.length === 0) {
sub.unsubscribe();
done();
}
}
);

setTimeout(async () => {
let postIndex = 0;
const comments = await DataStore.query(Comment);
for (const comment of comments) {
const newPost = await DataStore.save(
new Post({
title: `new post ${postIndex++}`,
})
);

await DataStore.save(
Comment.copyOf(comment, draft => {
draft.content = `updated: ${comment.content}`;
draft.post = newPost;
})
);
}
}, 1);
} catch (error) {
done(error);
}
});

test('attaches related hasOne properties consistently with query() on UPDATE', async done => {
try {
const expecteds = [
[
'first name 0',
'first name 1',
'first name 2',
'first name 3',
'first name 4',
],
[
'new first name 0',
'new first name 1',
'new first name 2',
'new first name 3',
'new first name 4',
],
];

for (let i = 0; i < 5; i++) {
await DataStore.save(
new User({
name: `user ${i}`,
profile: await DataStore.save(
new Profile({
firstName: `first name ${i}`,
lastName: `last name ${i}`,
})
),
})
);
}

const sub = DataStore.observeQuery(User).subscribe(
({ items, isSynced }) => {
const expected = expecteds.shift() || [];
expect(items.length).toBe(expected.length);

for (let i = 0; i < expected.length; i++) {
expect(items[i].name).toContain(`user ${i}`);
expect(items[i].profile.firstName).toEqual(expected[i]);
}

if (expecteds.length === 0) {
sub.unsubscribe();
done();
}
}
);

setTimeout(async () => {
let userIndex = 0;
const users = await DataStore.query(User);
for (const user of users) {
const newProfile = await DataStore.save(
new Profile({
firstName: `new first name ${userIndex++}`,
lastName: `new last name ${userIndex}`,
})
);

await DataStore.save(
User.copyOf(user, draft => {
draft.name = `updated: ${user.name}`;
draft.profile = newProfile;
})
);
}
}, 1);
} catch (error) {
done(error);
}
});
});

describe('DataStore tests', () => {
Expand Down
7 changes: 7 additions & 0 deletions packages/datastore/__tests__/IndexedDBAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'fake-indexeddb/auto';
import {
DataStore as DataStoreType,
initSchema as initSchemaType,
syncClasses,
} from '../src/datastore/datastore';
import { PersistentModelConstructor, SortDirection } from '../src/types';
import {
Expand All @@ -29,12 +30,18 @@ describe('IndexedDBAdapter tests', () => {
return await adapter.getAll('sync_MutationEvent');
}

async function clearOutbox(adapter) {
await pause(250);
return await adapter.delete(syncClasses['MutationEvent']);
}

({ initSchema, DataStore } = require('../src/datastore/datastore'));
addCommonQueryTests({
initSchema,
DataStore,
storageAdapter: Adapter,
getMutations,
clearOutbox,
});

describe('Query', () => {
Expand Down
25 changes: 25 additions & 0 deletions packages/datastore/__tests__/commonAdapterTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function addCommonQueryTests({
DataStore,
storageAdapter,
getMutations,
clearOutbox,
}) {
describe('Common `query()` cases', () => {
let Model: PersistentModelConstructor<Model>;
Expand Down Expand Up @@ -341,5 +342,29 @@ export function addCommonQueryTests({
postId: mutations[0].modelId,
});
});

it('only includes changed fields in mutations', async () => {
const profile = await DataStore.save(
new Profile({ firstName: 'original first', lastName: 'original last' })
);

await clearOutbox(adapter);

await DataStore.save(
Profile.copyOf(profile, draft => {
draft.firstName = 'new first';
})
);

const mutations = await getMutations(adapter);

expect(mutations.length).toBe(1);
expectMutation(mutations[0], {
firstName: 'new first',
_version: v => v === undefined || v === null,
_lastChangedAt: v => v === undefined || v === null,
_deleted: v => v === undefined || v === null,
});
});
});
}
Loading

0 comments on commit ca2a11b

Please sign in to comment.