UNPKG

@v4fire/client

Version:

V4Fire client core library

1,218 lines (988 loc) • 23 kB
/* eslint-disable max-lines,max-lines-per-function,require-atomic-updates */ // @ts-check /*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ /** * @typedef {import('playwright').Page} Page */ const h = include('tests/helpers').default; /** @param {Page} page */ module.exports = (page) => { beforeEach(async () => { await page.evaluate(() => { globalThis.removeCreatedComponents(); }); }); describe('i-block watching for mounted objects passed by paths', () => { describe('without caching of old values', () => { it('non-deep watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedArrayWatcher', (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1].path, args[1].originalPath ]); }); ctx.mountedArrayWatcher.push(1); ctx.mountedArrayWatcher.push(2); await ctx.nextTick(); ctx.mountedArrayWatcher.push(3); await ctx.nextTick(); ctx.mountedArrayWatcher.pop(); ctx.mountedArrayWatcher.shift(); await ctx.nextTick(); return res; }); expect(scan).toEqual([ [ [1, 2], [1, 2], ['mountedArrayWatcher'], ['mountedArrayWatcher'] ], [ [1, 2, 3], [1, 2, 3], ['mountedArrayWatcher'], ['mountedArrayWatcher'] ], [ [2], [2], ['mountedArrayWatcher'], ['mountedArrayWatcher'] ] ]); }); it('non-deep watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedArrayWatcher', {collapse: false}, (mutations) => { mutations.forEach(([val, oldVal, i]) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), i.path, i.originalPath ]); }); }); ctx.mountedArrayWatcher.push(1); ctx.mountedArrayWatcher.push(2); await ctx.nextTick(); ctx.mountedArrayWatcher.push(3); await ctx.nextTick(); ctx.mountedArrayWatcher.pop(); ctx.mountedArrayWatcher.shift(); await ctx.nextTick(); return res; }); expect(scan).toEqual([ [ 1, undefined, ['mountedArrayWatcher', 0], ['mountedArrayWatcher', 0] ], [ 2, undefined, ['mountedArrayWatcher', 1], ['mountedArrayWatcher', 1] ], [ 3, undefined, ['mountedArrayWatcher', 2], ['mountedArrayWatcher', 2] ], [ 2, 3, ['mountedArrayWatcher', 'length'], ['mountedArrayWatcher', 'length'] ], [ 2, 1, ['mountedArrayWatcher', 0], ['mountedArrayWatcher', 0] ], [ 1, 2, ['mountedArrayWatcher', 'length'], ['mountedArrayWatcher', 'length'] ] ]); }); it('non-deep immediate watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedArrayWatcher', {immediate: true}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx.mountedArrayWatcher.push(1); ctx.mountedArrayWatcher.push(2); await ctx.nextTick(); ctx.mountedArrayWatcher.push(3); await ctx.nextTick(); ctx.mountedArrayWatcher.pop(); ctx.mountedArrayWatcher.shift(); await ctx.nextTick(); return res; }); expect(scan).toEqual([ [[], [], undefined, undefined], [ [1], [1], ['mountedArrayWatcher'], ['mountedArrayWatcher'] ], [ [1, 2], [1, 2], ['mountedArrayWatcher'], ['mountedArrayWatcher'] ], [ [1, 2, 3], [1, 2, 3], ['mountedArrayWatcher'], ['mountedArrayWatcher'] ], [ [1, 2], [1, 2], ['mountedArrayWatcher'], ['mountedArrayWatcher'] ], [ [2, 2], [2, 2], ['mountedArrayWatcher'], ['mountedArrayWatcher'] ], [ [2], [2], ['mountedArrayWatcher'], ['mountedArrayWatcher'] ] ]); }); it('non-deep immediate watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedArrayWatcher', {immediate: true, collapse: false}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx.mountedArrayWatcher.push(1); ctx.mountedArrayWatcher.push(2); await ctx.nextTick(); ctx.mountedArrayWatcher.push(3); await ctx.nextTick(); ctx.mountedArrayWatcher.pop(); ctx.mountedArrayWatcher.shift(); await ctx.nextTick(); return res; }); expect(scan).toEqual([ [[], undefined, undefined, undefined], [ 1, undefined, ['mountedArrayWatcher', 0], ['mountedArrayWatcher', 0] ], [ 2, undefined, ['mountedArrayWatcher', 1], ['mountedArrayWatcher', 1] ], [ 3, undefined, ['mountedArrayWatcher', 2], ['mountedArrayWatcher', 2] ], [ 2, 3, ['mountedArrayWatcher', 'length'], ['mountedArrayWatcher', 'length'] ], [ 2, 1, ['mountedArrayWatcher', 0], ['mountedArrayWatcher', 0] ], [ 1, 2, ['mountedArrayWatcher', 'length'], ['mountedArrayWatcher', 'length'] ] ]); }); it('watching for the specified path', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedWatcher.a.c', (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1].path, args[1].originalPath ]); }); ctx.mountedWatcher.a = {b: 1}; ctx.mountedWatcher.a = {c: 2}; await ctx.nextTick(); ctx.mountedWatcher.a = {b: 3}; await ctx.nextTick(); ctx.mountedWatcher.a = {c: 3}; await ctx.nextTick(); ctx.mountedWatcher.a.c++; ctx.mountedWatcher.a.c++; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [ {a: {c: 2}}, {a: {c: 2}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {b: 3}}, {a: {b: 3}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {c: 3}}, {a: {c: 3}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {c: 5}}, {a: {c: 5}}, ['mountedWatcher'], ['mountedWatcher'] ] ]); }); it('watching for a computed field', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedComputed', (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1].path, args[1].originalPath ]); }); ctx.mountedWatcher.a = {b: 1}; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [ {a: {b: 1}}, undefined, ['mountedComputed'], ['mountedWatcher', 'a'] ] ]); }); it('immediate watching for the specified path', async () => { const target = await init(); const scan = await target.evaluate((ctx) => { const res = []; ctx.watch('mountedWatcher.a.c', {immediate: true}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx.mountedWatcher.a = {b: 1}; ctx.mountedWatcher.a = {c: 2}; ctx.mountedWatcher.a = {b: 3}; ctx.mountedWatcher.a = {c: 3}; ctx.mountedWatcher.a.c++; ctx.mountedWatcher.a.c++; return res; }); expect(scan).toEqual([ [{}, {}, undefined, undefined], [ {a: {c: 2}}, {a: {c: 2}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {b: 3}}, {a: {b: 3}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {c: 3}}, {a: {c: 3}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {c: 4}}, {a: {c: 4}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {c: 5}}, {a: {c: 5}}, ['mountedWatcher'], ['mountedWatcher'] ] ]); }); it('deep watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedWatcher', {deep: true}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1].path, args[1].originalPath ]); }); ctx.mountedWatcher.a = {b: {c: 1}}; ctx.mountedWatcher.a = {b: {c: 2}}; await ctx.nextTick(); ctx.mountedWatcher.a.b.c++; await ctx.nextTick(); ctx.mountedWatcher.a.b = {d: 1}; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [ {a: {b: {c: 2}}}, {a: {b: {c: 2}}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {b: {c: 3}}}, {a: {b: {c: 3}}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {b: {d: 1}}}, {a: {b: {d: 1}}}, ['mountedWatcher'], ['mountedWatcher'] ] ]); }); it('deep watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedWatcher', {deep: true, collapse: false}, (mutations) => { mutations.forEach(([val, oldVal, i]) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), i.path, i.originalPath ]); }); }); ctx.mountedWatcher.a = {b: {c: 1}}; ctx.mountedWatcher.a = {b: {c: 2}}; await ctx.nextTick(); ctx.mountedWatcher.a.b.c++; await ctx.nextTick(); ctx.mountedWatcher.a.b = {d: 1}; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [ {b: {c: 1}}, undefined, ['mountedWatcher', 'a'], ['mountedWatcher', 'a'] ], [ {b: {c: 2}}, {b: {c: 1}}, ['mountedWatcher', 'a'], ['mountedWatcher', 'a'] ], [ 3, 2, ['mountedWatcher', 'a', 'b', 'c'], ['mountedWatcher', 'a', 'b', 'c'] ], [ {d: 1}, {c: 3}, ['mountedWatcher', 'a', 'b'], ['mountedWatcher', 'a', 'b'] ] ]); }); it('deep immediate watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedWatcher', {deep: true, immediate: true}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx.mountedWatcher.a = {b: {c: 1}}; ctx.mountedWatcher.a = {b: {c: 2}}; await ctx.nextTick(); ctx.mountedWatcher.a.b.c++; await ctx.nextTick(); ctx.mountedWatcher.a.b = {d: 1}; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [{}, {}, undefined, undefined], [ {a: {b: {c: 1}}}, {a: {b: {c: 1}}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {b: {c: 2}}}, {a: {b: {c: 2}}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {b: {c: 3}}}, {a: {b: {c: 3}}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {b: {d: 1}}}, {a: {b: {d: 1}}}, ['mountedWatcher'], ['mountedWatcher'] ] ]); }); it('deep immediate watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedWatcher', {deep: true, immediate: true, collapse: false}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx.mountedWatcher.a = {b: {c: 1}}; ctx.mountedWatcher.a = {b: {c: 2}}; await ctx.nextTick(); ctx.mountedWatcher.a.b.c++; await ctx.nextTick(); ctx.mountedWatcher.a.b = {d: 1}; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [{}, undefined, undefined, undefined], [ {b: {c: 1}}, undefined, ['mountedWatcher', 'a'], ['mountedWatcher', 'a'] ], [ {b: {c: 2}}, {b: {c: 1}}, ['mountedWatcher', 'a'], ['mountedWatcher', 'a'] ], [ 3, 2, ['mountedWatcher', 'a', 'b', 'c'], ['mountedWatcher', 'a', 'b', 'c'] ], [ {d: 1}, {c: 3}, ['mountedWatcher', 'a', 'b'], ['mountedWatcher', 'a', 'b'] ] ]); }); }); describe('with caching of old values', () => { it('non-deep watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedArrayWatcher', (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i.originalPath ]); }); ctx.mountedArrayWatcher.push(1); ctx.mountedArrayWatcher.push(2); await ctx.nextTick(); ctx.mountedArrayWatcher.push(3); await ctx.nextTick(); ctx.mountedArrayWatcher.pop(); ctx.mountedArrayWatcher.shift(); await ctx.nextTick(); return res; }); expect(scan).toEqual([ [[1, 2], [], false, ['mountedArrayWatcher']], [[1, 2, 3], [1, 2], false, ['mountedArrayWatcher']], [[2], [1, 2, 3], false, ['mountedArrayWatcher']] ]); }); it('non-deep immediate watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedArrayWatcher', {immediate: true}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i?.originalPath ]); }); ctx.mountedArrayWatcher.push(1); ctx.mountedArrayWatcher.push(2); await ctx.nextTick(); ctx.mountedArrayWatcher.push(3); await ctx.nextTick(); ctx.mountedArrayWatcher.pop(); ctx.mountedArrayWatcher.shift(); await ctx.nextTick(); return res; }); expect(scan).toEqual([ [[], undefined, false, undefined], [ [1], [], false, ['mountedArrayWatcher'] ], [ [1, 2], [1], false, ['mountedArrayWatcher'] ], [ [1, 2, 3], [1, 2], false, ['mountedArrayWatcher'] ], [ [1, 2], [1, 2, 3], false, ['mountedArrayWatcher'] ], [ [2, 2], [1, 2], false, ['mountedArrayWatcher'] ], [ [2], [2, 2], false, ['mountedArrayWatcher'] ] ]); }); it('non-deep immediate watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedArrayWatcher', {immediate: true, collapse: false}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i?.originalPath ]); }); ctx.mountedArrayWatcher.push(1); ctx.mountedArrayWatcher.push(2); await ctx.nextTick(); ctx.mountedArrayWatcher.push(3); await ctx.nextTick(); ctx.mountedArrayWatcher.pop(); ctx.mountedArrayWatcher.shift(); await ctx.nextTick(); return res; }); expect(scan).toEqual([ [[], undefined, false, undefined], [ 1, undefined, false, ['mountedArrayWatcher', 0] ], [ 2, undefined, false, ['mountedArrayWatcher', 1] ], [ 3, undefined, false, ['mountedArrayWatcher', 2] ], [ 2, 3, false, ['mountedArrayWatcher', 'length'] ], [ 2, 1, false, ['mountedArrayWatcher', 0] ], [ 1, 2, false, ['mountedArrayWatcher', 'length'] ] ]); }); it('watching for the specified path', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedWatcher.a.c', (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i.originalPath ]); }); ctx.mountedWatcher.a = {b: 1}; ctx.mountedWatcher.a = {c: 2}; await ctx.nextTick(); ctx.mountedWatcher.a = {b: 3}; await ctx.nextTick(); ctx.mountedWatcher.a = {c: 3}; await ctx.nextTick(); ctx.mountedWatcher.a.c++; ctx.mountedWatcher.a.c++; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [{a: {c: 2}}, {}, false, ['mountedWatcher']], [{a: {b: 3}}, {a: {c: 2}}, false, ['mountedWatcher']], [{a: {c: 3}}, {a: {b: 3}}, false, ['mountedWatcher']], [{a: {c: 5}}, {a: {c: 3}}, false, ['mountedWatcher']] ]); }); it('immediate watching for the specified path', async () => { const target = await init(); const scan = await target.evaluate((ctx) => { const res = []; ctx.watch('mountedWatcher.a.c', {immediate: true}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), i?.path, i?.originalPath ]); }); ctx.mountedWatcher.a = {b: 1}; ctx.mountedWatcher.a = {c: 2}; ctx.mountedWatcher.a = {b: 3}; ctx.mountedWatcher.a = {c: 3}; ctx.mountedWatcher.a.c++; ctx.mountedWatcher.a.c++; return res; }); expect(scan).toEqual([ [{}, undefined, undefined, undefined], [ {a: {c: 2}}, {}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {b: 3}}, {a: {c: 2}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {c: 3}}, {a: {b: 3}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {c: 4}}, {a: {c: 3}}, ['mountedWatcher'], ['mountedWatcher'] ], [ {a: {c: 5}}, {a: {c: 4}}, ['mountedWatcher'], ['mountedWatcher'] ] ]); }); it('deep watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedWatcher', {deep: true}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i.originalPath ]); }); ctx.mountedWatcher.a = {b: {c: 1}}; ctx.mountedWatcher.a = {b: {c: 2}}; await ctx.nextTick(); ctx.mountedWatcher.a.b.c++; await ctx.nextTick(); ctx.mountedWatcher.a.b = {d: 1}; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [ {a: {b: {c: 2}}}, {}, false, ['mountedWatcher'] ], [ {a: {b: {c: 3}}}, {a: {b: {c: 2}}}, false, ['mountedWatcher'] ], [ {a: {b: {d: 1}}}, {a: {b: {c: 3}}}, false, ['mountedWatcher'] ] ]); }); it('deep immediate watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedWatcher', {deep: true, immediate: true}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i?.originalPath ]); }); ctx.mountedWatcher.a = {b: {c: 1}}; ctx.mountedWatcher.a = {b: {c: 2}}; await ctx.nextTick(); ctx.mountedWatcher.a.b.c++; await ctx.nextTick(); ctx.mountedWatcher.a.b = {d: 1}; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [{}, undefined, false, undefined], [ {a: {b: {c: 1}}}, {}, false, ['mountedWatcher'] ], [ {a: {b: {c: 2}}}, {a: {b: {c: 1}}}, false, ['mountedWatcher'] ], [ {a: {b: {c: 3}}}, {a: {b: {c: 2}}}, false, ['mountedWatcher'] ], [ {a: {b: {d: 1}}}, {a: {b: {c: 3}}}, false, ['mountedWatcher'] ] ]); }); it('deep immediate watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx) => { const res = []; ctx.watch('mountedWatcher', {deep: true, immediate: true, collapse: false}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i?.originalPath ]); }); ctx.mountedWatcher.a = {b: {c: 1}}; ctx.mountedWatcher.a = {b: {c: 2}}; await ctx.nextTick(); ctx.mountedWatcher.a.b.c++; await ctx.nextTick(); ctx.mountedWatcher.a.b = {d: 1}; await ctx.nextTick(); return res; }); expect(scan).toEqual([ [{}, undefined, false, undefined], [ {b: {c: 1}}, undefined, false, ['mountedWatcher', 'a'] ], [ {b: {c: 2}}, {b: {c: 1}}, false, ['mountedWatcher', 'a'] ], [ 3, 2, false, ['mountedWatcher', 'a', 'b', 'c'] ], [ {d: 1}, {c: 3}, false, ['mountedWatcher', 'a', 'b'] ] ]); }); }); }); async function init(attrs = {}) { await page.evaluate((attrs = {}) => { const scheme = [ { attrs: { id: 'target', ...attrs } } ]; globalThis.renderComponents('b-dummy-watch', scheme); }, attrs); return h.component.waitForComponent(page, '#target'); } };