From 36272f04e226d70e9b6387b329f8aff0a1981a3d Mon Sep 17 00:00:00 2001 From: Evyatar Date: Fri, 22 Sep 2023 02:44:55 +0100 Subject: [PATCH] minor(vest): Suite.subscribe for listening to state changes --- packages/vest/src/core/VestBus/VestBus.ts | 15 ++++-- packages/vest/src/suite/SuiteTypes.ts | 1 + .../src/suite/__tests__/subscribe.test.ts | 52 +++++++++++++++++++ packages/vest/src/suite/createSuite.ts | 4 +- website/docs/api_reference.md | 2 + .../docs/writing_your_suite/vests_suite.md | 11 ++++ 6 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 packages/vest/src/suite/__tests__/subscribe.test.ts diff --git a/packages/vest/src/core/VestBus/VestBus.ts b/packages/vest/src/core/VestBus/VestBus.ts index 98343771e..0fb989da2 100644 --- a/packages/vest/src/core/VestBus/VestBus.ts +++ b/packages/vest/src/core/VestBus/VestBus.ts @@ -1,3 +1,4 @@ +import { CB } from 'vest-utils'; import { Bus } from 'vestjs-runtime'; import { Events } from 'BusEvents'; @@ -13,7 +14,7 @@ import { VestTest } from 'VestTest'; import { useOmitOptionalFields } from 'omitOptionalFields'; import { useRunDoneCallbacks, useRunFieldCallbacks } from 'runCallbacks'; -// eslint-disable-next-line max-statements +// eslint-disable-next-line max-statements, max-lines-per-function export function useInitVestBus() { const VestBus = Bus.useBus(); @@ -74,9 +75,17 @@ export function useInitVestBus() { useResetSuite(); }); - return VestBus; + return { + subscribe, + }; - function on(event: Events, cb: (...args: any[]) => void) { + function subscribe(cb: CB) { + VestBus.on('*', () => { + cb(); + }); + } + + function on(event: Events | '*', cb: (...args: any[]) => void) { VestBus.on(event, (...args: any[]) => { // This is more concise, but it might be an overkill // if we're adding events that don't need to invalidate the cache diff --git a/packages/vest/src/suite/SuiteTypes.ts b/packages/vest/src/suite/SuiteTypes.ts index 859e33cfb..00efae51e 100644 --- a/packages/vest/src/suite/SuiteTypes.ts +++ b/packages/vest/src/suite/SuiteTypes.ts @@ -23,5 +23,6 @@ export type SuiteMethods = { reset: CB; remove: CB; resetField: CB; + subscribe: (cb: CB) => void; } & TTypedMethods & SuiteSelectors; diff --git a/packages/vest/src/suite/__tests__/subscribe.test.ts b/packages/vest/src/suite/__tests__/subscribe.test.ts new file mode 100644 index 000000000..1e8fcfc09 --- /dev/null +++ b/packages/vest/src/suite/__tests__/subscribe.test.ts @@ -0,0 +1,52 @@ +import wait from 'wait'; + +import { SuiteSerializer } from 'SuiteSerializer'; +import * as vest from 'vest'; + +describe('suite.subscribe', () => { + it('Should be a function', () => { + const suite = vest.create('suite', () => {}); + + expect(typeof suite.subscribe).toBe('function'); + }); + + it('Should call the callback on suite updates', async () => { + const cb = jest.fn(() => { + dumps.push(SuiteSerializer.serialize(suite)); + }); + let callCount = cb.mock.calls.length; + + const suite = vest.create('suite', () => { + expect(cb.mock.calls.length).toBeGreaterThan(callCount); + callCount = cb.mock.calls.length; + vest.test('field', () => {}); + expect(cb.mock.calls.length).toBeGreaterThan(callCount); + callCount = cb.mock.calls.length; + vest.test('field2', () => {}); + expect(cb.mock.calls.length).toBeGreaterThan(callCount); + callCount = cb.mock.calls.length; + vest.test('field3', () => false); + expect(cb.mock.calls.length).toBeGreaterThan(callCount); + callCount = cb.mock.calls.length; + vest.test('field4', async () => Promise.reject()); + expect(cb.mock.calls.length).toBeGreaterThan(callCount); + callCount = cb.mock.calls.length; + }); + + const dumps: string[] = []; + + suite.subscribe(cb); + expect(cb.mock.calls).toHaveLength(0); + suite(); + expect(cb.mock.calls.length).toBeGreaterThan(callCount); + callCount = cb.mock.calls.length; + + // expect some of the dumps to be different + expect(dumps.some((dump, i) => dump !== dumps[i - 1])).toBe(true); + + await wait(10); + + // now also after resolving the async test + expect(cb.mock.calls.length).toBeGreaterThan(callCount); + }); +}); diff --git a/packages/vest/src/suite/createSuite.ts b/packages/vest/src/suite/createSuite.ts index b01644b86..30d7c43c1 100644 --- a/packages/vest/src/suite/createSuite.ts +++ b/packages/vest/src/suite/createSuite.ts @@ -65,7 +65,8 @@ function createSuite< // We do this within the VestRuntime so that the suite methods // will be bound to the suite's stateRef and be able to access it. return VestRuntime.Run(stateRef, () => { - useInitVestBus(); + // @vx-allow use-use + const VestBus = useInitVestBus(); return assign( // We're also binding the suite to the stateRef, so that the suite @@ -80,6 +81,7 @@ function createSuite< reset: Bus.usePrepareEmitter(Events.RESET_SUITE), resetField: Bus.usePrepareEmitter(Events.RESET_FIELD), resume: VestRuntime.persist(useLoadSuite), + subscribe: VestBus.subscribe, ...bindSuiteSelectors(VestRuntime.persist(useCreateSuiteResult)), ...getTypedMethods(), } diff --git a/website/docs/api_reference.md b/website/docs/api_reference.md index 79ea419d7..80258d487 100644 --- a/website/docs/api_reference.md +++ b/website/docs/api_reference.md @@ -44,6 +44,7 @@ keywords: promisify, compose, staticSuite, + subscrib, ] --- @@ -59,6 +60,7 @@ Below is a list of all the API functions exposed by Vest. - [suite.remove](./writing_your_suite/vests_suite.md#removing-a-single-field-from-the-suite-state) - Removes a single field from the suite. - [suite.reset](./writing_your_suite/vests_suite.md#cleaning-up-our-validation-state) - Resets the suite to its initial state. - [suite.resetField](./writing_your_suite/vests_suite.md#cleaning-up-our-validation-state) - Resets a single field to an untested state. + - [suite.subscribe](./writing_your_suite/vests_suite.md#subscribing-to-suite-state-changes) - Subscribes to suite state changes. - [staticSuite](./server_side_validations.md) - creates a stateless suite that is used for server side validations. diff --git a/website/docs/writing_your_suite/vests_suite.md b/website/docs/writing_your_suite/vests_suite.md index a521b3312..0c13ab8af 100644 --- a/website/docs/writing_your_suite/vests_suite.md +++ b/website/docs/writing_your_suite/vests_suite.md @@ -87,3 +87,14 @@ To reset the validity of a single field, you can call `suite.resetField(fieldNam In some cases, you may want to remove a field from the suite state. For example, when the user removes a dynamically added field. In this case, you can call `suite.remove(fieldName)` to remove the field from the state and cancel any pending async validations that might still be running. Note that you don't need to use `suite.remove` very often, as most users can simply use `reset` and `omitWhen`. + +## Subscribing to Suite State Changes + +You can subscribe to changes in the suite state by calling `suite.subscribe(callback)`. The callback will be called whenever the suite state changes internally. + +```js +suite.subscribe(() => { + const result = suite.get(); + // ... Do something with the result +}); +```