├── .babelrc ├── src ├── helpers │ ├── index.ts │ └── set.js ├── index.ts ├── final-form-validation.ts └── final-form-validation.spec.ts ├── .eslintignore ├── .prettierrc ├── config └── test │ └── jest.json ├── .editorconfig ├── .gitignore ├── .circleci └── config.yml ├── .eslintrc.js ├── tsconfig.json ├── .vscode └── launch.json ├── typings └── index.d.ts ├── LICENSE ├── README.md ├── rollup.config.js └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import set from './set'; 2 | export { set }; 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .rpt2_cache 3 | .vscode 4 | config 5 | dist 6 | *.spec.ts 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "endOfLine": "auto" 5 | } 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { createFinalFormValidation } from './final-form-validation'; 2 | 3 | export { createFinalFormValidation }; 4 | -------------------------------------------------------------------------------- /config/test/jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": "../../", 3 | "preset": "ts-jest", 4 | "restoreMocks": true, 5 | "modulePathIgnorePatterns": ["/dist"] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist/ 4 | *.orig 5 | .idea/ 6 | */src/**/*.js.map 7 | *.log 8 | package-lock.json 9 | coverage/ 10 | .awcache/ 11 | .rpt2_cache 12 | react-app-env.d.ts 13 | .cache 14 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/test-ci-code 5 | docker: 6 | - image: circleci/node:10 7 | steps: 8 | - checkout 9 | - run: 10 | name: install 11 | command: 'npm install' 12 | - run: 13 | name: validate 14 | command: 'npm run validate' 15 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', 5 | 'prettier/@typescript-eslint', 6 | 'plugin:prettier/recommended', 7 | ], 8 | plugins: ['@typescript-eslint', 'prettier'], 9 | rules: { 10 | '@typescript-eslint/no-explicit-any': 'off', 11 | '@typescript-eslint/explicit-function-return-type': 'off', 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "noLib": false, 11 | "allowJs": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true 15 | }, 16 | "include": ["./src/**/*"] 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest selected file", 8 | "program": "${workspaceRoot}/node_modules/jest/bin/jest.js", 9 | "args": [ 10 | "${fileBasenameNoExtension}", 11 | "-c", 12 | "./config/test/jest.json", 13 | "--verbose", 14 | "-i", 15 | "--no-cache", 16 | "--watchAll" 17 | ], 18 | "console": "integratedTerminal", 19 | "internalConsoleOptions": "neverOpen" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ValidationSchema } from '@lemoncode/fonk'; 2 | 3 | /** 4 | * Main function to create an instance of FinalFormValidation. We could use `validateField`, `validateRecord` and/or `validateForm` to fire validations. 5 | * `updateValidationSchema`: to update validation schema after create form validation instance. 6 | * 7 | * **Arguments** 8 | * - ValidationSchema 9 | * 10 | * **Returns** 11 | * - FinalFormValidation instance. 12 | */ 13 | export function createFinalFormValidation( 14 | validationSchema: ValidationSchema 15 | ): FinalFormValidation; 16 | 17 | interface FinalFormValidation { 18 | validateField: (fieldId: string, value: any, values?: any) => Promise; 19 | 20 | validateRecord: ( 21 | values: any 22 | ) => Promise<{ recordErrors: Record }>; 23 | 24 | validateForm: ( 25 | values: any 26 | ) => Promise< 27 | Record | { recordErrors: Record } 28 | >; 29 | 30 | updateValidationSchema(validationSchema: ValidationSchema): void; 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lemoncode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fonk-final-form 2 | 3 | [![CircleCI](https://badgen.net/github/status/Lemoncode/fonk-final-form/master/ci?icon=circleci&label=circleci)](https://circleci.com/gh/Lemoncode/fonk-final-form/tree/master) 4 | [![NPM Version](https://badgen.net/npm/v/@lemoncode/fonk-final-form?icon=npm&label=npm)](https://www.npmjs.com/package/@lemoncode/fonk-final-form) 5 | [![bundle-size](https://badgen.net/bundlephobia/min/@lemoncode/fonk-final-form)](https://bundlephobia.com/result?p=@lemoncode/fonk-final-form) 6 | 7 | This package serves as the entry point to the React Final Form library. It is intended to be paired with the generic Fonk package, which is shipped as to npm. 8 | 9 | Start by reading this [post (Final Form + Fonk)](https://www.basefactor.com/react-final-form-validation-fonk) 10 | 11 | Check our [Fonk Documentation site](https://lemoncode.github.io/fonk-doc/) and [React-Final-Form](https://lemoncode.github.io/fonk-doc/react-final-form) section. 12 | 13 | # About Basefactor + Lemoncode 14 | 15 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 16 | 17 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 18 | 19 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 20 | 21 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 22 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import babel from 'rollup-plugin-babel'; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import typescript from 'rollup-plugin-typescript2'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | import { DEFAULT_EXTENSIONS } from '@babel/core'; 7 | import pkg from './package.json'; 8 | 9 | const builds = [ 10 | { format: 'esm', minify: false }, 11 | { format: 'cjs', minify: false }, 12 | { format: 'umd', minify: false }, 13 | { format: 'umd', minify: true }, 14 | ]; 15 | const extensions = [...DEFAULT_EXTENSIONS, '.ts']; 16 | 17 | export default builds.map(({ format, minify }) => { 18 | const minExtension = minify ? '.min' : ''; 19 | return { 20 | input: 'src/index.ts', 21 | output: { 22 | name: pkg.name, 23 | exports: 'named', 24 | file: `dist/${pkg.name}.${format}${minExtension}.js`, 25 | format, 26 | globals: { 27 | '@lemoncode/fonk': 'Fonk', 28 | }, // Necessary for externals libraries and umd format 29 | }, 30 | external: Object.keys(pkg.peerDependencies || {}), 31 | plugins: [ 32 | resolve(), 33 | commonjs(), 34 | typescript({ 35 | tsconfig: 'tsconfig.json', 36 | rollupCommonJSResolveHack: true, // To be compatible with commonjs plugin 37 | }), 38 | babel({ 39 | extensions, 40 | exclude: 'node_modules/**', 41 | }), 42 | minify ? terser() : null, 43 | ], 44 | }; 45 | }); 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lemoncode/fonk-final-form", 3 | "version": "2.3.4", 4 | "description": "Fonk library for use with react-final-form", 5 | "main": "dist/@lemoncode/fonk-final-form.cjs.js", 6 | "module": "dist/@lemoncode/fonk-final-form.esm.js", 7 | "typings": "typings/index.d.ts", 8 | "files": [ 9 | "dist", 10 | "typings" 11 | ], 12 | "scripts": { 13 | "build": "npm run clean && rollup --config", 14 | "clean": "rimraf dist .rpt2_cache package-lock.json", 15 | "validate": "npm run lint && npm run build && npm run test && npm run test:size", 16 | "lint": "eslint src --ext .ts ", 17 | "lint:fix": "npm run lint -- --fix", 18 | "test": "jest -c ./config/test/jest.json --verbose", 19 | "test:watch": "npm test -- --watchAll -i --no-cache", 20 | "test:size": "bundlesize", 21 | "deploy": "npm run validate && np", 22 | "deploy:beta": "npm run validate && np --tag=beta --any-branch" 23 | }, 24 | "bundlesize": [ 25 | { 26 | "path": "./dist/**/*.js", 27 | "maxSize": "11.5kB" 28 | } 29 | ], 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/Lemoncode/fonk-final-form.git" 33 | }, 34 | "keywords": [ 35 | "fonk", 36 | "validation", 37 | "form", 38 | "form validation", 39 | "validate", 40 | "async validation", 41 | "sync validation", 42 | "final-form", 43 | "react-final-form" 44 | ], 45 | "author": "Lemoncode", 46 | "license": "MIT", 47 | "bugs": { 48 | "url": "https://github.com/Lemoncode/fonk-final-form/issues" 49 | }, 50 | "homepage": "https://github.com/Lemoncode/fonk-final-form#readme", 51 | "peerDependencies": { 52 | "@lemoncode/fonk": "latest" 53 | }, 54 | "devDependencies": { 55 | "@babel/cli": "^7.5.5", 56 | "@babel/core": "^7.5.5", 57 | "@babel/preset-env": "^7.5.5", 58 | "@lemoncode/fonk": "latest", 59 | "@types/jest": "^24.0.18", 60 | "@typescript-eslint/eslint-plugin": "^2.0.0", 61 | "@typescript-eslint/parser": "^2.0.0", 62 | "bundlesize": "0.18.0", 63 | "eslint": "^6.2.1", 64 | "eslint-config-prettier": "^6.1.0", 65 | "eslint-plugin-prettier": "^3.1.0", 66 | "husky": "^3.0.4", 67 | "jest": "^24.9.0", 68 | "lint-staged": "^9.2.3", 69 | "np": "^5.0.3", 70 | "prettier": "^1.18.2", 71 | "pretty-quick": "^1.11.1", 72 | "rimraf": "^3.0.0", 73 | "rollup": "^1.20.0", 74 | "rollup-plugin-babel": "^4.3.3", 75 | "rollup-plugin-commonjs": "^10.0.2", 76 | "rollup-plugin-node-resolve": "^5.2.0", 77 | "rollup-plugin-terser": "^5.1.1", 78 | "rollup-plugin-typescript2": "^0.22.1", 79 | "ts-jest": "^24.0.2", 80 | "typescript": "^3.5.3" 81 | }, 82 | "husky": { 83 | "hooks": { 84 | "pre-commit": "lint-staged" 85 | } 86 | }, 87 | "lint-staged": { 88 | "src/**/*.{ts,tsx}": [ 89 | "npm run lint:fix", 90 | "pretty-quick — staged", 91 | "git add" 92 | ] 93 | }, 94 | "publishConfig": { 95 | "registry": "https://registry.npmjs.org/", 96 | "access": "public" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/final-form-validation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FormValidation, 3 | ValidationResult, 4 | ValidationSchema, 5 | createFormValidation, 6 | FormValidationResult, 7 | } from '@lemoncode/fonk'; 8 | 9 | import { set } from './helpers'; 10 | /* 11 | React Final form expects a validator to return null or undefined 12 | when  a given validation succeeds, adaptor to fulfill this 13 | requirement. 14 |  */ 15 | export class FinalFormValidation { 16 | formValidation: FormValidation = null; 17 | 18 | constructor(validationSchema: ValidationSchema) { 19 | this.formValidation = createFormValidation(validationSchema); 20 | } 21 | 22 | private mapErrorsToFinalFormErrorMessageStructure = (errors: { 23 | [fieldId: string]: ValidationResult; 24 | }): Record => { 25 | const finalFormErrors = {}; 26 | 27 | for (const key of Object.keys(errors)) { 28 | const errorMessage = 29 | errors[key] && !errors[key].succeeded ? errors[key].message : ''; 30 | set(finalFormErrors, key, errorMessage); 31 | } 32 | 33 | return finalFormErrors; 34 | }; 35 | 36 | public validateField = ( 37 | fieldId: string, 38 | value: any, 39 | values?: any 40 | ): Promise => { 41 | return this.formValidation 42 | .validateField(fieldId, value, values) 43 | .then(validationResult => 44 | !validationResult.succeeded ? validationResult.message : null 45 | ); 46 | }; 47 | 48 | public validateRecord = ( 49 | values: any 50 | ): Promise<{ recordErrors: Record }> => { 51 | return this.formValidation.validateRecord(values).then(validationResult => 52 | !validationResult.succeeded 53 | ? { 54 | recordErrors: { 55 | ...this.mapErrorsToFinalFormErrorMessageStructure( 56 | validationResult.recordErrors 57 | ), 58 | }, 59 | } 60 | : null 61 | ); 62 | }; 63 | 64 | private buildErrors = (validationResult: FormValidationResult) => { 65 | let errors = {}; 66 | 67 | errors = { 68 | ...this.mapErrorsToFinalFormErrorMessageStructure( 69 | validationResult.fieldErrors 70 | ), 71 | }; 72 | 73 | errors = { 74 | ...errors, 75 | recordErrors: this.mapErrorsToFinalFormErrorMessageStructure( 76 | validationResult.recordErrors 77 | ), 78 | }; 79 | 80 | return errors; 81 | }; 82 | 83 | public validateForm = ( 84 | values: any 85 | ): Promise< 86 | Record | { recordErrors: Record } 87 | > => { 88 | return this.formValidation 89 | .validateForm(values) 90 | .then(validationResult => 91 | !validationResult.succeeded ? this.buildErrors(validationResult) : null 92 | ); 93 | }; 94 | 95 | public updateValidationSchema = ( 96 | validationSchema: ValidationSchema 97 | ): void => { 98 | this.formValidation.updateValidationSchema(validationSchema); 99 | }; 100 | } 101 | 102 | export const createFinalFormValidation = ( 103 | validationSchema: ValidationSchema 104 | ): FinalFormValidation => new FinalFormValidation(validationSchema); 105 | -------------------------------------------------------------------------------- /src/helpers/set.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lodash (Custom Build) 3 | * Build: `lodash modularize exports="npm" -o ./` 4 | * Copyright jQuery Foundation and other contributors 5 | * Released under MIT license 6 | * Based on Underscore.js 1.8.3 7 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 8 | */ 9 | 10 | /** Used as the `TypeError` message for "Functions" methods. */ 11 | var FUNC_ERROR_TEXT = 'Expected a function'; 12 | 13 | /** Used to stand-in for `undefined` hash values. */ 14 | var HASH_UNDEFINED = '__lodash_hash_undefined__'; 15 | 16 | /** Used as references for various `Number` constants. */ 17 | var INFINITY = 1 / 0, 18 | MAX_SAFE_INTEGER = 9007199254740991; 19 | 20 | /** `Object#toString` result references. */ 21 | var funcTag = '[object Function]', 22 | genTag = '[object GeneratorFunction]', 23 | symbolTag = '[object Symbol]'; 24 | 25 | /** Used to match property names within property paths. */ 26 | var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, 27 | reIsPlainProp = /^\w*$/, 28 | reLeadingDot = /^\./, 29 | rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; 30 | 31 | /** 32 | * Used to match `RegExp` 33 | * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). 34 | */ 35 | var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; 36 | 37 | /** Used to match backslashes in property paths. */ 38 | var reEscapeChar = /\\(\\)?/g; 39 | 40 | /** Used to detect host constructors (Safari). */ 41 | var reIsHostCtor = /^\[object .+?Constructor\]$/; 42 | 43 | /** Used to detect unsigned integer values. */ 44 | var reIsUint = /^(?:0|[1-9]\d*)$/; 45 | 46 | /** Detect free variable `global` from Node.js. */ 47 | var freeGlobal = 48 | typeof global == 'object' && global && global.Object === Object && global; 49 | 50 | /** Detect free variable `self`. */ 51 | var freeSelf = 52 | typeof self == 'object' && self && self.Object === Object && self; 53 | 54 | /** Used as a reference to the global object. */ 55 | var root = freeGlobal || freeSelf || Function('return this')(); 56 | 57 | /** 58 | * Gets the value at `key` of `object`. 59 | * 60 | * @private 61 | * @param {Object} [object] The object to query. 62 | * @param {string} key The key of the property to get. 63 | * @returns {*} Returns the property value. 64 | */ 65 | function getValue(object, key) { 66 | return object == null ? undefined : object[key]; 67 | } 68 | 69 | /** 70 | * Checks if `value` is a host object in IE < 9. 71 | * 72 | * @private 73 | * @param {*} value The value to check. 74 | * @returns {boolean} Returns `true` if `value` is a host object, else `false`. 75 | */ 76 | function isHostObject(value) { 77 | // Many host objects are `Object` objects that can coerce to strings 78 | // despite having improperly defined `toString` methods. 79 | var result = false; 80 | if (value != null && typeof value.toString != 'function') { 81 | try { 82 | result = !!(value + ''); 83 | } catch (e) {} 84 | } 85 | return result; 86 | } 87 | 88 | /** Used for built-in method references. */ 89 | var arrayProto = Array.prototype, 90 | funcProto = Function.prototype, 91 | objectProto = Object.prototype; 92 | 93 | /** Used to detect overreaching core-js shims. */ 94 | var coreJsData = root['__core-js_shared__']; 95 | 96 | /** Used to detect methods masquerading as native. */ 97 | var maskSrcKey = (function() { 98 | var uid = /[^.]+$/.exec( 99 | (coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO) || '' 100 | ); 101 | return uid ? 'Symbol(src)_1.' + uid : ''; 102 | })(); 103 | 104 | /** Used to resolve the decompiled source of functions. */ 105 | var funcToString = funcProto.toString; 106 | 107 | /** Used to check objects for own properties. */ 108 | var hasOwnProperty = objectProto.hasOwnProperty; 109 | 110 | /** 111 | * Used to resolve the 112 | * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) 113 | * of values. 114 | */ 115 | var objectToString = objectProto.toString; 116 | 117 | /** Used to detect if a method is native. */ 118 | var reIsNative = RegExp( 119 | '^' + 120 | funcToString 121 | .call(hasOwnProperty) 122 | .replace(reRegExpChar, '\\$&') 123 | .replace( 124 | /hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, 125 | '$1.*?' 126 | ) + 127 | '$' 128 | ); 129 | 130 | /** Built-in value references. */ 131 | var Symbol = root.Symbol, 132 | splice = arrayProto.splice; 133 | 134 | /* Built-in method references that are verified to be native. */ 135 | var Map = getNative(root, 'Map'), 136 | nativeCreate = getNative(Object, 'create'); 137 | 138 | /** Used to convert symbols to primitives and strings. */ 139 | var symbolProto = Symbol ? Symbol.prototype : undefined, 140 | symbolToString = symbolProto ? symbolProto.toString : undefined; 141 | 142 | /** 143 | * Creates a hash object. 144 | * 145 | * @private 146 | * @constructor 147 | * @param {Array} [entries] The key-value pairs to cache. 148 | */ 149 | function Hash(entries) { 150 | var index = -1, 151 | length = entries ? entries.length : 0; 152 | 153 | this.clear(); 154 | while (++index < length) { 155 | var entry = entries[index]; 156 | this.set(entry[0], entry[1]); 157 | } 158 | } 159 | 160 | /** 161 | * Removes all key-value entries from the hash. 162 | * 163 | * @private 164 | * @name clear 165 | * @memberOf Hash 166 | */ 167 | function hashClear() { 168 | this.__data__ = nativeCreate ? nativeCreate(null) : {}; 169 | } 170 | 171 | /** 172 | * Removes `key` and its value from the hash. 173 | * 174 | * @private 175 | * @name delete 176 | * @memberOf Hash 177 | * @param {Object} hash The hash to modify. 178 | * @param {string} key The key of the value to remove. 179 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 180 | */ 181 | function hashDelete(key) { 182 | return this.has(key) && delete this.__data__[key]; 183 | } 184 | 185 | /** 186 | * Gets the hash value for `key`. 187 | * 188 | * @private 189 | * @name get 190 | * @memberOf Hash 191 | * @param {string} key The key of the value to get. 192 | * @returns {*} Returns the entry value. 193 | */ 194 | function hashGet(key) { 195 | var data = this.__data__; 196 | if (nativeCreate) { 197 | var result = data[key]; 198 | return result === HASH_UNDEFINED ? undefined : result; 199 | } 200 | return hasOwnProperty.call(data, key) ? data[key] : undefined; 201 | } 202 | 203 | /** 204 | * Checks if a hash value for `key` exists. 205 | * 206 | * @private 207 | * @name has 208 | * @memberOf Hash 209 | * @param {string} key The key of the entry to check. 210 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 211 | */ 212 | function hashHas(key) { 213 | var data = this.__data__; 214 | return nativeCreate 215 | ? data[key] !== undefined 216 | : hasOwnProperty.call(data, key); 217 | } 218 | 219 | /** 220 | * Sets the hash `key` to `value`. 221 | * 222 | * @private 223 | * @name set 224 | * @memberOf Hash 225 | * @param {string} key The key of the value to set. 226 | * @param {*} value The value to set. 227 | * @returns {Object} Returns the hash instance. 228 | */ 229 | function hashSet(key, value) { 230 | var data = this.__data__; 231 | data[key] = nativeCreate && value === undefined ? HASH_UNDEFINED : value; 232 | return this; 233 | } 234 | 235 | // Add methods to `Hash`. 236 | Hash.prototype.clear = hashClear; 237 | Hash.prototype['delete'] = hashDelete; 238 | Hash.prototype.get = hashGet; 239 | Hash.prototype.has = hashHas; 240 | Hash.prototype.set = hashSet; 241 | 242 | /** 243 | * Creates an list cache object. 244 | * 245 | * @private 246 | * @constructor 247 | * @param {Array} [entries] The key-value pairs to cache. 248 | */ 249 | function ListCache(entries) { 250 | var index = -1, 251 | length = entries ? entries.length : 0; 252 | 253 | this.clear(); 254 | while (++index < length) { 255 | var entry = entries[index]; 256 | this.set(entry[0], entry[1]); 257 | } 258 | } 259 | 260 | /** 261 | * Removes all key-value entries from the list cache. 262 | * 263 | * @private 264 | * @name clear 265 | * @memberOf ListCache 266 | */ 267 | function listCacheClear() { 268 | this.__data__ = []; 269 | } 270 | 271 | /** 272 | * Removes `key` and its value from the list cache. 273 | * 274 | * @private 275 | * @name delete 276 | * @memberOf ListCache 277 | * @param {string} key The key of the value to remove. 278 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 279 | */ 280 | function listCacheDelete(key) { 281 | var data = this.__data__, 282 | index = assocIndexOf(data, key); 283 | 284 | if (index < 0) { 285 | return false; 286 | } 287 | var lastIndex = data.length - 1; 288 | if (index == lastIndex) { 289 | data.pop(); 290 | } else { 291 | splice.call(data, index, 1); 292 | } 293 | return true; 294 | } 295 | 296 | /** 297 | * Gets the list cache value for `key`. 298 | * 299 | * @private 300 | * @name get 301 | * @memberOf ListCache 302 | * @param {string} key The key of the value to get. 303 | * @returns {*} Returns the entry value. 304 | */ 305 | function listCacheGet(key) { 306 | var data = this.__data__, 307 | index = assocIndexOf(data, key); 308 | 309 | return index < 0 ? undefined : data[index][1]; 310 | } 311 | 312 | /** 313 | * Checks if a list cache value for `key` exists. 314 | * 315 | * @private 316 | * @name has 317 | * @memberOf ListCache 318 | * @param {string} key The key of the entry to check. 319 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 320 | */ 321 | function listCacheHas(key) { 322 | return assocIndexOf(this.__data__, key) > -1; 323 | } 324 | 325 | /** 326 | * Sets the list cache `key` to `value`. 327 | * 328 | * @private 329 | * @name set 330 | * @memberOf ListCache 331 | * @param {string} key The key of the value to set. 332 | * @param {*} value The value to set. 333 | * @returns {Object} Returns the list cache instance. 334 | */ 335 | function listCacheSet(key, value) { 336 | var data = this.__data__, 337 | index = assocIndexOf(data, key); 338 | 339 | if (index < 0) { 340 | data.push([key, value]); 341 | } else { 342 | data[index][1] = value; 343 | } 344 | return this; 345 | } 346 | 347 | // Add methods to `ListCache`. 348 | ListCache.prototype.clear = listCacheClear; 349 | ListCache.prototype['delete'] = listCacheDelete; 350 | ListCache.prototype.get = listCacheGet; 351 | ListCache.prototype.has = listCacheHas; 352 | ListCache.prototype.set = listCacheSet; 353 | 354 | /** 355 | * Creates a map cache object to store key-value pairs. 356 | * 357 | * @private 358 | * @constructor 359 | * @param {Array} [entries] The key-value pairs to cache. 360 | */ 361 | function MapCache(entries) { 362 | var index = -1, 363 | length = entries ? entries.length : 0; 364 | 365 | this.clear(); 366 | while (++index < length) { 367 | var entry = entries[index]; 368 | this.set(entry[0], entry[1]); 369 | } 370 | } 371 | 372 | /** 373 | * Removes all key-value entries from the map. 374 | * 375 | * @private 376 | * @name clear 377 | * @memberOf MapCache 378 | */ 379 | function mapCacheClear() { 380 | this.__data__ = { 381 | hash: new Hash(), 382 | map: new (Map || ListCache)(), 383 | string: new Hash(), 384 | }; 385 | } 386 | 387 | /** 388 | * Removes `key` and its value from the map. 389 | * 390 | * @private 391 | * @name delete 392 | * @memberOf MapCache 393 | * @param {string} key The key of the value to remove. 394 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 395 | */ 396 | function mapCacheDelete(key) { 397 | return getMapData(this, key)['delete'](key); 398 | } 399 | 400 | /** 401 | * Gets the map value for `key`. 402 | * 403 | * @private 404 | * @name get 405 | * @memberOf MapCache 406 | * @param {string} key The key of the value to get. 407 | * @returns {*} Returns the entry value. 408 | */ 409 | function mapCacheGet(key) { 410 | return getMapData(this, key).get(key); 411 | } 412 | 413 | /** 414 | * Checks if a map value for `key` exists. 415 | * 416 | * @private 417 | * @name has 418 | * @memberOf MapCache 419 | * @param {string} key The key of the entry to check. 420 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 421 | */ 422 | function mapCacheHas(key) { 423 | return getMapData(this, key).has(key); 424 | } 425 | 426 | /** 427 | * Sets the map `key` to `value`. 428 | * 429 | * @private 430 | * @name set 431 | * @memberOf MapCache 432 | * @param {string} key The key of the value to set. 433 | * @param {*} value The value to set. 434 | * @returns {Object} Returns the map cache instance. 435 | */ 436 | function mapCacheSet(key, value) { 437 | getMapData(this, key).set(key, value); 438 | return this; 439 | } 440 | 441 | // Add methods to `MapCache`. 442 | MapCache.prototype.clear = mapCacheClear; 443 | MapCache.prototype['delete'] = mapCacheDelete; 444 | MapCache.prototype.get = mapCacheGet; 445 | MapCache.prototype.has = mapCacheHas; 446 | MapCache.prototype.set = mapCacheSet; 447 | 448 | /** 449 | * Assigns `value` to `key` of `object` if the existing value is not equivalent 450 | * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) 451 | * for equality comparisons. 452 | * 453 | * @private 454 | * @param {Object} object The object to modify. 455 | * @param {string} key The key of the property to assign. 456 | * @param {*} value The value to assign. 457 | */ 458 | function assignValue(object, key, value) { 459 | var objValue = object[key]; 460 | if ( 461 | !(hasOwnProperty.call(object, key) && eq(objValue, value)) || 462 | (value === undefined && !(key in object)) 463 | ) { 464 | object[key] = value; 465 | } 466 | } 467 | 468 | /** 469 | * Gets the index at which the `key` is found in `array` of key-value pairs. 470 | * 471 | * @private 472 | * @param {Array} array The array to inspect. 473 | * @param {*} key The key to search for. 474 | * @returns {number} Returns the index of the matched value, else `-1`. 475 | */ 476 | function assocIndexOf(array, key) { 477 | var length = array.length; 478 | while (length--) { 479 | if (eq(array[length][0], key)) { 480 | return length; 481 | } 482 | } 483 | return -1; 484 | } 485 | 486 | /** 487 | * The base implementation of `_.isNative` without bad shim checks. 488 | * 489 | * @private 490 | * @param {*} value The value to check. 491 | * @returns {boolean} Returns `true` if `value` is a native function, 492 | * else `false`. 493 | */ 494 | function baseIsNative(value) { 495 | if (!isObject(value) || isMasked(value)) { 496 | return false; 497 | } 498 | var pattern = 499 | isFunction(value) || isHostObject(value) ? reIsNative : reIsHostCtor; 500 | return pattern.test(toSource(value)); 501 | } 502 | 503 | /** 504 | * The base implementation of `_.set`. 505 | * 506 | * @private 507 | * @param {Object} object The object to modify. 508 | * @param {Array|string} path The path of the property to set. 509 | * @param {*} value The value to set. 510 | * @param {Function} [customizer] The function to customize path creation. 511 | * @returns {Object} Returns `object`. 512 | */ 513 | function baseSet(object, path, value, customizer) { 514 | if (!isObject(object)) { 515 | return object; 516 | } 517 | path = isKey(path, object) ? [path] : castPath(path); 518 | 519 | var index = -1, 520 | length = path.length, 521 | lastIndex = length - 1, 522 | nested = object; 523 | 524 | while (nested != null && ++index < length) { 525 | var key = toKey(path[index]), 526 | newValue = value; 527 | 528 | if (index != lastIndex) { 529 | var objValue = nested[key]; 530 | newValue = customizer ? customizer(objValue, key, nested) : undefined; 531 | if (newValue === undefined) { 532 | newValue = isObject(objValue) 533 | ? objValue 534 | : isIndex(path[index + 1]) 535 | ? [] 536 | : {}; 537 | } 538 | } 539 | assignValue(nested, key, newValue); 540 | nested = nested[key]; 541 | } 542 | return object; 543 | } 544 | 545 | /** 546 | * The base implementation of `_.toString` which doesn't convert nullish 547 | * values to empty strings. 548 | * 549 | * @private 550 | * @param {*} value The value to process. 551 | * @returns {string} Returns the string. 552 | */ 553 | function baseToString(value) { 554 | // Exit early for strings to avoid a performance hit in some environments. 555 | if (typeof value == 'string') { 556 | return value; 557 | } 558 | if (isSymbol(value)) { 559 | return symbolToString ? symbolToString.call(value) : ''; 560 | } 561 | var result = value + ''; 562 | return result == '0' && 1 / value == -INFINITY ? '-0' : result; 563 | } 564 | 565 | /** 566 | * Casts `value` to a path array if it's not one. 567 | * 568 | * @private 569 | * @param {*} value The value to inspect. 570 | * @returns {Array} Returns the cast property path array. 571 | */ 572 | function castPath(value) { 573 | return isArray(value) ? value : stringToPath(value); 574 | } 575 | 576 | /** 577 | * Gets the data for `map`. 578 | * 579 | * @private 580 | * @param {Object} map The map to query. 581 | * @param {string} key The reference key. 582 | * @returns {*} Returns the map data. 583 | */ 584 | function getMapData(map, key) { 585 | var data = map.__data__; 586 | return isKeyable(key) 587 | ? data[typeof key == 'string' ? 'string' : 'hash'] 588 | : data.map; 589 | } 590 | 591 | /** 592 | * Gets the native function at `key` of `object`. 593 | * 594 | * @private 595 | * @param {Object} object The object to query. 596 | * @param {string} key The key of the method to get. 597 | * @returns {*} Returns the function if it's native, else `undefined`. 598 | */ 599 | function getNative(object, key) { 600 | var value = getValue(object, key); 601 | return baseIsNative(value) ? value : undefined; 602 | } 603 | 604 | /** 605 | * Checks if `value` is a valid array-like index. 606 | * 607 | * @private 608 | * @param {*} value The value to check. 609 | * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. 610 | * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. 611 | */ 612 | function isIndex(value, length) { 613 | length = length == null ? MAX_SAFE_INTEGER : length; 614 | return ( 615 | !!length && 616 | (typeof value == 'number' || reIsUint.test(value)) && 617 | value > -1 && value % 1 == 0 && value < length 618 | ); 619 | } 620 | 621 | /** 622 | * Checks if `value` is a property name and not a property path. 623 | * 624 | * @private 625 | * @param {*} value The value to check. 626 | * @param {Object} [object] The object to query keys on. 627 | * @returns {boolean} Returns `true` if `value` is a property name, else `false`. 628 | */ 629 | function isKey(value, object) { 630 | if (isArray(value)) { 631 | return false; 632 | } 633 | var type = typeof value; 634 | if ( 635 | type == 'number' || 636 | type == 'symbol' || 637 | type == 'boolean' || 638 | value == null || 639 | isSymbol(value) 640 | ) { 641 | return true; 642 | } 643 | return ( 644 | reIsPlainProp.test(value) || 645 | !reIsDeepProp.test(value) || 646 | (object != null && value in Object(object)) 647 | ); 648 | } 649 | 650 | /** 651 | * Checks if `value` is suitable for use as unique object key. 652 | * 653 | * @private 654 | * @param {*} value The value to check. 655 | * @returns {boolean} Returns `true` if `value` is suitable, else `false`. 656 | */ 657 | function isKeyable(value) { 658 | var type = typeof value; 659 | return type == 'string' || 660 | type == 'number' || 661 | type == 'symbol' || 662 | type == 'boolean' 663 | ? value !== '__proto__' 664 | : value === null; 665 | } 666 | 667 | /** 668 | * Checks if `func` has its source masked. 669 | * 670 | * @private 671 | * @param {Function} func The function to check. 672 | * @returns {boolean} Returns `true` if `func` is masked, else `false`. 673 | */ 674 | function isMasked(func) { 675 | return !!maskSrcKey && maskSrcKey in func; 676 | } 677 | 678 | /** 679 | * Converts `string` to a property path array. 680 | * 681 | * @private 682 | * @param {string} string The string to convert. 683 | * @returns {Array} Returns the property path array. 684 | */ 685 | var stringToPath = memoize(function(string) { 686 | string = toString(string); 687 | 688 | var result = []; 689 | if (reLeadingDot.test(string)) { 690 | result.push(''); 691 | } 692 | string.replace(rePropName, function(match, number, quote, string) { 693 | result.push(quote ? string.replace(reEscapeChar, '$1') : number || match); 694 | }); 695 | return result; 696 | }); 697 | 698 | /** 699 | * Converts `value` to a string key if it's not a string or symbol. 700 | * 701 | * @private 702 | * @param {*} value The value to inspect. 703 | * @returns {string|symbol} Returns the key. 704 | */ 705 | function toKey(value) { 706 | if (typeof value == 'string' || isSymbol(value)) { 707 | return value; 708 | } 709 | var result = value + ''; 710 | return result == '0' && 1 / value == -INFINITY ? '-0' : result; 711 | } 712 | 713 | /** 714 | * Converts `func` to its source code. 715 | * 716 | * @private 717 | * @param {Function} func The function to process. 718 | * @returns {string} Returns the source code. 719 | */ 720 | function toSource(func) { 721 | if (func != null) { 722 | try { 723 | return funcToString.call(func); 724 | } catch (e) {} 725 | try { 726 | return func + ''; 727 | } catch (e) {} 728 | } 729 | return ''; 730 | } 731 | 732 | /** 733 | * Creates a function that memoizes the result of `func`. If `resolver` is 734 | * provided, it determines the cache key for storing the result based on the 735 | * arguments provided to the memoized function. By default, the first argument 736 | * provided to the memoized function is used as the map cache key. The `func` 737 | * is invoked with the `this` binding of the memoized function. 738 | * 739 | * **Note:** The cache is exposed as the `cache` property on the memoized 740 | * function. Its creation may be customized by replacing the `_.memoize.Cache` 741 | * constructor with one whose instances implement the 742 | * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) 743 | * method interface of `delete`, `get`, `has`, and `set`. 744 | * 745 | * @static 746 | * @memberOf _ 747 | * @since 0.1.0 748 | * @category Function 749 | * @param {Function} func The function to have its output memoized. 750 | * @param {Function} [resolver] The function to resolve the cache key. 751 | * @returns {Function} Returns the new memoized function. 752 | * @example 753 | * 754 | * var object = { 'a': 1, 'b': 2 }; 755 | * var other = { 'c': 3, 'd': 4 }; 756 | * 757 | * var values = _.memoize(_.values); 758 | * values(object); 759 | * // => [1, 2] 760 | * 761 | * values(other); 762 | * // => [3, 4] 763 | * 764 | * object.a = 2; 765 | * values(object); 766 | * // => [1, 2] 767 | * 768 | * // Modify the result cache. 769 | * values.cache.set(object, ['a', 'b']); 770 | * values(object); 771 | * // => ['a', 'b'] 772 | * 773 | * // Replace `_.memoize.Cache`. 774 | * _.memoize.Cache = WeakMap; 775 | */ 776 | function memoize(func, resolver) { 777 | if ( 778 | typeof func != 'function' || 779 | (resolver && typeof resolver != 'function') 780 | ) { 781 | throw new TypeError(FUNC_ERROR_TEXT); 782 | } 783 | var memoized = function() { 784 | var args = arguments, 785 | key = resolver ? resolver.apply(this, args) : args[0], 786 | cache = memoized.cache; 787 | 788 | if (cache.has(key)) { 789 | return cache.get(key); 790 | } 791 | var result = func.apply(this, args); 792 | memoized.cache = cache.set(key, result); 793 | return result; 794 | }; 795 | memoized.cache = new (memoize.Cache || MapCache)(); 796 | return memoized; 797 | } 798 | 799 | // Assign cache to `_.memoize`. 800 | memoize.Cache = MapCache; 801 | 802 | /** 803 | * Performs a 804 | * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) 805 | * comparison between two values to determine if they are equivalent. 806 | * 807 | * @static 808 | * @memberOf _ 809 | * @since 4.0.0 810 | * @category Lang 811 | * @param {*} value The value to compare. 812 | * @param {*} other The other value to compare. 813 | * @returns {boolean} Returns `true` if the values are equivalent, else `false`. 814 | * @example 815 | * 816 | * var object = { 'a': 1 }; 817 | * var other = { 'a': 1 }; 818 | * 819 | * _.eq(object, object); 820 | * // => true 821 | * 822 | * _.eq(object, other); 823 | * // => false 824 | * 825 | * _.eq('a', 'a'); 826 | * // => true 827 | * 828 | * _.eq('a', Object('a')); 829 | * // => false 830 | * 831 | * _.eq(NaN, NaN); 832 | * // => true 833 | */ 834 | function eq(value, other) { 835 | return value === other || (value !== value && other !== other); 836 | } 837 | 838 | /** 839 | * Checks if `value` is classified as an `Array` object. 840 | * 841 | * @static 842 | * @memberOf _ 843 | * @since 0.1.0 844 | * @category Lang 845 | * @param {*} value The value to check. 846 | * @returns {boolean} Returns `true` if `value` is an array, else `false`. 847 | * @example 848 | * 849 | * _.isArray([1, 2, 3]); 850 | * // => true 851 | * 852 | * _.isArray(document.body.children); 853 | * // => false 854 | * 855 | * _.isArray('abc'); 856 | * // => false 857 | * 858 | * _.isArray(_.noop); 859 | * // => false 860 | */ 861 | var isArray = Array.isArray; 862 | 863 | /** 864 | * Checks if `value` is classified as a `Function` object. 865 | * 866 | * @static 867 | * @memberOf _ 868 | * @since 0.1.0 869 | * @category Lang 870 | * @param {*} value The value to check. 871 | * @returns {boolean} Returns `true` if `value` is a function, else `false`. 872 | * @example 873 | * 874 | * _.isFunction(_); 875 | * // => true 876 | * 877 | * _.isFunction(/abc/); 878 | * // => false 879 | */ 880 | function isFunction(value) { 881 | // The use of `Object#toString` avoids issues with the `typeof` operator 882 | // in Safari 8-9 which returns 'object' for typed array and other constructors. 883 | var tag = isObject(value) ? objectToString.call(value) : ''; 884 | return tag == funcTag || tag == genTag; 885 | } 886 | 887 | /** 888 | * Checks if `value` is the 889 | * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) 890 | * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) 891 | * 892 | * @static 893 | * @memberOf _ 894 | * @since 0.1.0 895 | * @category Lang 896 | * @param {*} value The value to check. 897 | * @returns {boolean} Returns `true` if `value` is an object, else `false`. 898 | * @example 899 | * 900 | * _.isObject({}); 901 | * // => true 902 | * 903 | * _.isObject([1, 2, 3]); 904 | * // => true 905 | * 906 | * _.isObject(_.noop); 907 | * // => true 908 | * 909 | * _.isObject(null); 910 | * // => false 911 | */ 912 | function isObject(value) { 913 | var type = typeof value; 914 | return !!value && (type == 'object' || type == 'function'); 915 | } 916 | 917 | /** 918 | * Checks if `value` is object-like. A value is object-like if it's not `null` 919 | * and has a `typeof` result of "object". 920 | * 921 | * @static 922 | * @memberOf _ 923 | * @since 4.0.0 924 | * @category Lang 925 | * @param {*} value The value to check. 926 | * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 927 | * @example 928 | * 929 | * _.isObjectLike({}); 930 | * // => true 931 | * 932 | * _.isObjectLike([1, 2, 3]); 933 | * // => true 934 | * 935 | * _.isObjectLike(_.noop); 936 | * // => false 937 | * 938 | * _.isObjectLike(null); 939 | * // => false 940 | */ 941 | function isObjectLike(value) { 942 | return !!value && typeof value == 'object'; 943 | } 944 | 945 | /** 946 | * Checks if `value` is classified as a `Symbol` primitive or object. 947 | * 948 | * @static 949 | * @memberOf _ 950 | * @since 4.0.0 951 | * @category Lang 952 | * @param {*} value The value to check. 953 | * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. 954 | * @example 955 | * 956 | * _.isSymbol(Symbol.iterator); 957 | * // => true 958 | * 959 | * _.isSymbol('abc'); 960 | * // => false 961 | */ 962 | function isSymbol(value) { 963 | return ( 964 | typeof value == 'symbol' || 965 | (isObjectLike(value) && objectToString.call(value) == symbolTag) 966 | ); 967 | } 968 | 969 | /** 970 | * Converts `value` to a string. An empty string is returned for `null` 971 | * and `undefined` values. The sign of `-0` is preserved. 972 | * 973 | * @static 974 | * @memberOf _ 975 | * @since 4.0.0 976 | * @category Lang 977 | * @param {*} value The value to process. 978 | * @returns {string} Returns the string. 979 | * @example 980 | * 981 | * _.toString(null); 982 | * // => '' 983 | * 984 | * _.toString(-0); 985 | * // => '-0' 986 | * 987 | * _.toString([1, 2, 3]); 988 | * // => '1,2,3' 989 | */ 990 | function toString(value) { 991 | return value == null ? '' : baseToString(value); 992 | } 993 | 994 | /** 995 | * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, 996 | * it's created. Arrays are created for missing index properties while objects 997 | * are created for all other missing properties. Use `_.setWith` to customize 998 | * `path` creation. 999 | * 1000 | * **Note:** This method mutates `object`. 1001 | * 1002 | * @static 1003 | * @memberOf _ 1004 | * @since 3.7.0 1005 | * @category Object 1006 | * @param {Object} object The object to modify. 1007 | * @param {Array|string} path The path of the property to set. 1008 | * @param {*} value The value to set. 1009 | * @returns {Object} Returns `object`. 1010 | * @example 1011 | * 1012 | * var object = { 'a': [{ 'b': { 'c': 3 } }] }; 1013 | * 1014 | * _.set(object, 'a[0].b.c', 4); 1015 | * console.log(object.a[0].b.c); 1016 | * // => 4 1017 | * 1018 | * _.set(object, ['x', '0', 'y', 'z'], 5); 1019 | * console.log(object.x[0].y.z); 1020 | * // => 5 1021 | */ 1022 | function set(object, path, value) { 1023 | return object == null ? object : baseSet(object, path, value); 1024 | } 1025 | 1026 | module.exports = set; 1027 | -------------------------------------------------------------------------------- /src/final-form-validation.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createFinalFormValidation, 3 | FinalFormValidation, 4 | } from './final-form-validation'; 5 | import { 6 | ValidationSchema, 7 | ValidationResult, 8 | FieldValidationFunctionSync, 9 | RecordValidationFunctionAsync, 10 | RecordValidationFunctionSync, 11 | } from '@lemoncode/fonk'; 12 | 13 | describe('FormValidation', () => { 14 | it(`spec #1: should return an instance of FormValidation 15 |   when calling createFinalFormValidation 16 |   `, () => { 17 | // Arrange 18 | const validationSchema: ValidationSchema = {}; 19 | 20 | // Act 21 | const formValidation = createFinalFormValidation(validationSchema); 22 | 23 | // Assert 24 | expect(formValidation).toBeInstanceOf(FinalFormValidation); 25 | }); 26 | 27 | describe(`validateField`, () => { 28 | it(`spec #1:should execute a field validation (sync and using function in schema) and fail when 29 |     adding a field validation in the schema on a given field 30 |     firing a validation for that given field 31 |     `, done => { 32 | // Arrange 33 | const mockValidationFn = jest.fn().mockReturnValue({ 34 | type: 'MY_TYPE', 35 | succeeded: false, 36 | message: 'mymessage', 37 | }); 38 | 39 | const validationSchema: ValidationSchema = { 40 | field: { 41 | username: [mockValidationFn], 42 | }, 43 | }; 44 | 45 | // Act 46 | 47 | const formValidation = createFinalFormValidation(validationSchema); 48 | const result = formValidation.validateField('username', 'whatever'); 49 | 50 | // Assert 51 | result.then(validationResult => { 52 | expect(validationResult).toBe('mymessage'); 53 | expect(mockValidationFn).toHaveBeenCalled(); 54 | done(); 55 | }); 56 | }); 57 | 58 | it(`spec #2: should execute a field validation (async and using function in schema) and fail when 59 |     adding a field validation in the schema on a given field 60 |     firing a validation for that given field (include as well custom message override) 61 |     `, done => { 62 | // Arrange 63 | const mockValidationFn = jest.fn().mockResolvedValue({ 64 | type: 'MY_TYPE', 65 | succeeded: false, 66 | message: 'mymessage', 67 | }); 68 | 69 | const validationSchema: ValidationSchema = { 70 | field: { 71 | username: [mockValidationFn], 72 | }, 73 | }; 74 | 75 | // Act 76 | 77 | const formValidation = createFinalFormValidation(validationSchema); 78 | const result = formValidation.validateField('username', 'whatever'); 79 | 80 | // Assert 81 | result.then(validationResult => { 82 | expect(validationResult).toBe('mymessage'); 83 | expect(mockValidationFn).toHaveBeenCalled(); 84 | done(); 85 | }); 86 | }); 87 | 88 | it(`spec #3: should execute a field validation (defined as FullValidator, sync function in schema) and fail when 89 |     adding a field validation in the schema on a given field 90 |     firing a validation for that given field 91 |     `, done => { 92 | // Arrange 93 | const mockValidationFn = jest.fn( 94 | ({ message }): ValidationResult => ({ 95 | type: 'MY_TYPE', 96 | succeeded: false, 97 | message: message ? (message as string) : 'mymessage', 98 | }) 99 | ); 100 | 101 | const validationSchema: ValidationSchema = { 102 | field: { 103 | username: [ 104 | { 105 | validator: mockValidationFn, 106 | message: 'myoverriddenmessage', 107 | }, 108 | ], 109 | }, 110 | }; 111 | 112 | // Act 113 | 114 | const formValidation = createFinalFormValidation(validationSchema); 115 | const result = formValidation.validateField('username', 'whatever'); 116 | 117 | // Assert 118 | result.then(validationResult => { 119 | expect(validationResult).toBe('myoverriddenmessage'); 120 | expect(mockValidationFn).toHaveBeenCalled(); 121 | done(); 122 | }); 123 | }); 124 | 125 | it(`spec #4: should execute a field validation (defined as FullValidator, async function in schema) and fail when 126 |     adding a field validation in the schema on a given field 127 |     firing a validation for that given field 128 |     `, done => { 129 | // Arrange 130 | const mockValidationFn = jest.fn( 131 | ({ message }): Promise => 132 | Promise.resolve({ 133 | type: 'MY_TYPE', 134 | succeeded: false, 135 | message: message ? (message as string) : 'mymessage', 136 | }) 137 | ); 138 | 139 | const validationSchema: ValidationSchema = { 140 | field: { 141 | username: [ 142 | { 143 | validator: mockValidationFn, 144 | message: 'myoverriddenmessage', 145 | }, 146 | ], 147 | }, 148 | }; 149 | 150 | // Act 151 | 152 | const formValidation = createFinalFormValidation(validationSchema); 153 | const result = formValidation.validateField('username', 'whatever'); 154 | 155 | // Assert 156 | result.then(validationResult => { 157 | expect(validationResult).toBe('myoverriddenmessage'); 158 | expect(mockValidationFn).toHaveBeenCalled(); 159 | done(); 160 | }); 161 | }); 162 | 163 | it(`spec #5: should execute a field validation (defined as FullValidator, async function in schema) and fail when 164 |     adding a field validation in the schema, using all possible args 165 |     `, done => { 166 | // Arrange 167 | const mockValidationFn = jest.fn( 168 | ({ value, message, customArgs, values }): Promise => 169 | Promise.resolve({ 170 | type: 'MY_TYPE', 171 | succeeded: false, 172 | message: `${value} ${message} ${customArgs} ${values}`, 173 | }) 174 | ); 175 | 176 | const validationSchema: ValidationSchema = { 177 | field: { 178 | username: [ 179 | { 180 | validator: mockValidationFn, 181 | customArgs: 'custom-arg', 182 | message: 'myoverriddenmessage', 183 | }, 184 | ], 185 | }, 186 | }; 187 | 188 | // Act 189 | 190 | const formValidation = createFinalFormValidation(validationSchema); 191 | const result = formValidation.validateField( 192 | 'username', 193 | 'whatever', 194 | 'test-values' 195 | ); 196 | 197 | // Assert 198 | result.then(validationResult => { 199 | expect(validationResult).toBe( 200 | 'whatever myoverriddenmessage custom-arg test-values' 201 | ); 202 | expect(mockValidationFn).toHaveBeenCalled(); 203 | done(); 204 | }); 205 | }); 206 | 207 | it(`spec #6:should execute a field validation (sync and using full schema) passing 208 |       custom args and failed when customArgs.fail === true 209 |       `, done => { 210 | // Arrange 211 | const validator: FieldValidationFunctionSync = jest.fn( 212 | ({ customArgs }): ValidationResult => { 213 | if (customArgs['fail']) { 214 | return { 215 | type: 'MY_TYPE', 216 | succeeded: false, 217 | message: 'received custom args fail true', 218 | }; 219 | } else { 220 | return { 221 | type: 'MY_TYPE', 222 | succeeded: true, 223 | message: 'received custom args fail false', 224 | }; 225 | } 226 | } 227 | ); 228 | 229 | const validationSchema: ValidationSchema = { 230 | field: { 231 | username: [ 232 | { 233 | validator: validator, 234 | customArgs: { fail: true }, 235 | }, 236 | ], 237 | }, 238 | }; 239 | 240 | // Act 241 | 242 | const formValidation = createFinalFormValidation(validationSchema); 243 | const result = formValidation.validateField('username', 'whatever'); 244 | 245 | // Assert 246 | result.then(validationResult => { 247 | expect(validationResult).toBe('received custom args fail true'); 248 | expect(validator).toHaveBeenCalled(); 249 | done(); 250 | }); 251 | }); 252 | 253 | it(`spec #7:should execute a field validation (sync and using full schema) passing 254 |       custom args and succeeded when customArgs.fail === false 255 |       `, done => { 256 | // Arrange 257 | const validator: FieldValidationFunctionSync = jest.fn( 258 | ({ customArgs }): ValidationResult => { 259 | if (customArgs['fail']) { 260 | return { 261 | type: 'MY_TYPE', 262 | succeeded: false, 263 | message: 'received custom args fail true', 264 | }; 265 | } else { 266 | return { 267 | type: 'MY_TYPE', 268 | succeeded: true, 269 | message: 'received custom args fail false', 270 | }; 271 | } 272 | } 273 | ); 274 | 275 | const validationSchema: ValidationSchema = { 276 | field: { 277 | username: [ 278 | { 279 | validator: validator, 280 | customArgs: { fail: false }, 281 | }, 282 | ], 283 | }, 284 | }; 285 | 286 | // Act 287 | 288 | const formValidation = createFinalFormValidation(validationSchema); 289 | const result = formValidation.validateField('username', 'whatever'); 290 | 291 | // Assert 292 | result.then(validationResult => { 293 | expect(validationResult).toBeNull(); 294 | expect(validator).toHaveBeenCalled(); 295 | done(); 296 | }); 297 | }); 298 | 299 | it(`spec #8:should return succeed validation result 300 |     when adding two validators to a given field and both succeed 301 | `, done => { 302 | // Arrange 303 | const mockValidationFn1 = jest.fn().mockReturnValue({ 304 | type: 'MY_VALIDATOR_A', 305 | succeeded: true, 306 | message: 'mymessage', 307 | }); 308 | 309 | const mockValidationFn2 = jest.fn().mockReturnValue({ 310 | type: 'MY_VALIDATOR_B', 311 | succeeded: true, 312 | message: 'mymessage', 313 | }); 314 | 315 | const validationSchema: ValidationSchema = { 316 | field: { 317 | username: [mockValidationFn1, mockValidationFn2], 318 | }, 319 | }; 320 | 321 | // Act 322 | 323 | const formValidation = createFinalFormValidation(validationSchema); 324 | const result = formValidation.validateField('username', 'whatever'); 325 | 326 | // Assert 327 | result.then(validationResult => { 328 | expect(validationResult).toBeNull(); 329 | expect(mockValidationFn1).toHaveBeenCalled(); 330 | expect(mockValidationFn2).toHaveBeenCalled(); 331 | done(); 332 | }); 333 | }); 334 | 335 | it(`spec #9:should execute first validations for a given field and failed 336 | when adding two validators to a given field and first fails 337 | `, done => { 338 | // Arrange 339 | const mockValidationFn1 = jest.fn().mockReturnValue({ 340 | type: 'MY_VALIDATOR_A', 341 | succeeded: false, 342 | message: 'mymessageA', 343 | }); 344 | 345 | const mockValidationFn2 = jest.fn().mockReturnValue({ 346 | type: 'MY_VALIDATOR_B', 347 | succeeded: true, 348 | message: 'mymessageB', 349 | }); 350 | 351 | const validationSchema: ValidationSchema = { 352 | field: { 353 | username: [mockValidationFn1, mockValidationFn2], 354 | }, 355 | }; 356 | 357 | // Act 358 | 359 | const formValidation = createFinalFormValidation(validationSchema); 360 | const result = formValidation.validateField('username', 'whatever'); 361 | 362 | // Assert 363 | result.then(validationResult => { 364 | expect(validationResult).toBe('mymessageA'); 365 | expect(mockValidationFn1).toHaveBeenCalled(); 366 | expect(mockValidationFn2).not.toHaveBeenCalled(); 367 | done(); 368 | }); 369 | }); 370 | 371 | it(`spec #10:should execute two validations for a given field and failed 372 | when adding two validators to a given field and second fails 373 | `, done => { 374 | // Arrange 375 | const mockValidationFn1 = jest.fn().mockReturnValue({ 376 | type: 'MY_VALIDATOR_A', 377 | succeeded: true, 378 | message: 'mymessageA', 379 | }); 380 | 381 | const mockValidationFn2 = jest.fn().mockReturnValue({ 382 | type: 'MY_VALIDATOR_B', 383 | succeeded: false, 384 | message: 'mymessageB', 385 | }); 386 | 387 | const validationSchema: ValidationSchema = { 388 | field: { 389 | username: [mockValidationFn1, mockValidationFn2], 390 | }, 391 | }; 392 | 393 | // Act 394 | 395 | const formValidation = createFinalFormValidation(validationSchema); 396 | const result = formValidation.validateField('username', 'whatever'); 397 | 398 | // Assert 399 | result.then(validationResult => { 400 | expect(validationResult).toBe('mymessageB'); 401 | expect(mockValidationFn1).toHaveBeenCalled(); 402 | expect(mockValidationFn2).toHaveBeenCalled(); 403 | done(); 404 | }); 405 | }); 406 | 407 | it(`spec #11:should execute first validation for a given field and failed 408 | when adding two validators to a given field fails and second fails 409 | `, done => { 410 | // Arrange 411 | const mockValidationFn1 = jest.fn().mockReturnValue({ 412 | type: 'MY_VALIDATOR_A', 413 | succeeded: false, 414 | message: 'mymessageA', 415 | }); 416 | 417 | const mockValidationFn2 = jest.fn().mockReturnValue({ 418 | type: 'MY_VALIDATOR_B', 419 | succeeded: false, 420 | message: 'mymessageB', 421 | }); 422 | 423 | const validationSchema: ValidationSchema = { 424 | field: { 425 | username: [mockValidationFn1, mockValidationFn2], 426 | }, 427 | }; 428 | 429 | // Act 430 | 431 | const formValidation = createFinalFormValidation(validationSchema); 432 | const result = formValidation.validateField('username', 'whatever'); 433 | 434 | // Assert 435 | result.then(validationResult => { 436 | expect(validationResult).toBe('mymessageA'); 437 | expect(mockValidationFn1).toHaveBeenCalled(); 438 | expect(mockValidationFn2).not.toHaveBeenCalled(); 439 | done(); 440 | }); 441 | }); 442 | 443 | it(`spec #12:should execute validation for a given field and failed 444 | when adding one validator to a given nested field 445 | `, done => { 446 | // Arrange 447 | const mockValidationFn = jest.fn().mockReturnValue({ 448 | type: 'MY_VALIDATOR_A', 449 | succeeded: false, 450 | message: 'mymessageA', 451 | }); 452 | 453 | const validationSchema: ValidationSchema = { 454 | field: { 455 | 'nested.field': [mockValidationFn], 456 | }, 457 | }; 458 | 459 | // Act 460 | 461 | const formValidation = createFinalFormValidation(validationSchema); 462 | const result = formValidation.validateField('nested.field', 'whatever'); 463 | 464 | // Assert 465 | result.then(validationResult => { 466 | expect(validationResult).toBe('mymessageA'); 467 | expect(mockValidationFn).toHaveBeenCalled(); 468 | done(); 469 | }); 470 | }); 471 | 472 | it(`spec #13:should execute validation for a given field and failed 473 | when adding one validator to a given nested field with kebap case 474 | `, done => { 475 | // Arrange 476 | const mockValidationFn = jest.fn().mockReturnValue({ 477 | type: 'MY_VALIDATOR_A', 478 | succeeded: false, 479 | message: 'mymessageA', 480 | }); 481 | 482 | const validationSchema: ValidationSchema = { 483 | field: { 484 | 'this-is-a-nested.field': [mockValidationFn], 485 | }, 486 | }; 487 | 488 | // Act 489 | 490 | const formValidation = createFinalFormValidation(validationSchema); 491 | const result = formValidation.validateField( 492 | 'this-is-a-nested.field', 493 | 'whatever' 494 | ); 495 | 496 | // Assert 497 | result.then(validationResult => { 498 | expect(validationResult).toBe('mymessageA'); 499 | expect(mockValidationFn).toHaveBeenCalled(); 500 | done(); 501 | }); 502 | }); 503 | }); 504 | 505 | describe('validateRecords', () => { 506 | it(`#Spec 1: should failed form validation 507 |     when adding a record validation that fails (sync flavour function) 508 |     `, done => { 509 | // Arrange 510 | const mockValidationFn: RecordValidationFunctionSync = jest 511 | .fn() 512 | .mockReturnValue({ 513 | type: '', 514 | succeeded: false, 515 | message: 'mymessageA', 516 | }); 517 | 518 | const validationSchema: ValidationSchema = { 519 | record: { 520 | MY_RECORD_VALIDATION: [mockValidationFn], 521 | }, 522 | }; 523 | 524 | const values = {}; 525 | 526 | // Act 527 | 528 | const formValidation = createFinalFormValidation(validationSchema); 529 | const result = formValidation.validateRecord(values); 530 | 531 | // Assert 532 | result.then(validationResult => { 533 | expect(mockValidationFn).toHaveBeenCalled(); 534 | expect(validationResult).toEqual({ 535 | recordErrors: { 536 | MY_RECORD_VALIDATION: 'mymessageA', 537 | }, 538 | }); 539 | done(); 540 | }); 541 | }); 542 | 543 | it(`#Spec 2: should failed form validation 544 |     when adding a record validation that fails (async flavour function) 545 |     `, done => { 546 | // Arrange 547 | const mockValidationFn: RecordValidationFunctionSync = jest 548 | .fn() 549 | .mockResolvedValue({ 550 | type: '', 551 | succeeded: false, 552 | message: 'mymessageA', 553 | }); 554 | 555 | const validationSchema: ValidationSchema = { 556 | record: { 557 | MY_RECORD_VALIDATION: [mockValidationFn], 558 | }, 559 | }; 560 | 561 | const values = {}; 562 | 563 | // Act 564 | const formValidation = createFinalFormValidation(validationSchema); 565 | const result = formValidation.validateRecord(values); 566 | 567 | // Assert 568 | result.then(validationResult => { 569 | expect(mockValidationFn).toHaveBeenCalled(); 570 | expect(validationResult).toEqual({ 571 | recordErrors: { 572 | MY_RECORD_VALIDATION: 'mymessageA', 573 | }, 574 | }); 575 | done(); 576 | }); 577 | }); 578 | 579 | it(`#Spec 3: should failed form validation 580 |     when adding a record validation that fails (fullRecordValidationSchema entry, async validator) 581 |     `, done => { 582 | // Arrange 583 | const validationFn: RecordValidationFunctionAsync = jest.fn( 584 | ({ message }) => 585 | Promise.resolve({ 586 | type: '', 587 | succeeded: false, 588 | message: message ? (message as string) : 'mymessageA', 589 | }) 590 | ); 591 | 592 | const validationSchema: ValidationSchema = { 593 | record: { 594 | MY_RECORD_VALIDATION: [ 595 | { validator: validationFn, message: 'My custom message' }, 596 | ], 597 | }, 598 | }; 599 | 600 | const values = {}; 601 | 602 | // Act 603 | 604 | const formValidation = createFinalFormValidation(validationSchema); 605 | const result = formValidation.validateRecord(values); 606 | 607 | // Assert 608 | result.then(validationResult => { 609 | expect(validationFn).toHaveBeenCalled(); 610 | expect(validationResult).toEqual({ 611 | recordErrors: { 612 | MY_RECORD_VALIDATION: 'My custom message', 613 | }, 614 | }); 615 | done(); 616 | }); 617 | }); 618 | 619 | it(`#Spec 4: should failed form validation 620 |     when adding a record validation that fails (fullRecordValidationSchema entry, sync validator) 621 |     `, done => { 622 | // Arrange 623 | const validationFn: RecordValidationFunctionSync = jest.fn( 624 | ({ message }) => ({ 625 | type: '', 626 | succeeded: false, 627 | message: message ? (message as string) : 'mymessageA', 628 | }) 629 | ); 630 | 631 | const validationSchema: ValidationSchema = { 632 | record: { 633 | MY_RECORD_VALIDATION: [ 634 | { validator: validationFn, message: 'My custom message' }, 635 | ], 636 | }, 637 | }; 638 | 639 | const values = {}; 640 | 641 | // Act 642 | 643 | const formValidation = createFinalFormValidation(validationSchema); 644 | const result = formValidation.validateRecord(values); 645 | 646 | // Assert 647 | result.then(validationResult => { 648 | expect(validationFn).toHaveBeenCalled(); 649 | expect(validationResult).toEqual({ 650 | recordErrors: { 651 | MY_RECORD_VALIDATION: 'My custom message', 652 | }, 653 | }); 654 | done(); 655 | }); 656 | }); 657 | 658 | it(`#Spec 5: should failed form validation, and return back one validationResult on forms 659 |     when adding one record with two validation first fails, second succeeds 660 |     `, done => { 661 | // Arrange 662 | const validationFn1: RecordValidationFunctionSync = jest.fn( 663 | ({ message }) => ({ 664 | type: '', 665 | succeeded: false, 666 | message: message ? (message as string) : 'mymessageA', 667 | }) 668 | ); 669 | 670 | const validationFn2: RecordValidationFunctionSync = jest.fn( 671 | ({ message }) => ({ 672 | type: '', 673 | succeeded: true, 674 | message: message ? (message as string) : 'mymessageB', 675 | }) 676 | ); 677 | 678 | const validationSchema: ValidationSchema = { 679 | record: { 680 | MY_RECORD_VALIDATION: [validationFn1, validationFn2], 681 | }, 682 | }; 683 | 684 | const values = {}; 685 | 686 | // Act 687 | 688 | const formValidation = createFinalFormValidation(validationSchema); 689 | const result = formValidation.validateRecord(values); 690 | 691 | // Assert 692 | result.then(validationResult => { 693 | expect(validationFn1).toHaveBeenCalled(); 694 | expect(validationFn2).not.toHaveBeenCalled(); 695 | expect(validationResult).toEqual({ 696 | recordErrors: { 697 | MY_RECORD_VALIDATION: 'mymessageA', 698 | }, 699 | }); 700 | done(); 701 | }); 702 | }); 703 | 704 | it(`#Spec 6: should failed form validation, and return back one validationResult on forms 705 |     when adding one record with two validation first succeeds, second fails 706 |     `, done => { 707 | // Arrange 708 | const validationFn1: RecordValidationFunctionSync = jest.fn( 709 | ({ message }) => ({ 710 | type: '', 711 | succeeded: true, 712 | message: message ? (message as string) : 'mymessageA', 713 | }) 714 | ); 715 | 716 | const validationFn2: RecordValidationFunctionSync = jest.fn( 717 | ({ message }) => ({ 718 | type: '', 719 | succeeded: false, 720 | message: message ? (message as string) : 'mymessageB', 721 | }) 722 | ); 723 | 724 | const validationSchema: ValidationSchema = { 725 | record: { 726 | MY_RECORD_VALIDATION: [validationFn1, validationFn2], 727 | }, 728 | }; 729 | 730 | const values = {}; 731 | 732 | // Act 733 | 734 | const formValidation = createFinalFormValidation(validationSchema); 735 | const result = formValidation.validateRecord(values); 736 | 737 | // Assert 738 | result.then(validationResult => { 739 | expect(validationFn1).toHaveBeenCalled(); 740 | expect(validationFn2).toHaveBeenCalled(); 741 | expect(validationResult).toEqual({ 742 | recordErrors: { 743 | MY_RECORD_VALIDATION: 'mymessageB', 744 | }, 745 | }); 746 | done(); 747 | }); 748 | }); 749 | 750 | it(`#Spec 7: should failed form validation, and return back one validationResult on forms 751 |     when adding one record with two validation first fails, second fails 752 |     `, done => { 753 | // Arrange 754 | const validationFn1: RecordValidationFunctionSync = jest.fn( 755 | ({ message }) => ({ 756 | type: '', 757 | succeeded: false, 758 | message: message ? (message as string) : 'mymessageA', 759 | }) 760 | ); 761 | 762 | const validationFn2: RecordValidationFunctionSync = jest.fn( 763 | ({ message }) => ({ 764 | type: '', 765 | succeeded: false, 766 | message: message ? (message as string) : 'mymessageB', 767 | }) 768 | ); 769 | 770 | const validationSchema: ValidationSchema = { 771 | record: { 772 | MY_RECORD_VALIDATION: [validationFn1, validationFn2], 773 | }, 774 | }; 775 | 776 | const values = {}; 777 | 778 | // Act 779 | 780 | const formValidation = createFinalFormValidation(validationSchema); 781 | const result = formValidation.validateRecord(values); 782 | 783 | // Assert 784 | result.then(validationResult => { 785 | expect(validationFn1).toHaveBeenCalled(); 786 | expect(validationFn2).not.toHaveBeenCalled(); 787 | expect(validationResult).toEqual({ 788 | recordErrors: { 789 | MY_RECORD_VALIDATION: 'mymessageA', 790 | }, 791 | }); 792 | done(); 793 | }); 794 | }); 795 | 796 | it(`#Spec 8: should succed form validation, and return back one validationResult on forms 797 |     when adding one record with two validation first and second succeded 798 |     `, done => { 799 | // Arrange 800 | const validationFn1: RecordValidationFunctionSync = jest.fn( 801 | ({ message }) => ({ 802 | type: '', 803 | succeeded: true, 804 | message: message ? (message as string) : 'mymessageA', 805 | }) 806 | ); 807 | 808 | const validationFn2: RecordValidationFunctionSync = jest.fn( 809 | ({ message }) => ({ 810 | type: '', 811 | succeeded: true, 812 | message: message ? (message as string) : 'mymessageB', 813 | }) 814 | ); 815 | 816 | const validationSchema: ValidationSchema = { 817 | record: { 818 | MY_RECORD_VALIDATION: [validationFn1, validationFn2], 819 | }, 820 | }; 821 | 822 | const values = {}; 823 | 824 | // Act 825 | 826 | const formValidation = createFinalFormValidation(validationSchema); 827 | const result = formValidation.validateRecord(values); 828 | 829 | // Assert 830 | result.then(validationResult => { 831 | expect(validationFn1).toHaveBeenCalled(); 832 | expect(validationFn2).toHaveBeenCalled(); 833 | expect(validationResult).toBeNull(); 834 | done(); 835 | }); 836 | }); 837 | 838 | it(`#Spec 9: should fail form validation, and return back two validationResult on forms 839 |     when adding two record with one validation first and second fails 840 |     `, done => { 841 | // Arrange 842 | const validationFn1: RecordValidationFunctionSync = jest.fn( 843 | ({ message }) => ({ 844 | type: '', 845 | succeeded: false, 846 | message: message ? (message as string) : 'mymessageA', 847 | }) 848 | ); 849 | 850 | const validationFn2: RecordValidationFunctionSync = jest.fn( 851 | ({ message }) => ({ 852 | type: '', 853 | succeeded: false, 854 | message: message ? (message as string) : 'mymessageB', 855 | }) 856 | ); 857 | 858 | const validationSchema: ValidationSchema = { 859 | record: { 860 | MY_RECORD_VALIDATION1: [validationFn1], 861 | MY_RECORD_VALIDATION2: [validationFn2], 862 | }, 863 | }; 864 | 865 | const values = {}; 866 | 867 | // Act 868 | 869 | const formValidation = createFinalFormValidation(validationSchema); 870 | const result = formValidation.validateRecord(values); 871 | 872 | // Assert 873 | result.then(validationResult => { 874 | expect(validationFn1).toHaveBeenCalled(); 875 | expect(validationFn2).toHaveBeenCalled(); 876 | expect(validationResult).toEqual({ 877 | recordErrors: { 878 | MY_RECORD_VALIDATION1: 'mymessageA', 879 | MY_RECORD_VALIDATION2: 'mymessageB', 880 | }, 881 | }); 882 | done(); 883 | }); 884 | }); 885 | }); 886 | 887 | describe(`validateForm`, () => { 888 | it(`#Spec 1: should failed form validation 889 |     when adding a record validation that fails (sync flavour function) 890 |     `, done => { 891 | // Arrange 892 | const mockValidationFn: RecordValidationFunctionSync = jest 893 | .fn() 894 | .mockReturnValue({ 895 | type: '', 896 | succeeded: false, 897 | message: 'mymessageA', 898 | }); 899 | 900 | const validationSchema: ValidationSchema = { 901 | record: { 902 | MY_RECORD_VALIDATION: [mockValidationFn], 903 | }, 904 | }; 905 | 906 | const values = {}; 907 | 908 | // Act 909 | 910 | const formValidation = createFinalFormValidation(validationSchema); 911 | const result = formValidation.validateForm(values); 912 | 913 | // Assert 914 | result.then(validationResult => { 915 | expect(mockValidationFn).toHaveBeenCalled(); 916 | expect(validationResult).toEqual({ 917 | recordErrors: { 918 | MY_RECORD_VALIDATION: 'mymessageA', 919 | }, 920 | }); 921 | done(); 922 | }); 923 | }); 924 | 925 | it(`#Spec 2: should failed form validation 926 |     when adding a record validation that fails (async flavour function) 927 |     `, done => { 928 | // Arrange 929 | const mockValidationFn: RecordValidationFunctionSync = jest 930 | .fn() 931 | .mockResolvedValue({ 932 | type: '', 933 | succeeded: false, 934 | message: 'mymessageA', 935 | }); 936 | 937 | const validationSchema: ValidationSchema = { 938 | record: { 939 | MY_RECORD_VALIDATION: [mockValidationFn], 940 | }, 941 | }; 942 | 943 | const values = {}; 944 | 945 | // Act 946 | 947 | const formValidation = createFinalFormValidation(validationSchema); 948 | const result = formValidation.validateForm(values); 949 | 950 | // Assert 951 | result.then(validationResult => { 952 | expect(mockValidationFn).toHaveBeenCalled(); 953 | expect(validationResult).toEqual({ 954 | recordErrors: { 955 | MY_RECORD_VALIDATION: 'mymessageA', 956 | }, 957 | }); 958 | done(); 959 | }); 960 | }); 961 | 962 | it(`#Spec 3: should failed form validation 963 |     when adding a record validation that fails (fullRecordValidationSchema entry, async validator) 964 |     `, done => { 965 | // Arrange 966 | const validationFn: RecordValidationFunctionAsync = jest.fn( 967 | ({ message }) => 968 | Promise.resolve({ 969 | type: '', 970 | succeeded: false, 971 | message: message ? (message as string) : 'mymessageA', 972 | }) 973 | ); 974 | 975 | const validationSchema: ValidationSchema = { 976 | record: { 977 | MY_RECORD_VALIDATION: [ 978 | { validator: validationFn, message: 'My custom message' }, 979 | ], 980 | }, 981 | }; 982 | 983 | const values = {}; 984 | 985 | // Act 986 | 987 | const formValidation = createFinalFormValidation(validationSchema); 988 | const result = formValidation.validateForm(values); 989 | 990 | // Assert 991 | result.then(validationResult => { 992 | expect(validationFn).toHaveBeenCalled(); 993 | expect(validationResult).toEqual({ 994 | recordErrors: { 995 | MY_RECORD_VALIDATION: 'My custom message', 996 | }, 997 | }); 998 | done(); 999 | }); 1000 | }); 1001 | 1002 | it(`#Spec 4: should failed form validation 1003 |     when adding a record validation that fails (fullRecordValidationSchema entry, sync validator) 1004 |     `, done => { 1005 | // Arrange 1006 | const validationFn: RecordValidationFunctionSync = jest.fn( 1007 | ({ message }) => ({ 1008 | type: '', 1009 | succeeded: false, 1010 | message: message ? (message as string) : 'mymessageA', 1011 | }) 1012 | ); 1013 | 1014 | const validationSchema: ValidationSchema = { 1015 | record: { 1016 | MY_RECORD_VALIDATION: [ 1017 | { validator: validationFn, message: 'My custom message' }, 1018 | ], 1019 | }, 1020 | }; 1021 | 1022 | const values = {}; 1023 | 1024 | // Act 1025 | 1026 | const formValidation = createFinalFormValidation(validationSchema); 1027 | const result = formValidation.validateForm(values); 1028 | 1029 | // Assert 1030 | result.then(validationResult => { 1031 | expect(validationFn).toHaveBeenCalled(); 1032 | expect(validationResult).toEqual({ 1033 | recordErrors: { 1034 | MY_RECORD_VALIDATION: 'My custom message', 1035 | }, 1036 | }); 1037 | done(); 1038 | }); 1039 | }); 1040 | 1041 | it(`#Spec 5: should failed form validation, and return back one validationResult on forms 1042 |     when adding one record with two validation first fails, second succeeds 1043 |     `, done => { 1044 | // Arrange 1045 | const validationFn1: RecordValidationFunctionSync = jest.fn( 1046 | ({ message }) => ({ 1047 | type: '', 1048 | succeeded: false, 1049 | message: message ? (message as string) : 'mymessageA', 1050 | }) 1051 | ); 1052 | 1053 | const validationFn2: RecordValidationFunctionSync = jest.fn( 1054 | ({ message }) => ({ 1055 | type: '', 1056 | succeeded: true, 1057 | message: message ? (message as string) : 'mymessageB', 1058 | }) 1059 | ); 1060 | 1061 | const validationSchema: ValidationSchema = { 1062 | record: { 1063 | MY_RECORD_VALIDATION: [validationFn1, validationFn2], 1064 | }, 1065 | }; 1066 | 1067 | const values = {}; 1068 | 1069 | // Act 1070 | 1071 | const formValidation = createFinalFormValidation(validationSchema); 1072 | const result = formValidation.validateForm(values); 1073 | 1074 | // Assert 1075 | result.then(validationResult => { 1076 | expect(validationFn1).toHaveBeenCalled(); 1077 | expect(validationFn2).not.toHaveBeenCalled(); 1078 | expect(validationResult).toEqual({ 1079 | recordErrors: { 1080 | MY_RECORD_VALIDATION: 'mymessageA', 1081 | }, 1082 | }); 1083 | done(); 1084 | }); 1085 | }); 1086 | 1087 | it(`#Spec 6: should failed form validation, and return back one validationResult on forms 1088 |     when adding one record with two validation first succeeds, second fails 1089 |     `, done => { 1090 | // Arrange 1091 | const validationFn1: RecordValidationFunctionSync = jest.fn( 1092 | ({ message }) => ({ 1093 | type: '', 1094 | succeeded: true, 1095 | message: message ? (message as string) : 'mymessageA', 1096 | }) 1097 | ); 1098 | 1099 | const validationFn2: RecordValidationFunctionSync = jest.fn( 1100 | ({ message }) => ({ 1101 | type: '', 1102 | succeeded: false, 1103 | message: message ? (message as string) : 'mymessageB', 1104 | }) 1105 | ); 1106 | 1107 | const validationSchema: ValidationSchema = { 1108 | record: { 1109 | MY_RECORD_VALIDATION: [validationFn1, validationFn2], 1110 | }, 1111 | }; 1112 | 1113 | const values = {}; 1114 | 1115 | // Act 1116 | 1117 | const formValidation = createFinalFormValidation(validationSchema); 1118 | const result = formValidation.validateForm(values); 1119 | 1120 | // Assert 1121 | result.then(validationResult => { 1122 | expect(validationFn1).toHaveBeenCalled(); 1123 | expect(validationFn2).toHaveBeenCalled(); 1124 | expect(validationResult).toEqual({ 1125 | recordErrors: { 1126 | MY_RECORD_VALIDATION: 'mymessageB', 1127 | }, 1128 | }); 1129 | done(); 1130 | }); 1131 | }); 1132 | 1133 | it(`#Spec 7: should failed form validation, and return back one validationResult on forms 1134 |     when adding one record with two validation first fails, second fails 1135 |     `, done => { 1136 | // Arrange 1137 | const validationFn1: RecordValidationFunctionSync = jest.fn( 1138 | ({ message }) => ({ 1139 | type: '', 1140 | succeeded: false, 1141 | message: message ? (message as string) : 'mymessageA', 1142 | }) 1143 | ); 1144 | 1145 | const validationFn2: RecordValidationFunctionSync = jest.fn( 1146 | ({ message }) => ({ 1147 | type: '', 1148 | succeeded: false, 1149 | message: message ? (message as string) : 'mymessageB', 1150 | }) 1151 | ); 1152 | 1153 | const validationSchema: ValidationSchema = { 1154 | record: { 1155 | MY_RECORD_VALIDATION: [validationFn1, validationFn2], 1156 | }, 1157 | }; 1158 | 1159 | const values = {}; 1160 | 1161 | // Act 1162 | 1163 | const formValidation = createFinalFormValidation(validationSchema); 1164 | const result = formValidation.validateForm(values); 1165 | 1166 | // Assert 1167 | result.then(validationResult => { 1168 | expect(validationFn1).toHaveBeenCalled(); 1169 | expect(validationFn2).not.toHaveBeenCalled(); 1170 | expect(validationResult).toEqual({ 1171 | recordErrors: { 1172 | MY_RECORD_VALIDATION: 'mymessageA', 1173 | }, 1174 | }); 1175 | done(); 1176 | }); 1177 | }); 1178 | 1179 | it(`#Spec 8: should succed form validation, and return back one validationResult on forms 1180 |     when adding one record with two validation first and second succeded 1181 |     `, done => { 1182 | // Arrange 1183 | const validationFn1: RecordValidationFunctionSync = jest.fn( 1184 | ({ message }) => ({ 1185 | type: '', 1186 | succeeded: true, 1187 | message: message ? (message as string) : 'mymessageA', 1188 | }) 1189 | ); 1190 | 1191 | const validationFn2: RecordValidationFunctionSync = jest.fn( 1192 | ({ message }) => ({ 1193 | type: '', 1194 | succeeded: true, 1195 | message: message ? (message as string) : 'mymessageB', 1196 | }) 1197 | ); 1198 | 1199 | const validationSchema: ValidationSchema = { 1200 | record: { 1201 | MY_RECORD_VALIDATION: [validationFn1, validationFn2], 1202 | }, 1203 | }; 1204 | 1205 | const values = {}; 1206 | 1207 | // Act 1208 | 1209 | const formValidation = createFinalFormValidation(validationSchema); 1210 | const result = formValidation.validateForm(values); 1211 | 1212 | // Assert 1213 | result.then(validationResult => { 1214 | expect(validationFn1).toHaveBeenCalled(); 1215 | expect(validationFn2).toHaveBeenCalled(); 1216 | expect(validationResult).toBeNull(); 1217 | done(); 1218 | }); 1219 | }); 1220 | 1221 | it(`#Spec 9: should fail form validation, and return back two validationResult on forms 1222 |     when adding two record with one validation first and second fails 1223 |     `, done => { 1224 | // Arrange 1225 | const validationFn1: RecordValidationFunctionSync = jest.fn( 1226 | ({ message }) => ({ 1227 | type: '', 1228 | succeeded: false, 1229 | message: message ? (message as string) : 'mymessageA', 1230 | }) 1231 | ); 1232 | 1233 | const validationFn2: RecordValidationFunctionSync = jest.fn( 1234 | ({ message }) => ({ 1235 | type: '', 1236 | succeeded: false, 1237 | message: message ? (message as string) : 'mymessageB', 1238 | }) 1239 | ); 1240 | 1241 | const validationSchema: ValidationSchema = { 1242 | record: { 1243 | MY_RECORD_VALIDATION1: [validationFn1], 1244 | MY_RECORD_VALIDATION2: [validationFn2], 1245 | }, 1246 | }; 1247 | 1248 | const values = {}; 1249 | 1250 | // Act 1251 | 1252 | const formValidation = createFinalFormValidation(validationSchema); 1253 | const result = formValidation.validateForm(values); 1254 | 1255 | // Assert 1256 | result.then(validationResult => { 1257 | expect(validationFn1).toHaveBeenCalled(); 1258 | expect(validationFn2).toHaveBeenCalled(); 1259 | expect(validationResult).toEqual({ 1260 | recordErrors: { 1261 | MY_RECORD_VALIDATION1: 'mymessageA', 1262 | MY_RECORD_VALIDATION2: 'mymessageB', 1263 | }, 1264 | }); 1265 | done(); 1266 | }); 1267 | }); 1268 | 1269 | it(`#Spec 10: should failed form validation, and return back a field validation result 1270 |     a form validation result 1271 |     when adding one field validation that fails and record validation that fails 1272 |     `, done => { 1273 | // Arrange 1274 | const myFieldValidation: FieldValidationFunctionSync = jest.fn( 1275 | fieldValidatorArgs => ({ 1276 | type: 'MY_TYPE', 1277 | succeeded: false, 1278 | message: 'mymessageA', 1279 | }) 1280 | ); 1281 | 1282 | const myRecordValidation: RecordValidationFunctionSync = jest.fn( 1283 | ({ message }) => ({ 1284 | type: '', 1285 | succeeded: false, 1286 | message: message ? (message as string) : 'mymessageB', 1287 | }) 1288 | ); 1289 | 1290 | const validationSchema: ValidationSchema = { 1291 | field: { 1292 | username: [myFieldValidation], 1293 | }, 1294 | record: { 1295 | MY_RECORD_VALIDATION: [myRecordValidation], 1296 | }, 1297 | }; 1298 | 1299 | const values = {}; 1300 | 1301 | // Act 1302 | 1303 | const formValidation = createFinalFormValidation(validationSchema); 1304 | const result = formValidation.validateForm(values); 1305 | 1306 | // Assert 1307 | result.then(validationResult => { 1308 | expect(myFieldValidation).toHaveBeenCalled(); 1309 | expect(myRecordValidation).toHaveBeenCalled(); 1310 | expect(validationResult).toEqual({ 1311 | username: 'mymessageA', 1312 | recordErrors: { 1313 | MY_RECORD_VALIDATION: 'mymessageB', 1314 | }, 1315 | }); 1316 | done(); 1317 | }); 1318 | }); 1319 | 1320 | it(`#Spec 11: should failed form validation, and return back a one field validation result and one record validation result 1321 |     when adding one field validation that succeeds and record validation that fails 1322 |     `, done => { 1323 | // Arrange 1324 | const myFieldValidation: FieldValidationFunctionSync = jest.fn( 1325 | fieldValidatorArgs => ({ 1326 | type: 'MY_TYPE', 1327 | succeeded: true, 1328 | message: 'mymessageA', 1329 | }) 1330 | ); 1331 | 1332 | const myRecordValidation: RecordValidationFunctionSync = jest.fn( 1333 | ({ message }) => ({ 1334 | type: '', 1335 | succeeded: false, 1336 | message: message ? (message as string) : 'mymessageB', 1337 | }) 1338 | ); 1339 | 1340 | const validationSchema: ValidationSchema = { 1341 | field: { 1342 | username: [myFieldValidation], 1343 | }, 1344 | record: { 1345 | MY_RECORD_VALIDATION: [myRecordValidation], 1346 | }, 1347 | }; 1348 | 1349 | const values = {}; 1350 | 1351 | // Act 1352 | 1353 | const formValidation = createFinalFormValidation(validationSchema); 1354 | const result = formValidation.validateForm(values); 1355 | 1356 | // Assert 1357 | result.then(validationResult => { 1358 | expect(myFieldValidation).toHaveBeenCalled(); 1359 | expect(myRecordValidation).toHaveBeenCalled(); 1360 | expect(validationResult).toEqual({ 1361 | username: '', 1362 | recordErrors: { 1363 | MY_RECORD_VALIDATION: 'mymessageB', 1364 | }, 1365 | }); 1366 | done(); 1367 | }); 1368 | }); 1369 | 1370 | it(`#Spec 12: should failed form validation, and return back a field validation result 1371 |     and zero form validation result 1372 |     when adding one field validation that fails and record validation that succeeds 1373 |     `, done => { 1374 | // Arrange 1375 | const myFieldValidation: FieldValidationFunctionSync = jest.fn( 1376 | fieldValidatorArgs => ({ 1377 | type: 'MY_TYPE', 1378 | succeeded: false, 1379 | message: 'mymessageA', 1380 | }) 1381 | ); 1382 | 1383 | const myRecordValidation: RecordValidationFunctionSync = jest.fn( 1384 | ({ message }) => ({ 1385 | type: '', 1386 | succeeded: true, 1387 | message: message ? (message as string) : 'mymessageB', 1388 | }) 1389 | ); 1390 | 1391 | const validationSchema: ValidationSchema = { 1392 | field: { 1393 | username: [myFieldValidation], 1394 | }, 1395 | record: { 1396 | MY_RECORD_VALIDATION: [myRecordValidation], 1397 | }, 1398 | }; 1399 | 1400 | const values = {}; 1401 | 1402 | // Act 1403 | 1404 | const formValidation = createFinalFormValidation(validationSchema); 1405 | const result = formValidation.validateForm(values); 1406 | 1407 | // Assert 1408 | result.then(validationResult => { 1409 | expect(myFieldValidation).toHaveBeenCalled(); 1410 | expect(myRecordValidation).toHaveBeenCalled(); 1411 | expect(validationResult).toEqual({ 1412 | username: 'mymessageA', 1413 | recordErrors: { 1414 | MY_RECORD_VALIDATION: '', 1415 | }, 1416 | }); 1417 | done(); 1418 | }); 1419 | }); 1420 | 1421 | it(`#Spec 13: should succeed form validation, and return back one field validation result 1422 |     and one record validation result 1423 |     when adding one field validation that succeeds and record validation that succeeds 1424 |     `, done => { 1425 | // Arrange 1426 | const myFieldValidation: FieldValidationFunctionSync = jest.fn( 1427 | fieldValidatorArgs => ({ 1428 | type: 'MY_TYPE', 1429 | succeeded: true, 1430 | message: 'mymessageA', 1431 | }) 1432 | ); 1433 | 1434 | const myRecordValidation: RecordValidationFunctionSync = jest.fn( 1435 | ({ message }) => ({ 1436 | type: '', 1437 | succeeded: true, 1438 | message: message ? (message as string) : 'mymessageB', 1439 | }) 1440 | ); 1441 | 1442 | const validationSchema: ValidationSchema = { 1443 | field: { 1444 | username: [myFieldValidation], 1445 | }, 1446 | record: { 1447 | MY_RECORD_VALIDATION: [myRecordValidation], 1448 | }, 1449 | }; 1450 | 1451 | const values = { username: 'test-value' }; 1452 | 1453 | // Act 1454 | 1455 | const formValidation = createFinalFormValidation(validationSchema); 1456 | const result = formValidation.validateForm(values); 1457 | 1458 | // Assert 1459 | result.then(validationResult => { 1460 | expect(myFieldValidation).toHaveBeenCalled(); 1461 | expect(myRecordValidation).toHaveBeenCalled(); 1462 | expect(validationResult).toBeNull(); 1463 | done(); 1464 | }); 1465 | }); 1466 | 1467 | it(`#Spec 14: should fail form validation, and return one field validation result 1468 |     and form validation result 1469 |     when adding two fields validation that succeed with nested fields 1470 |     `, done => { 1471 | // Arrange 1472 | const myFieldValidation1: FieldValidationFunctionSync = jest.fn( 1473 | fieldValidatorArgs => ({ 1474 | type: 'MY_TYPE_A', 1475 | succeeded: true, 1476 | message: 'mymessageA', 1477 | }) 1478 | ); 1479 | const myFieldValidation2: FieldValidationFunctionSync = jest.fn( 1480 | fieldValidatorArgs => ({ 1481 | type: 'MY_TYPE_B', 1482 | succeeded: true, 1483 | message: 'mymessageB', 1484 | }) 1485 | ); 1486 | 1487 | const validationSchema: ValidationSchema = { 1488 | field: { 1489 | 'nested.field1': [myFieldValidation1], 1490 | 'nested.field2': [myFieldValidation2], 1491 | }, 1492 | }; 1493 | 1494 | const values = {}; 1495 | 1496 | // Act 1497 | 1498 | const formValidation = createFinalFormValidation(validationSchema); 1499 | const result = formValidation.validateForm(values); 1500 | 1501 | // Assert 1502 | result.then(validationResult => { 1503 | expect(myFieldValidation1).toHaveBeenCalled(); 1504 | expect(myFieldValidation2).toHaveBeenCalled(); 1505 | expect(validationResult).toBeNull(); 1506 | done(); 1507 | }); 1508 | }); 1509 | 1510 | it(`spec #15:should execute a validateForm with field (sync and using function in schema) and fail when 1511 |     adding a field validation in the schema on a given field 1512 |     firing a validation for that given field 1513 |     `, done => { 1514 | // Arrange 1515 | const mockValidationFn = jest.fn().mockReturnValue({ 1516 | type: 'MY_TYPE', 1517 | succeeded: false, 1518 | message: 'mymessage', 1519 | }); 1520 | 1521 | const validationSchema: ValidationSchema = { 1522 | field: { 1523 | username: [mockValidationFn], 1524 | }, 1525 | }; 1526 | 1527 | // Act 1528 | 1529 | const formValidation = createFinalFormValidation(validationSchema); 1530 | const values = {}; 1531 | const result = formValidation.validateForm(values); 1532 | 1533 | // Assert 1534 | result.then(validationResult => { 1535 | expect(mockValidationFn).toHaveBeenCalled(); 1536 | expect(validationResult).toEqual({ 1537 | username: 'mymessage', 1538 | recordErrors: {}, 1539 | }); 1540 | done(); 1541 | }); 1542 | }); 1543 | 1544 | it(`spec #16: should execute a validateForm with field (async and using function in schema) and fail when 1545 |     adding a field validation in the schema on a given field 1546 |     firing a validation for that given field (include as well custom message override) 1547 |     `, done => { 1548 | // Arrange 1549 | const mockValidationFn = jest.fn().mockResolvedValue({ 1550 | type: 'MY_TYPE', 1551 | succeeded: false, 1552 | message: 'mymessage', 1553 | }); 1554 | 1555 | const validationSchema: ValidationSchema = { 1556 | field: { 1557 | username: [mockValidationFn], 1558 | }, 1559 | }; 1560 | 1561 | // Act 1562 | 1563 | const formValidation = createFinalFormValidation(validationSchema); 1564 | const values = {}; 1565 | const result = formValidation.validateForm(values); 1566 | 1567 | // Assert 1568 | result.then(validationResult => { 1569 | expect(mockValidationFn).toHaveBeenCalled(); 1570 | expect(validationResult).toEqual({ 1571 | username: 'mymessage', 1572 | recordErrors: {}, 1573 | }); 1574 | done(); 1575 | }); 1576 | }); 1577 | 1578 | it(`spec #17: should execute a validateForm with field (defined as FullValidator, sync function in schema) and fail when 1579 |     adding a field validation in the schema on a given field 1580 |     firing a validation for that given field 1581 |     `, done => { 1582 | // Arrange 1583 | const mockValidationFn = jest.fn( 1584 | ({ message }): ValidationResult => ({ 1585 | type: 'MY_TYPE', 1586 | succeeded: false, 1587 | message: message ? (message as string) : 'mymessage', 1588 | }) 1589 | ); 1590 | 1591 | const validationSchema: ValidationSchema = { 1592 | field: { 1593 | username: [ 1594 | { 1595 | validator: mockValidationFn, 1596 | message: 'myoverriddenmessage', 1597 | }, 1598 | ], 1599 | }, 1600 | }; 1601 | 1602 | // Act 1603 | 1604 | const formValidation = createFinalFormValidation(validationSchema); 1605 | const values = {}; 1606 | const result = formValidation.validateForm(values); 1607 | 1608 | // Assert 1609 | result.then(validationResult => { 1610 | expect(mockValidationFn).toHaveBeenCalled(); 1611 | expect(validationResult).toEqual({ 1612 | username: 'myoverriddenmessage', 1613 | recordErrors: {}, 1614 | }); 1615 | done(); 1616 | }); 1617 | }); 1618 | 1619 | it(`spec #18: should execute a validateForm with field (defined as FullValidator, async function in schema) and fail when 1620 |     adding a field validation in the schema on a given field 1621 |     firing a validation for that given field 1622 |     `, done => { 1623 | // Arrange 1624 | const mockValidationFn = jest.fn( 1625 | ({ message }): Promise => 1626 | Promise.resolve({ 1627 | type: 'MY_TYPE', 1628 | succeeded: false, 1629 | message: message ? (message as string) : 'mymessage', 1630 | }) 1631 | ); 1632 | 1633 | const validationSchema: ValidationSchema = { 1634 | field: { 1635 | username: [ 1636 | { 1637 | validator: mockValidationFn, 1638 | message: 'myoverriddenmessage', 1639 | }, 1640 | ], 1641 | }, 1642 | }; 1643 | 1644 | // Act 1645 | 1646 | const formValidation = createFinalFormValidation(validationSchema); 1647 | const values = {}; 1648 | const result = formValidation.validateForm(values); 1649 | 1650 | // Assert 1651 | result.then(validationResult => { 1652 | expect(mockValidationFn).toHaveBeenCalled(); 1653 | expect(validationResult).toEqual({ 1654 | username: 'myoverriddenmessage', 1655 | recordErrors: {}, 1656 | }); 1657 | done(); 1658 | }); 1659 | }); 1660 | 1661 | it(`spec #19: should execute a validateForm with field (defined as FullValidator, async function in schema) and fail when 1662 |     adding a field validation in the schema, using all possible args 1663 |     `, done => { 1664 | // Arrange 1665 | const mockValidationFn = jest.fn( 1666 | ({ value, message, customArgs, values }): Promise => 1667 | Promise.resolve({ 1668 | type: 'MY_TYPE', 1669 | succeeded: false, 1670 | message: `${value} ${message} ${customArgs} ${JSON.stringify( 1671 | values 1672 | )}`, 1673 | }) 1674 | ); 1675 | 1676 | const validationSchema: ValidationSchema = { 1677 | field: { 1678 | username: [ 1679 | { 1680 | validator: mockValidationFn, 1681 | customArgs: 'custom-arg', 1682 | message: 'myoverriddenmessage', 1683 | }, 1684 | ], 1685 | }, 1686 | }; 1687 | 1688 | // Act 1689 | 1690 | const formValidation = createFinalFormValidation(validationSchema); 1691 | const values = { username: 'whatever' }; 1692 | const result = formValidation.validateForm(values); 1693 | 1694 | // Assert 1695 | result.then(validationResult => { 1696 | expect(mockValidationFn).toHaveBeenCalled(); 1697 | expect(validationResult).toEqual({ 1698 | username: 1699 | 'whatever myoverriddenmessage custom-arg {"username":"whatever"}', 1700 | recordErrors: {}, 1701 | }); 1702 | done(); 1703 | }); 1704 | }); 1705 | 1706 | it(`spec #20:should execute a validateForm with field (sync and using full schema) passing 1707 |       custom args and failed when customArgs.fail === true 1708 |       `, done => { 1709 | // Arrange 1710 | const mockValidationFn: FieldValidationFunctionSync = jest.fn( 1711 | ({ customArgs }): ValidationResult => { 1712 | if (customArgs['fail']) { 1713 | return { 1714 | type: 'MY_TYPE', 1715 | succeeded: false, 1716 | message: 'received custom args fail true', 1717 | }; 1718 | } else { 1719 | return { 1720 | type: 'MY_TYPE', 1721 | succeeded: true, 1722 | message: 'received custom args fail false', 1723 | }; 1724 | } 1725 | } 1726 | ); 1727 | 1728 | const validationSchema: ValidationSchema = { 1729 | field: { 1730 | username: [ 1731 | { 1732 | validator: mockValidationFn, 1733 | customArgs: { fail: true }, 1734 | }, 1735 | ], 1736 | }, 1737 | }; 1738 | 1739 | // Act 1740 | 1741 | const formValidation = createFinalFormValidation(validationSchema); 1742 | const values = {}; 1743 | const result = formValidation.validateForm(values); 1744 | 1745 | // Assert 1746 | result.then(validationResult => { 1747 | expect(mockValidationFn).toHaveBeenCalled(); 1748 | expect(validationResult).toEqual({ 1749 | username: 'received custom args fail true', 1750 | recordErrors: {}, 1751 | }); 1752 | done(); 1753 | }); 1754 | }); 1755 | 1756 | it(`spec #21:should execute a validateForm with field (sync and using full schema) passing 1757 |       custom args and succeeded when customArgs.fail === false 1758 |       `, done => { 1759 | // Arrange 1760 | const mockValidationFn: FieldValidationFunctionSync = jest.fn( 1761 | ({ customArgs }): ValidationResult => { 1762 | if (customArgs['fail']) { 1763 | return { 1764 | type: 'MY_TYPE', 1765 | succeeded: false, 1766 | message: 'received custom args fail true', 1767 | }; 1768 | } else { 1769 | return { 1770 | type: 'MY_TYPE', 1771 | succeeded: true, 1772 | message: 'received custom args fail false', 1773 | }; 1774 | } 1775 | } 1776 | ); 1777 | 1778 | const validationSchema: ValidationSchema = { 1779 | field: { 1780 | username: [ 1781 | { 1782 | validator: mockValidationFn, 1783 | customArgs: { fail: false }, 1784 | }, 1785 | ], 1786 | }, 1787 | }; 1788 | 1789 | // Act 1790 | 1791 | const formValidation = createFinalFormValidation(validationSchema); 1792 | const values = {}; 1793 | const result = formValidation.validateForm(values); 1794 | 1795 | // Assert 1796 | result.then(validationResult => { 1797 | expect(mockValidationFn).toHaveBeenCalled(); 1798 | expect(validationResult).toBeNull(); 1799 | done(); 1800 | }); 1801 | }); 1802 | 1803 | it(`spec #22:should return succeed validateForm with field 1804 |     when adding two validators to a given field and both succeed 1805 | `, done => { 1806 | // Arrange 1807 | const mockValidationFn1 = jest.fn().mockReturnValue({ 1808 | type: 'MY_VALIDATOR_A', 1809 | succeeded: true, 1810 | message: 'mymessage', 1811 | }); 1812 | 1813 | const mockValidationFn2 = jest.fn().mockReturnValue({ 1814 | type: 'MY_VALIDATOR_B', 1815 | succeeded: true, 1816 | message: 'mymessage', 1817 | }); 1818 | 1819 | const validationSchema: ValidationSchema = { 1820 | field: { 1821 | username: [mockValidationFn1, mockValidationFn2], 1822 | }, 1823 | }; 1824 | 1825 | // Act 1826 | 1827 | const formValidation = createFinalFormValidation(validationSchema); 1828 | const values = {}; 1829 | const result = formValidation.validateForm(values); 1830 | 1831 | // Assert 1832 | result.then(validationResult => { 1833 | expect(mockValidationFn1).toHaveBeenCalled(); 1834 | expect(mockValidationFn2).toHaveBeenCalled(); 1835 | expect(validationResult).toBeNull(); 1836 | done(); 1837 | }); 1838 | }); 1839 | 1840 | it(`spec #23:should execute first validations for a given field and failed validateForm with field 1841 | when adding two validators to a given field and first fails 1842 | `, done => { 1843 | // Arrange 1844 | const mockValidationFn1 = jest.fn().mockReturnValue({ 1845 | type: 'MY_VALIDATOR_A', 1846 | succeeded: false, 1847 | message: 'mymessageA', 1848 | }); 1849 | 1850 | const mockValidationFn2 = jest.fn().mockReturnValue({ 1851 | type: 'MY_VALIDATOR_B', 1852 | succeeded: true, 1853 | message: 'mymessageB', 1854 | }); 1855 | 1856 | const validationSchema: ValidationSchema = { 1857 | field: { 1858 | username: [mockValidationFn1, mockValidationFn2], 1859 | }, 1860 | }; 1861 | 1862 | // Act 1863 | 1864 | const formValidation = createFinalFormValidation(validationSchema); 1865 | const values = {}; 1866 | const result = formValidation.validateForm(values); 1867 | 1868 | // Assert 1869 | result.then(validationResult => { 1870 | expect(mockValidationFn1).toHaveBeenCalled(); 1871 | expect(mockValidationFn2).not.toHaveBeenCalled(); 1872 | expect(validationResult).toEqual({ 1873 | username: 'mymessageA', 1874 | recordErrors: {}, 1875 | }); 1876 | done(); 1877 | }); 1878 | }); 1879 | 1880 | it(`spec #24:should execute two validations for a given field and failed validateForm with field 1881 | when adding two validators to a given field and second fails 1882 | `, done => { 1883 | // Arrange 1884 | const mockValidationFn1 = jest.fn().mockReturnValue({ 1885 | type: 'MY_VALIDATOR_A', 1886 | succeeded: true, 1887 | message: 'mymessageA', 1888 | }); 1889 | 1890 | const mockValidationFn2 = jest.fn().mockReturnValue({ 1891 | type: 'MY_VALIDATOR_B', 1892 | succeeded: false, 1893 | message: 'mymessageB', 1894 | }); 1895 | 1896 | const validationSchema: ValidationSchema = { 1897 | field: { 1898 | username: [mockValidationFn1, mockValidationFn2], 1899 | }, 1900 | }; 1901 | 1902 | // Act 1903 | 1904 | const formValidation = createFinalFormValidation(validationSchema); 1905 | const values = {}; 1906 | const result = formValidation.validateForm(values); 1907 | 1908 | // Assert 1909 | result.then(validationResult => { 1910 | expect(mockValidationFn1).toHaveBeenCalled(); 1911 | expect(mockValidationFn2).toHaveBeenCalled(); 1912 | expect(validationResult).toEqual({ 1913 | username: 'mymessageB', 1914 | recordErrors: {}, 1915 | }); 1916 | done(); 1917 | }); 1918 | }); 1919 | 1920 | it(`spec #25:should execute first validation for a given field and failed validateForm with field 1921 | when adding two validators to a given field fails and second fails 1922 | `, done => { 1923 | // Arrange 1924 | const mockValidationFn1 = jest.fn().mockReturnValue({ 1925 | type: 'MY_VALIDATOR_A', 1926 | succeeded: false, 1927 | message: 'mymessageA', 1928 | }); 1929 | 1930 | const mockValidationFn2 = jest.fn().mockReturnValue({ 1931 | type: 'MY_VALIDATOR_B', 1932 | succeeded: false, 1933 | message: 'mymessageB', 1934 | }); 1935 | 1936 | const validationSchema: ValidationSchema = { 1937 | field: { 1938 | username: [mockValidationFn1, mockValidationFn2], 1939 | }, 1940 | }; 1941 | 1942 | // Act 1943 | 1944 | const formValidation = createFinalFormValidation(validationSchema); 1945 | const values = {}; 1946 | const result = formValidation.validateForm(values); 1947 | 1948 | // Assert 1949 | result.then(validationResult => { 1950 | expect(mockValidationFn1).toHaveBeenCalled(); 1951 | expect(mockValidationFn2).not.toHaveBeenCalled(); 1952 | expect(validationResult).toEqual({ 1953 | username: 'mymessageA', 1954 | recordErrors: {}, 1955 | }); 1956 | done(); 1957 | }); 1958 | }); 1959 | 1960 | it(`#Spec 26: should success form validation 1961 |     when adding two fields validation that succeed with nested fields with kebap case 1962 |     `, done => { 1963 | // Arrange 1964 | const myFieldValidation1: FieldValidationFunctionSync = jest.fn( 1965 | ({ value }) => ({ 1966 | type: 'MY_TYPE_A', 1967 | succeeded: true, 1968 | message: `mymessageA ${value}`, 1969 | }) 1970 | ); 1971 | const myFieldValidation2: FieldValidationFunctionSync = jest.fn( 1972 | ({ value }) => ({ 1973 | type: 'MY_TYPE_B', 1974 | succeeded: true, 1975 | message: `mymessageB ${value}`, 1976 | }) 1977 | ); 1978 | 1979 | const validationSchema: ValidationSchema = { 1980 | field: { 1981 | 'this-is-a-nested.field1': [myFieldValidation1], 1982 | 'nested.field-2': [myFieldValidation2], 1983 | }, 1984 | }; 1985 | 1986 | const values = { 1987 | 'this-is-a-nested': { 1988 | field1: 'value1', 1989 | }, 1990 | nested: { 1991 | 'field-2': 'value2', 1992 | }, 1993 | }; 1994 | 1995 | // Act 1996 | 1997 | const formValidation = createFinalFormValidation(validationSchema); 1998 | const result = formValidation.validateForm(values); 1999 | 2000 | // Assert 2001 | result.then(validationResult => { 2002 | expect(myFieldValidation1).toHaveBeenCalled(); 2003 | expect(myFieldValidation2).toHaveBeenCalled(); 2004 | expect(validationResult).toBeNull(); 2005 | done(); 2006 | }); 2007 | }); 2008 | 2009 | it(`#Spec 27: should fail form validation 2010 |     when adding two fields validation that succeed and fail with nested fields with kebap case 2011 |     `, done => { 2012 | // Arrange 2013 | const myFieldValidation1: FieldValidationFunctionSync = jest.fn( 2014 | ({ value }) => ({ 2015 | type: 'MY_TYPE_A', 2016 | succeeded: true, 2017 | message: `mymessageA ${value}`, 2018 | }) 2019 | ); 2020 | const myFieldValidation2: FieldValidationFunctionSync = jest.fn( 2021 | ({ value }) => ({ 2022 | type: 'MY_TYPE_B', 2023 | succeeded: false, 2024 | message: `mymessageB ${value}`, 2025 | }) 2026 | ); 2027 | 2028 | const validationSchema: ValidationSchema = { 2029 | field: { 2030 | 'this-is-a-nested.field1': [myFieldValidation1], 2031 | 'nested.field-2': [myFieldValidation2], 2032 | }, 2033 | }; 2034 | 2035 | const values = { 2036 | 'this-is-a-nested': { 2037 | field1: 'value1', 2038 | }, 2039 | nested: { 2040 | 'field-2': 'value2', 2041 | }, 2042 | }; 2043 | 2044 | // Act 2045 | 2046 | const formValidation = createFinalFormValidation(validationSchema); 2047 | const result = formValidation.validateForm(values); 2048 | 2049 | // Assert 2050 | result.then(validationResult => { 2051 | expect(myFieldValidation1).toHaveBeenCalled(); 2052 | expect(myFieldValidation2).toHaveBeenCalled(); 2053 | expect(validationResult).toEqual({ 2054 | nested: { 'field-2': 'mymessageB value2' }, 2055 | 'this-is-a-nested': { 2056 | field1: '', 2057 | }, 2058 | recordErrors: {}, 2059 | }); 2060 | done(); 2061 | }); 2062 | }); 2063 | }); 2064 | 2065 | describe('updateValidationSchema', () => { 2066 | it(`spec #1: should update validation schema when it feeds new validationSchema with one new field to validate`, async () => { 2067 | // Arrange 2068 | const values = { 2069 | field1: '', 2070 | field2: '', 2071 | }; 2072 | 2073 | const mockValidationFn = () => ({ 2074 | type: 'MY_TYPE', 2075 | succeeded: false, 2076 | message: 'mymessage', 2077 | }); 2078 | 2079 | const validationSchema: ValidationSchema = { 2080 | field: { 2081 | field1: [mockValidationFn], 2082 | }, 2083 | }; 2084 | 2085 | // Act 2086 | const formValidation = createFinalFormValidation(validationSchema); 2087 | const result1 = await formValidation.validateForm(values); 2088 | 2089 | const newValidationSchema: ValidationSchema = { 2090 | ...validationSchema, 2091 | field: { 2092 | ...validationSchema.field, 2093 | field2: [mockValidationFn], 2094 | }, 2095 | }; 2096 | formValidation.updateValidationSchema(newValidationSchema); 2097 | const result2 = await formValidation.validateForm(values); 2098 | 2099 | // Assert 2100 | expect(result1).toEqual({ 2101 | field1: 'mymessage', 2102 | recordErrors: {}, 2103 | }); 2104 | 2105 | expect(result2).toEqual({ 2106 | field1: 'mymessage', 2107 | field2: 'mymessage', 2108 | recordErrors: {}, 2109 | }); 2110 | }); 2111 | 2112 | it(`spec #2: should update validation schema when it feeds new validationSchema removing one field to validate`, async () => { 2113 | // Arrange 2114 | const values = { 2115 | field1: '', 2116 | field2: '', 2117 | }; 2118 | 2119 | const mockValidationFn = () => ({ 2120 | type: 'MY_TYPE', 2121 | succeeded: false, 2122 | message: 'mymessage', 2123 | }); 2124 | 2125 | const validationSchema: ValidationSchema = { 2126 | field: { 2127 | field1: [mockValidationFn], 2128 | field2: [mockValidationFn], 2129 | }, 2130 | }; 2131 | 2132 | // Act 2133 | const formValidation = createFinalFormValidation(validationSchema); 2134 | const result1 = await formValidation.validateForm(values); 2135 | 2136 | const newValidationSchema: ValidationSchema = { 2137 | ...validationSchema, 2138 | field: { 2139 | field1: [mockValidationFn], 2140 | }, 2141 | }; 2142 | formValidation.updateValidationSchema(newValidationSchema); 2143 | const result2 = await formValidation.validateForm(values); 2144 | 2145 | // Assert 2146 | expect(result1).toEqual({ 2147 | field1: 'mymessage', 2148 | field2: 'mymessage', 2149 | recordErrors: {}, 2150 | }); 2151 | 2152 | expect(result2).toEqual({ 2153 | field1: 'mymessage', 2154 | recordErrors: {}, 2155 | }); 2156 | }); 2157 | 2158 | it(`spec #3: should update validation schema when it feeds new validationSchema updating one field, adding new validator`, async () => { 2159 | // Arrange 2160 | const values = { 2161 | field1: '', 2162 | field2: '', 2163 | }; 2164 | 2165 | const mockValidationFn1 = () => ({ 2166 | type: 'MY_TYPE_1', 2167 | succeeded: true, 2168 | message: 'mymessage1', 2169 | }); 2170 | 2171 | const mockValidationFn2 = () => ({ 2172 | type: 'MY_TYPE_2', 2173 | succeeded: false, 2174 | message: 'mymessage2', 2175 | }); 2176 | 2177 | const validationSchema: ValidationSchema = { 2178 | field: { 2179 | field1: [mockValidationFn1], 2180 | }, 2181 | }; 2182 | 2183 | // Act 2184 | const formValidation = createFinalFormValidation(validationSchema); 2185 | const result1 = await formValidation.validateForm(values); 2186 | 2187 | const newValidationSchema: ValidationSchema = { 2188 | ...validationSchema, 2189 | field: { 2190 | field1: [...validationSchema.field.field1, mockValidationFn2], 2191 | }, 2192 | }; 2193 | formValidation.updateValidationSchema(newValidationSchema); 2194 | const result2 = await formValidation.validateForm(values); 2195 | 2196 | // Assert 2197 | expect(result1).toBeNull(); 2198 | 2199 | expect(result2).toEqual({ 2200 | field1: 'mymessage2', 2201 | recordErrors: {}, 2202 | }); 2203 | }); 2204 | 2205 | it(`spec #4: should update validation schema when it feeds new validationSchema updating one field, removing validator`, async () => { 2206 | // Arrange 2207 | const values = { 2208 | field1: '', 2209 | field2: '', 2210 | }; 2211 | 2212 | const mockValidationFn1 = () => ({ 2213 | type: 'MY_TYPE_1', 2214 | succeeded: true, 2215 | message: 'mymessage1', 2216 | }); 2217 | 2218 | const mockValidationFn2 = () => ({ 2219 | type: 'MY_TYPE_2', 2220 | succeeded: false, 2221 | message: 'mymessage2', 2222 | }); 2223 | 2224 | const validationSchema: ValidationSchema = { 2225 | field: { 2226 | field1: [mockValidationFn1, mockValidationFn2], 2227 | }, 2228 | }; 2229 | 2230 | // Act 2231 | const formValidation = createFinalFormValidation(validationSchema); 2232 | const result1 = await formValidation.validateForm(values); 2233 | 2234 | const newValidationSchema: ValidationSchema = { 2235 | ...validationSchema, 2236 | field: { 2237 | field1: [mockValidationFn1], 2238 | }, 2239 | }; 2240 | formValidation.updateValidationSchema(newValidationSchema); 2241 | const result2 = await formValidation.validateForm(values); 2242 | 2243 | // Assert 2244 | expect(result1).toEqual({ 2245 | field1: 'mymessage2', 2246 | recordErrors: {}, 2247 | }); 2248 | 2249 | expect(result2).toBeNull(); 2250 | }); 2251 | 2252 | it(`spec #5: should update validation schema when it feeds new validationSchema updating one field, updating error message`, async () => { 2253 | // Arrange 2254 | const values = { 2255 | field1: '', 2256 | field2: '', 2257 | }; 2258 | 2259 | const mockValidationFn: FieldValidationFunctionSync = ({ message }) => ({ 2260 | type: 'MY_TYPE_1', 2261 | succeeded: false, 2262 | message: message ? (message as string) : 'mymessage1', 2263 | }); 2264 | 2265 | const validationSchema: ValidationSchema = { 2266 | field: { 2267 | field1: [mockValidationFn], 2268 | }, 2269 | }; 2270 | 2271 | // Act 2272 | const formValidation = createFinalFormValidation(validationSchema); 2273 | const result1 = await formValidation.validateForm(values); 2274 | 2275 | const newValidationSchema: ValidationSchema = { 2276 | ...validationSchema, 2277 | field: { 2278 | field1: [ 2279 | { 2280 | validator: mockValidationFn, 2281 | message: 'updated error message', 2282 | }, 2283 | ], 2284 | }, 2285 | }; 2286 | formValidation.updateValidationSchema(newValidationSchema); 2287 | const result2 = await formValidation.validateForm(values); 2288 | 2289 | // Assert 2290 | expect(result1).toEqual({ 2291 | field1: 'mymessage1', 2292 | recordErrors: {}, 2293 | }); 2294 | 2295 | expect(result2).toEqual({ 2296 | field1: 'updated error message', 2297 | recordErrors: {}, 2298 | }); 2299 | }); 2300 | 2301 | it(`spec #6: should update validation schema when it feeds new validationSchema with one new record to validate`, async () => { 2302 | // Arrange 2303 | const values = { 2304 | field1: '', 2305 | field2: '', 2306 | }; 2307 | 2308 | const mockValidationFn = () => ({ 2309 | type: 'MY_TYPE', 2310 | succeeded: false, 2311 | message: 'mymessage', 2312 | }); 2313 | 2314 | const validationSchema: ValidationSchema = { 2315 | record: { 2316 | record1: [mockValidationFn], 2317 | }, 2318 | }; 2319 | 2320 | // Act 2321 | const formValidation = createFinalFormValidation(validationSchema); 2322 | const result1 = await formValidation.validateForm(values); 2323 | 2324 | const newValidationSchema: ValidationSchema = { 2325 | ...validationSchema, 2326 | record: { 2327 | ...validationSchema.record, 2328 | record2: [mockValidationFn], 2329 | }, 2330 | }; 2331 | formValidation.updateValidationSchema(newValidationSchema); 2332 | const result2 = await formValidation.validateForm(values); 2333 | 2334 | // Assert 2335 | expect(result1).toEqual({ 2336 | recordErrors: { 2337 | record1: 'mymessage', 2338 | }, 2339 | }); 2340 | 2341 | expect(result2).toEqual({ 2342 | recordErrors: { 2343 | record1: 'mymessage', 2344 | record2: 'mymessage', 2345 | }, 2346 | }); 2347 | }); 2348 | 2349 | it(`spec #7: should update validation schema when it feeds new validationSchema removing one record to validate`, async () => { 2350 | // Arrange 2351 | const values = { 2352 | record1: '', 2353 | record2: '', 2354 | }; 2355 | 2356 | const mockValidationFn = () => ({ 2357 | type: 'MY_TYPE', 2358 | succeeded: false, 2359 | message: 'mymessage', 2360 | }); 2361 | 2362 | const validationSchema: ValidationSchema = { 2363 | record: { 2364 | record1: [mockValidationFn], 2365 | record2: [mockValidationFn], 2366 | }, 2367 | }; 2368 | 2369 | // Act 2370 | const formValidation = createFinalFormValidation(validationSchema); 2371 | const result1 = await formValidation.validateForm(values); 2372 | 2373 | const newValidationSchema: ValidationSchema = { 2374 | ...validationSchema, 2375 | record: { 2376 | record1: [mockValidationFn], 2377 | }, 2378 | }; 2379 | formValidation.updateValidationSchema(newValidationSchema); 2380 | const result2 = await formValidation.validateForm(values); 2381 | 2382 | // Assert 2383 | expect(result1).toEqual({ 2384 | recordErrors: { 2385 | record1: 'mymessage', 2386 | record2: 'mymessage', 2387 | }, 2388 | }); 2389 | 2390 | expect(result2).toEqual({ 2391 | recordErrors: { 2392 | record1: 'mymessage', 2393 | }, 2394 | }); 2395 | }); 2396 | 2397 | it(`spec #8: should update validation schema when it feeds new validationSchema updating one record, adding new validator`, async () => { 2398 | // Arrange 2399 | const values = { 2400 | record1: '', 2401 | record2: '', 2402 | }; 2403 | 2404 | const mockValidationFn1 = () => ({ 2405 | type: 'MY_TYPE_1', 2406 | succeeded: true, 2407 | message: 'mymessage1', 2408 | }); 2409 | 2410 | const mockValidationFn2 = () => ({ 2411 | type: 'MY_TYPE_2', 2412 | succeeded: false, 2413 | message: 'mymessage2', 2414 | }); 2415 | 2416 | const validationSchema: ValidationSchema = { 2417 | record: { 2418 | record1: [mockValidationFn1], 2419 | }, 2420 | }; 2421 | 2422 | // Act 2423 | const formValidation = createFinalFormValidation(validationSchema); 2424 | const result1 = await formValidation.validateForm(values); 2425 | 2426 | const newValidationSchema: ValidationSchema = { 2427 | ...validationSchema, 2428 | record: { 2429 | record1: [...validationSchema.record.record1, mockValidationFn2], 2430 | }, 2431 | }; 2432 | formValidation.updateValidationSchema(newValidationSchema); 2433 | const result2 = await formValidation.validateForm(values); 2434 | 2435 | // Assert 2436 | expect(result1).toBeNull(); 2437 | 2438 | expect(result2).toEqual({ 2439 | recordErrors: { 2440 | record1: 'mymessage2', 2441 | }, 2442 | }); 2443 | }); 2444 | 2445 | it(`spec #9: should update validation schema when it feeds new validationSchema updating one record, removing validator`, async () => { 2446 | // Arrange 2447 | const values = { 2448 | record1: '', 2449 | record2: '', 2450 | }; 2451 | 2452 | const mockValidationFn1 = () => ({ 2453 | type: 'MY_TYPE_1', 2454 | succeeded: true, 2455 | message: 'mymessage1', 2456 | }); 2457 | 2458 | const mockValidationFn2 = () => ({ 2459 | type: 'MY_TYPE_2', 2460 | succeeded: false, 2461 | message: 'mymessage2', 2462 | }); 2463 | 2464 | const validationSchema: ValidationSchema = { 2465 | record: { 2466 | record1: [mockValidationFn1, mockValidationFn2], 2467 | }, 2468 | }; 2469 | 2470 | // Act 2471 | const formValidation = createFinalFormValidation(validationSchema); 2472 | const result1 = await formValidation.validateForm(values); 2473 | 2474 | const newValidationSchema: ValidationSchema = { 2475 | ...validationSchema, 2476 | record: { 2477 | record1: [mockValidationFn1], 2478 | }, 2479 | }; 2480 | formValidation.updateValidationSchema(newValidationSchema); 2481 | const result2 = await formValidation.validateForm(values); 2482 | 2483 | // Assert 2484 | expect(result1).toEqual({ 2485 | recordErrors: { 2486 | record1: 'mymessage2', 2487 | }, 2488 | }); 2489 | 2490 | expect(result2).toBeNull(); 2491 | }); 2492 | 2493 | it(`spec #10: should update validation schema when it feeds new validationSchema updating one record, updating error message`, async () => { 2494 | // Arrange 2495 | const values = { 2496 | record1: '', 2497 | record2: '', 2498 | }; 2499 | 2500 | const mockValidationFn: RecordValidationFunctionSync = ({ message }) => ({ 2501 | type: 'MY_TYPE_1', 2502 | succeeded: false, 2503 | message: message ? (message as string) : 'mymessage1', 2504 | }); 2505 | 2506 | const validationSchema: ValidationSchema = { 2507 | record: { 2508 | record1: [mockValidationFn], 2509 | }, 2510 | }; 2511 | 2512 | // Act 2513 | const formValidation = createFinalFormValidation(validationSchema); 2514 | const result1 = await formValidation.validateForm(values); 2515 | 2516 | const newValidationSchema: ValidationSchema = { 2517 | ...validationSchema, 2518 | record: { 2519 | record1: [ 2520 | { 2521 | validator: mockValidationFn, 2522 | message: 'updated error message', 2523 | }, 2524 | ], 2525 | }, 2526 | }; 2527 | formValidation.updateValidationSchema(newValidationSchema); 2528 | const result2 = await formValidation.validateForm(values); 2529 | 2530 | // Assert 2531 | expect(result1).toEqual({ 2532 | recordErrors: { 2533 | record1: 'mymessage1', 2534 | }, 2535 | }); 2536 | 2537 | expect(result2).toEqual({ 2538 | recordErrors: { 2539 | record1: 'updated error message', 2540 | }, 2541 | }); 2542 | }); 2543 | }); 2544 | }); 2545 | --------------------------------------------------------------------------------