From c1e31f307778fe9e77035b7e33f2cf145c2f745f Mon Sep 17 00:00:00 2001 From: Evyatar Date: Fri, 21 Jan 2022 12:33:50 +0200 Subject: [PATCH] add(vest): suite.resetField (#777) --- packages/vest/src/core/state/stateHooks.ts | 8 +++ .../core/suite/__tests__/resetField.test.ts | 69 +++++++++++++++++++ packages/vest/src/core/suite/create.ts | 7 +- packages/vest/src/core/test/VestTest.ts | 5 ++ packages/vest/src/core/vestBus.ts | 17 +++-- website/docs/api_reference.md | 1 + website/docs/understanding_state.md | 2 +- .../docs/writing_your_suite/vests_suite.md | 8 +++ 8 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 packages/vest/src/core/suite/__tests__/resetField.test.ts diff --git a/packages/vest/src/core/state/stateHooks.ts b/packages/vest/src/core/state/stateHooks.ts index aa36a92a3..d5f38dec5 100644 --- a/packages/vest/src/core/state/stateHooks.ts +++ b/packages/vest/src/core/state/stateHooks.ts @@ -97,3 +97,11 @@ export function useTestsFlat(): VestTest[] { return flatCache([current], () => nestedArray.flatten(current)); } + +export function useEachTestObject( + handler: (testObject: VestTest) => void +): void { + const testObjects = useTestsFlat(); + + testObjects.forEach(handler); +} diff --git a/packages/vest/src/core/suite/__tests__/resetField.test.ts b/packages/vest/src/core/suite/__tests__/resetField.test.ts new file mode 100644 index 000000000..11cba08db --- /dev/null +++ b/packages/vest/src/core/suite/__tests__/resetField.test.ts @@ -0,0 +1,69 @@ +import { create, test } from 'vest'; + +describe('suite.resetField', () => { + let suite; + + beforeEach(() => { + suite = create(() => { + test('field1', 'f1 error', () => false); + test('field2', 'f2 error', () => false); + }); + suite(); + }); + + it('Should reset the validity state of a field', () => { + expect(suite.get().hasErrors('field1')).toBe(true); + expect(suite.get().hasErrors('field2')).toBe(true); + expect(suite.get().getErrors('field1')).toEqual(['f1 error']); + expect(suite.get().getErrors('field2')).toEqual(['f2 error']); + suite.resetField('field1'); + expect(suite.get().hasErrors('field1')).toBe(false); + expect(suite.get().hasErrors('field2')).toBe(true); + expect(suite.get().getErrors('field1')).toEqual([]); + expect(suite.get().getErrors('field2')).toEqual(['f2 error']); + suite.resetField('field2'); + expect(suite.get().hasErrors('field1')).toBe(false); + expect(suite.get().hasErrors('field2')).toBe(false); + expect(suite.get().getErrors('field1')).toEqual([]); + expect(suite.get().getErrors('field2')).toEqual([]); + }); + + it('Should refresh the suite result', () => { + const res = suite.get(); + expect(res).toBe(suite.get()); + suite.resetField('field1'); + expect(res).not.toBe(suite.get()); + }); + + it('Should allow the field to keep updating (no final status)', () => { + suite.resetField('field1'); + expect(suite.get().hasErrors('field1')).toBe(false); + expect(suite.get().hasErrors('field2')).toBe(true); + suite(); + expect(suite.get().hasErrors('field1')).toBe(true); + expect(suite.get().hasErrors('field2')).toBe(true); + }); + + it('sanity', () => { + expect(suite.get().tests).toMatchInlineSnapshot(` + Object { + "field1": Object { + "errorCount": 1, + "errors": Array [ + "f1 error", + ], + "testCount": 1, + "warnCount": 0, + }, + "field2": Object { + "errorCount": 1, + "errors": Array [ + "f2 error", + ], + "testCount": 1, + "warnCount": 0, + }, + } + `); + }); +}); diff --git a/packages/vest/src/core/suite/create.ts b/packages/vest/src/core/suite/create.ts index a890b5a1f..fcec3ae6d 100644 --- a/packages/vest/src/core/suite/create.ts +++ b/packages/vest/src/core/suite/create.ts @@ -15,6 +15,7 @@ import { initBus, Events } from 'vestBus'; type CreateProperties = { get: () => TDraftResult; reset: () => void; + resetField: (fieldName: string) => void; remove: (fieldName: string) => void; }; @@ -64,6 +65,7 @@ function create( get: () => TDraftResult; reset: () => void; + resetField: (fieldName: string) => void; remove: (fieldName: string) => void; } @@ -91,10 +93,13 @@ function create( }), { get: context.bind(ctxRef, produceDraft), - reset: state.reset, remove: context.bind(ctxRef, (fieldName: string) => { bus.emit(Events.REMOVE_FIELD, fieldName); }), + reset: state.reset, + resetField: context.bind(ctxRef, (fieldName: string) => { + bus.emit(Events.RESET_FIELD, fieldName); + }), } ); diff --git a/packages/vest/src/core/test/VestTest.ts b/packages/vest/src/core/test/VestTest.ts index 620e80f9a..d638bf100 100644 --- a/packages/vest/src/core/test/VestTest.ts +++ b/packages/vest/src/core/test/VestTest.ts @@ -120,6 +120,11 @@ export default class VestTest { useRefreshTestObjects(); } + reset(): void { + this.status = STATUS_UNTESTED; + useRefreshTestObjects(); + } + omit(): void { this.setStatus(STATUS_OMITTED); } diff --git a/packages/vest/src/core/vestBus.ts b/packages/vest/src/core/vestBus.ts index 173de9bc8..34072d9e4 100644 --- a/packages/vest/src/core/vestBus.ts +++ b/packages/vest/src/core/vestBus.ts @@ -7,8 +7,9 @@ import matchingFieldName from 'matchingFieldName'; import omitOptionalTests from 'omitOptionalTests'; import removeTestFromState from 'removeTestFromState'; import { runFieldCallbacks, runDoneCallbacks } from 'runCallbacks'; -import { useTestsFlat } from 'stateHooks'; +import { useEachTestObject } from 'stateHooks'; +// eslint-disable-next-line max-lines-per-function export function initBus() { const bus = createBus(); @@ -34,9 +35,7 @@ export function initBus() { // Removes a certain field from the state. bus.on(Events.REMOVE_FIELD, (fieldName: string) => { - const testObjects = useTestsFlat(); - - testObjects.forEach(testObject => { + useEachTestObject(testObject => { if (matchingFieldName(testObject, fieldName)) { testObject.cancel(); removeTestFromState(testObject); @@ -44,6 +43,15 @@ export function initBus() { }); }); + // Resets a certain field in the state. + bus.on(Events.RESET_FIELD, (fieldName: string) => { + useEachTestObject(testObject => { + if (matchingFieldName(testObject, fieldName)) { + testObject.reset(); + } + }); + }); + return bus; } @@ -60,5 +68,6 @@ export function useBus() { export enum Events { TEST_COMPLETED = 'test_completed', REMOVE_FIELD = 'remove_field', + RESET_FIELD = 'reset_field', SUITE_COMPLETED = 'suite_completed', } diff --git a/website/docs/api_reference.md b/website/docs/api_reference.md index fb085eb13..a1b23b4f7 100644 --- a/website/docs/api_reference.md +++ b/website/docs/api_reference.md @@ -13,6 +13,7 @@ Below is a list of all the API functions exposed by Vest. - [suite.get](./writing_your_suite/vests_suite.md#using-suiteget) - Returns the current validation state of the suite. - [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. - [test](./writing_tests/using_the_test_function.md) - A single validation test inside your suite. diff --git a/website/docs/understanding_state.md b/website/docs/understanding_state.md index 1749a5e03..59302c3a9 100644 --- a/website/docs/understanding_state.md +++ b/website/docs/understanding_state.md @@ -43,7 +43,7 @@ In some cases, such as form reset, you want to discard of previous validation re ### Usage: -`.rese()` Is a property on your validation suite. Calling it will remove your suite's state. +`.reset()` Is a property on your validation suite. Calling it will remove your suite's state. ```js import { create } from 'vest'; diff --git a/website/docs/writing_your_suite/vests_suite.md b/website/docs/writing_your_suite/vests_suite.md index 2f81bee08..3dc831c57 100644 --- a/website/docs/writing_your_suite/vests_suite.md +++ b/website/docs/writing_your_suite/vests_suite.md @@ -55,6 +55,14 @@ This method is especially useful if we want to access our suite state from withi When you want to clean up the suite state, for example, when the user clears the form, or when you want to navigate out of the page in an SPA - but the user might return to it later on, you can call `suite.reset()`. This will reset the suite state and cancel any pending async validations that may still be running. +## Resetting a single field + +Sometimes you wish to only reset the validity of a single field, for example - if you want to to reset the validity as the user starts typing again and you only run the validation on blur. + +Simply call `suite.resetField(fieldName)` and that field will be reset to its untested state. + ## Removing a single field from the suite state +Note: You rarely need to use `suite.remove`, and this is mostly useful for external libraries validating on your behalf. Most users are fine using `reset` and `omitWhen`. + Sometimes we want to remove a certain field from the suite state. For example, when the user removed a dynamically added field. In this case, we can call `suite.remove(fieldName)`. This will remove the field from the suite state and cancel any pending async validations that may still be running.