UNPKG

@v4fire/client

Version:

V4Fire client core library

1,074 lines (872 loc) • 21.5 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', () => { ['regular', 'system'].forEach((type) => { describe(`${type} fields`, () => { const field = type === 'regular' ? 'complexObjStore' : 'systemComplexObjStore'; describe('without caching of old values', () => { it('non-deep watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(field, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1].path, args[1].originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; await ctx.nextTick(); ctx[field] = {a: {c: 3}}; await ctx.nextTick(); ctx[field].a.c++; ctx[field].a.c++; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [{a: {c: 2}}, {a: {b: 1}}, [field], [field]], [{a: {c: 3}}, {a: {c: 2}}, [field], [field]] ]); }); it('non-deep watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(field, {collapse: false}, (mutations) => { mutations.forEach(([val, oldVal, i]) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), i.path, i.originalPath ]); }); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; await ctx.nextTick(); ctx[field] = {a: {c: 3}}; await ctx.nextTick(); ctx[field].a.c++; ctx[field].a.c++; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [ {a: {b: 1}}, {a: {b: {c: 1, d: 2}}}, [field], [field] ], [ {a: {c: 2}}, {a: {b: 1}}, [field], [field] ], [ {a: {c: 3}}, {a: {c: 2}}, [field], [field] ] ]); }); it('non-deep immediate watching', async () => { const target = await init(); const scan = await target.evaluate((ctx, field) => { const res = []; ctx.watch(field, {immediate: true}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; ctx[field] = {a: {c: 3}}; ctx[field].a.c++; ctx[field].a.c++; return res; }, field); expect(scan).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, undefined, undefined ], [ {a: {b: 1}}, {a: {b: {c: 1, d: 2}}}, [field], [field] ], [ {a: {c: 2}}, {a: {b: 1}}, [field], [field] ], [ {a: {c: 3}}, {a: {c: 2}}, [field], [field] ] ]); }); it('watching for the specified path', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(`${field}.a.c`, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1].path, args[1].originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; await ctx.nextTick(); ctx[field] = {a: {b: 3}}; await ctx.nextTick(); ctx[field] = {a: {c: 3}}; await ctx.nextTick(); ctx[field].a.c++; ctx[field].a.c++; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [ {a: {c: 2}}, {a: {b: 1}}, [field, 'a', 'c'], [field] ], [ {a: {b: 3}}, {a: {c: 2}}, [field, 'a', 'c'], [field] ], [ {a: {c: 3}}, {a: {b: 3}}, [field, 'a', 'c'], [field] ], [ {a: {c: 5}}, {a: {c: 5}}, [field, 'a', 'c'], [field, 'a', 'c'] ] ]); }); it('immediate watching for the specified path', async () => { const target = await init(); const scan = await target.evaluate((ctx, field) => { const res = []; ctx.watch(`${field}.a.c`, {immediate: true}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; ctx[field] = {a: {b: 3}}; ctx[field] = {a: {c: 3}}; ctx[field].a.c++; ctx[field].a.c++; return res; }, field); expect(scan).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, undefined, undefined ], [ {a: {c: 2}}, {a: {b: 1}}, [field, 'a', 'c'], [field] ], [ {a: {b: 3}}, {a: {c: 2}}, [field, 'a', 'c'], [field] ], [ {a: {c: 3}}, {a: {b: 3}}, [field, 'a', 'c'], [field] ], [ {a: {c: 4}}, {a: {c: 4}}, [field, 'a', 'c'], [field, 'a', 'c'] ], [ {a: {c: 5}}, {a: {c: 5}}, [field, 'a', 'c'], [field, 'a', 'c'] ] ]); }); it('deep watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(field, {deep: true}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1].path, args[1].originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; await ctx.nextTick(); ctx[field] = {a: {c: 3}}; await ctx.nextTick(); ctx[field].a.c++; ctx[field].a.c++; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [{a: {c: 2}}, {a: {b: 1}}, [field], [field]], [{a: {c: 3}}, {a: {c: 2}}, [field], [field]], [{a: {c: 5}}, {a: {c: 5}}, [field], [field, 'a', 'c']] ]); }); it('deep watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(field, {deep: true, collapse: false}, (mutations) => { mutations.forEach(([val, oldVal, i]) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), i.path, i.originalPath ]); }); }); ctx[field] = {a: {b: 1}}; await ctx.nextTick(); ctx[field].a.b++; ctx[field].a.b++; await ctx.nextTick(); ctx[field].a = {d: 1}; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [ {a: {b: 1}}, {a: {b: {c: 1, d: 2}}}, [field], [field] ], [2, 1, [field], [field, 'a', 'b']], [3, 2, [field], [field, 'a', 'b']], [ {d: 1}, {b: 3}, [field], [field, 'a'] ] ]); }); it('deep immediate watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate((ctx, field) => { const res = []; ctx.watch(field, {deep: true, immediate: true, collapse: false}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field].a.b++; ctx[field].a.b++; ctx[field].a = {d: 1}; return res; }, field); expect(scan).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, undefined, undefined ], [ {a: {b: 1}}, {a: {b: {c: 1, d: 2}}}, [field], [field] ], [2, 1, [field], [field, 'a', 'b']], [3, 2, [field], [field, 'a', 'b']], [ {d: 1}, {b: 3}, [field], [field, 'a'] ] ]); }); it('removing watchers', async () => { const target = await init(); const scan = await target.evaluate((ctx, field) => { const res = []; ctx.watch(field, {deep: true, immediate: true, collapse: false, group: 'foo'}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx[field].a.b.c++; ctx.async.terminateWorker({group: 'foo'}); ctx[field].a.b.c++; return res; }, field); expect(scan).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, undefined, undefined ], [2, 1, [field], [field, 'a', 'b', 'c']] ]); }); it('suspending watchers', async () => { const target = await init(); const scan = await target.evaluate((ctx, field) => { const res = []; globalThis._res = res; ctx.watch(field, {deep: true, immediate: true, collapse: false, group: 'foo'}, (val, ...args) => { res.push([ Object.fastClone(val), Object.fastClone(args[0]), args[1]?.path, args[1]?.originalPath ]); }); ctx[field].a.b.c++; ctx.async.suspendEventListener({group: /foo/}); ctx[field].a.b.c++; return res; }, field); expect(scan).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, undefined, undefined ], [2, 1, [field], [field, 'a', 'b', 'c']] ]); const scan2 = await target.evaluate((ctx) => { const res = globalThis._res; ctx.async.unsuspendEventListener({group: /foo/}); return res; }); expect(scan2).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, undefined, undefined ], [2, 1, [field], [field, 'a', 'b', 'c']], [3, 2, [field], [field, 'a', 'b', 'c']] ]); }); }); describe('with caching of old values', () => { it('non-deep watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(field, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i.originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; await ctx.nextTick(); ctx[field] = {a: {c: 3}}; await ctx.nextTick(); ctx[field].a.c++; ctx[field].a.c++; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [{a: {c: 2}}, {a: {b: {c: 1, d: 2}}}, false, [field]], [{a: {c: 3}}, {a: {c: 2}}, false, [field]] ]); }); it('non-deep watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(field, {collapse: false}, (mutations) => { mutations.forEach(([val, oldVal, i]) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i.originalPath ]); }); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; await ctx.nextTick(); ctx[field] = {a: {c: 3}}; await ctx.nextTick(); ctx[field].a.c++; ctx[field].a.c++; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [ {a: {b: 1}}, {a: {b: {c: 1, d: 2}}}, false, [field] ], [ {a: {c: 2}}, {a: {b: 1}}, false, [field] ], [ {a: {c: 3}}, {a: {c: 2}}, false, [field] ] ]); }); it('non-deep immediate watching', async () => { const target = await init(); const scan = await target.evaluate((ctx, field) => { const res = []; ctx.watch(field, {immediate: true}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i?.originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; ctx[field] = {a: {c: 3}}; ctx[field].a.c++; ctx[field].a.c++; return res; }, field); expect(scan).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, false, undefined ], [ {a: {b: 1}}, {a: {b: {c: 1, d: 2}}}, false, [field] ], [ {a: {c: 2}}, {a: {b: 1}}, false, [field] ], [ {a: {c: 3}}, {a: {c: 2}}, false, [field] ] ]); }); it('watching for the specified path', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(`${field}.a.c`, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i.originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; await ctx.nextTick(); ctx[field] = {a: {b: 3}}; await ctx.nextTick(); ctx[field] = {a: {c: 3}}; await ctx.nextTick(); ctx[field].a.c++; ctx[field].a.c++; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [ {a: {c: 2}}, {a: {b: {c: 1, d: 2}}}, false, [field] ], [ {a: {b: 3}}, {a: {c: 2}}, false, [field] ], [ {a: {c: 3}}, {a: {b: 3}}, false, [field] ], [ {a: {c: 5}}, {a: {c: 3}}, false, [field, 'a', 'c'] ] ]); }); it('immediate watching for the specified path', async () => { const target = await init(); const scan = await target.evaluate((ctx, field) => { const res = []; ctx.watch(`${field}.a.c`, {immediate: true}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i?.originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; ctx[field] = {a: {b: 3}}; ctx[field] = {a: {c: 3}}; ctx[field].a.c++; ctx[field].a.c++; return res; }, field); expect(scan).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, false, undefined ], [ {a: {c: 2}}, {a: {b: {c: 1, d: 2}}}, false, [field] ], [ {a: {b: 3}}, {a: {c: 2}}, false, [field] ], [ {a: {c: 3}}, {a: {b: 3}}, false, [field] ], [ {a: {c: 4}}, {a: {c: 3}}, false, [field, 'a', 'c'] ], [ {a: {c: 5}}, {a: {c: 4}}, false, [field, 'a', 'c'] ] ]); }); it('deep watching', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(field, {deep: true}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i.originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field] = {a: {c: 2}}; await ctx.nextTick(); ctx[field] = {a: {c: 3}}; await ctx.nextTick(); ctx[field].a.c++; ctx[field].a.c++; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [ {a: {c: 2}}, {a: {b: {c: 1, d: 2}}}, false, [field] ], [ {a: {c: 3}}, {a: {c: 2}}, false, [field] ], [ {a: {c: 5}}, {a: {c: 3}}, false, [field, 'a', 'c'] ] ]); }); it('deep watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate(async (ctx, field) => { const res = []; ctx.watch(field, {deep: true, collapse: false}, (mutations) => { mutations.forEach(([val, oldVal, i]) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i.originalPath ]); }); }); ctx[field] = {a: {b: 1}}; await ctx.nextTick(); ctx[field].a.b++; ctx[field].a.b++; await ctx.nextTick(); ctx[field].a = {d: 1}; await ctx.nextTick(); return res; }, field); expect(scan).toEqual([ [ {a: {b: 1}}, {a: {b: {c: 1, d: 2}}}, false, [field] ], [2, 1, false, [field, 'a', 'b']], [3, 2, false, [field, 'a', 'b']], [ {d: 1}, {b: 3}, false, [field, 'a'] ] ]); }); it('deep immediate watching without collapsing', async () => { const target = await init(); const scan = await target.evaluate((ctx, field) => { const res = []; ctx.watch(field, {deep: true, immediate: true, collapse: false}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i?.originalPath ]); }); ctx[field] = {a: {b: 1}}; ctx[field].a.b++; ctx[field].a.b++; ctx[field].a = {d: 1}; return res; }, field); expect(scan).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, false, undefined ], [ {a: {b: 1}}, {a: {b: {c: 1, d: 2}}}, false, [field] ], [2, 1, false, [field, 'a', 'b']], [3, 2, false, [field, 'a', 'b']], [ {d: 1}, {b: 3}, false, [field, 'a'] ] ]); }); it('suspending watchers', async () => { const target = await init(); const scan = await target.evaluate((ctx, field) => { const res = []; globalThis._res = res; ctx.watch(field, {deep: true, immediate: true, collapse: false, group: 'foo'}, (val, oldVal, i) => { res.push([ Object.fastClone(val), Object.fastClone(oldVal), val === oldVal, i?.originalPath ]); }); ctx[field].a.b.c++; ctx.async.suspendEventListener({group: /foo/}); ctx[field].a.b.c++; return res; }, field); expect(scan).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, false, undefined ], [2, 1, false, [field, 'a', 'b', 'c']] ]); const scan2 = await target.evaluate((ctx) => { const res = globalThis._res; ctx.async.unsuspendEventListener({group: /foo/}); return res; }); expect(scan2).toEqual([ [ {a: {b: {c: 1, d: 2}}}, undefined, false, undefined ], [2, 1, false, [field, 'a', 'b', 'c']], [3, 2, false, [field, 'a', 'b', 'c']] ]); }); }); }); }); }); 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'); } };