mirror of
https://github.com/irislib/iris-messenger.git
synced 2024-10-18 06:03:22 +00:00
node & node test
This commit is contained in:
parent
74a4e1261b
commit
1e3480899f
@ -102,7 +102,7 @@ const Content = ({ standalone, isQuote, fullWidth, asInlineQuote, event, isPrevi
|
||||
</Show>
|
||||
<Show when={text?.length > 0}>
|
||||
<div
|
||||
className={`preformatted-wrap pb-1 ${emojiOnly && 'text-2xl'} ${
|
||||
className={`preformatted-wrap pb-1 ${emojiOnly && 'text-3xl'} ${
|
||||
fullWidth ? 'full-width-note' : ''
|
||||
} ${asInlineQuote ? 'inline-quote' : ''}`}
|
||||
>
|
||||
|
@ -6,129 +6,136 @@ import { Callback, Unsubscribe } from '@/state/types.ts';
|
||||
import Node from './Node';
|
||||
|
||||
describe('Node', () => {
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
node = new Node({ id: 'test', adapters: [new MemoryAdapter()] });
|
||||
});
|
||||
|
||||
it('should initialize with defaults', () => {
|
||||
const node = new Node({ adapters: [] });
|
||||
expect(node.id).toEqual('');
|
||||
describe('new Node()', () => {
|
||||
it('should initialize with defaults', () => {
|
||||
const newNode = new Node({ adapters: [] });
|
||||
expect(newNode.id).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
it('should subscribe and unsubscribe with on()', () => {
|
||||
const node = new Node({ id: 'test', adapters: [new MemoryAdapter()] });
|
||||
const mockCallback: Callback = vi.fn();
|
||||
describe('node.on()', () => {
|
||||
it('should subscribe and unsubscribe with on()', () => {
|
||||
const mockCallback: Callback = vi.fn();
|
||||
const unsubscribe: Unsubscribe = node.on(mockCallback);
|
||||
|
||||
const unsubscribe: Unsubscribe = node.on(mockCallback);
|
||||
expect(typeof unsubscribe).toBe('function');
|
||||
node.put('someValue');
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'someValue',
|
||||
'test',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
node.put('someValue');
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'someValue',
|
||||
'test',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
unsubscribe();
|
||||
node.put('someValue2');
|
||||
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should callback when subscribed after put()', () => {
|
||||
const node = new Node({ id: 'test', adapters: [new MemoryAdapter()] });
|
||||
const mockCallback: Callback = vi.fn();
|
||||
node.put('someValue');
|
||||
|
||||
node.on(mockCallback);
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'someValue',
|
||||
'test',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should trigger callback once when calling once()', async () => {
|
||||
const node = new Node({ id: 'test', adapters: [new MemoryAdapter()] });
|
||||
const mockCallback: Callback = vi.fn();
|
||||
node.put('someValue');
|
||||
|
||||
const result = await node.once(mockCallback);
|
||||
expect(result).toBe('someValue');
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'someValue',
|
||||
'test',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
node.put('someValue2');
|
||||
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should trigger map callbacks when children are present', async () => {
|
||||
const node = new Node({ id: 'test', adapters: [new MemoryAdapter()] });
|
||||
const mockCallback: Callback = vi.fn();
|
||||
|
||||
// Adding children to the node
|
||||
await node.get('child1').put('value1');
|
||||
await node.get('child2').put('value2');
|
||||
|
||||
const unsubscribe: Unsubscribe = node.map(mockCallback);
|
||||
|
||||
// Should trigger for both child nodes
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'value1',
|
||||
'test/child1',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'value2',
|
||||
'test/child2',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
unsubscribe();
|
||||
|
||||
// Add another child to ensure the callback is not called after unsubscribe
|
||||
await node.get('child3').put('value3');
|
||||
|
||||
// Should still have been called only twice
|
||||
expect(mockCallback).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should return children when on() is called on a branch node', async () => {
|
||||
const node = new Node({ id: 'settings', adapters: [new MemoryAdapter()] });
|
||||
const mockCallback: Callback = vi.fn();
|
||||
|
||||
// save settings object
|
||||
await node.put({
|
||||
theme: 'dark',
|
||||
fontSize: 14,
|
||||
unsubscribe();
|
||||
node.put('someValue2');
|
||||
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
node.on(mockCallback);
|
||||
it('should callback when subscribed after put()', () => {
|
||||
const mockCallback: Callback = vi.fn();
|
||||
node.put('someValue');
|
||||
node.on(mockCallback);
|
||||
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
{
|
||||
theme: 'dark',
|
||||
fontSize: 14,
|
||||
},
|
||||
'settings',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'someValue',
|
||||
'test',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
node.get('theme').on(mockCallback);
|
||||
describe('node.once()', () => {
|
||||
it('should trigger callback once when calling once()', async () => {
|
||||
const mockCallback: Callback = vi.fn();
|
||||
node.put('someValue');
|
||||
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'dark',
|
||||
'settings/theme',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
const result = await node.once(mockCallback);
|
||||
expect(result).toBe('someValue');
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'someValue',
|
||||
'test',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
node.put('someValue2');
|
||||
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('node.map()', () => {
|
||||
it('should trigger map callbacks when children are present', async () => {
|
||||
const mockCallback: Callback = vi.fn();
|
||||
await node.get('child1').put('value1');
|
||||
await node.get('child2').put('value2');
|
||||
|
||||
const unsubscribe: Unsubscribe = node.map(mockCallback);
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'value1',
|
||||
'test/child1',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
'value2',
|
||||
'test/child2',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
unsubscribe();
|
||||
await node.get('child3').put('value3');
|
||||
expect(mockCallback).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should trigger map callbacks when a new child is added', async () => {
|
||||
const node = new Node({ id: 'root', adapters: [new MemoryAdapter()] });
|
||||
const mockCallback: Callback = vi.fn();
|
||||
const unsubscribe = node.get('chats').map(mockCallback);
|
||||
await node.get('chats').get('someChatId').put({ id: 'someChatId' });
|
||||
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
{ id: 'someChatId' },
|
||||
'root/chats/someChatId',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Branch node behavior', () => {
|
||||
it('should return children when on() is called on a branch node', async () => {
|
||||
const settingsNode = new Node({ id: 'settings', adapters: [new MemoryAdapter()] });
|
||||
const mockCallback1: Callback = vi.fn();
|
||||
const mockCallback2: Callback = vi.fn();
|
||||
|
||||
await settingsNode.put({ theme: 'dark', fontSize: 14 });
|
||||
|
||||
settingsNode.on(mockCallback1);
|
||||
expect(mockCallback1).toHaveBeenCalledWith(
|
||||
{ theme: 'dark', fontSize: 14 },
|
||||
'settings',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
settingsNode.get('theme').on(mockCallback2);
|
||||
expect(mockCallback2).toHaveBeenCalledWith(
|
||||
'dark',
|
||||
'settings/theme',
|
||||
expect.any(Number),
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -12,6 +12,8 @@ type NodeProps = {
|
||||
parent?: Node | null;
|
||||
};
|
||||
|
||||
const DIR_VALUE = '__DIR__';
|
||||
|
||||
export default class Node {
|
||||
id: string;
|
||||
parent: Node | null;
|
||||
@ -58,7 +60,7 @@ export default class Node {
|
||||
|
||||
private async putBranch(value: Record<string, any>, updatedAt: number) {
|
||||
const promises = this.adapters.map((adapter) =>
|
||||
adapter.set(this.id, { value: '__DIR__', updatedAt }),
|
||||
adapter.set(this.id, { value: DIR_VALUE, updatedAt }),
|
||||
);
|
||||
const children = Object.keys(value);
|
||||
const childPromises = children.map((key) => this.get(key).put(value[key], updatedAt));
|
||||
@ -76,6 +78,12 @@ export default class Node {
|
||||
} else {
|
||||
await this.putLeaf(value, updatedAt);
|
||||
}
|
||||
|
||||
if (this.parent) {
|
||||
this.parent.map_subscriptions.forEach((callback) => {
|
||||
callback(value, this.id, updatedAt, () => {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,20 +92,35 @@ export default class Node {
|
||||
*/
|
||||
on(callback: Callback): Unsubscribe {
|
||||
let latest: NodeValue | null = null;
|
||||
if (this.children.size > 0) {
|
||||
// TODO handle branch node
|
||||
} else {
|
||||
// TODO handle leaf node
|
||||
}
|
||||
const cb = (value, path, updatedAt, unsubscribe) => {
|
||||
if (latest === null || latest.updatedAt < value.updatedAt) {
|
||||
if (value !== DIR_VALUE && (latest === null || latest.updatedAt < value.updatedAt)) {
|
||||
latest = { value, updatedAt };
|
||||
callback(value, path, updatedAt, unsubscribe);
|
||||
// TODO send to other adapters? or PubSub which decides where to send?
|
||||
}
|
||||
};
|
||||
const subId = this.counter++;
|
||||
this.on_subscriptions.set(subId, cb);
|
||||
const adapterSubs = this.adapters.map((adapter) => adapter.get(this.id, cb));
|
||||
|
||||
if (this.children.size > 0) {
|
||||
const aggregated: Record<string, any> = {};
|
||||
const keys = Array.from(this.children.keys());
|
||||
const total = keys.length;
|
||||
let count = 0;
|
||||
|
||||
keys.forEach((key) => {
|
||||
this.children.get(key)?.once((childValue) => {
|
||||
aggregated[key] = childValue;
|
||||
count++;
|
||||
|
||||
if (count === total) {
|
||||
callback(aggregated, this.id, Date.now(), () => {});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const unsubscribe = () => {
|
||||
this.on_subscriptions.delete(subId);
|
||||
adapterSubs.forEach((unsub) => unsub());
|
||||
|
Loading…
Reference in New Issue
Block a user