Skip to content

Commit

Permalink
feat: implement SharedStateCollection#onChange
Browse files Browse the repository at this point in the history
  • Loading branch information
b-ma committed Oct 18, 2024
1 parent 32ac627 commit 39aa58c
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/common/SharedState.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ ${JSON.stringify(initValues, null, 2)}`);
}

if (isPlainObject(arguments[0]) && isPlainObject(arguments[1])) {
logger.removed('`context` argument in SharedState.set(updates, context)', 'a regular parameter set with `event=true` behavior', '4.0.0-alpha.29');
logger.removed('`context` argument in SharedState.set(updates, context)', 'a regular parameter configured with `event=true`', '4.0.0-alpha.29');
}

if (arguments.length === 2 && isString(updates)) {
Expand Down
30 changes: 27 additions & 3 deletions src/common/SharedStateCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class SharedStateCollection {
#onUpdateCallbacks = new Set();
#onAttachCallbacks = new Set();
#onDetachCallbacks = new Set();
#onChangeCallbacks = new Set();
#unobserve = null;

constructor(stateManager, className, filter = null, options = {}) {
Expand Down Expand Up @@ -77,15 +78,16 @@ class SharedStateCollection {
this.#states.splice(index, 1);

this.#onDetachCallbacks.forEach(callback => callback(state));
this.#onChangeCallbacks.forEach(callback => callback());
});

state.onUpdate((newValues, oldValues) => {
Array.from(this.#onUpdateCallbacks).forEach(callback => {
callback(state, newValues, oldValues);
});
this.#onUpdateCallbacks.forEach(callback => callback(state, newValues, oldValues));
this.#onChangeCallbacks.forEach(callback => callback());
});

this.#onAttachCallbacks.forEach(callback => callback(state));
this.#onChangeCallbacks.forEach(callback => callback());
}, this.#options);
}

Expand Down Expand Up @@ -330,18 +332,40 @@ class SharedStateCollection {
return () => this.#onDetachCallbacks.delete(callback);
}

/**
* Register a function to execute on any change (i.e. create, delete or update)
* that occurs on the the collection.
*
* @param {Function} callback - callback to execute when a change occurs.
* @returns {Function} - Function that delete the registered listener.
* @example
* const collection = await client.stateManager.getCollection('player');
* collection.onChange(() => renderApp(), true);
*/
onChange(callback, executeListener = false) {
if (executeListener === true) {
callback();
}

this.#onChangeCallbacks.add(callback);

return () => this.#onChangeCallbacks.delete(callback);
}

/**
* Detach from the collection, i.e. detach all underlying shared states.
* @type {number}
*/
async detach() {
this.#unobserve();
this.#onAttachCallbacks.clear();
this.#onUpdateCallbacks.clear();

const promises = this.#states.map(state => state.detach());
await Promise.all(promises);

this.#onDetachCallbacks.clear();
this.#onChangeCallbacks.clear();
}

/**
Expand Down
34 changes: 34 additions & 0 deletions tests/states/SharedStateCollection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,40 @@ describe(`# SharedStateCollection`, () => {
});
});

describe(`## onChange()`, () => {
it(`should be executed on each collection change`, async () => {
const collection = await clients[1].stateManager.getCollection('a');
let called = 0;

collection.onChange(() => called += 1);

const state = await clients[0].stateManager.create('a');
await delay(10); // make sure the state is properly attached in collection
await state.set({ bool: true });
await delay(10);
await state.delete();
await delay(10);

assert.equal(called, 3);
});

it(`should be executed now if executeListener is true`, async () => {
const collection = await clients[1].stateManager.getCollection('a');
let called = 0;

collection.onChange(() => called += 1, true);

const state = await clients[0].stateManager.create('a');
await delay(10); // make sure the state is properly attached in collection
await state.set({ bool: true });
await delay(10);
await state.delete();
await delay(10);

assert.equal(called, 4);
});
});

describe(`## [Symbol.iterator]`, () => {
// this tends to show a bug
it(`should implement iterator API`, async () => {
Expand Down

0 comments on commit 39aa58c

Please sign in to comment.