Commit 01a1dc19 authored by nanahira's avatar nanahira

add observe-diff

parent 1578b0cc
...@@ -7,3 +7,4 @@ export * from './src/types'; ...@@ -7,3 +7,4 @@ export * from './src/types';
export * from './src/middleware-dispatcher'; export * from './src/middleware-dispatcher';
export * from './src/i18n'; export * from './src/i18n';
export * from './src/patch-string-in-object'; export * from './src/patch-string-in-object';
export * from './src/observe-diff';
export const observeDiff = <T>(
obj: T,
cb: <K extends keyof T>(change: {
type: 'add' | 'update' | 'delete';
key: K;
oldValue: T[K] | undefined;
newValue: T[K] | undefined;
}) => any,
): T => {
return new Proxy(obj as any, {
set(target, key: string | symbol, value) {
const oldValue = target[key];
const type = Object.prototype.hasOwnProperty.call(target, key)
? 'update'
: 'add';
target[key] = value;
cb({ type, key: key as any, oldValue, newValue: value });
return true;
},
deleteProperty(target, key: string | symbol) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
const oldValue = target[key];
delete target[key];
cb({ type: 'delete', key: key as any, oldValue, newValue: undefined });
return true;
}
return false;
},
});
};
// observe-diff.spec.ts
import { observeDiff } from '../src/observe-diff';
interface TestObj {
foo?: number | null;
bar?: string | null;
}
type Change<T> = Parameters<typeof observeDiff<T>>[1] extends (
change: infer C,
) => any
? C
: never;
describe('observeDiff', () => {
it('should emit update when existing key is changed', () => {
const obj: TestObj = { foo: 1, bar: 'x' };
const changes: Change<TestObj>[] = [];
const proxy = observeDiff(obj, (c) => changes.push(c));
proxy.foo = 2;
proxy.bar = 'y';
expect(changes).toEqual([
{
type: 'update',
key: 'foo',
oldValue: 1,
newValue: 2,
},
{
type: 'update',
key: 'bar',
oldValue: 'x',
newValue: 'y',
},
]);
});
it('should emit add when new key is assigned', () => {
const obj = {} as TestObj;
const changes: Change<TestObj>[] = [];
const proxy = observeDiff(obj, (c) => changes.push(c));
proxy.foo = 123;
proxy.bar = 'abc';
expect(changes).toEqual([
{
type: 'add',
key: 'foo',
oldValue: undefined,
newValue: 123,
},
{
type: 'add',
key: 'bar',
oldValue: undefined,
newValue: 'abc',
},
]);
});
it('should emit delete when property is deleted', () => {
const obj: TestObj = { foo: 1, bar: 'x' };
const changes: Change<TestObj>[] = [];
const proxy = observeDiff(obj, (c) => changes.push(c));
delete proxy.foo;
delete proxy.bar;
expect(changes).toEqual([
{
type: 'delete',
key: 'foo',
oldValue: 1,
newValue: undefined,
},
{
type: 'delete',
key: 'bar',
oldValue: 'x',
newValue: undefined,
},
]);
});
it('should track multiple changes on the same key in order', () => {
const obj: TestObj = { foo: 1 };
const changes: Change<TestObj>[] = [];
const proxy = observeDiff(obj, (c) => changes.push(c));
proxy.foo = 2;
proxy.foo = 3;
delete proxy.foo;
expect(changes).toEqual([
{
type: 'update',
key: 'foo',
oldValue: 1,
newValue: 2,
},
{
type: 'update',
key: 'foo',
oldValue: 2,
newValue: 3,
},
{
type: 'delete',
key: 'foo',
oldValue: 3,
newValue: undefined,
},
]);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment