From 04eec5c687f00d9707f4fd65fce7fe64ccbd42d2 Mon Sep 17 00:00:00 2001 From: Samuel Greene Date: Fri, 17 Nov 2023 03:32:13 -0500 Subject: [PATCH 1/3] add watchTree --- packages/client/src/listeners.js | 14 ++++++++- packages/client/src/listeners.test.js | 45 +++++++++++++++++++++++++++ packages/client/src/util/futil.js | 18 +++++++---- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/packages/client/src/listeners.js b/packages/client/src/listeners.js index 5131782ab..e43698580 100644 --- a/packages/client/src/listeners.js +++ b/packages/client/src/listeners.js @@ -2,8 +2,10 @@ import _ from 'lodash/fp.js' import { eventEmitter, hasSome } from './util/futil.js' import { encode } from './util/tree.js' +let matchesKeys = (keys, delta) => _.isEmpty(keys) || hasSome(keys, delta) + export let setupListeners = (tree) => { - let { on, emit } = eventEmitter() + let { on, onAny, emit } = eventEmitter() // Assume first arg is node which might have path tree.onChange = (node = {}, delta) => emit(encode(node.path), node, delta) // Public API @@ -12,4 +14,14 @@ export let setupListeners = (tree) => { // Trigger watcher if keys match or no keys passed if (_.isEmpty(keys) || hasSome(keys, delta)) f(node, delta) }) + tree.watchTree = (f, keys, path) => + onAny((eventPath, node, delta) => { + // already encoded, so isParent not needed + // Use case is, for example, watching a group and all its children + // might be better solved by watchable group flags + let matchesPath = _.isEmpty(path) || _.startsWith(path, eventPath) + // TODO: should getNode return root for [] or empty paths? + let treeNode = path ? tree.getNode(path) : tree.tree + if (matchesPath && matchesKeys(keys, delta)) f(treeNode, node, delta) + }) } diff --git a/packages/client/src/listeners.test.js b/packages/client/src/listeners.test.js index 3307e23b8..8e9b5861c 100644 --- a/packages/client/src/listeners.test.js +++ b/packages/client/src/listeners.test.js @@ -192,6 +192,51 @@ let AllTests = (ContextureClient) => { await tree.mutate(['root', 'results'], { pageSize: 2 }) expect(resultWatcher).toBeCalledTimes(2) }) + it('watchTree', async () => { + let service = sinon.spy(mockService({ delay: 10 })) + let tree = ContextureClient( + { service, debounce: 1}, + { + key: 'root', + join: 'and', + children: [ + { + key: 'filter', + type: 'facet', + field: 'facetfield', + values: ['some value'] + }, + { key: 'results', type: 'results' } + ], + } + ) + let filterDom = '' + let resultsDom = '' + tree.watchTree(root => { + filterDom = `
+

Facet

+ Field: ${root.children[0].field} + values: ${_.join(', ', root.children[0].values)} +

` + resultsDom = `${_.map( + result => + `\n${_.map(val => ``, _.values(result))}`, + root.children[1].context.results + )} +
${val}
` + }) + expect(filterDom).toBe('') + let action = tree.mutate(['root', 'filter'], {values: ['other Value']}) + expect(filterDom).toBe(`
+

Facet

+ Field: facetfield + values: other Value +

`) + await action + expect(resultsDom).toBe(` + +
some result
`) + }) }) } diff --git a/packages/client/src/util/futil.js b/packages/client/src/util/futil.js index 8f8e06088..7b1340172 100644 --- a/packages/client/src/util/futil.js +++ b/packages/client/src/util/futil.js @@ -7,18 +7,24 @@ export let hasSome = (keys, obj) => _.some(F.hasIn(obj), keys) // Sets up basic event emitter/listener registry with an array of listeners per topic // e.g. listeners: { topic1: [fn1, fn2, ...], topic2: [...], ... } -export let eventEmitter = (listeners = {}) => ({ - listeners, - emit: (topic, ...args) => _.over(listeners[topic])(...args), - on(topic, fn) { +// Also emit on a special symbol for all topics +let allTopics = Symbol('allTopics') +export let eventEmitter = (listeners = {}) => { + let emit = (topic, ...args) => { + _.over(listeners[topic])(...args) + _.over(listeners[allTopics])(topic, ...args) + } + let on = (topic, fn) => { if (!listeners[topic]) listeners[topic] = [] listeners[topic].push(fn) // unlisten return () => { listeners[topic] = _.without(fn, listeners[topic]) } - }, -}) + } + let onAny = (fn) => on(allTopics, fn) + return { listeners, emit, on, onAny } +} export let transformTreePostOrder = (next = F.traverse) => _.curry((f, x) => { From fadf2775bf495452a7c21d04ede6ce6eee214b37 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Fri, 17 Nov 2023 08:33:47 +0000 Subject: [PATCH 2/3] Fix code style issues with Prettier --- packages/client/src/listeners.js | 2 +- packages/client/src/listeners.test.js | 16 ++++++++-------- packages/client/src/util/futil.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/client/src/listeners.js b/packages/client/src/listeners.js index e43698580..2f27421c7 100644 --- a/packages/client/src/listeners.js +++ b/packages/client/src/listeners.js @@ -14,7 +14,7 @@ export let setupListeners = (tree) => { // Trigger watcher if keys match or no keys passed if (_.isEmpty(keys) || hasSome(keys, delta)) f(node, delta) }) - tree.watchTree = (f, keys, path) => + tree.watchTree = (f, keys, path) => onAny((eventPath, node, delta) => { // already encoded, so isParent not needed // Use case is, for example, watching a group and all its children diff --git a/packages/client/src/listeners.test.js b/packages/client/src/listeners.test.js index 8e9b5861c..702ebf1b5 100644 --- a/packages/client/src/listeners.test.js +++ b/packages/client/src/listeners.test.js @@ -195,7 +195,7 @@ let AllTests = (ContextureClient) => { it('watchTree', async () => { let service = sinon.spy(mockService({ delay: 10 })) let tree = ContextureClient( - { service, debounce: 1}, + { service, debounce: 1 }, { key: 'root', join: 'and', @@ -204,29 +204,29 @@ let AllTests = (ContextureClient) => { key: 'filter', type: 'facet', field: 'facetfield', - values: ['some value'] + values: ['some value'], }, - { key: 'results', type: 'results' } + { key: 'results', type: 'results' }, ], } ) let filterDom = '' let resultsDom = '' - tree.watchTree(root => { + tree.watchTree((root) => { filterDom = `

Facet

Field: ${root.children[0].field} values: ${_.join(', ', root.children[0].values)}

` resultsDom = `${_.map( - result => - `\n${_.map(val => ``, _.values(result))}`, - root.children[1].context.results + (result) => + `\n${_.map((val) => ``, _.values(result))}`, + root.children[1].context.results )}
${val}
${val}
` }) expect(filterDom).toBe('') - let action = tree.mutate(['root', 'filter'], {values: ['other Value']}) + let action = tree.mutate(['root', 'filter'], { values: ['other Value'] }) expect(filterDom).toBe(`

Facet

Field: facetfield diff --git a/packages/client/src/util/futil.js b/packages/client/src/util/futil.js index 7b1340172..705067ccb 100644 --- a/packages/client/src/util/futil.js +++ b/packages/client/src/util/futil.js @@ -14,7 +14,7 @@ export let eventEmitter = (listeners = {}) => { _.over(listeners[topic])(...args) _.over(listeners[allTopics])(topic, ...args) } - let on = (topic, fn) => { + let on = (topic, fn) => { if (!listeners[topic]) listeners[topic] = [] listeners[topic].push(fn) // unlisten From a501cb2330b8c6e5f60a8efb81b074cf04999628 Mon Sep 17 00:00:00 2001 From: Samuel Greene Date: Fri, 17 Nov 2023 03:35:09 -0500 Subject: [PATCH 3/3] update to jest --- packages/client/src/listeners.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/listeners.test.js b/packages/client/src/listeners.test.js index 702ebf1b5..c3c566558 100644 --- a/packages/client/src/listeners.test.js +++ b/packages/client/src/listeners.test.js @@ -193,7 +193,7 @@ let AllTests = (ContextureClient) => { expect(resultWatcher).toBeCalledTimes(2) }) it('watchTree', async () => { - let service = sinon.spy(mockService({ delay: 10 })) + let service = jest.fn(mockService({ delay: 10 })) let tree = ContextureClient( { service, debounce: 1 }, {