From 91b7aa07d55691345d0b81fb85cdedd47fd1ef84 Mon Sep 17 00:00:00 2001 From: Eran Machiels Date: Fri, 5 Feb 2021 09:06:24 +0100 Subject: [PATCH 1/2] WIP controlled errors --- src/components/ValidatorProvider.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/ValidatorProvider.tsx b/src/components/ValidatorProvider.tsx index 5204ecf..35c1df7 100644 --- a/src/components/ValidatorProvider.tsx +++ b/src/components/ValidatorProvider.tsx @@ -7,6 +7,7 @@ import { ValidatorArea } from '@/components/ValidatorArea'; export interface ValidatorProviderProps { rules?: RuleOptions; + errors?: Messages; children?: React.ReactNode | ((scope: ProviderScope) => React.ReactNode); } @@ -23,6 +24,13 @@ export class ValidatorProvider extends React.Component, + prevState: Readonly + ): void { + if () {} + } + /** * Add a new area to the provider */ From 1a4d2eddda905aae244a73b54a3c0687961b8ebd Mon Sep 17 00:00:00 2001 From: Eran Machiels Date: Sat, 6 Feb 2021 10:45:44 +0100 Subject: [PATCH 2/2] Added controlled errors --- .eslintrc | 3 +- __tests__/suits/Area.test.tsx | 23 +++++++++++++ __tests__/suits/Provider.test.tsx | 40 ++++++++++++++++++++++ package-lock.json | 14 ++++++++ package.json | 2 ++ src/components/ValidatorArea.tsx | 46 +++++++++++++++++++++++++ src/components/ValidatorProvider.tsx | 51 +++++++++++++++++++++++++--- 7 files changed, 174 insertions(+), 5 deletions(-) diff --git a/.eslintrc b/.eslintrc index be46d0c..f8e9646 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,6 +23,7 @@ "import/no-dynamic-require": "off", "global-require": "off", "quotes": ["error", "single", { "allowTemplateLiterals": true }], - "@typescript-eslint/indent": ["error", 4] + "@typescript-eslint/indent": ["error", 4], + "@typescript-eslint/no-non-null-assertion": ["off"] } } diff --git a/__tests__/suits/Area.test.tsx b/__tests__/suits/Area.test.tsx index c296e4f..53bbb27 100644 --- a/__tests__/suits/Area.test.tsx +++ b/__tests__/suits/Area.test.tsx @@ -425,4 +425,27 @@ describe('test ValidatorProvider', () => { await tick(); expect(area.find('div').text()).toBe('yes') }); + + it('should set errors in areas from props and change over time', () => { + const area = mount( + + + + ); + + expect(area.state().errors.length).toBe(1); + area.setProps({ errors: ['test error 2']}); + expect(area.state().errors.length).toBe(2); + }); + + it('should set errors from props', () => { + const area = mount( + + + + ); + + area.setProps({ errors: ['test error'] }); + expect(area.state().errors.length).toBe(1); + }); }) diff --git a/__tests__/suits/Provider.test.tsx b/__tests__/suits/Provider.test.tsx index a4aafc0..7490ac8 100644 --- a/__tests__/suits/Provider.test.tsx +++ b/__tests__/suits/Provider.test.tsx @@ -142,4 +142,44 @@ describe('test ValidatorProvider', () => { await tick(); expect(mockFn).toHaveBeenCalled() }); + + it('should set errors in areas from props and change over time', () => { + const provider = mount( + + + + + + ); + + expect(provider.find(ValidatorArea).state().errors.length).toBe(1); + provider.setProps({ errors: { test: ['test error 2'] } }); + expect(provider.find(ValidatorArea).state().errors.length).toBe(2); + }); + + it('should set errors in areas from props', () => { + const provider = mount( + + + + + + ); + + provider.setProps({ errors: { test: ['test error 2'] } }); + expect(provider.find(ValidatorArea).state().errors.length).toBe(1); + }); + + it('should ignore errors where no area is for', () => { + const provider = mount( + + + + + + ); + + provider.setProps({ errors: { not_existing: ['test error 2'] } }); + expect(provider.find(ValidatorArea).state().errors.length).toBe(0); + }); }) diff --git a/package-lock.json b/package-lock.json index 1f8d112..70b2ce2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", + "lodash": "^4.17.20", "react": "^16.14.0", "react-dom": "^16.14.0", "react-is": "^16.13.1" @@ -24,6 +25,7 @@ "@types/enzyme": "^3.10.8", "@types/enzyme-adapter-react-16": "^1.0.6", "@types/jest": "^26.0.20", + "@types/lodash": "^4.14.168", "@types/react": "^16.14.2", "@types/react-is": "^16.7.2", "@typescript-eslint/eslint-plugin": "^3.10.1", @@ -2553,6 +2555,12 @@ "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.168", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", + "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==", + "dev": true + }, "node_modules/@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -21758,6 +21766,12 @@ "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", "dev": true }, + "@types/lodash": { + "version": "4.14.168", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", + "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", diff --git a/package.json b/package.json index 8b344bb..f3fddfb 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", + "lodash": "^4.17.20", "react": "^16.14.0", "react-dom": "^16.14.0", "react-is": "^16.13.1" @@ -46,6 +47,7 @@ "@types/enzyme": "^3.10.8", "@types/enzyme-adapter-react-16": "^1.0.6", "@types/jest": "^26.0.20", + "@types/lodash": "^4.14.168", "@types/react": "^16.14.2", "@types/react-is": "^16.7.2", "@typescript-eslint/eslint-plugin": "^3.10.1", diff --git a/src/components/ValidatorArea.tsx b/src/components/ValidatorArea.tsx index afbb9d1..071b509 100644 --- a/src/components/ValidatorArea.tsx +++ b/src/components/ValidatorArea.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { isFragment } from 'react-is'; +import { isEqual } from 'lodash'; import { RuleOptions } from '@/RuleOptions'; import { AreaScope } from '@/AreaScope'; import { ValidatorContext } from '@/ValidatorContext'; @@ -8,6 +9,7 @@ import { Validator } from '@/Validator'; export interface ValidatorAreaProps { rules?: RuleOptions; name?: string; + errors?: string[]; children: React.ReactNode | ((scope: AreaScope) => React.ReactNode); validationName?: string; } @@ -63,6 +65,15 @@ export class ValidatorArea extends React.Component): void { + if (this.props.errors && this.props.errors.length) { + if (!prevProps.errors) { + this.setErrorsFromProps(this.props.errors); + } else if (prevProps.errors.length && !isEqual(prevProps.errors, this.props.errors)) { + this.setErrorsFromProps(this.props.errors); + } + } + } + + /** + * Sets the errors given via props in the indicated area + */ + private setErrorsFromProps(errors: string[]): void { + this.setState((prevState: ValidatorAreaState) => ({ + errors: [...prevState.errors, ...errors], + valid: false + })); + } + /** * Validate the area, or a given element when provided */ @@ -125,6 +156,21 @@ export class ValidatorArea extends React.Component ({ + ...prevState, + errors: [...prevState.errors, ...errors], + valid: false, + + })); + } + + /** + * Gets the name of the area + */ private getName(): string { if (this.inputRefs.length === 1 && this.inputRefs[0].getAttribute('name')) { return this.inputRefs[0].getAttribute('name') as string; diff --git a/src/components/ValidatorProvider.tsx b/src/components/ValidatorProvider.tsx index 35c1df7..531a208 100644 --- a/src/components/ValidatorProvider.tsx +++ b/src/components/ValidatorProvider.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { isEqual } from 'lodash'; import { Messages } from '@/Messages'; import { RuleOptions } from '@/RuleOptions'; import { ProviderScope } from '@/ProviderScope'; @@ -24,11 +25,49 @@ export class ValidatorProvider extends React.Component, - prevState: Readonly - ): void { - if () {} + prevProps: Readonly + ) { + if (this.props.errors && Object.keys(this.props.errors).length) { + if (!prevProps.errors || !Object.keys(prevProps.errors).length) { + this.setErrorsFromProps(this.props.errors); + } else if (Object.keys(prevProps.errors).length + && Object.keys(this.props.errors).length + && !isEqual(prevProps.errors, this.props.errors) + ) { + this.setErrorsFromProps(this.props.errors); + } + } + } + + /** + * Indicates whether an area exists with the given name + */ + private hasArea(name: string): boolean { + return !!Object.prototype.hasOwnProperty.call(this.state.areas, name); + } + + /** + * Sets the errors given via props in the indicated area + */ + private setErrorsFromProps(errors: Messages): void { + Object.keys(errors).forEach((key: string) => { + if (this.hasArea(key)) { + this.state.areas[key].addErrors(errors[key]); + } + }) + } + + private hasErrorsForArea(name: string): boolean { + return !!Object.prototype.hasOwnProperty.call(this.state.errors, name); } /** @@ -40,6 +79,10 @@ export class ValidatorProvider extends React.Component