mirror of
https://github.com/appy-one/acebase.git
synced 2026-05-25 22:01:21 -06:00
375 lines
14 KiB
JavaScript
375 lines
14 KiB
JavaScript
const { createTempDB } = require('./tempdb');
|
|
const { proxyAccess, IObservableLike, ObjectCollection } = require('acebase-core');
|
|
const util = require('util');
|
|
|
|
describe('DataProxy', () => {
|
|
|
|
it('Proxy1', async() => {
|
|
const { db, removeDB } = await createTempDB();
|
|
|
|
// Use AceBase's own basic Observable shim because rxjs is not installed
|
|
db.setObservable('shim');
|
|
|
|
const delay = () => new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
const ref = db.ref('observable_chats/chat3');
|
|
|
|
const proxy1 = await ref.proxy({
|
|
defaultValue: {
|
|
title: {
|
|
text: 'General chat',
|
|
updated_by: 'Ewout',
|
|
updated: new Date(),
|
|
},
|
|
created: new Date(),
|
|
participants: [],
|
|
messages: {},
|
|
removeMe: true,
|
|
},
|
|
});
|
|
|
|
let proxy1Mutations = [];
|
|
proxy1.on('mutation', (event) => {
|
|
const { snapshot: snap, isRemote: remote } = event;
|
|
// console.log(`[proxy1] chat was updated ${remote ? 'outside proxy' : 'by us'} at ${snap.ref.path}: `, { current: snap.val(), previous: snap.previous() });
|
|
proxy1Mutations.push({ remote, path: snap.ref.path, val: snap.val(), prev: snap.previous(), context: snap.context() });
|
|
// console.log(JSON.stringify(chat));
|
|
});
|
|
|
|
const proxy2 = await ref.proxy();
|
|
proxy2.value.getObservable().subscribe(chat => {
|
|
console.log(`[proxy2] Got new observer value`, chat);
|
|
});
|
|
let proxy2Mutations = [];
|
|
proxy2.on('mutation', (event) => {
|
|
const { snapshot: snap, isRemote: remote } = event;
|
|
// console.log(`[proxy2] chat was updated ${remote ? 'remotely' : 'by us'} at ${snap.ref.path}: `, { current: snap.val(), previous: snap.previous() });
|
|
proxy2Mutations.push({ remote, path: snap.ref.path, val: snap.val(), prev: snap.previous(), context: snap.context() });
|
|
});
|
|
proxy2.value.onChanged((value, previous, isRemote, context) => {
|
|
console.log(`[proxy2] chat changed ${isRemote ? 'remotely' : 'by us'}`);
|
|
});
|
|
|
|
const chat = proxy1.value;
|
|
chat.messages = {};
|
|
chat.title.text = 'Cool app chat';
|
|
|
|
const tx = await chat.startTransaction();
|
|
chat.title.text = 'Annoying chat';
|
|
chat.title.updated_by = 'Pessimist';
|
|
|
|
expect(chat.title.text).toBe('Annoying chat');
|
|
expect(chat.title.updated_by).toBe('Pessimist');
|
|
|
|
// Rollback!
|
|
tx.rollback();
|
|
|
|
// Values should have been rolled back:
|
|
expect(chat.title.text).toBe('Cool app chat');
|
|
expect(chat.title.updated_by).toBe('Ewout');
|
|
|
|
chat.onChanged((value, previous, isRemote, context) => {
|
|
console.log(`[proxy1] chat changed ${isRemote ? 'outside proxy' : 'by us'}`);
|
|
});
|
|
chat.participants.onChanged((value, previous, isRemote, context) => {
|
|
console.log(`[proxy1] participants changed`);
|
|
});
|
|
chat.messages.onChanged((value, previous, isRemote, context) => {
|
|
console.log(`[proxy1] messages changed`);
|
|
});
|
|
|
|
chat.title = {
|
|
text: 'Support chat',
|
|
updated_by: 'Ewout',
|
|
updated: new Date(),
|
|
};
|
|
|
|
await delay();
|
|
|
|
chat.participants = ['Ewout', 'World'];
|
|
// const participants = chat.participants;
|
|
chat.participants[0] = 'Me';
|
|
chat.participants[1] = 'You';
|
|
chat.participants.push('Blue');
|
|
chat.participants.splice(2, 0, 'True');
|
|
|
|
// chat.messages = {};
|
|
chat.messages.push({ from: 'Ewout', text: 'Hello world' });
|
|
chat.messages.push({ from: 'World', text: 'Hello Ewout, how are you?' });
|
|
chat.messages.push({ from: 'Ewout', text: 'Great! 🔥' });
|
|
// chat.participants.push('Annet');
|
|
|
|
delete chat.removeMe;
|
|
|
|
await delay();
|
|
expect(chat.participants[0]).toBe('Me');
|
|
expect(chat.participants[1]).toBe('You');
|
|
expect(chat.participants[2]).toBe('True');
|
|
expect(chat.participants[3]).toBe('Blue');
|
|
|
|
// Check array size
|
|
expect(chat.messages.toArray().length).toBe(3);
|
|
|
|
// All messages must be a proxied values
|
|
chat.messages.forEach(message => {
|
|
expect(util.types.isProxy(message)).toBeTrue(); // Test with util.types
|
|
expect(message[Symbol('isProxy')]).toBeTrue(); // Test with isProxy Symbol
|
|
});
|
|
|
|
for (let p of chat.participants) {
|
|
console.log(p);
|
|
}
|
|
for (let m of chat.messages) {
|
|
console.log(m.valueOf());
|
|
m.read = new Date();
|
|
}
|
|
|
|
await delay();
|
|
|
|
console.log(chat.messages.getTarget());
|
|
console.log(chat.messages.getTarget(false));
|
|
console.log(chat.messages.valueOf());
|
|
console.log(chat.messages.toString());
|
|
|
|
delete chat.messages;
|
|
await delay();
|
|
|
|
// expect(count).toBe(6);
|
|
// Check receivedMutations
|
|
console.log(proxy1Mutations);
|
|
console.log(proxy2Mutations);
|
|
|
|
await proxy1.destroy();
|
|
await proxy2.destroy();
|
|
|
|
removeDB();
|
|
}, 60000);
|
|
|
|
it('Proxy2', async() => {
|
|
const { db, removeDB } = await createTempDB();
|
|
|
|
const delay = () => new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
const ref = db.ref('proxy2');
|
|
const proxy = await ref.proxy({ defaultValue: { books: {} } });
|
|
|
|
let mutations = [];
|
|
proxy.on('mutation', (event) => {
|
|
const { snapshot: snap, isRemote: remote } = event;
|
|
mutations.push({ snap, remote, val: snap.val(), context: snap.context() });
|
|
// console.log(`Mutation on "/${snap.ref.path}" with context: `, snap.context(), snap.val());
|
|
});
|
|
const obj = proxy.value;
|
|
|
|
const book1 = { title: 'New book 1!', description: 'This is my first book' },
|
|
book2 = { title: 'New book 2', description: 'This is my second book' },
|
|
book3 = { title: 'New book 3', description: 'This is my third book' };
|
|
|
|
// Add book through the proxy, considered a local mutation
|
|
await obj.books.push(book1);
|
|
|
|
// Add another book through a reference, considered a remote mutation
|
|
await ref.child('books').push(book2);
|
|
|
|
// Add another book through a reference achieved through proxy, also considered a remote mutation
|
|
await obj.books.getRef().push(book3);
|
|
|
|
await delay();
|
|
|
|
expect(mutations.length).toEqual(3);
|
|
expect(mutations[0].remote).toBeFalse();
|
|
expect(mutations[0].val).toEqual(book1);
|
|
expect(mutations[1].remote).toBeTrue();
|
|
expect(mutations[1].val).toEqual(book2);
|
|
expect(mutations[2].remote).toBeTrue();
|
|
expect(mutations[2].val).toEqual(book3);
|
|
|
|
removeDB();
|
|
});
|
|
|
|
it('Proxy3', async() => {
|
|
// TODO: finish
|
|
const { db, removeDB } = await createTempDB();
|
|
|
|
// Use AceBase's own basic Observable shim because rxjs is not installed
|
|
db.setObservable('shim');
|
|
|
|
const movies = ObjectCollection.from(require('./dataset/movies.json'));
|
|
const proxy = await db.ref('movies').proxy({ defaultValue: movies });
|
|
|
|
// Compare proxied value with original
|
|
expect(proxy.value.valueOf()).toEqual(movies);
|
|
|
|
// Make a change to a movie through the proxy
|
|
const movieIDs = Object.keys(proxy.value);
|
|
const aMovie = proxy.value[movieIDs[0]];
|
|
|
|
removeDB();
|
|
});
|
|
|
|
it('OrderedCollectionProxy', async () => {
|
|
const { db, removeDB } = await createTempDB();
|
|
|
|
// Use AceBase's own basic Observable shim because rxjs is not installed
|
|
db.setObservable('shim');
|
|
|
|
const proxy = await db.ref('todo').proxy({ defaultValue: {} });
|
|
const todo = proxyAccess(proxy.value);
|
|
|
|
// Create transaction so we can monitor when changes have been persisted
|
|
let tx = await todo.startTransaction();
|
|
|
|
// Add some items to collection without sorting property
|
|
todo.push({ text: 'Build' });
|
|
todo.push({ text: 'Test' });
|
|
todo.push({ text: 'Fix' });
|
|
todo.push({ text: 'Release' });
|
|
|
|
// Create object collection proxy with defaults
|
|
let collection = proxyAccess(todo).getOrderedCollection();
|
|
let subscription = collection.getArrayObservable().subscribe(newArray => {
|
|
console.log(`Got new array:`, newArray.map(item => `${item.order}: ${item.text}`));
|
|
});
|
|
|
|
// Make sure the default sorting property "order" has been added to each item
|
|
let arr = collection.getArray();
|
|
expect(arr.length).toBe(4);
|
|
for (let i = 0; i < arr.length; i++) {
|
|
const item = arr[i];
|
|
expect(() => proxyAccess(item)).not.toThrow();
|
|
expect(item.order).not.toBeUndefined();
|
|
expect(item.order).toBe(i * 10);
|
|
}
|
|
|
|
await tx.commit();
|
|
|
|
// Now create another one using collection.add
|
|
tx = await todo.startTransaction();
|
|
collection.add({ text: 'Update' });
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr.length).toBe(5);
|
|
expect(arr[4].text).toBe('Update');
|
|
expect(arr[4].order).toBe(40);
|
|
|
|
// Now swap items 'Release' & 'Update'
|
|
tx = await todo.startTransaction();
|
|
collection.move(4, 3);
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr.length).toBe(5);
|
|
expect(arr[3].text).toBe('Update');
|
|
expect(arr[3].order).toBe(30);
|
|
expect(arr[4].text).toBe('Release');
|
|
expect(arr[4].order).toBe(40);
|
|
|
|
// Now move 'Test' to the end
|
|
tx = await todo.startTransaction();
|
|
collection.move(1, 4);
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr).toEqual([{ text: 'Build', order: 0 }, { text: 'Fix', order: 20 }, { text: 'Update', order: 30 }, { text: 'Release', order: 40 }, { text: 'Test', order: 50 } ]);
|
|
|
|
// Now move 'Test' in between 'Fix' & 'Update'
|
|
tx = await todo.startTransaction();
|
|
collection.move(4, 2);
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr).toEqual([{ text: 'Build', order: 0 }, { text: 'Fix', order: 20 }, { text: 'Test', order: 25 }, { text: 'Update', order: 30 }, { text: 'Release', order: 40 } ]);
|
|
|
|
// Now move 'Fix' in between 'Test' & 'Update' (swaps 'Fix' and 'Test')
|
|
tx = await todo.startTransaction();
|
|
collection.move(1, 2);
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr).toEqual([{ text: 'Build', order: 0 }, { text: 'Test', order: 20 }, { text: 'Fix', order: 25 }, { text: 'Update', order: 30 }, { text: 'Release', order: 40 } ]);
|
|
|
|
// Move 'Release' in between 'Test' & 'Fix'
|
|
tx = await todo.startTransaction();
|
|
collection.move(4, 2);
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr).toEqual([{ text: 'Build', order: 0 }, { text: 'Test', order: 20 }, { text: 'Release', order: 23 }, { text: 'Fix', order: 25 }, { text: 'Update', order: 30 } ]);
|
|
|
|
// Insert 'Debug' between 'Release' and 'Fix'
|
|
tx = await todo.startTransaction();
|
|
collection.add({ text: 'Debug' }, 3);
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr).toEqual([{ text: 'Build', order: 0 }, { text: 'Test', order: 20 }, { text: 'Release', order: 23 }, { text: 'Debug', order: 24 }, { text: 'Fix', order: 25 }, { text: 'Update', order: 30 } ]);
|
|
|
|
// Insert 'Got Issue' between 'Release' and 'Debug'
|
|
// This will trigger all orders to be regenerated - there is room for improvement here: order 23 could be set to 22, so the new item can get 23 instead.
|
|
tx = await todo.startTransaction();
|
|
collection.add({ text: 'Receive Issue' }, 3);
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr).toEqual([{ text: 'Build', order: 0 }, { text: 'Test', order: 10 }, { text: 'Release', order: 20 }, { text: 'Receive Issue', order: 30 }, { text: 'Debug', order: 40 }, { text: 'Fix', order: 50 }, { text: 'Update', order: 60 } ]);
|
|
|
|
// Remove items 1 at a time
|
|
while (arr.length > 0) {
|
|
tx = await todo.startTransaction();
|
|
collection.delete(0);
|
|
await tx.commit();
|
|
const newArr = collection.getArray();
|
|
expect(newArr.length).toBe(arr.length - 1);
|
|
expect(newArr).toEqual(arr.slice(1));
|
|
arr = newArr;
|
|
}
|
|
|
|
// Create multiple items
|
|
tx = await todo.startTransaction();
|
|
collection.add({ text: 'Build' });
|
|
collection.add({ text: 'Release' });
|
|
collection.add({ text: 'Update' });
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr).toEqual([{ text: 'Build', order: 0 }, { text: 'Release', order: 10 }, { text: 'Update', order: 20 }]);
|
|
|
|
// Prepend item
|
|
tx = await todo.startTransaction();
|
|
collection.add({ text: 'Think' }, 0);
|
|
await tx.commit();
|
|
arr = collection.getArray();
|
|
expect(arr).toEqual([{ text: 'Think', order: -10 }, { text: 'Build', order: 0 }, { text: 'Release', order: 10 }, { text: 'Update', order: 20 }]);
|
|
|
|
// Cleanup
|
|
subscription.unsubscribe();
|
|
removeDB();
|
|
});
|
|
|
|
it('proxy with cursor', async() => {
|
|
const { db, removeDB } = await createTempDB({ transactionLogging: true });
|
|
|
|
const delay = () => new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
const ref = db.ref('proxy');
|
|
const proxy = await ref.proxy({ defaultValue: { books: {} } });
|
|
|
|
let cursors = [];
|
|
proxy.on('cursor', (cursor) => {
|
|
console.log(`Got cursor ${cursor}`);
|
|
cursors.push(cursor);
|
|
});
|
|
const obj = proxy.value;
|
|
|
|
const book1 = { title: 'New book 1!', description: 'This is my first book' },
|
|
book2 = { title: 'New book 2', description: 'This is my second book' },
|
|
book3 = { title: 'New book 3', description: 'This is my third book' };
|
|
|
|
// Add book through the proxy, considered a local mutation
|
|
await obj.books.push(book1);
|
|
|
|
// Add another book through a reference, considered a remote mutation
|
|
await ref.child('books').push(book2);
|
|
|
|
// Add another book through a reference achieved through proxy, also considered a remote mutation
|
|
await obj.books.getRef().push(book3);
|
|
|
|
await delay();
|
|
|
|
expect(cursors.length).toEqual(3);
|
|
|
|
removeDB();
|
|
});
|
|
});
|