UNPKG

@v4fire/core

Version:
1,204 lines (1,203 loc) 37.9 kB
"use strict"; var _watch = _interopRequireWildcard(require("../../../core/object/watch")); var proxyEngine = _interopRequireWildcard(require("../../../core/object/watch/engines/proxy")); var accEngine = _interopRequireWildcard(require("../../../core/object/watch/engines/accessors")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } describe('core/object/watch', () => { const engines = new Map([['default', undefined], ['proxy', proxyEngine], ['accessors', accEngine]]); engines.forEach((engine, engineName) => { describe(`with the "${engineName}" engine`, () => { it('simple watching for an object', done => { const obj = { a: 1, b: 2 }, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { engine }, mutations => { spy(mutations.map(el => el.slice(0, 2).concat(el[2].path))); }); proxy.a = 2; expect(spy).not.toHaveBeenCalled(); proxy.a = 3; expect(spy).not.toHaveBeenCalled(); proxy.b = 4; expect(spy).not.toHaveBeenCalled(); setTimeout(() => { expect(spy).toHaveBeenCalledWith([[2, 1, 'a'], [3, 2, 'a'], [4, 2, 'b']]); done(); }, 15); }); it('watching for an object with the `immediate` option', () => { const obj = { a: 1, b: 2 }, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { immediate: true, engine }, (value, oldValue) => { spy(value, oldValue); }); proxy.a = 2; expect(spy).toHaveBeenCalledWith(2, 1); proxy.b = 4; expect(spy).toHaveBeenCalledWith(4, 2); }); it('watching for an object with collapsing', done => { const obj = { a: 1, b: 2 }, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { collapse: true, engine }, mutations => { spy(mutations.map(el => el.slice(0, 2).concat(el[2].path))); }); proxy.a = 2; expect(spy).not.toHaveBeenCalled(); proxy.a = 3; expect(spy).not.toHaveBeenCalled(); proxy.b = 4; expect(spy).not.toHaveBeenCalled(); setTimeout(() => { expect(spy).toHaveBeenCalledWith([[2, 1, 'a'], [3, 2, 'a'], [4, 2, 'b']]); done(); }, 15); }); it('deep watching for an object', () => { const obj = { a: { b: [], c: { e: 1 } } }, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { immediate: true, deep: true, engine }, (value, oldValue) => { spy(value, oldValue); }); proxy.a = proxy.a; expect(spy).not.toHaveBeenCalled(); proxy.a.b = [1, 2, 3]; expect(spy).toHaveBeenCalledWith([1, 2, 3], []); proxy.a.c.e = 4; expect(spy).toHaveBeenCalledWith(4, 1); }); it('deep watching for an object by a complex path', () => { const obj = { a: { b: [] }, c: { e: 1 } }; { const spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, 'a.b', { immediate: true, engine }, (value, oldValue) => { spy(value, oldValue); }); proxy.a.b = [1, 2, 3]; expect(spy).toHaveBeenCalledWith([1, 2, 3], []); } { const spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, 'c.e', { immediate: true, engine }, (value, oldValue) => { spy(value, oldValue); }); proxy.c = { e: 1 }; expect(spy).not.toHaveBeenCalled(); proxy.c = { e: 2 }; expect(spy).toHaveBeenCalledWith(2, 1); proxy.c.e++; expect(spy).toHaveBeenCalledWith(3, 2); } }); it('deep watching for an object by a complex path with collapsing', async () => { const obj = { a: { b: [] } }; const spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, 'a.b', { engine }, (value, oldValue, info) => { spy(value, oldValue, info.path, info.originalPath); }); proxy.a.b.push(1); proxy.a.b.push(2); await new Promise(r => setTimeout(r, 15)); expect(spy).toHaveBeenCalledWith([1, 2], [1, 2], ['a', 'b'], ['a', 'b', 1]); }); it('deep watching for an object by a complex path without collapsing', async () => { const obj = { a: { b: [] } }; const spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, 'a.b', { engine, collapse: false }, mutations => { mutations.forEach(([value, oldValue, info]) => { spy(value, oldValue, info.path, info.originalPath); }); }); proxy.a.b.push(1); proxy.a.b.push(2); await new Promise(r => setTimeout(r, 15)); expect(spy).toHaveBeenCalledWith(1, undefined, ['a', 'b'], ['a', 'b', 0]); expect(spy).toHaveBeenCalledWith(2, undefined, ['a', 'b'], ['a', 'b', 1]); }); it('deep watching for an array by a complex path without collapsing', async () => { const obj = { a: { b: [] } }; const spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, 'a.b.0', { engine, collapse: false }, mutations => { mutations.forEach(([value, oldValue, info]) => { spy(value, oldValue, info.path, info.originalPath); }); }); proxy.a.b.push(1); proxy.a.b.unshift({ e: 2 }); await new Promise(r => setTimeout(r, 15)); expect(spy).toHaveBeenCalledWith(1, undefined, ['a', 'b', '0'], ['a', 'b', 0]); expect(spy).toHaveBeenCalledWith({ e: 2 }, 1, ['a', 'b', '0'], ['a', 'b', 0]); if (engineName === 'proxy') { proxy.a.b[0].e++; await new Promise(r => setTimeout(r, 15)); expect(spy).toHaveBeenCalledWith(3, 2, ['a', 'b', '0'], ['a', 'b', 0, 'e']); } }); it('isolated watchers', () => { const obj = { a: { b: [], c: { e: 1 } } }, spy1 = jest.fn().mockName('spy1'), spy2 = jest.fn().mockName('spy2'); const handler = spy => (value, oldValue) => { spy(value, oldValue); }; const { proxy: proxy1 } = (0, _watch.default)(obj, { immediate: true, deep: true, engine }, handler(spy1)), { proxy: proxy2 } = (0, _watch.default)(obj, { immediate: true, deep: true, engine }, handler(spy2)); proxy1.a.b = [1, 2, 3]; expect(spy1).toHaveBeenCalledWith([1, 2, 3], []); expect(spy2).not.toHaveBeenCalled(); proxy2.a.c.e = 4; expect(spy1).toHaveBeenCalledTimes(1); expect(spy2).toHaveBeenCalledWith(4, 1); }); it('shared watchers', () => { const obj = { a: { b: [], c: { e: 1 } } }, spy1 = jest.fn().mockName('spy1'), spy2 = jest.fn().mockName('spy2'); const handler = spy => (value, oldValue) => { spy(value, oldValue); }; const { proxy: proxy1 } = (0, _watch.default)(obj, { immediate: true, deep: true, engine }, handler(spy1)), { proxy: proxy2 } = (0, _watch.default)(proxy1, { immediate: true, deep: true, engine }, handler(spy2)); proxy1.a.b = [1, 2, 3]; expect(spy1).toHaveBeenCalledWith([1, 2, 3], []); expect(spy2).toHaveBeenCalledWith([1, 2, 3], []); proxy2.a.c.e = 4; expect(spy1).toHaveBeenCalledWith(4, 1); expect(spy2).toHaveBeenCalledWith(4, 1); }); it('deep watching for an object with prototypes', () => { const obj = { a: { b: [], __proto__: { c: { e: 1 } } } }, protoSpy = jest.fn().mockName('with the prototype'), nonProtoSpy = jest.fn().mockName('without the prototype'); const handler = spy => (value, oldValue, info) => { spy(value, oldValue, info.fromProto); }; const { proxy: protoProxy } = (0, _watch.default)(obj, { immediate: true, deep: true, withProto: true, engine }, handler(protoSpy)), { proxy: nonProtoProxy } = (0, _watch.default)(protoProxy, { immediate: true, deep: true, engine }, handler(nonProtoSpy)); protoProxy.a.b = [1, 2, 3]; expect(protoSpy).toHaveBeenCalledWith([1, 2, 3], [], false); expect(nonProtoSpy).toHaveBeenCalledWith([1, 2, 3], [], false); nonProtoProxy.a.c.e = 4; expect(protoSpy).toHaveBeenCalledWith(4, 1, true); expect(nonProtoSpy).toHaveBeenCalledTimes(1); }); it('deep watching with collapsing', done => { const obj = { a: { b: [], c: { e: 1 } }, c: [] }, spy = jest.fn(); const opts = { deep: true, collapse: true, engine }; const { proxy } = (0, _watch.default)(obj, opts, mutations => { spy(mutations.map(el => el.slice(0, 2).concat(el[2].path))); }); proxy.c.push(1); expect(spy).not.toHaveBeenCalled(); proxy.a.b.push(1); expect(spy).not.toHaveBeenCalled(); proxy.a.c.e = 2; expect(spy).not.toHaveBeenCalled(); setTimeout(() => { expect(spy).toHaveBeenCalledWith([[[1], [1], 'c', 0], [{ b: [1], c: { e: 2 } }, { b: [1], c: { e: 2 } }, 'a', 'b', 0], [{ b: [1], c: { e: 2 } }, { b: [1], c: { e: 2 } }, 'a', 'c', 'e']]); done(); }, 15); }); it('deep watching with collapsing and the `immediate` option', () => { const obj = { a: { b: [], c: { e: 1 } }, c: [] }, spy = jest.fn(); const opts = { immediate: true, deep: true, collapse: true, engine }; const { proxy } = (0, _watch.default)(obj, opts, (value, oldValue) => { spy(value, oldValue); }); proxy.c.push(1); expect(spy).toHaveBeenCalledWith([1], [1]); proxy.a.b.push(1); expect(spy).toHaveBeenCalledWith({ b: [1], c: { e: 1 } }, { b: [1], c: { e: 1 } }); proxy.a.c.e = 2; expect(spy).toHaveBeenCalledWith({ b: [1], c: { e: 2 } }, { b: [1], c: { e: 2 } }); }); it('watching for getters with prefixes and postfixes', () => { const obj = { _a: 1, get a() { return this._a * 2; }, b: { $foo: { a: { b: 1 } }, get fooStore() { return this.$foo; }, get foo() { return this.fooStore.a.b * 3; } } }; const spy = jest.fn().mockName('global'), localSpy = jest.fn().mockName('local'); const opts = { immediate: true, deep: true, prefixes: ['_', '$'], postfixes: ['Store'], engine }; const { proxy } = (0, _watch.default)(obj, opts, (value, oldValue, info) => { spy(value, oldValue, info.path, info.parent && Object.select(info.parent, /value/i)); }); proxy._a = 2; expect(spy).toHaveBeenCalledWith(2, 1, ['_a'], undefined); expect(spy).toHaveBeenCalledWith(4, undefined, ['a'], { value: 2, oldValue: 1 }); proxy._a = 3; expect(spy).toHaveBeenCalledWith(3, 2, ['_a'], undefined); expect(spy).toHaveBeenCalledWith(6, 4, ['a'], { value: 3, oldValue: 2 }); proxy._a = 2; expect(spy).toHaveBeenCalledWith(2, 1, ['_a'], undefined); expect(spy).toHaveBeenCalledWith(4, undefined, ['a'], { value: 2, oldValue: 1 }); (0, _watch.default)(proxy, 'b.foo', opts, (value, oldValue, info) => { localSpy(value, oldValue, info.path); }); proxy.b.fooStore.a.b = 3; expect(spy).toHaveBeenCalledWith(3, 1, ['b', 'fooStore', 'a', 'b'], undefined); expect(spy).toHaveBeenCalledWith(undefined, undefined, ['b', 'foo', 'a', 'b'], { value: 3, oldValue: 1 }); expect(localSpy).toHaveBeenCalledWith(9, undefined, ['b', 'foo']); }); it('watching for getters with dependencies', () => { const fork = () => ({ _a: 1, get a() { return this._a * 2; }, b: { $foo: { a: { b: 1 } }, get fooStore() { return this.$foo; }, get foo() { return this.fooStore.a.b * 3; } } }); const deps = [{ a: ['_a'], 'b.foo': [['b', 'fooStore']], fooStore: ['b.$foo'] }, new Map([['a', ['_a']], [['b', 'foo'], [['b', 'fooStore']]], ['fooStore', ['b.$foo']]])]; for (let i = 0; i < deps.length; i++) { const obj = fork(), spy = jest.fn().mockName('global'), localSpy = jest.fn().mockName('local'); const opts = { immediate: true, deep: true, dependencies: deps[i], engine }; const { proxy } = (0, _watch.default)(obj, opts, (value, oldValue, info) => { spy(value, oldValue, info.path, info.parent && Object.select(info.parent, /value/i)); }); proxy._a = 2; expect(spy).toHaveBeenCalledWith(2, 1, ['_a'], undefined); expect(spy).toHaveBeenCalledWith(4, undefined, ['a'], { value: 2, oldValue: 1 }); proxy._a = 3; expect(spy).toHaveBeenCalledWith(3, 2, ['_a'], undefined); expect(spy).toHaveBeenCalledWith(6, 4, ['a'], { value: 3, oldValue: 2 }); (0, _watch.default)(proxy, 'b.foo', opts, (value, oldValue, info) => { localSpy(value, oldValue, info.path); }); proxy.b.fooStore.a.b = 3; expect(spy).toHaveBeenCalledWith(3, 1, ['b', 'fooStore', 'a', 'b'], undefined); expect(spy).toHaveBeenCalledWith(9, undefined, ['b', 'foo'], { value: 3, oldValue: 1 }); expect(localSpy).toHaveBeenCalledWith(9, undefined, ['b', 'foo']); } }); it('watching for the particular getter with dependencies', () => { const obj = { _a: 1, get a() { return this._a * 2; } }; const spy = jest.fn().mockName('global'); const opts = { immediate: true, dependencies: ['_a'], engine }; const { proxy } = (0, _watch.default)(obj, 'a', opts, (value, oldValue, info) => { spy(value, oldValue, info.path, info.parent && Object.select(info.parent, /value/i)); }); proxy._a = 2; expect(spy).toHaveBeenCalledWith(4, undefined, ['a'], { value: 2, oldValue: 1 }); proxy._a = 5; expect(spy).toHaveBeenCalledWith(10, 4, ['a'], { value: 5, oldValue: 2 }); }); it('watching for an array', () => { const arr = [], spy = jest.fn(); const { proxy } = (0, _watch.default)(arr, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); expect(proxy.push(1)).toBe(1); expect(spy).toHaveBeenCalledWith(1, undefined, [0]); expect(arr).toEqual([1]); expect(proxy.push(2, 3)).toBe(3); expect(spy).toHaveBeenCalledWith(2, undefined, [1]); expect(spy).toHaveBeenCalledWith(3, undefined, [2]); expect(arr).toEqual([1, 2, 3]); expect(proxy.pop()).toBe(3); expect(spy).toHaveBeenCalledWith(2, 3, ['length']); expect(arr).toEqual([1, 2]); expect(proxy.unshift(7)).toBe(3); expect(spy).toHaveBeenCalledWith(2, undefined, [2]); expect(spy).toHaveBeenCalledWith(1, 2, [1]); expect(spy).toHaveBeenCalledWith(7, 1, [0]); expect(arr).toEqual([7, 1, 2]); expect(proxy.unshift(5, 9)).toBe(5); expect(spy).toHaveBeenCalledWith(2, undefined, [4]); expect(spy).toHaveBeenCalledWith(1, undefined, [3]); expect(spy).toHaveBeenCalledWith(7, 2, [2]); expect(spy).toHaveBeenCalledWith(5, 7, [0]); expect(spy).toHaveBeenCalledWith(9, 1, [1]); expect(arr).toEqual([5, 9, 7, 1, 2]); expect(proxy.shift()).toBe(5); expect(spy).toHaveBeenCalledWith(9, 5, [0]); expect(arr).toEqual([9, 7, 1, 2]); expect(proxy.splice(1, 2, 98)).toEqual([7, 1]); expect(spy).toHaveBeenCalledWith(2, 1, [2]); expect(spy).toHaveBeenCalledWith(98, 7, [1]); expect(spy).toHaveBeenCalledWith(3, 4, ['length']); expect(arr).toEqual([9, 98, 2]); expect(proxy.splice(0, 0, 1, 2, 3, 4)).toEqual([]); expect(spy).toHaveBeenCalledWith(2, undefined, [6]); expect(spy).toHaveBeenCalledWith(98, undefined, [5]); expect(spy).toHaveBeenCalledWith(9, undefined, [4]); expect(spy).toHaveBeenCalledWith(1, 9, [0]); expect(spy).toHaveBeenCalledWith(2, 98, [1]); expect(spy).toHaveBeenCalledWith(3, 2, [2]); expect(spy).toHaveBeenCalledWith(4, undefined, [3]); expect(arr).toEqual([1, 2, 3, 4, 9, 98, 2]); }); it('array concatenation with proxy', () => { const arrOne = [1, 2, 3], arrTwo = ['foo', 'bar']; const { proxy: proxyOne } = (0, _watch.default)(arrOne, { immediate: true, engine }), { proxy: proxyTwo } = (0, _watch.default)(arrTwo, { immediate: true, engine }); const result = proxyOne.concat(proxyTwo); expect(result).toEqual([1, 2, 3, 'foo', 'bar']); }); it("watching for an array' iterator", () => { const arr = [{ a: 1 }], spy = jest.fn(); const { proxy } = (0, _watch.default)(arr, { deep: true, immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); [...proxy][0].a++; expect(spy).toHaveBeenCalledWith(2, 1, [0, 'a']); expect(arr).toEqual([{ a: 2 }]); [...proxy.values()][0].a++; expect(spy).toHaveBeenCalledWith(3, 2, [0, 'a']); expect(arr).toEqual([{ a: 3 }]); [...proxy.entries()][0][1].a++; expect(spy).toHaveBeenCalledWith(4, 3, [0, 'a']); expect(arr).toEqual([{ a: 4 }]); expect([...proxy.keys()].length).toBe(1); }); it('watching for a set', () => { const set = new Set([]), spy = jest.fn(); const { proxy } = (0, _watch.default)(set, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); expect(proxy.add(1)).toBe(proxy); expect(spy).toHaveBeenCalledWith(1, undefined, [1]); expect(set.has(1)).toBe(true); expect(proxy.add(2)).toBe(proxy); expect(spy).toHaveBeenCalledWith(2, undefined, [2]); expect(set.has(2)).toBe(true); expect(proxy.delete(2)).toBe(true); expect(spy).toHaveBeenCalledWith(undefined, 2, [2]); expect(set.has(2)).toBe(false); expect(proxy.clear()).toBeUndefined(); expect(spy).toHaveBeenCalledWith(undefined, undefined, []); expect(set.has(1)).toBe(false); }); it("watching for a set' iterator", () => { const key = { a: 1 }, set = new Set([key]), spy = jest.fn(); const { proxy } = (0, _watch.default)(set, { deep: true, immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); [...proxy][0].a++; expect(spy).toHaveBeenCalledWith(2, 1, [key, 'a']); expect(set).toEqual(new Set([{ a: 2 }])); [...proxy.values()][0].a++; expect(spy).toHaveBeenCalledWith(3, 2, [key, 'a']); expect(set).toEqual(new Set([{ a: 3 }])); [...proxy.entries()][0][1].a++; expect(spy).toHaveBeenCalledWith(4, 3, [key, 'a']); expect(set).toEqual(new Set([{ a: 4 }])); expect([...proxy.keys()].length).toBe(1); }); it('watching for a weak set', () => { const set = new WeakSet([]), spy = jest.fn(); const { proxy } = (0, _watch.default)(set, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); const key1 = []; expect(proxy.add(key1)).toBe(proxy); expect(spy).toHaveBeenCalledWith(key1, undefined, [key1]); expect(set.has(key1)).toBe(true); const key2 = {}; expect(proxy.add(key2)).toBe(proxy); expect(spy).toHaveBeenCalledWith(key2, undefined, [key2]); expect(set.has(key2)).toBe(true); expect(proxy.delete(key2)).toBe(true); expect(spy).toHaveBeenCalledWith(undefined, key2, [key2]); expect(set.has(key2)).toBe(false); }); it('watching for a map', () => { const map = new Map([[0, 1], [1, 2]]), spy = jest.fn(); const { proxy } = (0, _watch.default)(map, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); expect(proxy.set(0, 2)).toBe(proxy); expect(spy).toHaveBeenCalledWith(2, 1, [0]); expect(map.get(0)).toBe(2); expect(proxy.set(5, 2)).toBe(proxy); expect(spy).toHaveBeenCalledWith(2, undefined, [5]); expect(map.get(5)).toBe(2); expect(proxy.delete(5)).toBe(true); expect(spy).toHaveBeenCalledWith(undefined, 2, [5]); expect(map.has(5)).toBe(false); expect(proxy.clear()).toBeUndefined(); expect(spy).toHaveBeenCalledWith(undefined, undefined, []); expect(map.has(0)).toBe(false); expect(map.has(1)).toBe(false); }); it("watching for a map' iterator", () => { const key = { b: 1 }, map = new Map([[key, { a: 1 }]]), spy = jest.fn(); const { proxy } = (0, _watch.default)(map, { deep: true, immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); expect([...map]).toEqual([...new Map([[key, { a: 1 }]])]); [...proxy][0][1].a++; expect(spy).toHaveBeenCalledWith(2, 1, [key, 'a']); expect(map).toEqual(new Map([[key, { a: 2 }]])); [...proxy.values()][0].a++; expect(spy).toHaveBeenCalledWith(3, 2, [key, 'a']); expect(map).toEqual(new Map([[key, { a: 3 }]])); [...proxy.entries()][0][1].a++; expect(spy).toHaveBeenCalledWith(4, 3, [key, 'a']); expect(map).toEqual(new Map([[key, { a: 4 }]])); expect([...proxy.keys()].length).toBe(1); }); it('watching for a weak map', () => { const map = new WeakMap([]), spy = jest.fn(); const { proxy } = (0, _watch.default)(map, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); const key1 = []; expect(proxy.set(key1, 2)).toBe(proxy); expect(spy).toHaveBeenCalledWith(2, undefined, [key1]); expect(map.get(key1)).toBe(2); const key2 = {}; expect(proxy.set(key2, 23)).toBe(proxy); expect(spy).toHaveBeenCalledWith(23, undefined, [key2]); expect(map.get(key2)).toBe(23); expect(proxy.delete(key2)).toBe(true); expect(spy).toHaveBeenCalledWith(undefined, 23, [key2]); expect(map.has(key2)).toBe(false); }); it('tying a watcher to another object', () => { const another = {}, obj = { a: 1, b: 2 }, spy = jest.fn(); (0, _watch.default)(obj, { immediate: true, tiedWith: another, engine }, (value, oldValue) => { spy(value, oldValue); }); another.a = 2; expect(spy).toHaveBeenCalledWith(2, 1); another.b = 4; expect(spy).toHaveBeenCalledWith(4, 2); }); it('filtering of mutations', () => { const obj = { a: 1, b: 2 }, spy = jest.fn(); const eventFilter = (value, oldValue, info) => info.path.join('.') === 'a'; const { proxy } = (0, _watch.default)(obj, { immediate: true, eventFilter, engine }, (value, oldValue) => { spy(value, oldValue); }); proxy.b = 4; expect(spy).not.toHaveBeenCalled(); proxy.a = 2; expect(spy).toHaveBeenCalledWith(2, 1); }); it('modifying of mutations', () => { const obj = { a: 1, b: 2 }, spy = jest.fn(); const pathModifier = path => path.join('.') === 'a' ? ['b'] : path; const { proxy } = (0, _watch.default)(obj, { immediate: true, pathModifier, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); proxy.a = 2; expect(spy).toHaveBeenCalledWith(2, 1, ['b']); proxy.b = 4; expect(spy).toHaveBeenCalledWith(4, 2, ['b']); }); it('muting of mutations', () => { const obj = { a: 1, b: 2 }, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); (0, _watch.mute)(proxy); proxy.a = 2; expect(spy).not.toHaveBeenCalled(); (0, _watch.unmute)(proxy); proxy.b = 4; expect(spy).toHaveBeenCalledWith(4, 2, ['b']); }); it('marking a part of the watched object as unwatchable', () => { const obj = { a: 1, b: (0, _watch.unwatchable)({ c: 2, d: { e: 3 } }, engine) }, spy = jest.fn(); const { proxy, set } = (0, _watch.default)(obj, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); proxy.b.c = 3; proxy.b.d.e = 4; set('b.e', 6); expect(spy).not.toHaveBeenCalled(); proxy.a = 2; expect(spy).toHaveBeenCalled(); }); it('canceling of watching', () => { const obj = { a: 1, b: 2 }, spy = jest.fn(); const { proxy, unwatch } = (0, _watch.default)(obj, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); unwatch(); proxy.a = 2; expect(spy).not.toHaveBeenCalled(); }); it('setting of new properties', () => { { const obj = {}, spy = jest.fn(); const { proxy, set } = (0, _watch.default)(obj, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); set('a', 1); expect(spy).toHaveBeenCalledWith(1, undefined, ['a']); proxy.a = 2; expect(spy).toHaveBeenCalledWith(2, 1, ['a']); } { const obj = {}, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); (0, _watch.set)(proxy, 'a', 1, engine); expect(spy).toHaveBeenCalledWith(1, undefined, ['a']); proxy.a = 2; expect(spy).toHaveBeenCalledWith(2, 1, ['a']); } }); it('deep setting of new properties', () => { { const obj = {}, spy = jest.fn(); const { proxy, set } = (0, _watch.default)(obj, { immediate: true, engine, deep: true }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); set('a.b', 1); expect(spy).toHaveBeenCalledWith(1, undefined, ['a', 'b']); expect(proxy.a?.b).toBe(1); expect(obj.a?.b).toBe(1); proxy.a.b = 2; expect(spy).toHaveBeenCalledWith(2, 1, ['a', 'b']); expect(proxy.a?.b).toBe(2); expect(obj.a?.b).toBe(2); } { const obj = {}, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { immediate: true, engine, deep: true }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); (0, _watch.set)(proxy, 'a.b', 1, engine); expect(spy).toHaveBeenCalledWith(1, undefined, ['a', 'b']); expect(proxy.a?.b).toBe(1); expect(obj.a?.b).toBe(1); proxy.a.b = 2; expect(spy).toHaveBeenCalledWith(2, 1, ['a', 'b']); expect(proxy.a?.b).toBe(2); expect(obj.a?.b).toBe(2); } }); it('deleting of properties', () => { { const obj = { a: 1 }, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { immediate: true, engine }, (value, oldValue, info) => { spy(value, info.path); }); delete proxy.a; expect(spy).not.toHaveBeenCalled(); proxy.a = 2; expect(spy).not.toHaveBeenCalled(); (0, _watch.set)(proxy, 'a', 1, engine); expect(spy).toHaveBeenCalledWith(1, ['a']); } { const obj = { a: 1 }, spy = jest.fn(); const watcher = (0, _watch.default)(obj, { immediate: true, deep: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); watcher.delete('a'); expect(spy).toHaveBeenCalledWith(undefined, 1, ['a']); watcher.proxy.a = 2; expect(spy).not.toHaveBeenCalledWith(2, undefined, ['a']); watcher.set('a', 3); expect(spy).toHaveBeenCalledWith(3, 2, ['a']); watcher.set('b.c', 3); expect(spy).toHaveBeenCalledWith(3, undefined, ['b', 'c']); } { const obj = { a: 1 }, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); (0, _watch.unset)(proxy, 'a', engine); expect(spy).toHaveBeenCalledWith(undefined, 1, ['a']); proxy.a = 2; expect(spy).not.toHaveBeenCalledWith(2, undefined, ['a']); (0, _watch.set)(proxy, 'a', 3, engine); expect(spy).toHaveBeenCalledWith(3, 2, ['a']); } }); it('modifying prototype values', () => { const obj = { a: { a: 1 }, __proto__: { b: { b: 1 } } }, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { deep: true, immediate: true, withProto: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); proxy.b.b++; expect(spy).toHaveBeenCalledWith(2, 1, ['b', 'b']); proxy.a.a++; expect(spy).toHaveBeenCalledWith(2, 1, ['a', 'a']); expect(proxy.b.b).toBe(2); expect(proxy.a.a).toBe(2); }); it('isProxy', () => { expect((0, _watch.isProxy)((0, _watch.default)({}, { immediate: true, engine }).proxy)).toBe(true); expect((0, _watch.isProxy)(null)).toBe(false); expect((0, _watch.isProxy)({})).toBe(false); }); if (engineName === 'proxy') { it("shouldn't wrap readonly non-configurable properties", () => { const obj = {}, nested = { a: 1 }; Object.defineProperty(obj, 'foo', { value: nested }); const { proxy } = (0, _watch.default)(obj, { deep: true, engine }); expect(proxy.foo).toBe(nested); }); it('should watch properties added via `Object.defineProperty`', () => { const obj = {}, spy = jest.fn(); const { proxy } = (0, _watch.default)(obj, { immediate: true, engine }, (value, oldValue, info) => { spy(value, oldValue, info.path); }); Object.defineProperty(proxy, 'bla', { enumerable: true, value: 10 }); expect(spy).toHaveBeenCalledWith(10, undefined, ['bla']); expect(Object.getOwnPropertyDescriptor(proxy, 'bla')).toEqual({ configurable: false, writable: false, enumerable: true, value: 10 }); }); } }); }); });