├── .eslintrc.json ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── CHANGELOG.md ├── DOCUMENTATION.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── Base.ts ├── Bindings.ts ├── Field.ts ├── Form.ts ├── Options.ts ├── State.ts ├── Validator.ts ├── composer.ts ├── index.ts ├── models │ ├── BaseInterface.ts │ ├── BindingsInterface.ts │ ├── FieldInterface.ts │ ├── FieldProps.ts │ ├── FormInterface.ts │ ├── OptionsInterface.ts │ ├── OptionsModel.ts │ ├── SharedActionsInterface.ts │ ├── SharedEventsInterface.ts │ ├── SharedHelpersInterface.ts │ ├── SharedInitializerInterface.ts │ ├── SharedUtilsInterface.ts │ ├── StateInterface.ts │ └── ValidatorInterface.ts ├── parser.ts ├── props.ts ├── utils.ts └── validators │ ├── DVR.ts │ ├── JOI.ts │ ├── SVK.ts │ ├── VJF.ts │ ├── YUP.ts │ └── ZOD.ts ├── tests ├── .eslintrc ├── computed │ ├── _.flat.ts │ ├── _.nested.ts │ ├── flat.hasError.ts │ ├── flat.isDirty.ts │ ├── flat.isEmpty.ts │ ├── flat.isPristine.ts │ ├── flat.isValid.ts │ └── nested.isValid.ts ├── data │ ├── _.fixes.ts │ ├── _.flat.ts │ ├── _.methods.ts │ ├── _.nested.ts │ ├── extension │ │ ├── _.async.ts │ │ ├── _.bindings.ts │ │ ├── dvr.ts │ │ ├── svk.ts │ │ └── vjf.ts │ └── forms │ │ ├── fixes │ │ ├── form.376.ts │ │ ├── form.425.ts │ │ ├── form.454.ts │ │ ├── form.472.ts │ │ ├── form.480.ts │ │ ├── form.481.ts │ │ ├── form.492.ts │ │ ├── form.495.ts │ │ ├── form.505.ts │ │ ├── form.507.ts │ │ ├── form.514.ts │ │ ├── form.518.ts │ │ ├── form.519.ts │ │ ├── form.531.ts │ │ ├── form.544.ts │ │ ├── form.585.ts │ │ ├── form.613.ts │ │ ├── form.626a.ts │ │ ├── form.626b.ts │ │ ├── form.a.ts │ │ ├── form.a1.ts │ │ ├── form.b.ts │ │ ├── form.c.ts │ │ ├── form.d_.ts │ │ ├── form.e.ts │ │ ├── form.f.ts │ │ ├── form.g.ts │ │ ├── form.h.ts │ │ ├── form.i.ts │ │ ├── form.l.ts │ │ ├── form.m.ts │ │ ├── form.n.ts │ │ ├── form.o.ts │ │ ├── form.p.ts │ │ ├── form.q.ts │ │ ├── form.q1.ts │ │ ├── form.q2.ts │ │ ├── form.r.ts │ │ ├── form.s.ts │ │ ├── form.t.ts │ │ ├── form.u.ts │ │ ├── form.v.ts │ │ └── form.z.ts │ │ ├── flat │ │ ├── form.a.ts │ │ ├── form.b.ts │ │ ├── form.c.ts │ │ ├── form.d_.ts │ │ ├── form.e.ts │ │ ├── form.f.ts │ │ ├── form.g.ts │ │ ├── form.h.ts │ │ ├── form.i.ts │ │ ├── form.l.ts │ │ ├── form.m.ts │ │ ├── form.n.ts │ │ ├── form.o.ts │ │ ├── form.p.ts │ │ ├── form.q.ts │ │ ├── form.r.ts │ │ ├── form.s.ts │ │ └── form.t.ts │ │ ├── form.empty.constructor.ts │ │ ├── forms.fieldClass.ts │ │ ├── methods │ │ └── form.set.ts │ │ └── nested │ │ ├── form.a.ts │ │ ├── form.a1.ts │ │ ├── form.a2.ts │ │ ├── form.b.ts │ │ ├── form.c.ts │ │ ├── form.d_.ts │ │ ├── form.e.ts │ │ ├── form.e2.ts │ │ ├── form.f.ts │ │ ├── form.g.ts │ │ ├── form.h.ts │ │ ├── form.i.ts │ │ ├── form.l.ts │ │ ├── form.m.ts │ │ ├── form.m1.ts │ │ ├── form.n.ts │ │ ├── form.o.ts │ │ ├── form.p.ts │ │ ├── form.q.ts │ │ ├── form.r.ts │ │ ├── form.s.ts │ │ ├── form.s2.ts │ │ ├── form.t.ts │ │ ├── form.t1.ts │ │ ├── form.u.ts │ │ ├── form.v.ts │ │ ├── form.v2.ts │ │ ├── form.v3.ts │ │ ├── form.v4.ts │ │ ├── form.x.ts │ │ ├── form.z.ts │ │ ├── form.z1.ts │ │ ├── form.z2.ts │ │ ├── form.z3.ts │ │ └── form.z4.ts ├── fixes.labels.ts ├── fixes.props.ts ├── fixes.submit.ts ├── fixes.types.ts ├── fixes.validation.ts ├── fixes.values.ts ├── flat.actions.ts ├── flat.props.ts ├── flat.submit.ts ├── flat.types.ts ├── nested.actions.ts ├── nested.hooks.ts ├── nested.labels.ts ├── nested.performance.ts ├── nested.props.ts ├── nested.state.ts ├── nested.submit.ts ├── nested.validation.ts ├── nested.values.ts ├── promises │ ├── _.fixes.ts │ ├── _.flat.ts │ ├── _.nested.ts │ ├── fixes.validate.ts │ ├── flat.validate.ts │ └── nested.validate.ts ├── test.computed.ts ├── test.fieldClass.ts ├── test.options.ts ├── test.promises.ts ├── test.set.method.ts ├── test.umd.html └── utils.test.ts ├── tsconfig.json ├── umd.html └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "legacyDecorators": true 7 | } 8 | }, 9 | "plugins": ["@typescript-eslint", "import"], 10 | "rules": { 11 | "react/forbid-prop-types": 0, 12 | "class-methods-use-this": 0, 13 | "function-paren-newline": 0, 14 | "no-confusing-arrow": 0, 15 | "padded-blocks": 0, 16 | "quote-props": 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: mobx-react-form 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Mobx React Form 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | CI: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | - run: npm install --force 13 | - run: npm run cover 14 | - run: npm run coverage:check 15 | - run: npm run build 16 | - run: npm run coverage:report 17 | - name: Upload Coverage to Codecov 18 | uses: codecov/codecov-action@v3 19 | with: 20 | fail_ci_if_error: true 21 | files: ./coverage/lcov.info 22 | verbose: true 23 | token: ${{ secrets.CODECOV_TOKEN }} 24 | - name: Semantic Release 25 | uses: cycjimmy/semantic-release-action@v3 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo 3 | /demo/build 4 | /demo/node_modules 5 | /es6 6 | /lib 7 | /node_modules 8 | /umd 9 | /.nyc_output 10 | /.idea 11 | /.bit 12 | .DS_Store 13 | npm-debug.log 14 | coverage.lcov 15 | codecov.yml 16 | yarn-error.log 17 | .qodo 18 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.19.1 2 | -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | https://foxhound87.github.io/mobx-react-form/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Claudio Savino 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 | -------------------------------------------------------------------------------- /src/Bindings.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import { $try } from "./utils"; 3 | 4 | import { FieldPropsEnum, FieldPropsType } from "./models/FieldProps"; 5 | import { BindingsInterface } from "./models/BindingsInterface"; 6 | 7 | export default class Bindings implements BindingsInterface { 8 | templates = { 9 | // default: ({ field, form, props, keys, $try }) => ({ 10 | // [keys.id]: $try(props.id, field.id), 11 | // }), 12 | }; 13 | 14 | rewriters = { 15 | default: { 16 | id: FieldPropsEnum.id, 17 | name: FieldPropsEnum.name, 18 | type: FieldPropsEnum.type, 19 | value: FieldPropsEnum.value, 20 | checked: FieldPropsEnum.checked, 21 | label: FieldPropsEnum.label, 22 | placeholder: FieldPropsEnum.placeholder, 23 | disabled: FieldPropsEnum.disabled, 24 | autoComplete: FieldPropsEnum.autoComplete, 25 | onChange: FieldPropsEnum.onChange, 26 | onBlur: FieldPropsEnum.onBlur, 27 | onFocus: FieldPropsEnum.onFocus, 28 | autoFocus: FieldPropsEnum.autoFocus, 29 | inputMode: FieldPropsEnum.inputMode, 30 | onKeyUp: FieldPropsEnum.onKeyUp, 31 | onKeyDown: FieldPropsEnum.onKeyDown, 32 | }, 33 | }; 34 | 35 | register(bindings: FieldPropsType): Bindings { 36 | _.each(bindings, (val, key) => { 37 | if ((typeof val === 'function')) _.merge(this.templates, { [key]: val }); 38 | if (_.isPlainObject(val)) _.merge(this.rewriters, { [key]: val }); 39 | }); 40 | 41 | return this; 42 | } 43 | 44 | load(field: any, name: string = FieldPropsEnum.default, props: FieldPropsType) { 45 | const args = ({ 46 | keys: _.get(this.rewriters, FieldPropsEnum.default), 47 | form: field.state.form, 48 | field, 49 | props, 50 | $try, 51 | }); 52 | 53 | if (_.has(this.templates, FieldPropsEnum.default)) { 54 | return _.get(this.templates, name)(args); 55 | } 56 | 57 | if (_.has(this.rewriters, name)) { 58 | const $bindings = {}; 59 | 60 | _.each(_.get(this.rewriters, name), ($v, $k) => 61 | _.merge($bindings, { [$v]: $try(props[$k], field[$k]) }) 62 | ); 63 | 64 | return $bindings; 65 | } 66 | 67 | return _.get(this.templates, name)(args); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Options.ts: -------------------------------------------------------------------------------- 1 | import { 2 | observable, 3 | action, 4 | toJS, 5 | extendObservable, 6 | set, 7 | makeObservable, 8 | } from "mobx"; 9 | 10 | import _ from "lodash"; 11 | 12 | import { uniqueId } from "./utils"; 13 | 14 | import {OptionsModel} from "./models/OptionsModel"; 15 | import {OptionsInterface} from "./models/OptionsInterface"; 16 | 17 | export default class Options implements OptionsInterface { 18 | options: OptionsModel = { 19 | uniqueId, 20 | fallback: true, 21 | fallbackValue: "", 22 | defaultGenericError: null, 23 | submitThrowsError: true, 24 | showErrorsOnInit: false, 25 | showErrorsOnSubmit: true, 26 | showErrorsOnBlur: true, 27 | showErrorsOnChange: true, 28 | showErrorsOnClear: false, 29 | showErrorsOnReset: true, 30 | validateOnInit: true, 31 | validateOnSubmit: true, 32 | validateOnBlur: true, 33 | validateOnChange: false, 34 | validateOnChangeAfterInitialBlur: false, 35 | validateOnChangeAfterSubmit: false, 36 | validateOnClear: false, 37 | validateOnReset: false, 38 | validateDisabledFields: false, 39 | validateDeletedFields: false, 40 | validatePristineFields: true, 41 | strictSet: false, 42 | strictUpdate: false, 43 | strictDelete: true, 44 | strictSelect: true, 45 | softDelete: false, 46 | retrieveOnlyDirtyFieldsValues: false, 47 | retrieveOnlyEnabledFieldsValues: false, 48 | retrieveOnlyEnabledFieldsErrors: false, 49 | retrieveNullifiedEmptyStrings: false, 50 | autoTrimValue: false, 51 | autoParseNumbers: false, 52 | removeNullishValuesInArrays: false, 53 | preserveDeletedFieldsValues: false, 54 | validationDebounceWait: 250, 55 | validationDebounceOptions: { 56 | leading: false, 57 | trailing: true, 58 | }, 59 | stopValidationOnError: false, 60 | validationPluginsOrder: undefined, 61 | resetValidationBeforeValidate: true, 62 | validateTrimmedValue: false, 63 | applyInputConverterOnInit: true, 64 | applyInputConverterOnSet: true, 65 | applyInputConverterOnUpdate: true, 66 | }; 67 | 68 | constructor() { 69 | makeObservable(this, { 70 | options: observable, 71 | set: action, 72 | }); 73 | } 74 | 75 | get(key: string, field: any = null): OptionsModel { 76 | // handle field option 77 | if (_.has(field, "path")) { 78 | if (_.has(field.$options, key)) { 79 | return field.$options[key]; 80 | } 81 | } 82 | 83 | // fallback on global form options 84 | if (key) return _.get(this.options, key); 85 | return toJS(this.options); 86 | } 87 | 88 | set(options: OptionsModel): void { 89 | if (set) { 90 | set(this.options, options); 91 | } else { 92 | extendObservable(this.options, options); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/composer.ts: -------------------------------------------------------------------------------- 1 | import { FieldPropsEnum } from "./models/FieldProps"; 2 | import { FormInterface } from "./models/FormInterface"; 3 | 4 | export const composer = (forms: { [key in string]: FormInterface }) => { 5 | 6 | const instances = (): { [key in string]: FormInterface } => forms; 7 | 8 | const select = (key: string): FormInterface => forms[key]; 9 | 10 | const check = (prop: string): any => Object 11 | .entries(forms) 12 | .reduce((acc: object, entry: any) => 13 | Object.assign(acc, { 14 | [entry[0]]: (entry[1] as FormInterface).check(prop) 15 | }), {}); 16 | 17 | const get = (prop: string): any => Object 18 | .entries(forms) 19 | .reduce((acc: object, entry: any) => 20 | Object.assign(acc, { 21 | [entry[0]]: (entry[1] as FormInterface).get(prop) 22 | }), {}); 23 | 24 | const valid = () => Object.values(check(FieldPropsEnum.isValid)) 25 | .every(((val: boolean) => val === true)); 26 | 27 | const error = () => Object.values(check(FieldPropsEnum.hasError)) 28 | .some(((val: boolean) => val === true)); 29 | 30 | const clear = ({ deep = true, execHook = false } 31 | : { deep?: boolean, execHook?: boolean } = { 32 | deep: true, execHook: false, 33 | }): void[] => Object.values(forms).map((form: FormInterface) => form.clear(deep, execHook)); 34 | 35 | const reset = ({ deep = true, execHook = false } 36 | : { deep?: boolean, execHook?: boolean } = { 37 | deep: true, execHook: false, 38 | }): void[] => Object.values(forms).map((form: FormInterface) => form.reset(deep, execHook)); 39 | 40 | const validate = ({ showErrors = true } 41 | : { showErrors?: boolean } = { 42 | showErrors: true, 43 | }): Promise => 44 | Promise.all(Object.values(forms) 45 | .map((form: FormInterface) => form.validate({ showErrors }))) 46 | .then(() => ({ 47 | composer: composer(forms), 48 | valid: valid(), 49 | error: error(), 50 | errors: get('error'), 51 | values: get('value'), 52 | })); 53 | 54 | const submit = ({ validate = true, execOnSubmitHook = false, execValidationHooks = false } 55 | : { validate?: boolean, execOnSubmitHook?: boolean, execValidationHooks?: boolean } = { 56 | validate: true, execOnSubmitHook: false, execValidationHooks: false, 57 | }): Promise => 58 | Promise.all(Object.values(forms) 59 | .map((form: FormInterface) => form.submit({}, 60 | { execOnSubmitHook, execValidationHooks , validate } 61 | ))) 62 | .then(() => ({ 63 | composer: composer(forms), 64 | valid: valid(), 65 | error: error(), 66 | errors: get('error'), 67 | values: get('value'), 68 | })); 69 | 70 | return { 71 | instances, 72 | select, 73 | check, 74 | get, 75 | validate, 76 | submit, 77 | clear, 78 | reset, 79 | } 80 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Form from "./Form"; 2 | import Field from "./Field"; 3 | 4 | export default Form; 5 | export { Form, Field }; 6 | -------------------------------------------------------------------------------- /src/models/BaseInterface.ts: -------------------------------------------------------------------------------- 1 | import { ObservableMap } from "mobx"; 2 | import {SharedActionsInterface} from "./SharedActionsInterface"; 3 | import {SharedEventsInterface} from "./SharedEventsInterface"; 4 | import {SharedHelpersInterface} from "./SharedHelpersInterface"; 5 | import {SharedInitializerInterface} from "./SharedInitializerInterface"; 6 | import {SharedUtilsInferface} from "./SharedUtilsInterface"; 7 | import {StateInterface} from "./StateInterface"; 8 | export interface BaseInterface 9 | extends SharedInitializerInterface, 10 | SharedActionsInterface, 11 | SharedEventsInterface, 12 | SharedUtilsInferface, 13 | SharedHelpersInterface { 14 | state: StateInterface; 15 | fields: ObservableMap; 16 | path: string | undefined | null; 17 | $hooks: any; 18 | $handlers: any; 19 | $submitted: number; 20 | $submitting: boolean; 21 | $validated: number; 22 | $validating: boolean; 23 | $touched: boolean; 24 | $changed: number; 25 | changed: number; 26 | submitted: number; 27 | submitting: boolean; 28 | validated: number; 29 | validating: boolean; 30 | hasIncrementalKeys: boolean; 31 | hasNestedFields: boolean; 32 | size: number; 33 | execHook(name: string, fallback?: any): any; 34 | execHandler(name: string, args: any, fallback?: any): any; 35 | intercept(opt: any): any; 36 | observe(opt: any): any; 37 | onClear(args: any): any; 38 | onReset(args: any): any; 39 | onSubmit(args: any): any; 40 | onAdd(args: any): any; 41 | onDel(args: any): any; 42 | } 43 | 44 | export default BaseInterface; -------------------------------------------------------------------------------- /src/models/BindingsInterface.ts: -------------------------------------------------------------------------------- 1 | import Bindings from "../Bindings"; 2 | import { FieldPropsEnum, FieldPropsType } from "./FieldProps"; 3 | 4 | export interface BindingsInterface { 5 | templates: { 6 | [index: string]: { 7 | [key: string]: ({ field, props, keys }: any) => any; 8 | }; 9 | }; 10 | rewriters: { 11 | [index: string]: { 12 | [key: string]: FieldPropsEnum; 13 | }; 14 | }; 15 | 16 | load: ( 17 | field: any, 18 | name: string, 19 | props: FieldPropsType 20 | ) => FieldPropsType; 21 | 22 | register: (bindings: FieldPropsType) => Bindings; 23 | } 24 | 25 | export default BindingsInterface; -------------------------------------------------------------------------------- /src/models/FieldInterface.ts: -------------------------------------------------------------------------------- 1 | import {BaseInterface} from "./BaseInterface"; 2 | import {OptionsModel} from "./OptionsModel"; 3 | import {StateInterface} from "./StateInterface"; 4 | export interface FieldInterface extends BaseInterface { 5 | incremental: boolean; 6 | hasInitialNestedFields: boolean; 7 | id: string | undefined; 8 | key: string | undefined; 9 | name: string | undefined; 10 | options: OptionsModel | undefined; 11 | type: string | undefined; 12 | label: string | undefined; 13 | placeholder: string | undefined; 14 | extra: any; 15 | errorSync: string | null; 16 | errorAsync: string | null; 17 | validationErrorStack: string[]; 18 | validationFunctionsData: any[]; 19 | validationAsyncData: { valid: boolean, message: string | null }; 20 | debouncedValidation: any; 21 | autoFocus: boolean; 22 | inputMode: string; 23 | ref: any; 24 | showError: boolean; 25 | checkValidationErrors: boolean; 26 | checked: any; 27 | value: any; 28 | initial: any; 29 | default: any; 30 | actionRunning: boolean; 31 | bindings: any; 32 | disabled: boolean; 33 | related: string[] | undefined; 34 | rules: string[] | undefined; 35 | validators: any[] | undefined; 36 | validatedValue: any; 37 | error: string | null; 38 | hasError: boolean; 39 | isValid: boolean; 40 | isDefault: boolean; 41 | isDirty: boolean; 42 | isPristine: boolean; 43 | isEmpty: boolean; 44 | resetting: boolean; 45 | clearing: boolean; 46 | focused: boolean; 47 | blurred: boolean; 48 | touched: boolean; 49 | deleted: boolean; 50 | // handlers 51 | onSync(args: any): any; 52 | onChange(args: any): any; 53 | onToggle(args: any): any; 54 | onBlur(args: any): any; 55 | onFocus(args: any): any; 56 | onDrop(args: any): any; 57 | onKeyDown(args: any): any; 58 | onKeyUp(args: any): any; 59 | 60 | // methods: { 61 | setupField( 62 | key: string, 63 | path: string, 64 | struct: string, 65 | data: any, 66 | props: any, 67 | update: boolean 68 | ): void; 69 | getComputedProp(key: any): any; 70 | // checkValidationPlugins(): void; 71 | initNestedFields(field: any, update: boolean): void; 72 | invalidate(message?: string, deep?: boolean, async?: boolean): void; 73 | setValidationAsyncData(valid: boolean, message?: string|null): void; 74 | resetValidation(deep: boolean): void; 75 | clear(deep?: boolean): void; 76 | reset(deep?: boolean): void; 77 | focus(): void; 78 | showErrors(show?: boolean, deep?: boolean): void; 79 | observeValidationOnBlur(): void; 80 | observeValidationOnChange(): void; 81 | disposeValidationOnBlur(): void; 82 | disposeValidationOnChange(): void; 83 | initMOBXEvent(type: string): void; 84 | bind(props?: any): any; 85 | } 86 | 87 | export interface FieldConstructor { 88 | key: string; 89 | path?: string; 90 | struct?: string; 91 | data?: any; 92 | props?: any; 93 | update?: boolean; 94 | state: StateInterface; 95 | } 96 | 97 | export default FieldInterface; -------------------------------------------------------------------------------- /src/models/FieldProps.ts: -------------------------------------------------------------------------------- 1 | export enum FieldPropsEnum { 2 | key = "key", 3 | id = "id", 4 | path = "path", 5 | name = "name", 6 | fields = "fields", 7 | ref= "ref", 8 | type = "type", 9 | computed = "computed", 10 | value = "value", 11 | initial = "initial", 12 | default = "default", 13 | checked = "checked", 14 | label = "label", 15 | placeholder = "placeholder", 16 | error = "error", 17 | validatedWith = "validatedWith", 18 | validators = "validators", 19 | rules = "rules", 20 | related = "related", 21 | options = "options", 22 | extra = "extra", 23 | bindings = "bindings", 24 | hooks = "hooks", 25 | handlers = "handlers", 26 | converter="converter", 27 | input="input", 28 | output="output", 29 | interceptors = "interceptors", 30 | observers = "observers", 31 | // computed 32 | disabled = "disabled", 33 | deleted = "deleted", 34 | blurred = "blurred", 35 | validating = "validating", 36 | submitting = "submitting", 37 | clearing = "clearing", 38 | resetting = "resetting", 39 | changed = "changed", 40 | touched = "touched", 41 | focused = "focused", 42 | isEmpty = "isEmpty", 43 | isDefault = "isDefault", 44 | isPristine = "isPristine", 45 | isDirty = "isDirty", 46 | isValid = "isValid", 47 | hasError = "hasError", 48 | // handlers 49 | onInit = "onInit", 50 | onSync = "onSync", 51 | onChange = "onChange", 52 | onBlur = "onBlur", 53 | onFocus = "onFocus", 54 | onToggle = "onToggle", 55 | onDrop = "onDrop", 56 | onSubmit = "onSubmit", 57 | onReset = "onReset", 58 | onClear = "onClear", 59 | onAdd = "onAdd", 60 | onDel = "onDel", 61 | autoFocus = "autoFocus", 62 | inputMode = "inputMode", 63 | onKeyDown = "onKeyDown", 64 | onKeyUp = 'onKeyUp', 65 | class = "class", 66 | nullable = "nullable", 67 | autoComplete = "autoComplete", 68 | } 69 | 70 | export type FieldPropsType = { 71 | [key in FieldPropsEnum]?: any; 72 | } 73 | 74 | export enum AllowedFieldPropsTypes { 75 | computed = 'computed', 76 | observable = 'observable', 77 | editable = 'editable', 78 | all = 'all', 79 | } 80 | 81 | export enum FieldPropsOccurrence { 82 | some = 'some', 83 | every = 'every', 84 | } 85 | 86 | export enum SeparatedPropsMode { 87 | computed = 'computed', 88 | values = 'values', 89 | labels = 'labels', 90 | placeholders = 'placeholders', 91 | defaults = 'defaults', 92 | initials = 'initials', 93 | disabled = 'disabled', 94 | deleted = 'deleted', 95 | types = 'types', 96 | related = 'related', 97 | rules = 'rules', 98 | options = 'options', 99 | bindings = 'bindings', 100 | extra = 'extra', 101 | hooks = 'hooks', 102 | handlers = 'handlers', 103 | validatedWith = 'validatedWith', 104 | validators = 'validators', 105 | observers = 'observers', 106 | interceptors = 'interceptors', 107 | converters = 'converters', 108 | input = 'input', 109 | output = 'output', 110 | autoFocus = 'autoFocus', 111 | inputMode = 'inputMode', 112 | refs = 'refs', 113 | classes = 'classes', 114 | nullable = 'nullable', 115 | autoComplete = 'autoComplete', 116 | } 117 | -------------------------------------------------------------------------------- /src/models/FormInterface.ts: -------------------------------------------------------------------------------- 1 | import Field from "../Field"; 2 | import { BaseInterface } from "./BaseInterface"; 3 | import { FieldInterface, FieldConstructor } from "./FieldInterface"; 4 | import { OptionsModel } from "./OptionsModel"; 5 | import { ValidatorInterface, ValidationPlugins } from "./ValidatorInterface"; 6 | export interface FormInterface extends BaseInterface { 7 | name: string; 8 | validator: ValidatorInterface; 9 | debouncedValidation: any; 10 | // getters 11 | validatedValues: object; 12 | clearing: boolean; 13 | resetting: boolean; 14 | error: string | null; 15 | hasError: boolean; 16 | isValid: boolean; 17 | isPristine: boolean; 18 | isDirty: boolean; 19 | isDefault: boolean; 20 | isEmpty: boolean; 21 | focused: boolean; 22 | touched: boolean; 23 | disabled: boolean; 24 | // methods 25 | // init($fields: any): void; 26 | invalidate(message?: string | null, deep ?: boolean): void; 27 | showErrors(show?: boolean): void; 28 | clear(deep?: boolean, execHook?: boolean): void; 29 | reset(deep?: boolean, execHook?: boolean): void; 30 | 31 | makeField(data: FieldConstructor, FieldClass?: typeof Field): FieldInterface; 32 | } 33 | 34 | export interface FieldsDefinitions { 35 | struct?: string[]; 36 | fields?: any; 37 | computed?: any; 38 | values?: any; 39 | labels?: any; 40 | placeholders?: any; 41 | defaults?: any; 42 | initials?: any; 43 | disabled?: any; 44 | deleted?: any; 45 | types?: any; 46 | related?: any; 47 | rules?: any; 48 | options?: any; 49 | bindings?: any; 50 | extra?: any; 51 | hooks?: any; 52 | handlers?: any; 53 | validatedWith?: any; 54 | validators?: any; 55 | observers?: any; 56 | interceptors?: any; 57 | input?: any; 58 | output?: any; 59 | autoFocus?: any; 60 | inputMode?: any; 61 | refs?: any; 62 | classes?: object; 63 | nullable?: any; 64 | converters?: any; 65 | } 66 | 67 | export interface FormConfig { 68 | name?: string; 69 | options?: OptionsModel; 70 | plugins?: ValidationPlugins; 71 | bindings?: any; 72 | hooks?: any; 73 | handlers?: any; 74 | } 75 | 76 | export default FormInterface; 77 | -------------------------------------------------------------------------------- /src/models/OptionsInterface.ts: -------------------------------------------------------------------------------- 1 | import {OptionsModel} from "./OptionsModel"; 2 | 3 | export interface OptionsInterface { 4 | options: OptionsModel; 5 | get: (key?: string, instance?: any) => any; 6 | set: (options: OptionsModel) => void; 7 | } 8 | 9 | export default OptionsInterface; -------------------------------------------------------------------------------- /src/models/SharedActionsInterface.ts: -------------------------------------------------------------------------------- 1 | import { ValidateOptions } from "./ValidatorInterface"; 2 | 3 | export interface SubmitOptions { 4 | execOnSubmitHook: boolean, 5 | execValidationHooks: boolean, 6 | validate: boolean 7 | }; 8 | 9 | export interface SubmitHooks { 10 | onSubmit?(instance): void 11 | onSuccess?(instance): void 12 | onError?(instance): void 13 | } 14 | 15 | export interface SharedActionsInterface { 16 | validate(opt?: ValidateOptions, obj?: ValidateOptions): Promise; 17 | submit(hooks: SubmitHooks, opt: SubmitOptions): Promise; 18 | check(prop: string, deep?: boolean): boolean; 19 | deepCheck(type: string, prop: string, fields: any): any; 20 | update(fields: any): void; 21 | deepUpdate(fields: any, path: string, recursion: boolean): void; 22 | get(prop?: any, strict?: boolean): any; 23 | deepGet(prop: any, fields: any): any; 24 | set(prop: any, data?: any): void; 25 | deepSet(prop: any, data: any, path: string, recursion: boolean): void; 26 | add(obj: any): any; 27 | del(path?: string): any; 28 | } 29 | 30 | export default SharedActionsInterface; -------------------------------------------------------------------------------- /src/models/SharedEventsInterface.ts: -------------------------------------------------------------------------------- 1 | export interface SharedEventsInterface { 2 | MOBXEvent(config: any): void; 3 | dispose(config: any): void; 4 | disposeAll(): void; 5 | disposeSingle(config: any): void; 6 | } 7 | 8 | export default SharedEventsInterface; -------------------------------------------------------------------------------- /src/models/SharedHelpersInterface.ts: -------------------------------------------------------------------------------- 1 | export interface SharedHelpersInterface { 2 | $(key: string|number): any; 3 | values(): any; 4 | errors(): any; 5 | labels(): any; 6 | placeholders(): any; 7 | defaults(): any; 8 | initials(): any; 9 | types(): any; 10 | } 11 | 12 | export default SharedHelpersInterface; -------------------------------------------------------------------------------- /src/models/SharedInitializerInterface.ts: -------------------------------------------------------------------------------- 1 | export interface SharedInitializerInterface { 2 | initFields(initial: any, update: boolean): void; 3 | initField(key: string, path: string, data: any, update: boolean): any; 4 | } 5 | 6 | export default SharedInitializerInterface; -------------------------------------------------------------------------------- /src/models/SharedUtilsInterface.ts: -------------------------------------------------------------------------------- 1 | export interface SharedUtilsInferface { 2 | select: (path: string|number, fields?: any, isStrict?: boolean) => any; 3 | container: (path?: string) => any; 4 | has: (path: string) => boolean; 5 | map: (cb: any) => any; 6 | each: (iteratee: any, fields?: any, depth?: number) => any; 7 | reduce: (iteratee: any, acc: any) => any; 8 | } 9 | 10 | export default SharedUtilsInferface; -------------------------------------------------------------------------------- /src/models/StateInterface.ts: -------------------------------------------------------------------------------- 1 | import {BindingsInterface} from "./BindingsInterface"; 2 | import {FormInterface} from "./FormInterface"; 3 | import {OptionsInterface} from "./OptionsInterface"; 4 | 5 | export enum RuntimeMode { 6 | mixed = "mixed", 7 | unified = "unified", 8 | separated = "separated", 9 | } 10 | 11 | export interface StateInterface { 12 | mode: RuntimeMode; 13 | strict: boolean; 14 | form: FormInterface; 15 | options: OptionsInterface; 16 | bindings: BindingsInterface; 17 | 18 | $struct: string[]; 19 | $extra: any; 20 | 21 | disposers: { 22 | interceptor: any; 23 | observer: any; 24 | }; 25 | 26 | initial: { 27 | props: any; 28 | fields: any; 29 | }; 30 | 31 | current: { 32 | props: any; 33 | fields: any; 34 | }; 35 | 36 | initProps(initial: any): void; 37 | struct(data?: string[]): string[]; 38 | get(type: any, subtype: any): any; 39 | set(type: string, subtype: string, state: any): void; 40 | extra(data?: any): any | null; 41 | observeOptions(): void; 42 | } 43 | 44 | export default StateInterface; -------------------------------------------------------------------------------- /src/models/ValidatorInterface.ts: -------------------------------------------------------------------------------- 1 | import Form from "../Form"; 2 | import Field from "../Field"; 3 | import { FieldInterface } from "./FieldInterface"; 4 | import { FormInterface } from "./FormInterface"; 5 | import { StateInterface } from "./StateInterface"; 6 | 7 | export interface ValidatorConstructor { 8 | form: FormInterface; 9 | plugins: ValidationPlugins; 10 | } 11 | 12 | export interface ValidateOptionsInterface { 13 | showErrors?: boolean; 14 | related?: boolean; 15 | field?: FieldInterface; 16 | path?: string; 17 | } 18 | 19 | export type ValidateOptions = string | ValidateOptionsInterface | Form | Field; 20 | 21 | export interface ValidatorInterface { 22 | promises: Promise[]; 23 | form: FormInterface; 24 | drivers: any; 25 | plugins: ValidationPlugins; 26 | error: string | null; 27 | initDrivers(): void; 28 | validate(opt?: ValidateOptions, obj?: ValidateOptions): Promise; 29 | validateField(opt: ValidateOptionsInterface): void; 30 | validateRelatedFields(field: any, showErrors: boolean): void; 31 | } 32 | 33 | export type ValidationPackage = any; 34 | 35 | export type ExtendPlugin = (args: { 36 | validator: TValidator; 37 | form: FormInterface; 38 | }) => void; 39 | 40 | export interface ValidationPluginConfig { 41 | package: TValidator; 42 | schema?: any; 43 | options?: any; 44 | extend?: ExtendPlugin; 45 | } 46 | 47 | export interface ValidationPluginConstructor { 48 | config: ValidationPluginConfig; 49 | state: StateInterface; 50 | promises: Promise[]; 51 | } 52 | 53 | export interface ValidationPluginInterface 54 | extends ValidationPluginConstructor { 55 | validator: TValidator; 56 | schema?: any; 57 | extend?: ExtendPlugin; 58 | validate(field: FieldInterface): void; 59 | class?(constructor: ValidationPluginConstructor): void; 60 | } 61 | 62 | export type ValidationPlugin = { 63 | class: any; 64 | config?: ValidationPluginConfig; 65 | }; 66 | 67 | export interface ValidationPlugins { 68 | [key: string]: ValidationPlugin | undefined; 69 | vjf?: ValidationPlugin; 70 | dvr?: ValidationPlugin; 71 | svk?: ValidationPlugin; 72 | yup?: ValidationPlugin; 73 | zod?: ValidationPlugin; 74 | joi?: ValidationPlugin; 75 | } 76 | 77 | export type DriversMap = { 78 | [key in keyof ValidationPlugins]: ValidationPluginInterface; 79 | } 80 | 81 | export enum ValidationHooks { 82 | onSuccess = 'onSuccess', 83 | onError = 'onError', 84 | } 85 | 86 | export default ValidatorInterface; -------------------------------------------------------------------------------- /src/validators/JOI.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import FieldInterface from "src/models/FieldInterface"; 3 | import FormInterface from "src/models/FormInterface"; 4 | import StateInterface from "src/models/StateInterface"; 5 | import { 6 | ValidationPlugin, 7 | ValidationPluginConfig, 8 | ValidationPluginConstructor, 9 | ValidationPluginInterface, 10 | } from "../models/ValidatorInterface"; 11 | 12 | class JOI implements ValidationPluginInterface { 13 | promises: Promise[]; 14 | config: ValidationPluginConfig; 15 | state: StateInterface; 16 | extend?: (args: { validator: TValidator; form: FormInterface }) => void; 17 | validator: TValidator; 18 | schema: any; 19 | 20 | constructor({ 21 | config, 22 | state = null, 23 | promises = [], 24 | }: ValidationPluginConstructor) { 25 | this.state = state; 26 | this.promises = promises; 27 | this.config = config; 28 | this.extend = config?.extend; 29 | this.validator = config.package; 30 | this.schema = config.schema; 31 | this.extendValidator(); 32 | } 33 | 34 | extendValidator(): void { 35 | if (typeof this.extend === "function") { 36 | this.extend({ 37 | validator: this.validator, 38 | form: this.state.form, 39 | }); 40 | } 41 | } 42 | 43 | validate(field: FieldInterface): void { 44 | const data = this.state.form.validatedValues; 45 | const { error } = this.schema.validate(data, { abortEarly: false }); 46 | 47 | if (!error) return; 48 | 49 | const fieldPathArray = field.path.split("."); 50 | 51 | const fieldErrors = error.details 52 | .filter((detail) => { 53 | const errorPathString = detail.path.join("."); 54 | const fieldPathString = fieldPathArray.join("."); 55 | return ( 56 | errorPathString === fieldPathString || 57 | errorPathString.startsWith(`${fieldPathString}.`) 58 | ); 59 | }) 60 | .map((detail) => { 61 | const label = detail.context?.label || detail.path.join("."); 62 | const message = detail.message.replace(`${detail.path.join(".")}`, label); 63 | return message; 64 | }); 65 | 66 | if (fieldErrors.length) { 67 | field.validationErrorStack = fieldErrors; 68 | } 69 | } 70 | } 71 | 72 | export default ( 73 | config?: ValidationPluginConfig 74 | ): ValidationPlugin => ({ 75 | class: JOI, 76 | config, 77 | }); -------------------------------------------------------------------------------- /src/validators/YUP.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ValidationPlugin, 3 | ValidationPluginConfig, 4 | ValidationPluginConstructor, 5 | ValidationPluginInterface, 6 | } from "../models/ValidatorInterface"; 7 | import FormInterface from "src/models/FormInterface"; 8 | import StateInterface from "src/models/StateInterface"; 9 | 10 | class YUP implements ValidationPluginInterface { 11 | promises: Promise[] = []; 12 | config: ValidationPluginConfig; 13 | state: StateInterface; 14 | extend?: (args: { validator: TValidator; form: FormInterface }) => void; 15 | validator: TValidator; 16 | schema: any; 17 | 18 | constructor({ 19 | config, 20 | state = null, 21 | promises = [], 22 | }: ValidationPluginConstructor) { 23 | this.state = state; 24 | this.promises = promises; 25 | this.extend = config?.extend; 26 | this.validator = config.package; 27 | this.schema = config.schema(this.validator); 28 | this.extendValidator(); 29 | } 30 | 31 | // Metodo per estendere il validatore 32 | extendValidator(): void { 33 | if (typeof this.extend === 'function') { 34 | this.extend({ 35 | validator: this.validator, 36 | form: this.state.form, 37 | }); 38 | } 39 | } 40 | 41 | // Metodo di validazione principale 42 | validate(field: any): void { 43 | const fieldValidationPromise = this.createValidationPromise(field); 44 | this.promises.push(fieldValidationPromise); 45 | } 46 | 47 | // Creazione della promise per la validazione 48 | private createValidationPromise(field: any): Promise { 49 | return new Promise((resolve) => { 50 | this.schema 51 | .validateAt(field.path, this.state.form.values(), { strict: true }) 52 | .then(() => this.handleAsyncPasses(field, resolve)) 53 | .catch((error) => this.handleAsyncFails(field, resolve, error)); 54 | }); 55 | } 56 | // Gestione dei successi della validazione asincrona 57 | private handleAsyncPasses(field: any, resolve: Function): void { 58 | field.setValidationAsyncData(true); 59 | resolve(); 60 | } 61 | 62 | // Gestione dei fallimenti della validazione asincrona 63 | private handleAsyncFails(field: any, resolve: Function, error: any): void { 64 | // Yup a volte restituisce errori senza path (es. array vuoti) 65 | const isSameField = error.path === field.path || error.path === undefined; 66 | 67 | if (isSameField) { 68 | const message = error.message?.replace(error.path ?? field.path, field.label); 69 | field.setValidationAsyncData(false, message); 70 | this.executeAsyncValidation(field); 71 | } 72 | 73 | resolve(undefined); 74 | } 75 | 76 | // Esecuzione della validazione asincrona 77 | private executeAsyncValidation(field: any): void { 78 | if (field.validationAsyncData.valid === false) { 79 | field.invalidate(field.validationAsyncData.message, false, true); 80 | } 81 | } 82 | } 83 | 84 | export default ( 85 | config?: ValidationPluginConfig 86 | ): ValidationPlugin => ({ 87 | class: YUP, 88 | config, 89 | }); -------------------------------------------------------------------------------- /src/validators/ZOD.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import FieldInterface from "src/models/FieldInterface"; 3 | import FormInterface from "src/models/FormInterface"; 4 | import StateInterface from "src/models/StateInterface"; 5 | import { ZodSchema } from "zod"; 6 | import { 7 | ValidationPlugin, 8 | ValidationPluginConfig, 9 | ValidationPluginConstructor, 10 | ValidationPluginInterface, 11 | } from "../models/ValidatorInterface"; 12 | 13 | export class ZOD implements ValidationPluginInterface { 14 | promises: Promise[]; 15 | config: ValidationPluginConfig; 16 | state: StateInterface; 17 | extend?: (args: { validator: TValidator; form: FormInterface }) => void; 18 | validator: any; 19 | schema: ZodSchema; 20 | 21 | constructor({ 22 | config, 23 | state = null, 24 | promises = [], 25 | }: ValidationPluginConstructor) { 26 | this.state = state; 27 | this.promises = promises; 28 | this.config = config; 29 | this.extend = config?.extend; 30 | this.validator = config.package; 31 | this.schema = config.schema as ZodSchema; 32 | 33 | this.extendValidator(); 34 | } 35 | 36 | extendValidator(): void { 37 | if (typeof this.extend === "function") { 38 | this.extend({ 39 | validator: this.validator, 40 | form: this.state.form, 41 | }); 42 | } 43 | } 44 | 45 | validate(field: FieldInterface): void { 46 | const result = this.schema.safeParse(field.state.form.validatedValues); 47 | 48 | if (result.success) return; 49 | 50 | const fieldErrors = _.get((result as any).error.format(), field.path)?._errors; 51 | 52 | if (fieldErrors?.length) { 53 | field.validationErrorStack = fieldErrors; 54 | } 55 | } 56 | } 57 | 58 | 59 | export default ( 60 | config?: ValidationPluginConfig 61 | ): ValidationPlugin => ({ 62 | class: ZOD, 63 | config, 64 | }); -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/computed/_.flat.ts: -------------------------------------------------------------------------------- 1 | import isValid from './flat.isValid'; 2 | import hasError from './flat.hasError'; 3 | import isDirty from './flat.isDirty'; 4 | import isEmpty from './flat.isEmpty'; 5 | import isPristine from './flat.isPristine'; 6 | 7 | export default { 8 | isValid, hasError, isDirty, isEmpty, isPristine, 9 | }; 10 | -------------------------------------------------------------------------------- /tests/computed/_.nested.ts: -------------------------------------------------------------------------------- 1 | import isValid from './nested.isValid'; 2 | 3 | export default { isValid }; 4 | -------------------------------------------------------------------------------- /tests/computed/flat.hasError.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | export default ($) => { 4 | describe('Form hasError', () => { 5 | it('$A hasError should be false', () => expect($.$A.hasError).to.be.false); 6 | it('$B hasError should be true', () => expect($.$B.hasError).to.be.true); 7 | it('$C hasError should be true', () => expect($.$C.hasError).to.be.true); 8 | it('$D hasError should be true', () => expect($.$D.hasError).to.be.true); 9 | it('$E hasError should be false', () => expect($.$E.hasError).to.be.false); 10 | it('$F hasError should be true', () => expect($.$F.hasError).to.be.true); 11 | it('$G hasError should be false', () => expect($.$G.hasError).to.be.false); 12 | it('$H hasError should be false', () => expect($.$H.hasError).to.be.false); 13 | it('$I hasError should be false', () => expect($.$I.hasError).to.be.false); 14 | it('$L hasError should be true', () => expect($.$L.hasError).to.be.true); 15 | it('$M hasError should be true', () => expect($.$M.hasError).to.be.true); 16 | it('$N hasError should be true', () => expect($.$N.hasError).to.be.true); 17 | it('$O hasError should be false', () => expect($.$O.hasError).to.be.false); 18 | it('$P hasError should be true', () => expect($.$P.hasError).to.be.true); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /tests/computed/flat.isDirty.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | export default ($) => { 4 | describe('Form isDirty', () => { 5 | it('$A isDirty should be false', () => expect($.$A.isDirty).to.be.false); 6 | it('$B isDirty should be false', () => expect($.$B.isDirty).to.be.false); 7 | it('$C isDirty should be false', () => expect($.$C.isDirty).to.be.false); 8 | it('$D isDirty should be true', () => expect($.$D.isDirty).to.be.true); 9 | it('$E isDirty should be false', () => expect($.$E.isDirty).to.be.false); 10 | it('$F isDirty should be false', () => expect($.$F.isDirty).to.be.false); 11 | it('$G isDirty should be false', () => expect($.$G.isDirty).to.be.false); 12 | it('$H isDirty should be false', () => expect($.$H.isDirty).to.be.false); 13 | it('$I isDirty should be false', () => expect($.$I.isDirty).to.be.false); 14 | it('$L isDirty should be false', () => expect($.$L.isDirty).to.be.false); // 15 | it('$M isDirty should be true', () => expect($.$M.isDirty).to.be.true); 16 | it('$N isDirty should be false', () => expect($.$N.isDirty).to.be.false); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /tests/computed/flat.isEmpty.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | export default ($) => { 4 | describe('Form isEmpty', () => { 5 | it('$A isEmpty should be false', () => expect($.$A.isEmpty).to.be.false); 6 | it('$B isEmpty should be true', () => expect($.$B.isEmpty).to.be.true); 7 | it('$C isEmpty should be false', () => expect($.$C.isEmpty).to.be.false); 8 | it('$D isEmpty should be false', () => expect($.$D.isEmpty).to.be.false); 9 | it('$E isEmpty should be false', () => expect($.$E.isEmpty).to.be.false); 10 | it('$F isEmpty should be false', () => expect($.$F.isEmpty).to.be.false); 11 | it('$G isEmpty should be true', () => expect($.$G.isEmpty).to.be.true); 12 | it('$H isEmpty should be false', () => expect($.$H.isEmpty).to.be.false); 13 | it('$I isEmpty should be false', () => expect($.$I.isEmpty).to.be.false); 14 | it('$L isEmpty should be true', () => expect($.$L.isEmpty).to.be.true); 15 | it('$M isEmpty should be false', () => expect($.$M.isEmpty).to.be.false); 16 | it('$N isEmpty should be false', () => expect($.$N.isEmpty).to.be.false); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /tests/computed/flat.isPristine.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | export default ($) => { 4 | describe('Form isPristine', () => { 5 | it('$A isPristine should be true', () => expect($.$A.isPristine).to.be.true); 6 | it('$B isPristine should be true', () => expect($.$B.isPristine).to.be.true); 7 | it('$C isPristine should be true', () => expect($.$C.isPristine).to.be.true); 8 | it('$D isPristine should be false', () => expect($.$D.isPristine).to.be.false); 9 | it('$E isPristine should be true', () => expect($.$E.isPristine).to.be.true); 10 | it('$F isPristine should be true', () => expect($.$F.isPristine).to.be.true); 11 | it('$G isPristine should be true', () => expect($.$G.isPristine).to.be.true); 12 | it('$H isPristine should be true', () => expect($.$H.isPristine).to.be.true); 13 | it('$I isPristine should be true', () => expect($.$I.isPristine).to.be.true); 14 | it('$L isPristine should be true', () => expect($.$L.isPristine).to.be.true); // 15 | it('$M isPristine should be false', () => expect($.$M.isPristine).to.be.false); 16 | it('$N isPristine should be true', () => expect($.$N.isPristine).to.be.true); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /tests/computed/flat.isValid.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | export default ($) => { 4 | describe('Form isValid', () => { 5 | it('$A isValid should be true', () => expect($.$A.isValid).to.be.true); 6 | it('$B isValid should be false', () => expect($.$B.isValid).to.be.false); 7 | it('$C isValid should be false', () => expect($.$C.isValid).to.be.false); 8 | it('$D isValid should be false', () => expect($.$D.isValid).to.be.false); 9 | it('$E isValid should be true', () => expect($.$E.isValid).to.be.true); 10 | it('$F isValid should be false', () => expect($.$F.isValid).to.be.false); 11 | it('$G isValid should be true', () => expect($.$G.isValid).to.be.true); 12 | it('$H isValid should be true', () => expect($.$H.isValid).to.be.true); 13 | it('$I isValid should be true', () => expect($.$I.isValid).to.be.true); 14 | it('$L isValid should be false', () => expect($.$L.isValid).to.be.false); 15 | it('$M isValid should be false', () => expect($.$M.isValid).to.be.false); 16 | it('$N isValid should be false', () => expect($.$N.isValid).to.be.false); 17 | it('$O isValid should be true', () => expect($.$O.isValid).to.be.true); 18 | it('$P isValid should be false', () => expect($.$P.isValid).to.be.false); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /tests/computed/nested.isValid.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | export default ($) => { 4 | describe('Nested Form isValid', () => { 5 | it('$A isValid should be false', () => expect($.$A.isValid).to.be.false); 6 | it('$R isValid should be true', () => expect($.$R.isValid).to.be.true); 7 | it('$S isValid should be false', () => expect($.$S.isValid).to.be.false); 8 | it('$U isValid should be false', () => expect($.$U.isValid).to.be.false); 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /tests/data/_.fixes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | FIXES 3 | */ 4 | import $formEmptyConstructor from "./forms/form.empty.constructor"; 5 | 6 | import $A from "./forms/fixes/form.a"; 7 | import $A1 from "./forms/fixes/form.a1"; 8 | import $B from "./forms/fixes/form.b"; 9 | import $C from "./forms/fixes/form.c"; 10 | import $D from "./forms/fixes/form.d_"; 11 | import $E from "./forms/fixes/form.e"; 12 | import $F from "./forms/fixes/form.f"; 13 | import $G from "./forms/fixes/form.g"; 14 | import $H from "./forms/fixes/form.h"; 15 | import $I from "./forms/fixes/form.i"; 16 | import $L from "./forms/fixes/form.l"; 17 | import $M from "./forms/fixes/form.m"; 18 | import $N from "./forms/fixes/form.n"; 19 | import $O from "./forms/fixes/form.o"; 20 | import $P from "./forms/fixes/form.p"; 21 | import $Q from "./forms/fixes/form.q"; 22 | import $Q1 from "./forms/fixes/form.q1"; 23 | import $Q2 from "./forms/fixes/form.q2"; 24 | import $R from "./forms/fixes/form.r"; 25 | import $S from "./forms/fixes/form.s"; 26 | import $T from "./forms/fixes/form.t"; 27 | import $U from "./forms/fixes/form.u"; 28 | import $V from "./forms/fixes/form.v"; 29 | import $Z from "./forms/fixes/form.z"; 30 | 31 | import $425 from "./forms/fixes/form.425"; 32 | import $472 from "./forms/fixes/form.472"; 33 | import $480 from "./forms/fixes/form.480"; 34 | import $481 from "./forms/fixes/form.481"; 35 | import $492 from "./forms/fixes/form.492"; 36 | import $495 from "./forms/fixes/form.495"; 37 | import $505 from "./forms/fixes/form.505"; 38 | import $507 from "./forms/fixes/form.507"; 39 | import $514 from "./forms/fixes/form.514"; 40 | import $613 from "./forms/fixes/form.613"; 41 | import $544 from "./forms/fixes/form.544"; 42 | import $454 from "./forms/fixes/form.454"; 43 | import $518 from "./forms/fixes/form.518"; 44 | import $376 from "./forms/fixes/form.376"; 45 | import $519 from "./forms/fixes/form.519"; 46 | 47 | import $585 from "./forms/fixes/form.585"; 48 | import $531 from "./forms/fixes/form.531"; 49 | 50 | import { form626a as $626a } from "./forms/fixes/form.626a"; 51 | import { form626b as $626b } from "./forms/fixes/form.626b"; 52 | 53 | export default { 54 | $formEmptyConstructor, 55 | $A, 56 | $A1, 57 | $B, 58 | $C, 59 | $D, 60 | $E, 61 | $F, 62 | $G, 63 | $H, 64 | $I, 65 | $L, 66 | $M, 67 | $N, 68 | $O, 69 | $P, 70 | $Q, 71 | $Q1, 72 | $Q2, 73 | $R, 74 | $S, 75 | $T, 76 | $U, 77 | $V, 78 | $Z, 79 | 80 | $425, 81 | $472, 82 | $480, 83 | $481, 84 | $492, 85 | $495, 86 | $505, 87 | $507, 88 | $514, 89 | $613, 90 | $544, 91 | $454, 92 | $518, 93 | $376, 94 | $519, 95 | 96 | $585, 97 | $531, 98 | 99 | $626a, 100 | $626b, 101 | }; 102 | -------------------------------------------------------------------------------- /tests/data/_.flat.ts: -------------------------------------------------------------------------------- 1 | /** 2 | FLAT 3 | */ 4 | import $A from "./forms/flat/form.a"; 5 | import $B from "./forms/flat/form.b"; 6 | import $C from "./forms/flat/form.c"; 7 | import $D from "./forms/flat/form.d_"; 8 | import $E from "./forms/flat/form.e"; 9 | import $F from "./forms/flat/form.f"; 10 | import $G from "./forms/flat/form.g"; 11 | import $H from "./forms/flat/form.h"; 12 | import $I from "./forms/flat/form.i"; 13 | import $L from "./forms/flat/form.l"; 14 | import $M from "./forms/flat/form.m"; 15 | import $N from "./forms/flat/form.n"; 16 | import $O from "./forms/flat/form.o"; 17 | import $P from "./forms/flat/form.p"; 18 | import $Q from "./forms/flat/form.q"; 19 | import $R from "./forms/flat/form.r"; 20 | import $T from "./forms/flat/form.t"; 21 | 22 | export default { 23 | $A, 24 | $B, 25 | $C, 26 | $D, 27 | $E, 28 | $F, 29 | $G, 30 | $H, 31 | $I, 32 | $L, 33 | $M, 34 | $N, 35 | $O, 36 | $P, 37 | $Q, 38 | $R, 39 | $T, 40 | }; 41 | -------------------------------------------------------------------------------- /tests/data/_.methods.ts: -------------------------------------------------------------------------------- 1 | /** 2 | METHODS 3 | */ 4 | import $set from "./forms/methods/form.set"; 5 | 6 | export default { 7 | $set, 8 | }; 9 | -------------------------------------------------------------------------------- /tests/data/_.nested.ts: -------------------------------------------------------------------------------- 1 | /** 2 | NESTED 3 | */ 4 | import $A from "./forms/nested/form.a"; 5 | import $A1 from "./forms/nested/form.a1"; 6 | import $A2 from "./forms/nested/form.a2"; 7 | import $B from "./forms/nested/form.b"; 8 | import $C from "./forms/nested/form.c"; 9 | import $D from "./forms/nested/form.d_"; 10 | import $E from "./forms/nested/form.e"; 11 | import $E2 from "./forms/nested/form.e2"; 12 | import $F from "./forms/nested/form.f"; 13 | import $G from "./forms/nested/form.g"; 14 | import $H from "./forms/nested/form.h"; 15 | import $I from "./forms/nested/form.i"; 16 | import $L from "./forms/nested/form.l"; 17 | import $M from "./forms/nested/form.m"; 18 | import $M1 from "./forms/nested/form.m1"; 19 | import $N from "./forms/nested/form.n"; 20 | import $O from "./forms/nested/form.o"; 21 | import $P from "./forms/nested/form.p"; 22 | import $Q from "./forms/nested/form.q"; 23 | import $R from "./forms/nested/form.r"; 24 | import $S from "./forms/nested/form.s"; 25 | import $S2 from "./forms/nested/form.s2"; 26 | import $T from "./forms/nested/form.t"; 27 | import $T1 from "./forms/nested/form.t1"; 28 | import $U from "./forms/nested/form.u"; 29 | import $V from "./forms/nested/form.v"; 30 | import $V2 from "./forms/nested/form.v2"; 31 | import $V3 from "./forms/nested/form.v3"; 32 | import $V4 from "./forms/nested/form.v4"; 33 | import $Z from "./forms/nested/form.z"; 34 | import $Z1 from "./forms/nested/form.z1"; 35 | import $Z2 from "./forms/nested/form.z2"; 36 | import $Z3 from "./forms/nested/form.z3"; 37 | import $Z4 from "./forms/nested/form.z4"; 38 | import $X from "./forms/nested/form.x"; 39 | 40 | export default { 41 | $A, 42 | $A1, 43 | $A2, 44 | $B, 45 | $C, 46 | $D, 47 | $E, 48 | $E2, 49 | $F, 50 | $G, 51 | $H, 52 | $I, 53 | $L, 54 | $M, 55 | $M1, 56 | $N, 57 | $O, 58 | $P, 59 | $Q, 60 | $R, 61 | $S, 62 | $S2, 63 | $T, 64 | $T1, 65 | $U, 66 | $V, 67 | $V2, 68 | $V3, 69 | $V4, 70 | $Z, 71 | $Z1, 72 | $Z2, 73 | $Z3, 74 | $Z4, 75 | $X, 76 | }; 77 | -------------------------------------------------------------------------------- /tests/data/extension/_.async.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | 3 | const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 4 | 5 | export default (query: any = {}, ignoreCase?) => { 6 | // assume these usernames are in the database 7 | const db = [ 8 | { user: "Claudio" }, 9 | { user: "FoxHound" }, 10 | { user: "Steve Jobs" }, 11 | ]; 12 | 13 | const checkLowerCase = (o: any) => 14 | _.lowerCase(o.user) === _.lowerCase(query.user); 15 | 16 | return sleep(25).then( 17 | () => _.find(db, ignoreCase ? checkLowerCase : query) || [] 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /tests/data/extension/_.bindings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Fields Bindings 3 | */ 4 | export default { 5 | MaterialTextFieldTemplate: ({ field, props, $try }) => ({ 6 | type: $try(props.type, field.type), 7 | id: $try(props.id, field.id), 8 | name: $try(props.name, field.name), 9 | value: $try(props.value, field.value), 10 | floatingLabelText: $try(props.label, field.label), 11 | hintText: $try(props.placeholder, field.placeholder), 12 | errorText: $try(props.error, field.error), 13 | disabled: $try(props.disabled, field.disabled), 14 | onChange: $try(props.onChange, field.onChange), 15 | onFocus: $try(props.onFocus, field.onFocus), 16 | onBlur: $try(props.onBlur, field.onBlur), 17 | }), 18 | 19 | MaterialTextFieldRewriter: { 20 | id: "id", 21 | name: "name", 22 | type: "type", 23 | value: "value", 24 | label: "floatingLabelText", 25 | placeholder: "hintText", 26 | disabled: "disabled", 27 | error: "errorText", 28 | onChange: "onChange", 29 | onFocus: "onFocus", 30 | onBlur: "onBlur", 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /tests/data/extension/dvr.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import simulateAsyncFindUserCall from "./_.async"; 3 | 4 | const asyncRules = { 5 | checkUser: (value, attr, key, passes) => { 6 | const msg = `Hey! The :attribute ${value} is already taken.`; 7 | // show error if the call does not returns entries 8 | simulateAsyncFindUserCall( 9 | { user: value }, 10 | _.lowerCase(attr) === "ignorecase" 11 | ).then((items: any) => 12 | items.length === 0 ? passes() : passes(false, msg) 13 | ); 14 | }, 15 | }; 16 | 17 | // const rules = { 18 | // telephone: { 19 | // function: value => value.match(/^\d{3}-\d{3}-\d{4}$/), 20 | // message: 'The :attribute phone number is not in the format XXX-XXX-XXXX.', 21 | // }, 22 | // }; 23 | 24 | export default ({ validator }) => 25 | Object.keys(asyncRules).forEach((key) => 26 | validator.registerAsync(key, asyncRules[key]) 27 | ); 28 | 29 | // export default ({ validator }) => Object.keys(rules) 30 | // .forEach((key) => validator.register(key, rules[key].function, rules[key].message)); 31 | -------------------------------------------------------------------------------- /tests/data/extension/svk.ts: -------------------------------------------------------------------------------- 1 | import simulateAsyncFindUserCall from "./_.async"; 2 | 3 | const extend = { 4 | keywords: { 5 | range: { 6 | type: "number", 7 | compile: (sch, parentSchema) => { 8 | const min = sch[0]; 9 | const max = sch[1]; 10 | 11 | return parentSchema.exclusiveRange === true 12 | ? (data) => data > min && data < max 13 | : (data) => data >= min && data <= max; 14 | }, 15 | }, 16 | checkUser: { 17 | async: true, 18 | validate: (field, value) => 19 | simulateAsyncFindUserCall({ [field]: value }).then( 20 | (items: any) => items.length === 0 21 | ), 22 | }, 23 | }, 24 | // formats: {}, 25 | }; 26 | 27 | export default ({ validator }) => { 28 | Object.keys(extend.keywords).forEach((key) => 29 | validator.addKeyword(key, extend.keywords[key]) 30 | ); 31 | // Object.keys(extend.formats).forEach(key => validator.addFormat(key, extend.formats[key])); 32 | }; 33 | -------------------------------------------------------------------------------- /tests/data/extension/vjf.ts: -------------------------------------------------------------------------------- 1 | import simulateAsyncFindUserCall from "./_.async"; 2 | 3 | export function checkUser({ field }) { 4 | const msg = `Hey! The username ${field.value} is already taken.`; 5 | // show error if the call does not returns entries 6 | return simulateAsyncFindUserCall({ user: field.value }).then((items: any) => [ 7 | items.length === 0, 8 | msg, 9 | ]); 10 | } 11 | 12 | export function shouldBeEqualTo(target) { 13 | return ({ field, form }) => { 14 | const fieldsAreEquals = form.select(target).value === field.value; 15 | return [ 16 | fieldsAreEquals, 17 | `The ${field.label} should be equals to ${form.select(target).label}`, 18 | ]; 19 | }; 20 | } 21 | 22 | export function isEmailByValidator({ field, validator }) { 23 | const isValid = validator.isEmail(field.value); 24 | return [isValid, `The ${field.label} should be an email address.`]; 25 | } 26 | 27 | export function isEmail({ field }) { 28 | const isValid = field.value.indexOf("@") > 0; 29 | return [isValid, `The ${field.label} should be an email address.`]; 30 | } 31 | 32 | export function isInt({ field }) { 33 | const isValid = Number.isInteger(field.value); 34 | return [isValid, `The ${field.label} should be an Integer.`]; 35 | } 36 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.376.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import validatorjs from "validatorjs"; 3 | import { Form } from "../../../../src"; 4 | import { FormInterface } from "../../../../src/models/FormInterface"; 5 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 6 | import dvr from "../../../../src/validators/DVR"; 7 | 8 | const plugins: ValidationPlugins = { 9 | dvr: dvr({ package: validatorjs }) 10 | } 11 | 12 | // const struct = [ 13 | // 'suitability', 14 | // 'suitability.project_completion', 15 | // 'suitability.answers', 16 | // 'suitability.answers.q1', 17 | // ]; 18 | 19 | const fields = [{ 20 | name: 'suitability', 21 | label: 'Suitability', 22 | value: 'hello', 23 | fields: [{ 24 | name: 'project_completion', 25 | label: 'Project Completion', 26 | }, { 27 | name: 'answers', 28 | labels: 'Answers', 29 | fields: [{ 30 | name: 'q1', 31 | label: 'Question 1', 32 | rules: 'required' 33 | }] 34 | }] 35 | }]; 36 | 37 | 38 | class NewForm extends Form { 39 | hooks() { 40 | return { 41 | onInit(form: FormInterface) { 42 | describe("Check deep validation:", () => { 43 | it("form isValid should be false", () => 44 | expect(form.isValid).to.be.false); 45 | }); 46 | 47 | describe("Check deep label:", () => { 48 | it("field `suitability` label should be `Question 1`", () => 49 | expect(form.$('suitability').label).to.be.equal('Suitability')); 50 | 51 | it("field `suitability.project_completion` label should be `Question 1`", () => 52 | expect(form.$('suitability.project_completion').label).to.be.equal('Project Completion')); 53 | 54 | it("field `suitability.project_completion.q1` label should be `Question 1`", () => 55 | expect(form.$('suitability.answers.q1').label).to.be.equal('Question 1')); 56 | 57 | it("field `suitability.project_completion.q1` isValid should be `false`", () => 58 | expect(form.$('suitability.answers.q1').isValid).to.be.false); 59 | 60 | it("field `suitability.project_completion.q1` error should be `Question 1 is required`", () => 61 | expect(form.$('suitability.answers.q1').error).to.be.equal('The Question 1 field is required.')); 62 | }); 63 | 64 | describe("Check deep values():", () => { 65 | it("form values() should be equal to...", () => 66 | expect(form.values()).to.be.deep.equal({ 67 | suitability: { 68 | project_completion: '', 69 | answers: { 70 | q1: '' 71 | } 72 | } 73 | })); 74 | }); 75 | } 76 | } 77 | } 78 | } 79 | 80 | export default new NewForm({ fields }, { name: "$376", plugins, options: { 81 | showErrorsOnInit: true 82 | } }); 83 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.425.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const fields = [ 4 | { 5 | name: "1a", 6 | value: " ", 7 | label: "1aa", 8 | }, 9 | { 10 | name: "2a", 11 | value: " ", 12 | label: "2aa", 13 | }, 14 | { 15 | name: "3a", 16 | value: " ", 17 | label: "3aa", 18 | }, 19 | ]; 20 | 21 | class NewForm extends Form {} 22 | 23 | export default new NewForm({ fields }, { name: "Fixes-425" }); 24 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.454.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Form } from "../../../../src"; 3 | import { FormInterface } from "../../../../src/models/FormInterface"; 4 | 5 | const fields = [ 6 | 'any.type', 7 | 'any.other' 8 | ]; 9 | 10 | const values = { 11 | any: { 12 | type: 'a', 13 | other: 'b', 14 | }, 15 | }; 16 | 17 | class NewForm extends Form { 18 | hooks() { 19 | return { 20 | onInit(form: FormInterface) { 21 | describe("Check initial values state:", () => { 22 | it("form `values()` should be equal `true`", () => expect(form.values()).to.be.deep.equal(values)); 23 | it("form any `value` should be equal `a`", () => expect(form.$('any').value).to.be.deep.equal(values.any)); 24 | it("form any.type `value` should be equal `a`", () => expect(form.$('any.type').value).to.be.equal(values.any.type)); 25 | it("form any.other `value` should be equal `b`", () => expect(form.$('any.other').value).to.be.equal(values.any.other)); 26 | }); 27 | 28 | } 29 | } 30 | } 31 | } 32 | 33 | export default new NewForm({ fields, values }, { name: "$454" }); 34 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.472.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const fields = {}; 4 | 5 | class HookedForm extends Form { 6 | hooks() { 7 | return { 8 | onSuccess: form => form.values(), 9 | onSubmit: instance => instance 10 | }; 11 | } 12 | } 13 | 14 | export default new HookedForm({ fields }, { name: 'Fixes-472' }); 15 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.480.ts: -------------------------------------------------------------------------------- 1 | import validatorjs from "validatorjs"; 2 | import { Form } from "../../../../src"; 3 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 4 | import dvr from "../../../../src/validators/DVR"; 5 | 6 | const plugins: ValidationPlugins = { 7 | dvr: dvr({ package: validatorjs }), 8 | }; 9 | 10 | const fields = [ 11 | { 12 | name: "passwordConfirm", 13 | label: "Password Confirmation", 14 | rules: "required_if:passwordRequired,true", 15 | value: "", 16 | }, 17 | { 18 | name: "passwordRequired", 19 | label: "Password Required", 20 | type: "checkbox", 21 | value: true, 22 | output: (value) => (value === true ? "true" : "false"), 23 | }, 24 | ]; 25 | 26 | class NewForm extends Form {} 27 | 28 | export default new NewForm({ fields }, { plugins, name: "Fixes-480" }); 29 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.481.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const values = { 4 | length: 0, // ISSUE #481 5 | }; 6 | 7 | class NewForm extends Form {} 8 | 9 | export default new NewForm({ values }, { name: "Fixes-481" }); 10 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.492.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const fields = ["club", "club.name", "club.city"]; 4 | 5 | const labels = { 6 | club: "Club", 7 | "club.name": "Name", 8 | "club.city": "City", 9 | }; 10 | 11 | const values = { 12 | club: null, 13 | }; 14 | 15 | class NewForm extends Form {} 16 | 17 | export default new NewForm( 18 | { fields, labels, values }, 19 | { name: "Field definition" } 20 | ); 21 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.495.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const fields = ["club", "club.name", "club.city"]; 4 | 5 | const labels = { 6 | club: "Club", 7 | "club.name": "Name", 8 | "club.city": "City", 9 | }; 10 | 11 | const values = { 12 | club: { 13 | name: "JJSC", 14 | city: "Taipei", 15 | }, 16 | }; 17 | 18 | class NewForm extends Form {} 19 | 20 | export default new NewForm({ fields, labels, values }, { name: "$495" }); 21 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.505.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { expect } from "chai"; 3 | 4 | const fields = ["club", "club.name", "club.city"]; 5 | 6 | const labels = { 7 | club: "Club", 8 | "club.name": "Name", 9 | "club.city": "City", 10 | }; 11 | 12 | const values = { 13 | club: { 14 | name: "JJSC", 15 | city: "Taipei", 16 | area: "JJ", 17 | }, 18 | }; 19 | 20 | class NewForm extends Form {} 21 | 22 | export default new NewForm( 23 | { fields, labels, values }, 24 | { name: "$505", options: { fallback: false } } 25 | ); 26 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.507.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const fields = ["people", "people[]", "people[].name", "people[].birthday"]; 4 | 5 | const types = { 6 | "people[].birthday": "date", 7 | }; 8 | 9 | const values = { 10 | people: [ 11 | { 12 | name: "adam", 13 | birthday: null, 14 | }, 15 | ], 16 | }; 17 | 18 | class NewForm extends Form {} 19 | 20 | export default new NewForm({ fields, types, values }, { name: "$507" }); 21 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.514.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const fields = [ 4 | "priority", 5 | "itineraryItems[].hotel.name", 6 | "itineraryItems[].hotel.starRating", 7 | ]; 8 | 9 | const input = { 10 | priority: (p) => (p === "" ? -1 : Number(p)), 11 | "itineraryItems[].hotel.starRating": (rate) => { 12 | return Number(rate); 13 | }, 14 | }; 15 | 16 | class NewForm extends Form { 17 | hooks() { 18 | return { 19 | onInit(form) { 20 | form.update({ 21 | priority: "1", 22 | itineraryItems: [ 23 | { 24 | hotel: { 25 | name: "Shangri-La Hotel", 26 | starRating: "5.0", 27 | }, 28 | }, 29 | { 30 | hotel: null, 31 | }, 32 | { 33 | hotel: { 34 | name: "Trump Hotel", 35 | starRating: "5.0", 36 | }, 37 | }, 38 | ], 39 | }); 40 | }, 41 | }; 42 | } 43 | } 44 | 45 | export default new NewForm({ fields, input }, { name: "Form 514" }); 46 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.518.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import validatorjs from "validatorjs"; 3 | import { Form } from "../../../../src"; 4 | import { FormInterface } from "../../../../src/models/FormInterface"; 5 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 6 | import dvr from "../../../../src/validators/DVR"; 7 | 8 | const plugins: ValidationPlugins = { 9 | dvr: dvr({ package: validatorjs }) 10 | } 11 | 12 | const struct = [ 13 | 'any.type', 14 | 'any.other' 15 | ]; 16 | 17 | const labels = { 18 | any: { 19 | type: 'Type', 20 | other: 'Other', 21 | }, 22 | } 23 | 24 | const values = { 25 | any: { 26 | type: 'x', 27 | other: 'y', 28 | }, 29 | }; 30 | 31 | const disabled = { 32 | 'any.type': false, 33 | 'any.other': true, 34 | }; 35 | 36 | const rules = { 37 | any: { 38 | type: 'integer', 39 | other: 'integer', 40 | } 41 | } 42 | 43 | const errorMessageForAnyTypeField = 'The Type must be an integer.'; 44 | 45 | class NewForm extends Form { 46 | hooks() { 47 | return { 48 | onInit(form: FormInterface) { 49 | describe("Check initial values state:", () => { 50 | it("form isValid should be false", () => expect(form.isValid).to.be.equal(false)); 51 | it("form hasError should be false", () => expect(form.hasError).to.be.equal(true)); 52 | 53 | it("form `$('any').disabled` should be false", () => expect(form.$('any').disabled).to.be.equal(false)); 54 | 55 | it("form `$('any.other').isValid` should be true", () => expect(form.$('any.other').isValid).to.be.equal(true)); 56 | it("form `$('any.type').isValid` should be false", () => expect(form.$('any.type').isValid).to.be.equal(false)); 57 | 58 | it("form `$('any.type').value` should be `x`", () => expect(form.$('any.type').value).to.be.equal('x')); 59 | it("form `$('any.other').value` should be `y`", () => expect(form.$('any.other').value).to.be.equal('y')); 60 | 61 | it("form `$('any.other')` should be disabled", () => expect(form.$('any.other').disabled).to.be.equal(true)); 62 | it("form `$('any.type')` should be disabled", () => expect(form.$('any.type').disabled).to.be.equal(false)); 63 | 64 | it("form `$('any.other').error` should be true", () => expect(form.$('any.other').error).to.be.equal(null)); 65 | it("form `$('any.type').error` should be false", () => expect(form.$('any.type').error).to.be.equal(errorMessageForAnyTypeField)); 66 | 67 | it("form `values()` should not have disabled field `other`", () => expect(form.values()).to.be.deep.equal({ 68 | any: { 69 | type: 'x' 70 | // other: 'y, // should be unfedined 71 | } 72 | })); 73 | 74 | it("form `errors()` should not have disabled field `other`", () => expect(form.errors()).to.be.deep.equal({ 75 | any: { 76 | type: errorMessageForAnyTypeField, 77 | // other: null, // should be unfedined 78 | } 79 | })); 80 | }); 81 | } 82 | } 83 | } 84 | } 85 | 86 | export default new NewForm({ struct, values, rules, disabled, labels }, { name: "$518", plugins, options: { 87 | showErrorsOnInit: true, 88 | retrieveOnlyEnabledFieldsValues: true, 89 | retrieveOnlyEnabledFieldsErrors: true, 90 | validateDisabledFields: false, 91 | validateDeletedFields: false, 92 | } }); 93 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.519.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import validatorjs from "validatorjs"; 3 | import { Form } from "../../../../src"; 4 | import { FormInterface } from "../../../../src/models/FormInterface"; 5 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 6 | import vjf from "../../../../src/validators/VJF"; 7 | import dvr from "../../../../src/validators/DVR"; 8 | 9 | const plugins: ValidationPlugins = { 10 | vjf: vjf(), 11 | dvr: dvr({ package: validatorjs }) 12 | }; 13 | 14 | const weekValidator = ({ field, form }) => { 15 | // form.invalidate(); // <--- need to work without calling invalidate() 16 | return [false, "Error ..."]; 17 | }; 18 | 19 | const fields = [ 20 | { 21 | name: "week", 22 | label: "week", 23 | validators: [weekValidator], 24 | fields: [ 25 | { 26 | name: "MONDAY", 27 | label: "MONDAY", 28 | disabled: true, 29 | fields: [ 30 | { 31 | name: "start", 32 | label: "start", 33 | // validators: [timeValidator] 34 | }, 35 | { 36 | name: "end", 37 | label: "end", 38 | // validators: [timeValidator] 39 | } 40 | ], 41 | }, 42 | { 43 | name: "TUESDAY", 44 | label: "TUESDAY", 45 | disabled: true, 46 | fields: [ 47 | { 48 | name: "start", 49 | label: "start" 50 | }, 51 | { 52 | name: "end", 53 | label: "end" 54 | } 55 | ], 56 | } 57 | // ...other days 58 | ] 59 | } 60 | ]; 61 | 62 | const hooks = { 63 | onInit(form) { 64 | form.submit(); 65 | }, 66 | }; 67 | 68 | const form = new Form({ fields }, { 69 | plugins, 70 | hooks, 71 | name: '$519', 72 | options: { 73 | validateOnInit: true, 74 | showErrorsOnInit: true, 75 | retrieveOnlyEnabledFieldsValues: true, 76 | validateDisabledFields: false 77 | }}); 78 | 79 | form.set('hooks', { 80 | onSuccess(form: FormInterface) {}, 81 | onError(form: FormInterface) {} 82 | }) 83 | 84 | export default form; -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.531.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Form } from "../../../../src"; 3 | import { FormInterface } from "../../../../src/models/FormInterface"; 4 | 5 | const fields = [ 6 | 'name', 7 | 'conditions[]', 8 | 'conditions[].action', 9 | 'conditions[].action.applyBy', 10 | 'conditions[].action.amount', 11 | ] 12 | 13 | const labels = { 14 | name: 'Rule Name', 15 | 'conditions[].action.applyBy': 'Apply By', 16 | 'conditions[].action.amount': 'Discount Amount', 17 | } 18 | 19 | const types = { 20 | 'conditions[].action.amount': 'number', 21 | } 22 | 23 | const input = { 24 | "conditions[].action.applyBy": (value) => value === null ? '' : value, 25 | "conditions[].action.amount": (value) => value === null ? 0 : value 26 | }; 27 | 28 | const output = { 29 | "conditions[].action.applyBy": (value) => value === null ? '' : value, 30 | "conditions[].action.amount": (value) => value === null ? 0 : value 31 | }; 32 | 33 | // const converters = { 34 | // "conditions[].action.applyBy": (value) => value === null ? '' : value, 35 | // "conditions[].action.amount": (value) => value === null ? 0 : value 36 | // }; 37 | 38 | const values = { 39 | name: 'someone', 40 | conditions: [{ 41 | action: { 42 | applyBy: null, 43 | amount: null, 44 | }, 45 | }] 46 | } 47 | 48 | const valuesToMatch = { 49 | name: 'someone', 50 | conditions: [{ 51 | action: { 52 | applyBy: '', 53 | amount: 0, 54 | }, 55 | }] 56 | } 57 | 58 | 59 | class NewForm extends Form { 60 | hooks() { 61 | return { 62 | onInit(form: FormInterface) { 63 | describe("Check deep isDirty:", () => { 64 | it("form isDirty should be false", () => expect(form.isDirty).to.be.false); 65 | it("conditions isDirty should be false", () => expect(form.$('conditions').isDirty).to.be.false); 66 | it("conditions[0].action isDirty should be false", () => expect(form.$('conditions[0].action').isDirty).to.be.false); 67 | }); 68 | 69 | describe("Check deep values:", () => { 70 | // it("form values should be equal to form initials", () => expect(form.values()).to.be.deep.equal(form.initials())); 71 | it("conditions value should be equal values", () => expect(form.$('conditions').value).to.be.deep.equal(valuesToMatch.conditions)); 72 | it("conditions values() should be equal values", () => expect(form.$('conditions').values()).to.be.deep.equal(valuesToMatch.conditions)); 73 | }); 74 | } 75 | } 76 | } 77 | } 78 | 79 | export default new NewForm({ fields, labels, types, values, input, output }, { name: "$531" }); 80 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.544.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Form } from "../../../../src"; 3 | import { FormInterface } from "../../../../src/models/FormInterface"; 4 | 5 | 6 | const fields = { 7 | test: ['', '', 'x'] 8 | } 9 | 10 | class NewForm extends Form { 11 | hooks() { 12 | return { 13 | onInit(form: FormInterface) { 14 | describe("Check initial values:", () => { 15 | it("`values()` should be equal `['', '', 'x']`", () => 16 | expect(form.values()).to.be.deep.equal({ 17 | test: ['', '', 'x'] 18 | })); 19 | 20 | it("`test.value` should be equal `['', '', 'x']`", () => 21 | expect(form.$('test').value).to.be.deep.equal(['', '', 'x'])); 22 | }); 23 | } 24 | } 25 | } 26 | } 27 | 28 | export default new NewForm({ fields }, { name: "$544", options: { 29 | removeNullishValuesInArrays: false 30 | } }); 31 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.585.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import validatorjs from "validatorjs"; 3 | import { Form } from "../../../../src"; 4 | import { FormInterface } from "../../../../src/models/FormInterface"; 5 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 6 | import dvr from "../../../../src/validators/DVR"; 7 | 8 | const plugins: ValidationPlugins = { 9 | dvr: dvr({ package: validatorjs }) 10 | } 11 | 12 | const schema = { 13 | fields: [ 14 | "email", 15 | "account", 16 | "account.id", 17 | "account.name" 18 | ], 19 | input: { 20 | "account.id": (value) => value === null ? '' : value 21 | }, 22 | output: { 23 | "account.id": (value) => value === null ? '' : value 24 | }, 25 | labels: { email: "Email" }, 26 | rules: { email: "required|email|string|between:5,25" }, 27 | values: { 28 | email: "myemail@apple.com", 29 | account: { 30 | id: null, 31 | name: "John" 32 | } 33 | } 34 | }; 35 | 36 | const valuesToMatch = { 37 | email: "myemail@apple.com", 38 | account: { 39 | id: '', 40 | name: "John" 41 | } 42 | } 43 | 44 | 45 | class NewForm extends Form { 46 | hooks() { 47 | return { 48 | onInit(form: FormInterface) { 49 | describe("Check deep isDirty:", () => { 50 | it("form isDirty should be false", () => expect(form.isDirty).to.be.false); 51 | it("account isDirty should be false", () => expect(form.$('account').isDirty).to.be.false); 52 | }); 53 | 54 | describe("Check deep values:", () => { 55 | it("form values should be equal to form initials", () => expect(form.values()).to.be.deep.equal(form.initials())); 56 | it("account value should be equal valuesToMatch.account", () => expect(form.$('account').value).to.be.deep.equal(valuesToMatch.account)); 57 | it("account values() should be equal valuesToMatch.account", () => expect(form.$('account').values()).to.be.deep.equal(valuesToMatch.account)); 58 | }); 59 | } 60 | } 61 | } 62 | } 63 | 64 | export default new NewForm(schema, { name: "$585", plugins }); 65 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.613.ts: -------------------------------------------------------------------------------- 1 | import validatorjs from "validatorjs"; 2 | import { expect } from "chai"; 3 | import { Form } from "../../../../src"; 4 | import { FormInterface } from "../../../../src/models/FormInterface"; 5 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 6 | import dvr from "../../../../src/validators/DVR"; 7 | 8 | const plugins: ValidationPlugins = { 9 | dvr: dvr({ package: validatorjs }) 10 | } 11 | 12 | const fields = [ 13 | "club", 14 | "club.name", 15 | "club.city" 16 | ]; 17 | 18 | const values = { 19 | club: { 20 | name: "Yellow", 21 | city: "Milan", 22 | }, 23 | }; 24 | 25 | const rules = { 26 | club: { 27 | name: "integer", 28 | city: "string", 29 | }, 30 | } 31 | 32 | const checkDefaultErrorState = (form: FormInterface) => { 33 | it("form `isValid` should be equal `true`", () => expect(form.isValid).to.be.true); 34 | it("form `hasError` should be equal `false`", () => expect(form.hasError).to.be.false); 35 | it("form `isDirty` should be equal `false`", () => expect(form.isDirty).to.be.false); 36 | it("form `isPristine` should be equal `true`", () => expect(form.isPristine).to.be.true); 37 | it("form.$('club') `isValid` should be equal `true`", () => expect(form.$('club').isValid).to.be.true); 38 | it("form.$('club') `hasError` should be equal `false`", () => expect(form.$('club').hasError).to.be.false); 39 | it("form.$('club') `isPristine` should be equal `true`", () => expect(form.$('club').isPristine).to.be.true); it("form.$('club') `isDirty` should be equal `false`", () => expect(form.$('club').isDirty).to.be.false); 40 | it("form.$('club') `isPristine` should be equal `true`", () => expect(form.$('club').isPristine).to.be.true); 41 | it("form `values` should be equal `false`", () => expect(form.values()).to.be.deep.equal(values)); 42 | it("form `errors` should be equal `{ club: { name: null, city: null } }`", () => 43 | expect(form.errors()).to.be.deep.equal({ 44 | club: { name: null, city: null } 45 | })); 46 | } 47 | 48 | class NewForm extends Form { 49 | hooks() { 50 | return { 51 | onInit(form: FormInterface) { 52 | describe("Check initial error state:", () => { 53 | checkDefaultErrorState(form); 54 | }); 55 | 56 | describe("Check error state after clear:", () => { 57 | form.clear(); 58 | checkDefaultErrorState(form); 59 | }); 60 | 61 | describe("Check error state after reset:", () => { 62 | form.reset(); 63 | checkDefaultErrorState(form); 64 | }); 65 | } 66 | } 67 | } 68 | } 69 | 70 | export default new NewForm({ fields, values, rules }, { name: "$613", plugins, options: { 71 | validateOnInit: false, 72 | validateOnClear: false, 73 | validateOnReset: false, 74 | } }); 75 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.626a.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | import * as yup from 'yup'; 4 | import Form from '../../../../src'; 5 | import $yup from "../../../../src/validators/YUP"; 6 | 7 | const plugins = { 8 | dvr: $yup({ 9 | package: yup, 10 | schema: (y: any) => 11 | y.object().shape({ 12 | password: y.string().required('Password is required'), 13 | passwordConfirm: y 14 | .string() 15 | .required('Confirm password is required') 16 | .oneOf([y.ref('password')], 'Passwords must match'), 17 | }), 18 | }), 19 | }; 20 | 21 | const fields = [ 22 | { 23 | name: 'password', 24 | label: 'Password', 25 | placeholder: 'Enter password', 26 | }, 27 | { 28 | name: 'passwordConfirm', 29 | label: 'Confirm Password', 30 | placeholder: 'Confirm password', 31 | }, 32 | ]; 33 | 34 | export const form626a = new Form({ fields }, { plugins }); 35 | 36 | 37 | describe('Yup plugin with yup.ref()', () => { 38 | it('should invalidate passwordConfirm when different from password', async () => { 39 | form626a.reset() 40 | form626a.$('password').set('123456'); 41 | form626a.$('passwordConfirm').set('654321'); 42 | 43 | await form626a.validate(); 44 | 45 | expect(form626a.isValid).to.be.false; 46 | expect(form626a.$('passwordConfirm').hasError).to.be.true; 47 | expect(form626a.$('passwordConfirm').error).to.equal('Passwords must match'); 48 | }); 49 | 50 | it('should validate when password and passwordConfirm match', async () => { 51 | form626a.reset() 52 | form626a.$('password').set('123456'); 53 | form626a.$('passwordConfirm').set('123456'); 54 | 55 | await form626a.validate(); 56 | 57 | expect(form626a.isValid).to.be.true; 58 | expect(form626a.$('passwordConfirm').hasError).to.be.false; 59 | }); 60 | }); -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.626b.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | import * as yup from 'yup'; 4 | import Form from '../../../../src'; 5 | import $yup from "../../../../src/validators/YUP"; 6 | 7 | const plugins = { 8 | dvr: $yup({ 9 | package: yup, 10 | schema: (y: any) => 11 | y.object().shape({ 12 | subscribe: y.boolean(), 13 | email: y.string().when('subscribe', { 14 | is: true, 15 | then: (schema: any) => schema.required('Email is required if subscribed'), 16 | otherwise: (schema: any) => schema.notRequired(), 17 | }), 18 | }), 19 | }), 20 | }; 21 | 22 | const fields = [ 23 | { 24 | name: 'subscribe', 25 | label: 'Subscribe', 26 | type: 'checkbox', 27 | }, 28 | { 29 | name: 'email', 30 | label: 'Email', 31 | placeholder: 'Enter your email', 32 | }, 33 | ]; 34 | 35 | export const form626b = new Form({ fields }, { plugins }); 36 | 37 | 38 | describe('Yup plugin with yup.when()', () => { 39 | it('should require email when subscribe is true', async () => { 40 | form626b.reset(); 41 | form626b.$('subscribe').set(true); 42 | form626b.$('email').set(''); 43 | 44 | await form626b.validate(); 45 | 46 | expect(form626b.isValid).to.be.false; 47 | expect(form626b.$('email').hasError).to.be.true; 48 | expect(form626b.$('email').error).to.equal('Email is required if subscribed'); 49 | }); 50 | 51 | it('should not require email when subscribe is false', async () => { 52 | form626b.reset(); 53 | form626b.$('subscribe').set(false); 54 | form626b.$('email').set(''); 55 | 56 | await form626b.validate(); 57 | 58 | expect(form626b.isValid).to.be.true; 59 | expect(form626b.$('email').hasError).to.be.false; 60 | }); 61 | 62 | it('should pass validation if email is set when subscribe is true', async () => { 63 | form626b.reset(); 64 | form626b.$('subscribe').set(true); 65 | form626b.$('email').set('test@example.com'); 66 | 67 | await form626b.validate(); 68 | 69 | expect(form626b.isValid).to.be.true; 70 | expect(form626b.$('email').hasError).to.be.false; 71 | }); 72 | }); -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.a.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const fields = ["qwerty"]; 4 | 5 | const values = { qwerty: 0 }; 6 | 7 | class NewForm extends Form { 8 | hooks() { 9 | return { 10 | onInit(form) { 11 | form.state.options.set({ 12 | loadingMessage: "Custom Loading Message...", 13 | }); 14 | }, 15 | }; 16 | } 17 | } 18 | 19 | export default new NewForm({ fields, values }, { name: "Fixes-A" }); 20 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.b.ts: -------------------------------------------------------------------------------- 1 | import validatorjs from "validatorjs"; 2 | import { Form } from "../../../../src"; 3 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 4 | import dvr from "../../../../src/validators/DVR"; 5 | 6 | const plugins: ValidationPlugins = { 7 | dvr: dvr({ package: validatorjs }), 8 | }; 9 | 10 | const fields = [ 11 | "people", 12 | "people[]", 13 | "inventoryLevel.value", 14 | "addOns[].nested.value", 15 | ]; 16 | 17 | const values = { 18 | emptyArray: [], 19 | people: [1, 2, 3, 4, 5, 6], 20 | inventoryLevel: { 21 | value: 2, 22 | }, 23 | 24 | addOns: [ 25 | { 26 | nested: { 27 | value: 3, 28 | }, 29 | }, 30 | ], 31 | }; 32 | 33 | const rules = { 34 | emptyArray: "required|array", 35 | people: "required|array|between:3,5", 36 | }; 37 | 38 | export default new Form( 39 | { fields, values, rules }, 40 | { plugins, name: "Fixes-B" } 41 | ); 42 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.c.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const fields = [ 4 | 'itineraryItems[].hotel.name', 5 | 'itineraryItems[].hotel.starRating', 6 | ]; 7 | 8 | class NewForm extends Form { 9 | 10 | hooks() { 11 | return { 12 | onInit(form) { 13 | form.update({ 14 | itineraryItems: [{ 15 | hotel: { 16 | name: 'Shangri-La Hotel', 17 | starRating: '5.0', 18 | }, 19 | }, { 20 | hotel: null, 21 | }, { 22 | hotel: { 23 | name: 'Trump Hotel', 24 | starRating: '5.0', 25 | }, 26 | }], 27 | }); 28 | 29 | form.$('itineraryItems[0].hotel').update({ 30 | name: 'The Plaza', 31 | starRating: '4.0', 32 | }); 33 | 34 | form.$('itineraryItems[1].hotel').update({ 35 | name: 'Beverly Hills Hotel', 36 | starRating: '5.0', 37 | }); 38 | }, 39 | }; 40 | } 41 | } 42 | 43 | export default new NewForm({ fields }, { name: 'Fixes-C' }); 44 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.d_.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = [ 5 | "itineraryItem", 6 | "itineraryItems[].hotel.name", 7 | "itineraryItems[].hotel.starRating", 8 | ]; 9 | 10 | class NewForm extends Form { 11 | hooks() { 12 | return { 13 | onInit(form: FormInterface) { 14 | form.update({ 15 | itineraryItems: [ 16 | { 17 | hotel: { 18 | name: "Shangri-La Hotel", 19 | starRating: "5.0", 20 | }, 21 | }, 22 | { 23 | hotel: null, 24 | }, 25 | { 26 | hotel: { 27 | name: "Trump Hotel", 28 | starRating: "5.0", 29 | }, 30 | }, 31 | ], 32 | }); 33 | 34 | form.$("itineraryItems").map((field) => 35 | field.update({ 36 | hotel: { 37 | name: "New Test Name", 38 | starRating: "5.0", 39 | }, 40 | }) 41 | ); 42 | 43 | form.map((field) => { 44 | // eslint-disable-line 45 | if (field.key === "itineraryItem") { 46 | field.set("itinerary-item-value"); 47 | } 48 | }); 49 | }, 50 | }; 51 | } 52 | } 53 | 54 | export default new NewForm({ fields }, { name: "Fixes-C" }); 55 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.e.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Form } from "../../../../src"; 3 | import { FormInterface } from "../../../../src/models/FormInterface"; 4 | 5 | const fields = [ 6 | "places[]", 7 | "test", 8 | ]; 9 | 10 | const extra = { 11 | places: ["a", "b", "c"], 12 | test: { 13 | testExtraProp: 'test', 14 | testExtraFunction: () => ({ 15 | test: 'test', 16 | }) 17 | } 18 | }; 19 | 20 | const values = { 21 | places: ["NY", "NJ"], 22 | }; 23 | 24 | const hooks = { 25 | places: { 26 | onClear(fieldset) { 27 | it('Fixes-E $(places).clear() should call onClear() hook on fieldset', () => { 28 | expect(fieldset.values()).to.deep.equal([]); 29 | }) 30 | } 31 | } 32 | } 33 | 34 | class NewForm extends Form { 35 | hooks() { 36 | return { 37 | onInit(form: FormInterface) { 38 | form.$("places").clear(); 39 | 40 | describe("Check extra data and function:", () => { 41 | it("$('test') extra prop should be defined and equal 'test'", () => expect(form.$('test').extra.testExtraProp).to.be.equal('test')); 42 | it("$('test') extra function should not be undefined", () => expect(form.$('test').extra.testExtraFunction).to.not.be.undefined); 43 | it("$('test') extra function should return 'test'", () => expect(form.$('test').extra.testExtraFunction()).to.be.deep.equal({ 44 | test: 'test' 45 | })); 46 | }); 47 | }, 48 | }; 49 | } 50 | } 51 | 52 | export default new NewForm({ fields, values, extra, hooks }, { options: { 53 | removeNullishValuesInArrays: true, 54 | }, name: "Fixes-E" }); 55 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.f.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = [ 5 | "ids[]", 6 | "inventoryLevel", 7 | "inventoryLevel.value", 8 | "places[]", 9 | "skills[]", 10 | "date[]", 11 | "members[].hobbies[]", 12 | ]; 13 | 14 | const values = { 15 | date: new Date(1976, 5, 2), 16 | inventoryLevel: { 17 | value: 2, 18 | }, 19 | places: ["NY", "NJ"], 20 | }; 21 | 22 | class NewForm extends Form { 23 | hooks() { 24 | return { 25 | onInit(form: FormInterface) { 26 | form.update({ ids: [1, 2, 3] }); 27 | form.update({ places: ["NY", "NJ", "AR"] }); 28 | form.update({ places: ["NY", "NJ"] }); 29 | form.update({ skills: [] }); 30 | form.update({ date: new Date(1976, 6, 3) }); 31 | 32 | form.update({ 33 | members: [ 34 | { 35 | hobbies: ["Soccer", "Baseball", "Basket"], 36 | }, 37 | { 38 | hobbies: [], 39 | }, 40 | ], 41 | }); 42 | }, 43 | }; 44 | } 45 | } 46 | 47 | export default new NewForm({ fields, values }, { name: "Fixes-F" }); 48 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.g.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = ["items[].name"]; 5 | 6 | const labels = { 7 | "items[]": "ItemLabel", 8 | "items[].name": "ItemsNameLabel", 9 | }; 10 | 11 | class NewForm extends Form { 12 | hooks() { 13 | return { 14 | onInit(form: FormInterface) { 15 | const items = form.$("items"); 16 | for (let i = 0; i <= 20; i++) items.add(); // eslint-disable-line 17 | }, 18 | }; 19 | } 20 | } 21 | 22 | export default new NewForm({ fields, labels }, { name: "Fixes-G" }); 23 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.h.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | import { shouldBeEqualTo } from "../../extension/vjf"; 4 | 5 | const fields = [ 6 | "singleFieldArray", 7 | "singleFieldEmptyArray", 8 | "singleFieldEmptyObject", 9 | "items[].name", 10 | "items[].alternateName", 11 | ]; 12 | 13 | const labels = { 14 | "items[].name": "Name Label", 15 | "items[].alternateName": "Alternate Name Label", 16 | }; 17 | 18 | const values = { 19 | items: [ 20 | { 21 | name: "Item #A", 22 | alternateName: "Alternate Name #AA", 23 | }, 24 | { 25 | name: "Item #B", 26 | }, 27 | ], 28 | }; 29 | 30 | class NewForm extends Form { 31 | hooks() { 32 | return { 33 | onInit(form: FormInterface) { 34 | form.update({ 35 | items: [ 36 | ...form.$("items").values(), 37 | { 38 | name: "Item #3", 39 | alternateName: "Alternate Name #3", 40 | }, 41 | ], 42 | }); 43 | 44 | this.$("singleFieldArray").set(["x"]); 45 | this.$("singleFieldEmptyArray").set([]); 46 | this.$("singleFieldEmptyObject").set({}); 47 | 48 | this.$("items") 49 | .$(0) 50 | .$("name") 51 | .set("validators", [shouldBeEqualTo("items[0].alternateName")]); 52 | this.$("items") 53 | .$(0) 54 | .$("name") 55 | .set("related", ["items[0].alternateName"]); 56 | this.$("items").$(0).$("name").set("extra", ["a", "b", "c"]); 57 | }, 58 | }; 59 | } 60 | } 61 | 62 | export default new NewForm({ fields, labels, values }, { name: "Fixes-H" }); 63 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.i.ts: -------------------------------------------------------------------------------- 1 | import validatorjs from "validatorjs"; 2 | import dvr from "../../../../src/validators/DVR"; 3 | import { Form } from "../../../../src"; 4 | import { FormInterface } from "../../../../src/models/FormInterface"; 5 | 6 | const fields = [ 7 | "deep", 8 | "deep.nested", 9 | "deep.nested.column2", 10 | "deep.nested.column3", 11 | "deep.nested.column2[].title", 12 | "deep.nested.column3[].title", 13 | "layout.column1", 14 | "layout.column1[].title", 15 | "users[].settings[].name", 16 | "users[].settings[].active", 17 | "users[].settings[].bool", 18 | "users[].settings[].anotherBool", 19 | ]; 20 | 21 | const types = { 22 | "users[].settings[].anotherBool": "checkbox", 23 | }; 24 | 25 | const rules = { 26 | "layout.column1[].title": "string|required", 27 | "layout.column2[].title": "string|required", 28 | "deep.nested.column2[].title": "string|required", 29 | "deep.nested.column3[].title": "string|required", 30 | }; 31 | 32 | const defaults = { 33 | "users[].settings[].name": "Default Name", 34 | "users[].settings[].active": true, 35 | "users[].settings[].bool": true, 36 | }; 37 | 38 | const initials = { 39 | "users[].settings[].name": "Initial Name", 40 | "users[].settings[].active": true, 41 | "users[].settings[].bool": false, 42 | }; 43 | 44 | class NewForm extends Form { 45 | plugins() { 46 | return { 47 | dvr: dvr({ package: validatorjs }), 48 | }; 49 | } 50 | 51 | hooks() { 52 | return { 53 | onInit(form: FormInterface) { 54 | // form.update({ 55 | // users: [{ 56 | // settings: [{ 57 | // active: false, 58 | // }], 59 | // }], 60 | // }); 61 | 62 | // form.$('users[0].settings[0]').update({ 63 | // active: false, 64 | // }); 65 | 66 | form.$("users").add(); 67 | form.$("users[0].settings[0].active").set("value", false); 68 | 69 | form.$("layout").update({ 70 | column1: [ 71 | { 72 | title: "THE NEW TITLE", 73 | }, 74 | ], 75 | }); 76 | 77 | form.$("deep.nested").update({ 78 | column2: [ 79 | { 80 | title: "THE NEW TITLE", 81 | }, 82 | ], 83 | column3: [ 84 | { 85 | title: "THE NEW TITLE", 86 | }, 87 | ], 88 | }); 89 | }, 90 | }; 91 | } 92 | } 93 | 94 | export default new NewForm( 95 | { 96 | fields, 97 | rules, 98 | defaults, 99 | initials, 100 | types, 101 | }, 102 | { name: "Fixes-I" } 103 | ); 104 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.l.ts: -------------------------------------------------------------------------------- 1 | import validatorjs from "validatorjs"; 2 | import { expect } from "chai"; 3 | import { Form } from "../../../../src"; 4 | 5 | import dvr from "../../../../src/validators/DVR"; 6 | import vjf from "../../../../src/validators/VJF"; 7 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 8 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 9 | 10 | const fields = ["email"]; 11 | 12 | const values = { 13 | email: "s.jobs@apple.com", 14 | }; 15 | 16 | const rules = { 17 | email: "required|email", 18 | }; 19 | 20 | class NewForm extends Form { 21 | plugins(): ValidationPlugins { 22 | return { 23 | vjf: vjf(), 24 | dvr: dvr({ package: validatorjs }), 25 | }; 26 | } 27 | 28 | options(): OptionsModel { 29 | return { 30 | validateOnChange: false, 31 | }; 32 | } 33 | 34 | hooks() { 35 | return { 36 | onInit() { 37 | this.$("email").set("type", "email"); // #415 38 | this.$("email").set("value", "notAnEmail"); 39 | 40 | describe("Form $L onInit() checks", () => { 41 | it('$L state.options "validateOnChange" should be false', () => 42 | expect(this.state.options.get("validateOnChange")).to.be.false); 43 | 44 | it('$L email value should be equal to "notAnEmail"', () => 45 | expect(this.$("email").value).to.be.equal("notAnEmail")); 46 | 47 | it("$L email hasError should be false", () => 48 | expect(this.$("email").hasError).to.be.false); 49 | 50 | it("$L form hasError should be false", () => 51 | expect(this.hasError).to.be.false); 52 | 53 | it("$L form isValid should be true", () => 54 | expect(this.isValid).to.be.true); 55 | }); 56 | }, 57 | }; 58 | } 59 | } 60 | 61 | export default new NewForm({ fields, values, rules }, { name: "Fixes-L" }); 62 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.m.ts: -------------------------------------------------------------------------------- 1 | import validatorjs from "validatorjs"; 2 | import { Form } from "../../../../src"; 3 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 4 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 5 | import dvr from "../../../../src/validators/DVR"; 6 | 7 | const fields = [ 8 | "jobs", 9 | "jobs[].jobId", 10 | "jobs[].companyName", 11 | "number", 12 | "people[].name", 13 | "array[].name", 14 | "items[].name", 15 | ]; 16 | 17 | const input = { 18 | 'people[].name': (value) => value === null ? '' : value, 19 | "array[].name": (value) => value === null ? '' : value, 20 | } 21 | 22 | const values = { 23 | jobs: [], 24 | number: 1, 25 | people: [ 26 | { 27 | name: "bob", 28 | }, 29 | ], 30 | array: [ 31 | { 32 | name: "bob", 33 | }, 34 | ], 35 | items: [ 36 | { 37 | name: "bob", 38 | }, 39 | ], 40 | }; 41 | 42 | const rules = { 43 | "jobs[].companyName": "required|string|between:3,75", 44 | }; 45 | 46 | const plugins: ValidationPlugins = { 47 | dvr: dvr({ package: validatorjs }), 48 | }; 49 | 50 | class NewForm extends Form { 51 | options(): OptionsModel { 52 | return { 53 | validateOnChange: true, 54 | }; 55 | } 56 | 57 | hooks() { 58 | return { 59 | onInit() { 60 | this.$("jobs").add({ 61 | jobId: 1, 62 | companyName: "x", 63 | }); 64 | 65 | this.$("number").set(0); 66 | 67 | this.$("people").set([{ name: null }]); 68 | 69 | this.$("items").set("value", [{ name: 0 }]); 70 | 71 | this.update({ 72 | array: [{ name: null }, { name: null }, { name: null }], 73 | }); 74 | }, 75 | }; 76 | } 77 | } 78 | 79 | export default new NewForm( 80 | { fields, values, rules, input }, 81 | { plugins, name: "Fixes-M" } 82 | ); 83 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.n.ts: -------------------------------------------------------------------------------- 1 | import validatorjs from "validatorjs"; 2 | import { Form } from "../../../../src"; 3 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 4 | import dvr from "../../../../src/validators/DVR"; 5 | 6 | // const fields = [{ 7 | // name: 'name', 8 | // label: 'Name', 9 | // rules: 'required|string|between:5,50', 10 | // }, { 11 | // name: 'address', 12 | // label: 'Address', 13 | // fields: [{ 14 | // name: 'street', 15 | // label: 'Street', 16 | // rules: 'required|string', 17 | // }, { 18 | // name: 'zip', 19 | // label: 'ZIP Code', 20 | // rules: 'required|string', 21 | // }], 22 | // }]; 23 | 24 | const fields = ["name", "address", "address.street", "address.zip"]; 25 | 26 | const values = { 27 | name: "fatty", 28 | address: { 29 | street: "123 Fake St.", 30 | zip: "12345", 31 | }, 32 | }; 33 | 34 | const rules = { 35 | name: "required|string|between:5,50", 36 | "address.street": "required|string", 37 | "address.zip": "required|string", 38 | }; 39 | 40 | const plugins: ValidationPlugins = { 41 | dvr: dvr({ package: validatorjs }), 42 | }; 43 | 44 | class NewForm extends Form {} 45 | 46 | export default new NewForm( 47 | { fields, rules, values }, 48 | { plugins, name: "Fixes-N" } 49 | ); 50 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.o.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const struct = [ 4 | 'roles[]', 5 | 'roles[].id', 6 | 'array[]', 7 | 'array[].id', 8 | ]; 9 | 10 | class NewForm extends Form { 11 | 12 | hooks() { 13 | return { 14 | onInit() { 15 | this.$('array').add(); // 0 16 | this.$('array').add(); // 1 17 | this.$('array').add(); // 2 18 | 19 | this.$('array').del(0); 20 | 21 | const proxy = new Proxy({ 22 | preventDefault: () => {}, 23 | }, {}); 24 | 25 | this.$('array').onDel(proxy, 1); 26 | this.$('array[2]').onDel(proxy); 27 | }, 28 | }; 29 | } 30 | } 31 | 32 | export default new NewForm({ struct }, { name: 'Fixes-O' }); 33 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.p.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const fields = [{ 4 | name: 'name', 5 | label: 'Name', 6 | }, { 7 | name: 'address', 8 | label: 'Address', 9 | fields: [{ 10 | name: 'street', 11 | label: 'Street', 12 | }, { 13 | name: 'zip', 14 | label: 'ZIP Code', 15 | }], 16 | }]; 17 | 18 | const values = { 19 | name: 'fatty', 20 | address: { 21 | street: '123 Fake St.', 22 | zip: '12345', 23 | }, 24 | }; 25 | 26 | const labels = { 27 | name: 'fatty-label', 28 | address: { 29 | street: 'street-label', 30 | zip: 'zip-label', 31 | }, 32 | }; 33 | 34 | class NewForm extends Form {} 35 | 36 | export default new NewForm({ fields, values, labels }, { name: 'Fixes-P' }); 37 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.q.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const fields = [ 4 | // FIX: #282 (reserved keywords) 5 | 'incident', 6 | 'incident[].type', 7 | 'incident[].value', 8 | 'incident[].options', 9 | 'arrayFieldA[]', 10 | 'arrayFieldA[].id', 11 | 'arrayFieldA[].name', 12 | 'arrayFieldB[]', 13 | 'arrayFieldB[].id', 14 | 'arrayFieldB[].name', 15 | 'arrayFieldB[].value', 16 | ]; 17 | 18 | class NewForm extends Form { 19 | 20 | hooks() { 21 | return { 22 | onInit() { 23 | // FIX: #282 (reserved keywords) 24 | this.$('incident').add(); 25 | 26 | // FIX #324 27 | this.update({ arrayFieldA: [{ id: 1, name: 'name' }] }); 28 | this.update({ arrayFieldB: [{ id: 1, name: 'name', value: 'some val' }] }); 29 | }, 30 | }; 31 | } 32 | } 33 | 34 | export default new NewForm({ fields }); 35 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.q1.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const struct = [ 4 | 'tags[]', 5 | 'tags[].id', 6 | 'tags[].name', 7 | ]; 8 | 9 | const fields = [{ 10 | name: 'tags', 11 | label: 'Tags!!!', 12 | }, { 13 | name: 'other', 14 | label: 'Other!!!', 15 | fields: [{ 16 | name: 'nested', 17 | value: 'nested-value', 18 | }], 19 | }]; 20 | 21 | class NewForm extends Form { 22 | 23 | hooks() { 24 | return { 25 | onInit() { 26 | this.$('tags').add([{ 27 | id: 'x', 28 | name: 'y', 29 | }]); 30 | 31 | // EQUIVALENT 32 | // this.$('tags').add(); 33 | // this.$('tags[0]').set({ 34 | // id: 'x', 35 | // name: 'y', 36 | // }); 37 | 38 | // EQUIVALENT 39 | // this.update({ 40 | // tags: [{ 41 | // id: 'x', 42 | // name: 'y', 43 | // }], 44 | // }); 45 | }, 46 | }; 47 | } 48 | 49 | } 50 | 51 | export default new NewForm({ struct, fields }, { name: 'Fixes-Q1' }); 52 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.q2.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const fields = [ 4 | 'multiselectArray', 5 | 'multiselectObject', 6 | 'tags[]', 7 | 'tags[].id', 8 | 'tags[].name', 9 | ]; 10 | 11 | const values = { 12 | multiselectArray: ['iMac', 'iPhone'], 13 | multiselectObject: { value: 'watch', label: 'Watch' }, 14 | }; 15 | 16 | class NewForm extends Form {} 17 | 18 | export default new NewForm({ fields, values }, { name: 'Fixes-Q2' }); 19 | 20 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.r.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const fields = [{ 4 | name: 'organization', 5 | fields: [{ 6 | name: 'nested', 7 | }], 8 | }]; 9 | 10 | class NewForm extends Form {} 11 | 12 | export default new NewForm({ fields }, { name: 'Fixes-R' }); 13 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.s.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const struct = ["array"]; 4 | 5 | class NewForm extends Form { 6 | hooks() { 7 | return { 8 | onInit() { 9 | this.$("array").add({ name: "item_to_delete" }); 10 | this.del("array.item_to_delete"); 11 | 12 | this.$("array").add({ name: "item_to_delete2" }); 13 | this.$("array").del("item_to_delete2"); 14 | 15 | this.add({ name: "item_to_delete_3" }); 16 | this.del("item_to_delete_3"); 17 | }, 18 | }; 19 | } 20 | } 21 | 22 | export default new NewForm({ struct }, { name: "Fixes-S" }); 23 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.u.ts: -------------------------------------------------------------------------------- 1 | import validatorjs from "validatorjs"; 2 | import { Form } from "../../../../src"; 3 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 4 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 5 | import dvr from "../../../../src/validators/DVR"; 6 | 7 | const fields = ["from", "to"]; 8 | 9 | const rules = { 10 | from: "before_or_equal:to", 11 | to: "after:from", 12 | }; 13 | 14 | const labels = { 15 | from: "FROM", 16 | to: "TO", 17 | }; 18 | 19 | const plugins: ValidationPlugins = { 20 | dvr: dvr({ package: validatorjs }), 21 | }; 22 | 23 | const values = { 24 | from: new Date(2021, 7), 25 | to: new Date(2021, 6), 26 | }; 27 | 28 | class NewForm extends Form { 29 | options(): OptionsModel { 30 | return { 31 | validateOnInit: true, 32 | showErrorsOnInit: true, 33 | }; 34 | } 35 | } 36 | 37 | export default new NewForm( 38 | { fields, rules, labels, values }, 39 | { plugins, name: "Fixes-U" } 40 | ); 41 | -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.v.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Form } from "../../../../src"; 3 | import { FormInterface } from "../../../../src/models/FormInterface"; 4 | 5 | const generateNewPiece = () => ({ 6 | length: 10, 7 | width: 10, 8 | height: 10 9 | }); 10 | 11 | export default new Form( 12 | { 13 | fields: [ 14 | "pieces[]", 15 | "pieces[].length", 16 | "pieces[].width", 17 | "pieces[].height" 18 | ], 19 | values: { pieces: [{ length: 1, width: 2, height: 3 }] } 20 | }, 21 | { 22 | options: { 23 | // preserveDeletedFieldsValues: true 24 | }, 25 | hooks: { 26 | onInit(form: FormInterface) { 27 | form.$("pieces").add({ 28 | fields: generateNewPiece(), 29 | // values: generateNewPiece(), // EQUIVALENT 30 | }); 31 | } 32 | } 33 | } 34 | ); -------------------------------------------------------------------------------- /tests/data/forms/fixes/form.z.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Form } from "../../../../src"; 3 | import { FormInterface } from "../../../../src/models/FormInterface"; 4 | import { expect } from "chai"; 5 | 6 | const values = { pieces: [{ id: "2", blocks: [{ id: "1" }] }] }; 7 | 8 | const removeBlocks = { pieces: [{ id: "3", blocks: [] }] }; 9 | 10 | export default new Form( 11 | { 12 | fields: [ 13 | "pieces[]", 14 | "pieces[].blocks[]", 15 | "pieces[].blocks[].id", 16 | "pieces[].id" 17 | ], 18 | values, 19 | }, 20 | { 21 | name: 'Fixes-Z', 22 | hooks: { 23 | onInit(form: FormInterface) { 24 | 25 | form.update(removeBlocks); 26 | 27 | describe("Nested udpate()", () => 28 | it('blocks should be empty array', () => 29 | expect(form.values()).to.be.deep.equal(removeBlocks))); 30 | } 31 | } 32 | } 33 | ); -------------------------------------------------------------------------------- /tests/data/forms/flat/form.a.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import validator from "validator"; 3 | import validatorjs from "validatorjs"; 4 | 5 | import { Form } from "../../../../src"; 6 | 7 | import dvr from "../../../../src/validators/DVR"; 8 | import vjf from "../../../../src/validators/VJF"; 9 | import svk from "../../../../src/validators/SVK"; 10 | 11 | import svkExtend from "../../extension/svk"; 12 | import { isEmailByValidator } from "../../extension/vjf"; 13 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 14 | 15 | /* ------------------------ 16 | This form should be VALID 17 | ------------------------- */ 18 | 19 | const fields = { 20 | username: { 21 | label: "Username", 22 | value: "SteveJobs", 23 | rules: "alpha|between:6,20", 24 | placeholder: "Username Placeholder", 25 | }, 26 | email: { 27 | label: "Email", 28 | value: "s.jobs@apple.com", 29 | validators: [isEmailByValidator], 30 | }, 31 | password: { 32 | label: "Password", 33 | value: "thinkdifferent", 34 | rules: "confirmed", 35 | }, 36 | password_confirmation: { 37 | label: "Password Confirmation", 38 | value: "thinkdifferent", 39 | }, 40 | passwordConfirmation1: { 41 | label: "Password Confirmation 1", 42 | value: "thinkdifferent", 43 | rules: "required|same:password", // target: password_confirmation 44 | }, 45 | passwordConfirmation2: { 46 | label: "Password Confirmation 2", 47 | rules: "required", 48 | value: null, // should invalidate the field 49 | disabled: true, // should not validate the field 50 | }, 51 | terms: { 52 | label: "Accept Terms", 53 | value: true, 54 | }, 55 | devSkills: { 56 | label: "Dev Skills", 57 | value: 5, 58 | input: (value: any) => value.toString(), // (user to store) 59 | output: (value: any) => Number(value), // (store to user) 60 | }, 61 | revenue: { 62 | label: "Revenue (Billion $)", 63 | value: "233.715", 64 | }, 65 | assets: { 66 | label: "Assets (Billion $)", 67 | value: 305.277, 68 | }, 69 | }; 70 | 71 | const schema = { 72 | type: "object", 73 | properties: { 74 | // username: { type: 'string', minLength: 6, maxLength: 20 }, 75 | email: { type: "string", format: "email", minLength: 5, maxLength: 20 }, 76 | password: { type: "string", minLength: 6, maxLength: 20 }, 77 | terms: { enum: [true, false] }, 78 | devSkills: { range: [5, 10] }, 79 | revenue: { type: "number" }, 80 | assets: { type: "number" }, 81 | }, 82 | }; 83 | 84 | const plugins: ValidationPlugins = { 85 | vjf: vjf({ package: validator }), 86 | dvr: dvr({ package: validatorjs }), 87 | svk: svk({ 88 | package: ajv, 89 | extend: svkExtend, 90 | schema, 91 | }), 92 | }; 93 | 94 | export default new Form({ fields }, { plugins, name: "Flat-A" }); 95 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.b.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import validatorjs from "validatorjs"; 3 | 4 | import { Form } from "../../../../src"; 5 | import svkExtend from "../../extension/svk"; 6 | 7 | import dvr from "../../../../src/validators/DVR"; 8 | import svk from "../../../../src/validators/SVK"; 9 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 10 | 11 | const fields = { 12 | username: { 13 | label: "Username", 14 | }, 15 | email: { 16 | label: "Email", 17 | rules: "required|email|between:5,20", 18 | }, 19 | password: { 20 | label: "Password", 21 | }, 22 | devSkills: { 23 | label: "Dev Skills", 24 | }, 25 | }; 26 | 27 | const schema = { 28 | type: "object", 29 | properties: { 30 | username: { type: "string", minLength: 6, maxLength: 20 }, 31 | // email: { type: 'string', format: 'email', minLength: 5, maxLength: 20 }, 32 | password: { type: "string", minLength: 6, maxLength: 20 }, 33 | devSkills: { range: [1, 10] }, 34 | }, 35 | }; 36 | 37 | const plugins: ValidationPlugins = { 38 | dvr: dvr({ package: validatorjs }), 39 | svk: svk({ 40 | schema, 41 | package: ajv, 42 | extend: svkExtend, 43 | }), 44 | }; 45 | 46 | const options = { 47 | alwaysShowDefaultError: true, 48 | defaultGenericError: "Custom Generic Error", 49 | }; 50 | 51 | export default new Form({ fields }, { options, plugins, name: "Flat-B" }); 52 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.c.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import { Form } from "../../../../src"; 3 | import svkExtend from "../../extension/svk"; 4 | import svk from "../../../../src/validators/SVK"; 5 | import { FormInterface } from "../../../../src/models/FormInterface"; 6 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 7 | 8 | const fields = { 9 | username: { 10 | label: "Username", 11 | value: "", 12 | }, 13 | email: { 14 | label: "Email", 15 | value: "", 16 | }, 17 | password: { 18 | label: "Password", 19 | value: "", 20 | }, 21 | devSkills: { 22 | label: "Dev Skills", 23 | value: 1, 24 | }, 25 | }; 26 | 27 | const schema = { 28 | type: "object", 29 | properties: { 30 | username: { type: "string", minLength: 6, maxLength: 20 }, 31 | email: { type: "string", format: "email", minLength: 5, maxLength: 20 }, 32 | password: { type: "string", minLength: 6, maxLength: 20 }, 33 | devSkills: { range: [1, 10] }, 34 | }, 35 | }; 36 | 37 | const plugins: ValidationPlugins = { 38 | svk: svk({ 39 | package: ajv, 40 | extend: svkExtend, 41 | schema, 42 | }), 43 | }; 44 | 45 | class NewForm extends Form { 46 | hooks() { 47 | return { 48 | onInit(form: FormInterface) { 49 | form.invalidate("The user already exist"); 50 | }, 51 | }; 52 | } 53 | } 54 | 55 | export default new NewForm({ fields }, { plugins, name: "Flat-C" }); 56 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.d_.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import ajv from "ajv"; 3 | import { Form } from "../../../../src"; 4 | import svkExtend from "../../extension/svk"; 5 | import svk from "../../../../src/validators/SVK"; 6 | import { FormInterface } from "../../../../src/models/FormInterface"; 7 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 8 | 9 | const fields = { 10 | username: "SteveJobs", 11 | email: "s.jobs@apple.com", 12 | password: "thinkdifferent", 13 | terms: true, 14 | devSkills: 10, // 10 should fail with exclusiveRange on 15 | revenue: "233.715", 16 | assets: 305.277, 17 | }; 18 | 19 | const labels = { 20 | username: "Username", 21 | email: "E-mail", 22 | password: "Password", 23 | terms: "Accept Terms of Service", 24 | }; 25 | 26 | const schema = { 27 | type: "object", 28 | properties: { 29 | username: { type: "string", minLength: 6, maxLength: 20 }, 30 | email: { type: "string", format: "email", minLength: 5, maxLength: 20 }, 31 | password: { type: "string", minLength: 6, maxLength: 20 }, 32 | terms: { enum: [true, false] }, 33 | devSkills: { range: [1, 10], exclusiveRange: true }, 34 | revenue: { type: "number" }, 35 | assets: { type: "number" }, 36 | }, 37 | }; 38 | 39 | class NewForm extends Form { 40 | plugins(): ValidationPlugins { 41 | return { 42 | svk: svk({ 43 | package: ajv, 44 | extend: svkExtend, 45 | schema, 46 | }), 47 | }; 48 | } 49 | 50 | options(): OptionsModel { 51 | return { 52 | validateOnChange: true, 53 | }; 54 | } 55 | 56 | hooks() { 57 | return { 58 | onInit(form: FormInterface) { 59 | form.update({ 60 | username: "JonathanIve", 61 | terms: false, 62 | assets: 0, 63 | revenue: "aaa", 64 | undefined: true, // this field does not exists (strictUpdate) 65 | }); 66 | }, 67 | }; 68 | } 69 | } 70 | 71 | export default new NewForm({ fields, labels }, { name: "Flat-D" }); 72 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.e.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import validatorjs from "validatorjs"; 3 | import { Form } from "../../../../src"; 4 | import { isEmail, shouldBeEqualTo } from "../../extension/vjf"; 5 | import svkExtend from "../../extension/svk"; 6 | 7 | import vjf from "../../../../src/validators/VJF"; 8 | import dvr from "../../../../src/validators/DVR"; 9 | import svk from "../../../../src/validators/SVK"; 10 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 11 | 12 | const fields = { 13 | username: { 14 | label: "Username", 15 | value: "s.jobs@apple.com", 16 | validators: [isEmail, shouldBeEqualTo("email")], 17 | }, 18 | email: { 19 | label: "Email", 20 | value: "s.jobs@apple.com", 21 | validators: [isEmail], 22 | }, 23 | password: { 24 | label: "Password", 25 | value: "thinkdifferent", 26 | }, 27 | devSkills: { 28 | label: "Dev Skills", 29 | value: 5, 30 | }, 31 | validatedDifferently: { 32 | value: "x", 33 | label: 1, 34 | rules: "integer", 35 | validatedWith: "label", 36 | }, 37 | }; 38 | 39 | const schema = { 40 | type: "object", 41 | properties: { 42 | username: { type: "string", minLength: 6, maxLength: 20 }, 43 | // email: { type: 'string', format: 'email', minLength: 5, maxLength: 20 }, 44 | password: { type: "string", minLength: 6, maxLength: 20 }, 45 | devSkills: { range: [5, 10] }, 46 | }, 47 | }; 48 | 49 | const plugins: ValidationPlugins = { 50 | vjf: vjf(), 51 | dvr: dvr({ package: validatorjs }), 52 | svk: svk({ 53 | package: ajv, 54 | extend: svkExtend, 55 | schema, 56 | }), 57 | }; 58 | 59 | export default new Form({ fields }, { plugins, name: "Flat-E" }); 60 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.f.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import { Form } from "../../../../src"; 3 | import { shouldBeEqualTo } from "../../extension/vjf"; 4 | import svkExtend from "../../extension/svk"; 5 | 6 | import vjf from "../../../../src/validators/VJF"; 7 | import svk from "../../../../src/validators/SVK"; 8 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 9 | 10 | const schema = { 11 | type: "object", 12 | properties: { 13 | username: { type: "string", minLength: 6, maxLength: 20 }, 14 | email: { type: "string", format: "email", minLength: 5, maxLength: 20 }, 15 | password: { type: "string", minLength: 6, maxLength: 20 }, 16 | devSkills: { range: [5, 10] }, 17 | }, 18 | }; 19 | 20 | const plugins: ValidationPlugins = { 21 | vjf: vjf(), 22 | svk: svk({ 23 | package: ajv, 24 | extend: svkExtend, 25 | schema, 26 | }), 27 | }; 28 | 29 | const fields = { 30 | username: { 31 | label: "Username", 32 | value: "SteveJobs", 33 | validators: [shouldBeEqualTo("email")], 34 | related: ["email"], 35 | }, 36 | email: { 37 | label: "Email", 38 | value: "s.jobs@apple.com", 39 | }, 40 | password: { 41 | label: "Password", 42 | value: "thinkdifferent", 43 | }, 44 | devSkills: { 45 | label: "Dev Skills", 46 | value: 5, 47 | }, 48 | }; 49 | 50 | export default new Form({ fields }, { plugins, name: "Flat-F" }); 51 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.g.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const fields = { 4 | username: { 5 | label: 'Username', 6 | }, 7 | email: { 8 | label: 'Email', 9 | }, 10 | password: { 11 | label: 'Password', 12 | }, 13 | }; 14 | 15 | export default new Form({ fields }, { name: 'Flat-G' }); 16 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.h.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import { Form } from "../../../../src"; 3 | import svkExtend from "../../extension/svk"; 4 | import svk from "../../../../src/validators/SVK"; 5 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 6 | 7 | const schema = { 8 | type: "object", 9 | properties: { 10 | username: { 11 | type: "string", 12 | minLength: 6, 13 | maxLength: 20, 14 | title: "Username", 15 | default: "SteveJobs", 16 | }, 17 | email: { 18 | type: "string", 19 | format: "email", 20 | minLength: 5, 21 | maxLength: 20, 22 | title: "Email", 23 | default: "s.jobs@apple.com", 24 | }, 25 | password: { 26 | type: "string", 27 | minLength: 6, 28 | maxLength: 20, 29 | title: "Password", 30 | default: "thinkdifferent", 31 | }, 32 | devSkills: { 33 | range: [5, 10], 34 | /* title: 'Dev Skills', default: 5 */ 35 | }, 36 | }, 37 | }; 38 | 39 | const plugins: ValidationPlugins = { 40 | svk: svk({ 41 | package: ajv, 42 | extend: svkExtend, 43 | schema, 44 | }), 45 | }; 46 | 47 | export default new Form({}, { plugins, name: "Flat-H" }); 48 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.i.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import { Form } from "../../../../src"; 3 | import svk from "../../../../src/validators/SVK"; 4 | import { FormInterface } from "../../../../src/models/FormInterface"; 5 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 6 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 7 | 8 | const fields = { 9 | username: { 10 | label: "Username", 11 | value: "SteveJobs", 12 | }, 13 | email: { 14 | label: "Email", 15 | value: "s.jobs@apple.com", 16 | }, 17 | password: { 18 | label: "Password", 19 | value: "thinkdifferent", 20 | }, 21 | }; 22 | 23 | const schema = { 24 | type: "object", 25 | properties: { 26 | username: { type: "string", minLength: 6, maxLength: 20 }, 27 | email: { type: "string", format: "email", minLength: 5, maxLength: 20 }, 28 | password: { type: "string", minLength: 6, maxLength: 20 }, 29 | }, 30 | }; 31 | 32 | const plugins: ValidationPlugins = { 33 | svk: svk({ 34 | package: ajv, 35 | schema, 36 | }), 37 | }; 38 | 39 | class NewForm extends Form { 40 | options(): OptionsModel { 41 | return { 42 | strictUpdate: true, 43 | }; 44 | } 45 | 46 | hooks() { 47 | return { 48 | onInit(form: FormInterface) { 49 | form.update({ username: "JonathanIve" }); 50 | form.reset(); // to default or initial values 51 | }, 52 | }; 53 | } 54 | } 55 | 56 | export default new NewForm({ fields }, { plugins, name: "Flat-I" }); 57 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.l.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import { Form } from "../../../../src"; 3 | import svkExtend from "../../extension/svk"; 4 | import svk from "../../../../src/validators/SVK"; 5 | import { FormInterface } from "../../../../src/models/FormInterface"; 6 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 7 | 8 | const fields = { 9 | username: { 10 | label: "Username", 11 | value: "SteveJobs", 12 | }, 13 | email: { 14 | label: "Email", 15 | value: "s.jobs@apple.com", 16 | }, 17 | password: { 18 | label: "Password", 19 | value: "thinkdifferent", 20 | }, 21 | }; 22 | 23 | const schema = { 24 | $async: true, 25 | type: "object", 26 | properties: { 27 | username: { 28 | type: "string", 29 | minLength: 6, 30 | maxLength: 20, 31 | checkUser: "user", 32 | }, 33 | email: { 34 | type: "string", 35 | format: "email", 36 | minLength: 5, 37 | maxLength: 20, 38 | }, 39 | password: { type: "string", minLength: 6, maxLength: 20 }, 40 | }, 41 | }; 42 | 43 | const plugins: ValidationPlugins = { 44 | svk: svk({ 45 | package: ajv, 46 | extend: svkExtend, 47 | schema, 48 | }), 49 | }; 50 | 51 | class NewForm extends Form { 52 | hooks() { 53 | return { 54 | onInit(form: FormInterface) { 55 | form.clear(); // to empty values 56 | }, 57 | }; 58 | } 59 | } 60 | 61 | export default new NewForm({ fields }, { plugins, name: "Flat-L" }); 62 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.m.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import validatorjs from "validatorjs"; 3 | 4 | import { Form } from "../../../../src"; 5 | import dvrExtend from "../../extension/dvr"; 6 | 7 | import dvr from "../../../../src/validators/DVR"; 8 | import svk from "../../../../src/validators/SVK"; 9 | import { FormInterface } from "../../../../src/models/FormInterface"; 10 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 11 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 12 | 13 | const fields = { 14 | username: { 15 | label: "Username", 16 | value: "SteveJobs", 17 | default: "Claudio", 18 | rules: "checkUser:ignoreCase", 19 | }, 20 | email: { 21 | label: "Email", 22 | value: "s.jobs@apple.com", 23 | }, 24 | password: { 25 | label: "Password", 26 | value: "thinkdifferent", 27 | }, 28 | }; 29 | 30 | const schema = { 31 | type: "object", 32 | properties: { 33 | username: { type: "string", minLength: 6, maxLength: 20 }, 34 | email: { type: "string", format: "email", minLength: 5, maxLength: 20 }, 35 | password: { type: "string", minLength: 6, maxLength: 20 }, 36 | }, 37 | }; 38 | 39 | const plugins: ValidationPlugins = { 40 | svk: svk({ 41 | package: ajv, 42 | schema, 43 | }), 44 | dvr: dvr({ 45 | package: validatorjs, 46 | extend: dvrExtend, 47 | }), 48 | }; 49 | 50 | const options: OptionsModel = { 51 | validateOnReset: true, 52 | validateOnClear: true, 53 | }; 54 | 55 | class NewForm extends Form { 56 | hooks() { 57 | return { 58 | onInit(form: FormInterface) { 59 | // subsequent clear and reset 60 | form.clear(); // to empty values 61 | form.reset(); // to default or initial values 62 | }, 63 | }; 64 | } 65 | } 66 | 67 | export default new NewForm({ fields }, { plugins, options, name: "Flat-M" }); 68 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.n.ts: -------------------------------------------------------------------------------- 1 | import ajv from "ajv"; 2 | import { Form } from "../../../../src"; 3 | import { checkUser } from "../../extension/vjf"; 4 | 5 | import svk from "../../../../src/validators/SVK"; 6 | import vjf from "../../../../src/validators/VJF"; 7 | import { FormInterface } from "../../../../src/models/FormInterface"; 8 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 9 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 10 | 11 | const fields = { 12 | username: { 13 | label: "Username", 14 | value: "SteveJobs", 15 | validators: [checkUser], // (promise) should fail 16 | }, 17 | email: { 18 | label: "Email", 19 | value: "12345", // should fail 20 | }, 21 | password: { 22 | label: "Password", 23 | value: "thinkdifferent", 24 | }, 25 | }; 26 | 27 | const schema = { 28 | type: "object", 29 | properties: { 30 | username: { type: "string", minLength: 6, maxLength: 20 }, 31 | email: { type: "string", format: "email", minLength: 5, maxLength: 20 }, 32 | password: { type: "string", minLength: 6, maxLength: 20 }, 33 | }, 34 | }; 35 | 36 | const plugins: ValidationPlugins = { 37 | vjf: vjf(), 38 | svk: svk({ 39 | package: ajv, 40 | schema, 41 | }), 42 | }; 43 | 44 | const options: OptionsModel = { 45 | validateOnReset: true, 46 | validateOnClear: true, 47 | }; 48 | 49 | class NewForm extends Form { 50 | hooks() { 51 | return { 52 | onInit(form: FormInterface) { 53 | // subsequent clear and reset 54 | form.clear(); // to empty values 55 | form.reset(); // to default or initial values 56 | }, 57 | }; 58 | } 59 | } 60 | 61 | export default new NewForm({ fields }, { options, plugins, name: "Flat-N" }); 62 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.o.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 4 | 5 | const values = { 6 | username: "SteveJobs", 7 | email: "s.jobs@apple.com", 8 | terms: true, 9 | }; 10 | 11 | const labels = { 12 | username: "Username", 13 | email: "Email", 14 | password: "Password", 15 | passwordConfirm: "Confirm Password", 16 | terms: "Accept Terms of Service", 17 | }; 18 | 19 | class NewForm extends Form { 20 | options(): OptionsModel { 21 | return { 22 | validateOnChange: true, 23 | }; 24 | } 25 | 26 | hooks() { 27 | return { 28 | onInit(form: FormInterface) { 29 | form.invalidate(); 30 | 31 | form.update({ 32 | undefined: "undefined", 33 | username: "TestUser", 34 | }); 35 | 36 | form.set("label", { 37 | undefined: "undefined", 38 | email: "E-mail", 39 | }); 40 | }, 41 | }; 42 | } 43 | } 44 | 45 | export default new NewForm({ values, labels }, { name: "Flat-O" }); 46 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.p.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import validatorjs from "validatorjs"; 3 | import { Form } from "../../../../src"; 4 | import dvrExtend from "../../extension/dvr"; 5 | import { shouldBeEqualTo } from "../../extension/vjf"; 6 | 7 | import dvr from "../../../../src/validators/DVR"; 8 | import vjf from "../../../../src/validators/VJF"; 9 | import { FormInterface } from "../../../../src/models/FormInterface"; 10 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 11 | 12 | const fields = ["username", "email", "password", "passwordConfirm", "terms"]; 13 | 14 | const values = { 15 | username: "SteveJobs", 16 | email: "s.jobs@apple.com", 17 | terms: true, 18 | }; 19 | 20 | const defaults = { 21 | username: "TestUser", 22 | }; 23 | 24 | const labels = { 25 | passwordConfirm: "Confirm Password", 26 | }; 27 | 28 | const validators = { 29 | email: [shouldBeEqualTo("username")], // should fail 30 | }; 31 | 32 | const rules = { 33 | username: "email", // should fail 34 | }; 35 | 36 | const disabled = { 37 | terms: true, 38 | }; 39 | 40 | const options: OptionsModel = { 41 | validateOnReset: true, 42 | validateOnClear: true, 43 | }; 44 | 45 | class NewForm extends Form { 46 | plugins(): ValidationPlugins { 47 | return { 48 | vjf: vjf(), 49 | dvr: dvr({ 50 | package: validatorjs, 51 | extend: dvrExtend, 52 | }), 53 | }; 54 | } 55 | 56 | hooks() { 57 | return { 58 | onInit(form: FormInterface) { 59 | form.$("username").set("label", "UserName"); 60 | form.reset(); 61 | }, 62 | }; 63 | } 64 | } 65 | 66 | export default new NewForm( 67 | { 68 | fields, 69 | values, 70 | defaults, 71 | labels, 72 | disabled, 73 | validators, 74 | rules, 75 | }, 76 | { options, name: "Flat-P" } 77 | ); 78 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.q.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const fields = [{ 4 | name: 'username', 5 | label: 'Username', 6 | computed: () => 'SteveJobs', // test computed value 7 | }, { 8 | name: 'email', 9 | label: 'Email', 10 | value: ' s.jobs@apple.com ', // should be trimmed on initial validation and pass validation 11 | options: { 12 | validateTrimmedValue: true // <--- 13 | } 14 | }]; 15 | 16 | export default new Form({ fields }, { name: 'Flat-Q' }); 17 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.r.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import { FieldConstructor } from "./../../../../src/models/FieldInterface"; 3 | import MobxReactForm, { Field } from "../../../../src"; 4 | import { isEmail, shouldBeEqualTo } from "../../extension/vjf"; 5 | import vjf from "../../../../src/validators/VJF"; 6 | import { FormInterface } from "../../../../src/models/FormInterface"; 7 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 8 | 9 | const fields = { 10 | email: { 11 | label: "Username", 12 | value: "s.jobs@apple.com", 13 | validators: [isEmail], 14 | related: ["emailConfirm"], 15 | autoComplete: "on", 16 | }, 17 | emailConfirm: { 18 | label: "Email", 19 | value: "s.jobs@apple.com", 20 | validators: [isEmail, shouldBeEqualTo("email")], 21 | }, 22 | }; 23 | 24 | class NewField extends Field { 25 | newFieldProp = false; 26 | 27 | constructor(data: FieldConstructor) { 28 | super(data); 29 | 30 | this.newFieldProp = true; 31 | } 32 | } 33 | 34 | class NewForm extends MobxReactForm { 35 | makeField(data: FieldConstructor) { 36 | return new NewField(data); 37 | } 38 | 39 | options(): OptionsModel { 40 | return { 41 | validateOnChange: true, 42 | }; 43 | } 44 | 45 | plugins(): ValidationPlugins { 46 | return { 47 | xyz: vjf(), // custom xyz plugin 48 | }; 49 | } 50 | 51 | hooks() { 52 | return { 53 | onInit(form: FormInterface) { 54 | form.update({ 55 | email: "invalid", 56 | }); 57 | }, 58 | }; 59 | } 60 | } 61 | 62 | export default new NewForm({ fields }, { name: "Flat-R" }); 63 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.s.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import validatorjs from "validatorjs"; 3 | import MobxReactForm from "../../../../src"; 4 | import dvr from "../../../../src/validators/DVR"; 5 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 6 | 7 | const fields = { 8 | email: { 9 | label: "Email", 10 | value: "", 11 | rules: "required|email", 12 | }, 13 | }; 14 | 15 | class NewForm extends MobxReactForm { 16 | options(): OptionsModel { 17 | return { 18 | validateOnInit: true, 19 | showErrorsOnInit: true, 20 | }; 21 | } 22 | 23 | plugins(): ValidationPlugins { 24 | return { 25 | dvr: dvr({ package: validatorjs }), 26 | }; 27 | } 28 | } 29 | 30 | export default new NewForm({ fields }, { name: "Flat-S" }); 31 | -------------------------------------------------------------------------------- /tests/data/forms/flat/form.t.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const fields = ["username", "email", "country"]; 4 | 5 | const values = { 6 | username: "", 7 | email: "", 8 | country: "", 9 | }; 10 | 11 | const defaults = { 12 | username: "TestUser", 13 | email: "s.jobs@apple.com", 14 | country: "USA", 15 | }; 16 | 17 | const labels = { 18 | username: "User name", 19 | email: "Email", 20 | country: "Country", 21 | }; 22 | 23 | class NewForm extends Form { 24 | } 25 | 26 | export default new NewForm( 27 | { 28 | fields, 29 | values, 30 | defaults, 31 | labels, 32 | }, 33 | { name: "Flat-T" } 34 | ); 35 | -------------------------------------------------------------------------------- /tests/data/forms/form.empty.constructor.ts: -------------------------------------------------------------------------------- 1 | import MobxReactForm from "../../../src"; 2 | 3 | // EMPTY CONSTRUCTOR 4 | export default new MobxReactForm(); // do not change! -------------------------------------------------------------------------------- /tests/data/forms/forms.fieldClass.ts: -------------------------------------------------------------------------------- 1 | import { Form, Field } from "../../../src"; 2 | 3 | export enum FieldPaths { 4 | standardField = "standardField", 5 | nestedCustomField = "standardField.nestedCustomField", 6 | customField = "customField", 7 | nestedStandardField = "customField.nestedStandardField", 8 | } 9 | 10 | export class CustomField extends Field {} 11 | 12 | export const $SEPARATED = new Form({ 13 | fields: Object.values(FieldPaths), 14 | classes: { 15 | [FieldPaths.customField]: CustomField, 16 | [FieldPaths.nestedCustomField]: CustomField, 17 | }, 18 | }); 19 | 20 | export const $UNIFIED = new Form({ 21 | fields: [ 22 | { 23 | name: "standardField", 24 | fields: [ 25 | { 26 | name: "nestedCustomField", 27 | class: CustomField, 28 | }, 29 | ], 30 | }, 31 | { 32 | name: "customField", 33 | class: CustomField, 34 | fields: [ 35 | { 36 | name: "nestedStandardField", 37 | }, 38 | ], 39 | }, 40 | ], 41 | }); 42 | 43 | export class OverrideCustomField extends Field {} 44 | 45 | export class OverrideForm extends Form { 46 | setup() { 47 | return { 48 | fields: Object.values(FieldPaths), 49 | classes: { 50 | [FieldPaths.customField]: CustomField, 51 | }, 52 | }; 53 | } 54 | 55 | makeField(data, FieldClass) { 56 | switch (data.path) { 57 | case FieldPaths.nestedCustomField: { 58 | return new OverrideCustomField(data); 59 | } 60 | default: { 61 | return super.makeField(data, FieldClass); 62 | } 63 | } 64 | } 65 | } 66 | 67 | export const $OVERRIDE = new OverrideForm(); 68 | -------------------------------------------------------------------------------- /tests/data/forms/methods/form.set.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FieldPropsEnum } from "../../../../src/models/FieldProps"; 3 | import { FormInterface } from "../../../../src/models/FormInterface"; 4 | 5 | 6 | const fields = [ 7 | 'test', 8 | ] 9 | 10 | const hooks = { 11 | onInit(form: FormInterface) { 12 | form.$('test').set(FieldPropsEnum.disabled, true); 13 | form.$('test').set(FieldPropsEnum.deleted, true); 14 | form.$('test').set(FieldPropsEnum.type, 'number'); 15 | form.$('test').set(FieldPropsEnum.initial, 'test'); 16 | form.$('test').set(FieldPropsEnum.default, 'test'); 17 | form.$('test').set(FieldPropsEnum.label, 'test'); 18 | form.$('test').set(FieldPropsEnum.placeholder, 'test'); 19 | form.$('test').set(FieldPropsEnum.related, ['test']); 20 | } 21 | } 22 | 23 | export default new Form({ fields }, { hooks }); -------------------------------------------------------------------------------- /tests/data/forms/nested/form.a.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import { Form } from "../../../../src"; 3 | import { isEmail, shouldBeEqualTo } from "../../extension/vjf"; 4 | import vjf from "../../../../src/validators/VJF"; 5 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 6 | import { FormInterface } from "../../../../src/models/FormInterface"; 7 | 8 | const fields = [ 9 | { 10 | name: "user", 11 | label: "User", 12 | fields: [ 13 | { 14 | name: "email", 15 | label: "Email", 16 | validators: [isEmail], 17 | related: ["user.emailConfirm"], 18 | }, 19 | { 20 | name: "emailConfirm", 21 | label: "Confirm Email", 22 | value: "s.jobs@apple.com", 23 | validators: [isEmail, shouldBeEqualTo("user.email")], 24 | }, 25 | { 26 | name: "password", 27 | label: "Password", 28 | value: "thinkdifferent", 29 | }, 30 | { 31 | name: "devSkills", 32 | label: "Dev Skills", 33 | value: 5, 34 | }, 35 | ], 36 | }, 37 | ]; 38 | 39 | const input = { 40 | "user.devSkills": (value) => value.toString(), 41 | }; 42 | 43 | const output = { 44 | user: () => {}, 45 | "user.devSkills": (value) => Number(value), 46 | }; 47 | 48 | class NewForm extends Form { 49 | options(): OptionsModel { 50 | return { 51 | validateOnInit: false, 52 | }; 53 | } 54 | 55 | plugins(): ValidationPlugins { 56 | return { 57 | vjf: vjf(), 58 | }; 59 | } 60 | 61 | hooks() { 62 | return { 63 | onInit(form: FormInterface) { 64 | form.state.extra({ foo: "bar" }); 65 | form.update({ user: { email: "notAnEmail" } }); 66 | form.set("label", { user: { emailConfirm: "Confirm User Email" } }); 67 | form.set("default", { user: { emailConfirm: "Default Value" } }); 68 | form.$("user.password").invalidate("Password Invalid"); 69 | }, 70 | }; 71 | } 72 | } 73 | 74 | export default new NewForm({ fields, input, output }, { name: "Nested-A" }); 75 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.a1.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Form } from "../../../../src"; 3 | 4 | const fields = [ 5 | 'user', 6 | 'user.firstname', 7 | 'user.lastname', 8 | ]; 9 | 10 | export default new Form({ fields }, { name: "Nested-A1", options: { 11 | retrieveNullifiedEmptyStrings: true, 12 | } }); 13 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.a2.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Form } from "../../../../src"; 3 | 4 | const fields = [ 5 | 'user', 6 | 'user.firstname', 7 | 'user.lastname', 8 | ]; 9 | 10 | export default new Form({ fields }, { name: "Nested-A2", options: { 11 | fallbackValue: null, 12 | } }); 13 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.b.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = [ 5 | { 6 | name: "state", 7 | label: "State", 8 | value: "USA", 9 | fields: [ 10 | { 11 | name: "city", 12 | label: "City", 13 | value: "New York", 14 | fields: [ 15 | { 16 | name: "places", 17 | label: "NY Places", 18 | fields: [ 19 | { 20 | name: "centralPark", 21 | label: "Central Park", 22 | value: true, 23 | }, 24 | { 25 | name: "statueOfLiberty", 26 | label: "Statue of Liberty", 27 | value: false, 28 | }, 29 | { 30 | name: "empireStateBuilding", 31 | label: "Empire State Building", 32 | value: true, 33 | }, 34 | ], 35 | }, 36 | ], 37 | }, 38 | ], 39 | }, 40 | ]; 41 | 42 | class NewForm extends Form { 43 | hooks() { 44 | return { 45 | onInit(form: FormInterface) { 46 | form.state.extra(["a", "b", "c"]); 47 | form.$("state.city.places").set("label", "NY Cool Places"); 48 | form.$("state.city.places").update({ 49 | empireStateBuilding: false, 50 | centralPark: false, 51 | }); 52 | }, 53 | }; 54 | } 55 | } 56 | 57 | export default new NewForm({ fields }, { name: "Nested-B" }); 58 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.c.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = { 5 | state: { 6 | label: "State", 7 | value: "USA", 8 | fields: { 9 | city: { 10 | label: "City", 11 | value: "New York", 12 | fields: { 13 | places: { 14 | label: "NY Places", 15 | fields: { 16 | centralPark: { 17 | label: "Central Park", 18 | value: true, 19 | }, 20 | statueOfLiberty: { 21 | label: "Statue of Liberty", 22 | value: false, 23 | }, 24 | empireStateBuilding: { 25 | label: "Empire State Building", 26 | value: true, 27 | }, 28 | brooklynBridge: { 29 | label: "Brooklyn Bridge", 30 | value: true, 31 | }, 32 | }, 33 | }, 34 | }, 35 | }, 36 | }, 37 | }, 38 | }; 39 | 40 | class NewForm extends Form { 41 | hooks() { 42 | return { 43 | onInit(form: FormInterface) { 44 | form.$("state.city.places").set("label", "NY Cool Places"); 45 | form.$("state.city.places").update({ 46 | empireStateBuilding: false, 47 | centralPark: false, 48 | }); 49 | }, 50 | }; 51 | } 52 | } 53 | 54 | export default new NewForm({ fields }, { name: "Nested-C" }); 55 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.d_.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = { 5 | state: { 6 | label: "State", 7 | value: "USA", 8 | fields: { 9 | city: { 10 | label: "City", 11 | value: "New York", 12 | fields: { 13 | places: { 14 | label: "NY Places", 15 | fields: { 16 | centralPark: { 17 | label: "Central Park", 18 | value: true, 19 | }, 20 | statueOfLiberty: { 21 | label: "Statue of Liberty", 22 | value: false, 23 | }, 24 | empireStateBuilding: { 25 | label: "Empire State Building", 26 | value: true, 27 | }, 28 | brooklynBridge: { 29 | label: "Brooklyn Bridge", 30 | value: true, 31 | }, 32 | }, 33 | }, 34 | }, 35 | }, 36 | }, 37 | }, 38 | }; 39 | 40 | class NewForm extends Form { 41 | hooks() { 42 | return { 43 | onInit(form: FormInterface) { 44 | form.$("state.city").clear(true); 45 | }, 46 | }; 47 | } 48 | } 49 | 50 | export default new NewForm({ fields }, { name: "Nested-D" }); 51 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.e.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import ajv from "ajv"; 3 | import { Form } from "../../../../src"; 4 | import svkExtend from "../../extension/svk"; 5 | import svk from "../../../../src/validators/SVK"; 6 | 7 | const fields = { 8 | state: { 9 | label: "State", 10 | value: "USA", 11 | fields: { 12 | city: { 13 | label: "City", 14 | value: "New York", 15 | fields: { 16 | places: { 17 | label: "NY Places", 18 | fields: { 19 | centralPark: { 20 | label: "Central Park", 21 | value: true, 22 | }, 23 | statueOfLiberty: { 24 | label: "Statue of Liberty", 25 | value: false, 26 | }, 27 | empireStateBuilding: { 28 | label: "Empire State Building", 29 | value: true, 30 | }, 31 | brooklynBridge: { 32 | label: "Brooklyn Bridge", 33 | value: true, 34 | }, 35 | }, 36 | }, 37 | }, 38 | }, 39 | }, 40 | }, 41 | }; 42 | 43 | const schema = { 44 | type: "object", 45 | properties: { 46 | state: { 47 | type: "integer", 48 | }, 49 | "state.city": { 50 | type: "integer", 51 | }, 52 | }, 53 | }; 54 | 55 | const plugins: ValidationPlugins = { 56 | svk: svk({ 57 | package: ajv, 58 | extend: svkExtend, 59 | schema, 60 | }), 61 | }; 62 | 63 | export default new Form({ fields }, { plugins, name: "Nested-E" }); 64 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.e2.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { expect } from "chai"; 3 | import ajv from "ajv"; 4 | import FormInterface from "../../../../src/models/FormInterface"; 5 | import OptionsModel from "../../../../src/models/OptionsModel"; 6 | import { Form } from "../../../../src"; 7 | import svk from "../../../../src/validators/SVK"; 8 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 9 | 10 | 11 | const fields = [ 12 | "products[].name", 13 | "products[].qty", 14 | "products[].amount", 15 | ]; 16 | 17 | const values = { 18 | products: [{ 19 | name: 'a', 20 | qty: -1, 21 | amount: -1, 22 | }] 23 | } 24 | 25 | const labels = { 26 | 'products[].qty': 'Quantity', 27 | 'products[].amount': 'Amount', 28 | }; 29 | 30 | const schema = { 31 | type: "object", 32 | properties: { 33 | products: { 34 | type: "array", 35 | items: { 36 | type: "object", 37 | properties: { 38 | name: { 39 | type: "string", 40 | }, 41 | qty: { 42 | type: "integer", 43 | minimum: 0, 44 | }, 45 | amount: { 46 | type: "number", 47 | minimum: 0, 48 | }, 49 | }, 50 | } 51 | }, 52 | }, 53 | }; 54 | 55 | const plugins: ValidationPlugins = { 56 | svk: svk({ 57 | package: ajv, 58 | schema, 59 | }), 60 | }; 61 | 62 | const options: OptionsModel = { 63 | validateOnInit: true, 64 | showErrorsOnInit: true, 65 | }; 66 | 67 | export default new Form({ 68 | fields, 69 | values, 70 | labels, 71 | }, { 72 | plugins, 73 | options, 74 | name: "Nested-E2", 75 | hooks: { 76 | onInit(form: FormInterface) { 77 | describe("Check ajv validation flag", () => { 78 | it('products[0].qty hasError should be true', () => expect(form.$('products[0].qty').hasError).to.be.true); 79 | it('products[0].amount hasError should be true', () => expect(form.$('products[0].amount').hasError).to.be.true); 80 | }); 81 | 82 | describe("Check ajv validation errors", () => { 83 | it('products[0].qty error should equal zod error', () => expect(form.$('products[0].qty').error).to.be.equal('Quantity should be >= 0')); 84 | it('products[0].amount error should equal zod error', () => expect(form.$('products[0].amount').error).to.be.equal('Amount should be >= 0')); 85 | }); 86 | 87 | } 88 | } 89 | }); 90 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.f.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import validatorjs from "validatorjs"; 3 | import { Form } from "../../../../src"; 4 | import dvr from "../../../../src/validators/DVR"; 5 | 6 | const plugins: ValidationPlugins = { 7 | dvr: dvr({ package: validatorjs }), 8 | }; 9 | 10 | const fields = { 11 | state: { 12 | label: "State", 13 | value: "USA", 14 | rules: "integer", 15 | fields: { 16 | city: { 17 | label: "City", 18 | value: "New York", 19 | rules: "integer", 20 | fields: { 21 | places: { 22 | label: "NY Places", 23 | value: "NY Places", 24 | rules: "integer", 25 | fields: { 26 | centralPark: { 27 | label: "Central Park", 28 | value: true, 29 | }, 30 | statueOfLiberty: { 31 | label: "Statue of Liberty", 32 | value: false, 33 | }, 34 | empireStateBuilding: { 35 | label: "Empire State Building", 36 | value: true, 37 | }, 38 | brooklynBridge: { 39 | label: "Brooklyn Bridge", 40 | value: true, 41 | }, 42 | }, 43 | }, 44 | }, 45 | }, 46 | }, 47 | }, 48 | }; 49 | 50 | export default new Form({ fields }, { plugins, name: "Nested-F" }); 51 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.g.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import validatorjs from "validatorjs"; 3 | import { Form } from "../../../../src"; 4 | import dvr from "../../../../src/validators/DVR"; 5 | 6 | const plugins: ValidationPlugins = { 7 | dvr: dvr({ package: validatorjs }), 8 | }; 9 | 10 | const fields = { 11 | state: { 12 | label: "State", 13 | value: "USA", 14 | fields: { 15 | city: { 16 | label: "City", 17 | value: "New York", 18 | fields: { 19 | places: { 20 | label: "NY Places", 21 | value: "NY Places", 22 | fields: { 23 | centralPark: { 24 | label: "Central Park", 25 | value: true, 26 | }, 27 | statueOfLiberty: { 28 | label: "Statue of Liberty", 29 | value: false, 30 | }, 31 | empireStateBuilding: { 32 | label: "Empire State Building", 33 | value: true, 34 | }, 35 | brooklynBridge: { 36 | label: "Brooklyn Bridge", 37 | value: true, 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | }, 44 | }, 45 | }; 46 | 47 | const rules = { 48 | state: "integer", 49 | "state.city": "integer", 50 | "state.city.places": "integer", 51 | }; 52 | 53 | export default new Form({ fields, rules }, { plugins, name: "Nested-G" }); 54 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.h.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import { Form } from "../../../../src"; 3 | import { isInt } from "../../extension/vjf"; 4 | import vjf from "../../../../src/validators/VJF"; 5 | 6 | const plugins: ValidationPlugins = { 7 | vjf: vjf(), 8 | }; 9 | 10 | const fields = { 11 | state: { 12 | label: "State", 13 | value: "USA", 14 | fields: { 15 | city: { 16 | label: "City", 17 | value: "New York", 18 | fields: { 19 | places: { 20 | label: "NY Places", 21 | value: "NY Places", 22 | fields: { 23 | centralPark: { 24 | label: "Central Park", 25 | value: true, 26 | }, 27 | statueOfLiberty: { 28 | label: "Statue of Liberty", 29 | value: false, 30 | }, 31 | empireStateBuilding: { 32 | label: "Empire State Building", 33 | value: true, 34 | }, 35 | brooklynBridge: { 36 | label: "Brooklyn Bridge", 37 | value: true, 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | }, 44 | }, 45 | }; 46 | 47 | const validators = { 48 | state: [isInt], 49 | "state.city": [isInt], 50 | "state.city.places": [isInt], 51 | }; 52 | 53 | export default new Form({ fields, validators }, { plugins, name: "Nested-H" }); 54 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.i.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const values = { 4 | state: { 5 | city: { 6 | places: { 7 | centralPark: true, 8 | statueOfLiberty: false, 9 | empireStateBuilding: true, 10 | brooklynBridge: false, 11 | }, 12 | }, 13 | }, 14 | }; 15 | 16 | const labels = { 17 | state: { 18 | city: { 19 | places: { 20 | centralPark: 'Central Park', 21 | statueOfLiberty: 'Statue of Liberty', 22 | empireStateBuilding: 'Empire State Building', 23 | brooklynBridge: 'Brooklyn Bridge', 24 | }, 25 | }, 26 | }, 27 | }; 28 | 29 | export default new Form({ values, labels }, { name: 'Nested-I' }); 30 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.l.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | 3 | const fields = { 4 | state: { 5 | city: { 6 | places: { 7 | centralPark: true, 8 | statueOfLiberty: false, 9 | empireStateBuilding: true, 10 | brooklynBridge: false, 11 | }, 12 | }, 13 | }, 14 | }; 15 | 16 | export default new Form({ fields }, { name: 'Nested-L' }); 17 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.m.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | 3 | const fields = { 4 | club: "HELLO", 5 | members: [ 6 | { 7 | firstname: "Clint", 8 | lastname: "Eastwood", 9 | hobbies: ["Soccer", "Baseball"], 10 | }, 11 | { 12 | firstname: "Charlie", 13 | lastname: "Chaplin", 14 | hobbies: ["Golf", "Basket"], 15 | }, 16 | ], 17 | }; 18 | 19 | export default new Form({ fields }, { name: "Nested-M" }); 20 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.m1.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import * as yup from "yup"; 3 | import { Form } from "../../../../src"; 4 | import $yup from "../../../../src/validators/YUP"; 5 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 6 | 7 | const fields = { 8 | club: null, 9 | members: [ 10 | { 11 | firstname: null, 12 | lastname: "Eastwood", 13 | yearOfBirth: 1930, 14 | hobbies: ["Soccer", "Baseball"], 15 | }, 16 | { 17 | firstname: "Charlie", 18 | lastname: "Chaplin", 19 | yearOfBirth: null, 20 | hobbies: ["Golf", null], 21 | }, 22 | ], 23 | }; 24 | 25 | const input = { 26 | "club": (value) => (value === null ? "" : value), 27 | "memebers[].firstname": (value) => (value === null ? "" : value), 28 | "memebers[].yearOfBirth": (value) => (value === null ? "" : value), 29 | "memebers[].hobbies[]": (value) => (value === null ? "" : value), 30 | } 31 | 32 | const labels = { 33 | club: "The Club", 34 | "members[]": "The Members", 35 | "members[].firstname": "The First Name", 36 | "members[].lastname": "The Last Name", 37 | "members[].yearOfBirth": "The Year of Birth", 38 | "members[].hobbies[]": "The Hobbie", 39 | }; 40 | 41 | const schema = (y) => 42 | y.object().shape({ 43 | club: y.string().required().nullable(), 44 | members: y.array().of( 45 | y.object().shape({ 46 | firstname: y.string().required().nullable(), 47 | lastname: y.string().required(), 48 | yearOfBirth: y.number().required().positive().integer().nullable(), 49 | hobbies: y.array().of(y.string().required().nullable()), 50 | }) 51 | ), 52 | }); 53 | 54 | const plugins: ValidationPlugins = { 55 | yup: $yup({ 56 | package: yup, 57 | schema, 58 | }), 59 | }; 60 | 61 | const options: OptionsModel = { 62 | showErrorsOnInit: true, 63 | }; 64 | 65 | export default new Form( 66 | { fields, labels, input }, 67 | { plugins, options, name: "Nested-M1" } 68 | ); 69 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.n.ts: -------------------------------------------------------------------------------- 1 | import { Form } from '../../../../src'; 2 | import NewFormBindings from '../../extension/_.bindings'; 3 | 4 | const fields = [ 5 | 'nested.field', 6 | 'club.name', 7 | 'club.city', 8 | 'members', 9 | 'members[].firstname', 10 | 'members[].lastname', 11 | 'members[].hobbies', 12 | 'members[].hobbies[]', 13 | ]; 14 | 15 | const values = { 16 | nested: { 17 | field: 5, 18 | }, 19 | club: null, 20 | members: [{ 21 | firstname: 'Clint', 22 | lastname: '', 23 | hobbies: ['Soccer', 'Baseball'], 24 | }, { 25 | firstname: null, 26 | lastname: 'Chaplin', 27 | hobbies: ['Golf', 'Basket'], 28 | }], 29 | }; 30 | 31 | const input = { 32 | 'members[].firstname': value => value === null ? '' : value, 33 | 'nested.field': value => value.toString(), 34 | }; 35 | 36 | const output = { 37 | 'nested.field': value => Number(value), 38 | }; 39 | 40 | const labels = { 41 | 'members[].firstname': 'First Name Label', 42 | }; 43 | 44 | const placeholders = { 45 | 'club': 'Insert Club', 46 | 'club.name': 'Insert Club Name', 47 | 'club.city': 'Insert Club City', 48 | 'members': 'Insert All Members', 49 | 'members[].firstname': 'Insert First Name', 50 | 'members[].lastname': 'Insert Last Name', 51 | 'members[].hobbies[]': 'Insert Hobbies', // this is overwritten by props 52 | }; 53 | 54 | const bindings = { 55 | 'club.name': 'MaterialTextFieldRewriter', 56 | 'club.city': 'MaterialTextFieldRewriter', 57 | 'members[].firstname': 'MaterialTextFieldTemplate', 58 | 'members[].lastname': 'MaterialTextFieldTemplate', 59 | 'members[].hobbies[]': 'MaterialTextFieldRewriter', 60 | }; 61 | 62 | class NewForm extends Form { 63 | 64 | bindings() { 65 | return NewFormBindings; 66 | } 67 | } 68 | 69 | export default new NewForm({ 70 | fields, values, input, output, placeholders, bindings, labels, 71 | }, { name: 'Nested-N' }); 72 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.o.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = [ 5 | "club.name", 6 | "club.city", 7 | "members", 8 | "members[].firstname", 9 | "members[].lastname", 10 | "members[].hobbies", 11 | "members[].hobbies[]", 12 | ]; 13 | 14 | const labels = { 15 | club: "Label Club", 16 | "club.name": "Label Club Name", 17 | "club.city": "Label Club City", 18 | members: "Label Members", 19 | "members[].firstname": "Label Member FirstName", 20 | "members[].lastname": "Label Member LastName", 21 | "members[].hobbies": "Label Member Hobby", 22 | }; 23 | 24 | const input = { 25 | "club.name": (value) => value === null ? '' : value, 26 | "members[].firstname": (value) => value === null ? '' : value, 27 | } 28 | 29 | class NewForm extends Form { 30 | hooks() { 31 | return { 32 | onInit(form: FormInterface) { 33 | form.update({ 34 | club: { 35 | name: null, 36 | city: "New York", 37 | }, 38 | members: [ 39 | { 40 | firstname: "Clint", 41 | lastname: "Eastwood", 42 | hobbies: ["Soccer", "Baseball"], 43 | }, 44 | { 45 | firstname: null, 46 | lastname: "Chaplin", 47 | hobbies: ["Golf", "Basket"], 48 | }, 49 | ], 50 | }); 51 | }, 52 | }; 53 | } 54 | } 55 | 56 | export default new NewForm({ fields, labels, input }, { name: "Nested-O" }); 57 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.p.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = [ 5 | "club.name", 6 | "club.city", 7 | "members", 8 | "members[].firstname", 9 | "members[].lastname", 10 | "members[].hobbies", 11 | "members[].hobbies[]", 12 | ]; 13 | 14 | class NewForm extends Form { 15 | hooks() { 16 | return { 17 | onInit(form: FormInterface) { 18 | form.update({ 19 | club: { 20 | name: "HELLO", 21 | city: "NY", 22 | }, 23 | members: [ 24 | { 25 | firstname: "Clint", 26 | lastname: "Eastwood", 27 | hobbies: ["Soccer", "Baseball"], 28 | }, 29 | { 30 | firstname: null, 31 | lastname: "Chaplin", 32 | hobbies: ["Golf", "Basket"], 33 | }, 34 | ], 35 | }); 36 | 37 | form.reset(); 38 | }, 39 | }; 40 | } 41 | } 42 | 43 | export default new NewForm({ fields }, { name: "Nested-P" }); 44 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.q.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = [ 5 | "club.name", 6 | "club.city", 7 | "members", 8 | "members[].firstname", 9 | "members[].lastname", 10 | "members[].hobbies", 11 | "members[].hobbies[]", 12 | ]; 13 | 14 | class NewForm extends Form { 15 | hooks() { 16 | return { 17 | onInit(form: FormInterface) { 18 | form.update({ 19 | club: { 20 | name: "HELLO", 21 | city: "NY", 22 | }, 23 | members: [ 24 | { 25 | firstname: "Clint", 26 | lastname: "Eastwood", 27 | hobbies: ["Soccer", "Baseball"], 28 | }, 29 | { 30 | firstname: "Charlie", 31 | lastname: "Chaplin", 32 | hobbies: ["Golf", "Basket"], 33 | }, 34 | ], 35 | }); 36 | 37 | // form.reset(); 38 | }, 39 | }; 40 | } 41 | } 42 | 43 | export default new NewForm({ fields }, { name: "Nested-Q" }); 44 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.r.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPlugins } from "./../../../../src/models/ValidatorInterface"; 2 | import { expect } from "chai"; 3 | import validatorjs from "validatorjs"; 4 | import { Form } from "../../../../src"; 5 | import dvr from "../../../../src/validators/DVR"; 6 | 7 | const plugins: ValidationPlugins = { 8 | dvr: dvr({ package: validatorjs }), 9 | }; 10 | 11 | const fields = [ 12 | "club.name", 13 | "club.city", 14 | "members", 15 | "members[].firstname", 16 | "members[].lastname", 17 | "members[].hobbies", 18 | "members[].hobbies[]", 19 | ]; 20 | 21 | const rules = { 22 | email: [ 23 | "required", 24 | "regex:^[A-Za-z0-9](([_.-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([.-]?[a-zA-Z0-9]+)*).([A-Za-z]{2,})$", 25 | ], 26 | "club.name": "required|string", 27 | "club.city": "required|string", 28 | "members[].firstname": "required|string", 29 | "members[].lastname": "required|string", 30 | "members[].hobbies": "required|string", 31 | "members[].hobbies[]": "required|string", 32 | }; 33 | 34 | const values = { 35 | email: "s.jobs@apple.com", 36 | club: { 37 | name: "HELLO", 38 | city: "NY", 39 | }, 40 | members: [ 41 | { 42 | firstname: "Clint", 43 | lastname: "Eastwood", 44 | hobbies: ["Soccer", "Baseball"], 45 | }, 46 | { 47 | firstname: "Charlie", 48 | lastname: "Chaplin", 49 | hobbies: ["Golf", "Basket"], 50 | }, 51 | ], 52 | }; 53 | 54 | const checkFieldset = (fieldset) => 55 | describe("Nested Form onSuccess()", () => 56 | it('Fieldset should have "path" prop', () => 57 | expect(fieldset).to.have.property("path"))); 58 | 59 | const submit = { 60 | onSubmit(fieldset) { 61 | it('$R.submit() should call onSubmit callback', () => { 62 | expect(fieldset.submitted).to.equal(1); 63 | }) 64 | }, 65 | onSuccess(fieldset) { 66 | checkFieldset(fieldset); 67 | }, 68 | onError(fieldset) { 69 | checkFieldset(fieldset); 70 | }, 71 | }; 72 | 73 | const hooks = { 74 | club: submit, 75 | members: submit, 76 | "members[]": submit, 77 | }; 78 | 79 | export default new Form( 80 | { 81 | fields, 82 | rules, 83 | values, 84 | hooks, 85 | }, 86 | { 87 | name: "Nested-R", 88 | plugins, 89 | hooks: { 90 | onInit(form) { 91 | form.$('club').submit(); 92 | } 93 | } 94 | } 95 | ); 96 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.t1.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 3 | 4 | const fields = ["hobbies[]"]; 5 | 6 | class NewForm extends Form { 7 | options(): OptionsModel { 8 | return { 9 | softDelete: true, 10 | }; 11 | } 12 | 13 | hooks() { 14 | return { 15 | onInit(form) { 16 | form.$("hobbies").add({ value: "AAA" }); 17 | form.$("hobbies").add({ value: "BBB" }); 18 | form.$("hobbies").add({ value: "CCC" }); 19 | 20 | form.del("hobbies[1]"); 21 | }, 22 | }; 23 | } 24 | } 25 | 26 | export default new NewForm({ fields }, { name: "Nested-T1" }); 27 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.u.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Form } from "../../../../src"; 3 | import { isEmail, shouldBeEqualTo } from "../../extension/vjf"; 4 | import vjf from "../../../../src/validators/VJF"; 5 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 6 | import { OptionsModel } from "../../../../src/models/OptionsModel"; 7 | 8 | const fields = [ 9 | "user", 10 | // TO FIX 11 | "user.email", 12 | "user.emailConfirm", 13 | ]; 14 | 15 | const labels = { 16 | user: { 17 | email: "Email", 18 | emailConfirm: "Confirm Email", 19 | }, 20 | }; 21 | 22 | const values = { 23 | user: { 24 | email: "s.jobs@apple.com", 25 | emailConfirm: "s.jobs@apple.com", 26 | }, 27 | }; 28 | 29 | const validators = { 30 | "user.email": [isEmail], 31 | "user.emailConfirm": [isEmail, shouldBeEqualTo("user.email")], 32 | }; 33 | 34 | const related = { 35 | "user.email": ["user.emailConfirm"], 36 | }; 37 | 38 | class NewForm extends Form { 39 | plugins(): ValidationPlugins { 40 | return { 41 | vjf: vjf(), 42 | }; 43 | } 44 | 45 | options(): OptionsModel { 46 | return { 47 | validateOnChange: true, 48 | showErrorsOnChange: false, 49 | }; 50 | } 51 | 52 | hooks() { 53 | return { 54 | onInit() { 55 | // this.$('user.email') 56 | // .on('update', ({ path, event, change }) => { 57 | // describe('Check Nested-U $("user.email").on("update") hook', () => { 58 | // it('event should be equal to change.name', () => 59 | // expect(event).to.be.equal(change.name)); 60 | // it('event should be equal to change.name', () => 61 | // expect(path).to.be.equal('user.email')); 62 | // }); 63 | // }); 64 | 65 | this.observe({ 66 | path: "user.email", 67 | key: "value", 68 | call: ({ change }) => 69 | describe("Check Nested-U observer", () => 70 | it('change.newValue should be equal to "notAnEmail"', () => 71 | expect(change.newValue).to.be.equal("notAnEmail"))), 72 | }); 73 | 74 | this.update({ user: { email: "notAnEmail" } }); 75 | }, 76 | }; 77 | } 78 | } 79 | 80 | export default new NewForm( 81 | { 82 | fields, 83 | values, 84 | labels, 85 | validators, 86 | related, 87 | }, 88 | { name: "Nested-U" } 89 | ); 90 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.v.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = [ 5 | 'user.id', 6 | 'user.name', 7 | 'test.email', 8 | ] 9 | 10 | const hooks = { 11 | onInit(form: FormInterface) { 12 | form.$('user.id').set('value', 'user-id'); 13 | form.$('user.name').set('value', 'user-init'); 14 | form.$('user.name').set('disabled', true); 15 | form.$('user.name').set('autoFocus', true); 16 | form.$('user.name').set('inputMode', "text"); 17 | form.$('user.name').set('ref', "ref"); 18 | 19 | }, 20 | onChange(form: FormInterface) { 21 | form.$('user.name').set('value', 'user-changed'); 22 | form.$('test.email').sync('test@email'); 23 | } 24 | } 25 | 26 | export default new Form( 27 | { fields }, 28 | { hooks, name: "Nested-V" } 29 | ); -------------------------------------------------------------------------------- /tests/data/forms/nested/form.v2.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FormInterface } from "../../../../src/models/FormInterface"; 3 | 4 | const fields = [ 5 | 'user.id', 6 | 'user.name', 7 | 'test.email', 8 | ] 9 | 10 | const hooks = { 11 | onInit(form: FormInterface) { 12 | form.$('user.id').set('value', 'user-id'); 13 | form.$('user.name').set('value', 'user-init'); 14 | }, 15 | onChange(form: FormInterface) { 16 | form.$('user.name').set('value', 'user-changed'); 17 | form.$('test.email').sync('test@email'); 18 | } 19 | } 20 | 21 | export default new Form( 22 | { fields }, 23 | { hooks, name: "Nested-V2" } 24 | ); -------------------------------------------------------------------------------- /tests/data/forms/nested/form.v3.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FieldInterface } from "../../../../src/models/FieldInterface"; 3 | import { FormInterface } from "../../../../src/models/FormInterface"; 4 | 5 | const fields = [ 6 | 'account[].id', 7 | 'user[].name', 8 | 'test[].prop', 9 | 'final' 10 | ]; 11 | 12 | const hooks = { 13 | account: { 14 | onAdd(field: FieldInterface) { 15 | field.select(0).$('id').set('account-id'); 16 | } 17 | }, 18 | test: { 19 | onAdd(field: FieldInterface) { 20 | field.state.form.$('final').set('final-value') 21 | } 22 | } 23 | } 24 | 25 | const formHooks = { 26 | onInit(form: FormInterface) { 27 | form.$('account').add(); 28 | form.$('user').add(); 29 | form.$('test').add(); 30 | form.$('test').del(); 31 | }, 32 | onChange(form: FormInterface) { 33 | form.$('user[0].name').set('value', 'user-changed'); 34 | } 35 | } 36 | 37 | export default new Form( 38 | { fields, hooks }, 39 | { hooks: formHooks, name: "Nested-V3" } 40 | ); -------------------------------------------------------------------------------- /tests/data/forms/nested/form.v4.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "../../../../src"; 2 | import { FieldInterface } from "../../../../src/models/FieldInterface"; 3 | import { FormInterface } from "../../../../src/models/FormInterface"; 4 | 5 | const fields = { 6 | user: { 7 | fields: { 8 | id: {}, 9 | email: {}, 10 | }, 11 | hooks: { 12 | onChange(field: FieldInterface) { 13 | field.$('email').sync('user@email') 14 | } 15 | } 16 | } 17 | } 18 | 19 | 20 | const hooks = { 21 | onInit(form: FormInterface) { 22 | // form.$('user.id').sync(1) 23 | form.$('user').update({ 24 | id: 1 25 | }) 26 | }, 27 | } 28 | 29 | export default new Form( 30 | { fields }, 31 | { hooks, name: "Nested-V4" } 32 | ); -------------------------------------------------------------------------------- /tests/data/forms/nested/form.x.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Form } from "../../../../src"; 3 | 4 | export default new Form( 5 | { 6 | fields: [ 7 | "products[].name", 8 | "products[].qty", 9 | "products[].amount", 10 | "products[].total", 11 | "total" 12 | ], 13 | computed: { 14 | 'products[].total': ({ field }) => { 15 | const qty = field.container()?.$('qty')?.value; 16 | const amount = field.container()?.$('amount')?.value; 17 | return qty * amount; 18 | }, 19 | 20 | 'total': ({ form }) => 21 | form.$("products")?.reduce((acc, field) => acc + field.$("total").value, 0), 22 | 23 | }, 24 | labels: { 25 | "products[].name": "Product Name", 26 | "products[].qty": "Quantity", 27 | "products[].amount": "Amount $" 28 | }, 29 | placeholders: { 30 | "products[].name": "Insert Product Name" 31 | }, 32 | types: { 33 | "products[].qty": "number", 34 | "products[].amount": "number", 35 | "products[].total": "number" 36 | }, 37 | }, 38 | { 39 | name: '$x', 40 | options: { 41 | strictSelect: false, 42 | autoParseNumbers: true, 43 | }, 44 | hooks: { 45 | onInit(form) { 46 | form.$("products").add(); 47 | form.$("products").add(); 48 | form.$("products[0].qty").set(2); 49 | form.$("products[0].amount").set(5); 50 | form.$("products[1].qty").set(3); 51 | form.$("products[1].amount").set(5); 52 | 53 | describe("Check computed values", () => { 54 | it("form $x products[0].total value", () => 55 | expect(form.$('products[0].total').value).to.be.equal(10)); 56 | 57 | it("form $x products[1].total value", () => 58 | expect(form.$('products[1].total').value).to.be.equal(15)); 59 | 60 | it("form $x total value", () => 61 | expect(form.$('total').value).to.be.equal(25)); 62 | }); 63 | } 64 | } 65 | } 66 | ); -------------------------------------------------------------------------------- /tests/data/forms/nested/form.z.ts: -------------------------------------------------------------------------------- 1 | import validatorjs from "validatorjs"; 2 | import { Form } from "../../../../src"; 3 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 4 | import dvr from "../../../../src/validators/DVR"; 5 | 6 | const fields = [ 7 | "data[].f1", 8 | "data[].f2", 9 | "data[].f3", 10 | "data[].f4", 11 | "data[].f5", 12 | "data[].f6", 13 | "data[].f7", 14 | ]; 15 | 16 | const values = { 17 | data: Array.from(Array(300).keys()).map(() => ({ 18 | f1: Math.random(), 19 | f2: Math.random(), 20 | f3: Math.random(), 21 | f4: Math.random(), 22 | f5: Math.random(), 23 | f6: Math.random(), 24 | f7: Math.random(), 25 | })), 26 | }; 27 | 28 | const plugins: ValidationPlugins = { 29 | dvr: dvr({ package: validatorjs }), 30 | }; 31 | 32 | export default new Form( 33 | { 34 | fields, 35 | values, 36 | }, 37 | { plugins, name: "Nested-R" } 38 | ); 39 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.z1.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { expect } from "chai"; 3 | import z from "zod"; 4 | import FormInterface from "../../../../src/models/FormInterface"; 5 | import OptionsModel from "../../../../src/models/OptionsModel"; 6 | import { Form } from "../../../../src"; 7 | import zod from "../../../../src/validators/ZOD"; 8 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 9 | 10 | 11 | const fields = [ 12 | "user.username", 13 | "user.email", 14 | "user.password", 15 | "user.passwordConfirm", 16 | ]; 17 | 18 | const values = { 19 | user: { 20 | username: 'a', 21 | email: 'notAValidEmail@', 22 | password: 'x', 23 | passwordConfirm: 'mysecretpassword', 24 | } 25 | } 26 | 27 | const schema = z.object({ 28 | user: z.object({ 29 | username: z.string().min(3), 30 | email: z.string().email(), 31 | password: z.string().min(6).max(25), 32 | passwordConfirm: z.string().min(6).max(25), 33 | }) 34 | .refine((data) => data.password === data.passwordConfirm, { 35 | message: "Passwords do not match", 36 | path: ["passwordConfirm"], // path of error 37 | }) 38 | .optional(), 39 | }) 40 | 41 | const plugins: ValidationPlugins = { 42 | zod: zod({ 43 | package: z, 44 | schema, 45 | }), 46 | }; 47 | 48 | const options: OptionsModel = { 49 | validateOnInit: true, 50 | showErrorsOnInit: true, 51 | }; 52 | 53 | export default new Form({ 54 | fields, 55 | values, 56 | }, { 57 | plugins, 58 | options, 59 | name: "Nested-Z1", 60 | hooks: { 61 | onInit(form: FormInterface) { 62 | describe("Check zod validation flag", () => { 63 | it('user.username hasError should be true', () => expect(form.$('user.username').hasError).to.be.true); 64 | it('user.email hasError should be true', () => expect(form.$('user.email').hasError).to.be.true); 65 | it('user.password hasError should be true', () => expect(form.$('user.password').hasError).to.be.true); 66 | it('user.passwordConfirm hasError should be true', () => expect(form.$('user.passwordConfirm').hasError).to.be.true); 67 | }); 68 | 69 | describe("Check zod validation errors", () => { 70 | it('user.username error should equal zod error', () => expect(form.$('user.username').error).to.be.equal('String must contain at least 3 character(s)')); 71 | it('user.email error should equal zod error', () => expect(form.$('user.email').error).to.be.equal('Invalid email')); 72 | it('user.password error should equal zod error', () => expect(form.$('user.password').error).to.be.equal('String must contain at least 6 character(s)')); 73 | it('user.passwordConfirm error should equal zod error', () => expect(form.$('user.passwordConfirm').error).to.be.equal('Passwords do not match')); 74 | }); 75 | 76 | } 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.z2.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { expect } from "chai"; 3 | import z from "zod"; 4 | import FormInterface from "../../../../src/models/FormInterface"; 5 | import OptionsModel from "../../../../src/models/OptionsModel"; 6 | import { Form } from "../../../../src"; 7 | import zod from "../../../../src/validators/ZOD"; 8 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 9 | 10 | 11 | const fields = [ 12 | "products[].name", 13 | "products[].qty", 14 | "products[].amount", 15 | ]; 16 | 17 | const values = { 18 | products: [{ 19 | name: 'a', 20 | qty: -1, 21 | amount: -1, 22 | }] 23 | } 24 | 25 | const schema = z.object({ 26 | products: z.array( 27 | z.object({ 28 | name: z.string().min(3), 29 | qty: z.number().min(0), 30 | amount: z.number().min(0), 31 | })) 32 | .optional(), 33 | }) 34 | 35 | const plugins: ValidationPlugins = { 36 | zod: zod({ 37 | package: z, 38 | schema, 39 | }), 40 | }; 41 | 42 | const options: OptionsModel = { 43 | validateOnInit: true, 44 | showErrorsOnInit: true, 45 | }; 46 | 47 | export default new Form({ 48 | fields, 49 | values, 50 | }, { 51 | plugins, 52 | options, 53 | name: "Nested-Z2", 54 | hooks: { 55 | onInit(form: FormInterface) { 56 | describe("Check zod validation flag", () => { 57 | it('products[0].name hasError should be true', () => expect(form.$('products[0].name').hasError).to.be.true); 58 | it('products[0].qty hasError should be true', () => expect(form.$('products[0].qty').hasError).to.be.true); 59 | it('products[0].amount hasError should be true', () => expect(form.$('products[0].amount').hasError).to.be.true); 60 | }); 61 | 62 | describe("Check zod validation errors", () => { 63 | it('products[0].name error should equal zod error', () => expect(form.$('products[0].name').error).to.be.equal('String must contain at least 3 character(s)')); 64 | it('products[0].qty error should equal zod error', () => expect(form.$('products[0].qty').error).to.be.equal('Number must be greater than or equal to 0')); 65 | it('products[0].amount error should equal zod error', () => expect(form.$('products[0].amount').error).to.be.equal('Number must be greater than or equal to 0')); 66 | }); 67 | 68 | } 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.z3.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { expect } from "chai"; 3 | import j from "joi"; 4 | import FormInterface from "../../../../src/models/FormInterface"; 5 | import OptionsModel from "../../../../src/models/OptionsModel"; 6 | import { Form } from "../../../../src"; 7 | import joi from "../../../../src/validators/JOI"; 8 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 9 | 10 | 11 | const fields = [ 12 | "user.username", 13 | "user.email", 14 | "user.password", 15 | "user.passwordConfirm", 16 | ]; 17 | 18 | const values = { 19 | user: { 20 | username: 'a', 21 | email: 'notAValidEmail@', 22 | password: 'x', 23 | passwordConfirm: 'mysecretpassword', 24 | } 25 | } 26 | 27 | const schema = j.object({ 28 | user: j.object({ 29 | username: j.string().min(3).required().label('Username'), 30 | email: j.string().email().required(), 31 | password: j.string().min(6).max(25).required(), 32 | passwordConfirm: j.string().min(6).max(25).valid(j.ref('password')).required().messages({ 33 | 'any.only': 'Passwords do not match', 34 | 'any.required': 'Password confirmation is required', 35 | }), 36 | }).required() 37 | }); 38 | 39 | const plugins: ValidationPlugins = { 40 | joi: joi({ 41 | package: j, 42 | schema, 43 | }), 44 | }; 45 | 46 | const options: OptionsModel = { 47 | validateOnInit: true, 48 | showErrorsOnInit: true, 49 | }; 50 | 51 | export default new Form({ 52 | fields, 53 | values, 54 | }, { 55 | plugins, 56 | options, 57 | name: "Nested-Z3", 58 | hooks: { 59 | onInit(form: FormInterface) { 60 | describe("Check joi validation flag", () => { 61 | it('user.username hasError should be true', () => expect(form.$('user.username').hasError).to.be.true); 62 | it('user.email hasError should be true', () => expect(form.$('user.email').hasError).to.be.true); 63 | it('user.password hasError should be true', () => expect(form.$('user.password').hasError).to.be.true); 64 | it('user.passwordConfirm hasError should be true', () => expect(form.$('user.passwordConfirm').hasError).to.be.true); 65 | }); 66 | 67 | describe("Check joi validation errors", () => { 68 | it('user.username error should equal joi error', () => expect(form.$('user.username').error).to.be.equal('"Username" length must be at least 3 characters long')); 69 | it('user.email error should equal joi error', () => expect(form.$('user.email').error).to.be.equal('"user.email" must be a valid email')); 70 | it('user.password error should equal joi error', () => expect(form.$('user.password').error).to.be.equal('"user.password" length must be at least 6 characters long')); 71 | it('user.passwordConfirm error should equal joi error', () => expect(form.$('user.passwordConfirm').error).to.be.equal('Passwords do not match')); 72 | }); 73 | 74 | } 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /tests/data/forms/nested/form.z4.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { expect } from "chai"; 3 | import j from "joi"; 4 | import FormInterface from "../../../../src/models/FormInterface"; 5 | import OptionsModel from "../../../../src/models/OptionsModel"; 6 | import { Form } from "../../../../src"; 7 | import joi from "../../../../src/validators/JOI"; 8 | import { ValidationPlugins } from "../../../../src/models/ValidatorInterface"; 9 | 10 | 11 | const fields = [ 12 | "products[].name", 13 | "products[].qty", 14 | "products[].amount", 15 | ]; 16 | 17 | const values = { 18 | products: [{ 19 | name: 'a', 20 | qty: -1, 21 | amount: -1, 22 | }] 23 | } 24 | 25 | // const schema = z.object({ 26 | // products: z.array( 27 | // z.object({ 28 | // name: z.string().min(3), 29 | // qty: z.number().min(0), 30 | // amount: z.number().min(0), 31 | // })) 32 | // .optional(), 33 | // }) 34 | 35 | const schema = j.object({ 36 | products: j.array().items( 37 | j.object({ 38 | name: j.string().min(3).required(), 39 | qty: j.number().integer().min(0).required(), 40 | amount: j.number().min(0).required(), 41 | }).optional() 42 | ) 43 | }); 44 | 45 | const plugins: ValidationPlugins = { 46 | joi: joi({ 47 | package: j, 48 | schema, 49 | }), 50 | }; 51 | 52 | const options: OptionsModel = { 53 | validateOnInit: true, 54 | showErrorsOnInit: true, 55 | }; 56 | 57 | export default new Form({ 58 | fields, 59 | values, 60 | }, { 61 | plugins, 62 | options, 63 | name: "Nested-Z4", 64 | hooks: { 65 | onInit(form: FormInterface) { 66 | describe("Check joi validation flag", () => { 67 | it('products[0].name hasError should be true', () => expect(form.$('products[0].name').hasError).to.be.true); 68 | it('products[0].qty hasError should be true', () => expect(form.$('products[0].qty').hasError).to.be.true); 69 | it('products[0].amount hasError should be true', () => expect(form.$('products[0].amount').hasError).to.be.true); 70 | }); 71 | 72 | describe("Check joi validation errors", () => { 73 | it('products[0].name error should equal joi error', () => expect(form.$('products[0].name').error).to.be.equal('"products[0].name" length must be at least 3 characters long')); 74 | it('products[0].qty error should equal joi error', () => expect(form.$('products[0].qty').error).to.be.equal('"products[0].qty" must be greater than or equal to 0')); 75 | it('products[0].amount error should equal joi error', () => expect(form.$('products[0].amount').error).to.be.equal('"products[0].amount" must be greater than or equal to 0')); 76 | }); 77 | 78 | } 79 | } 80 | }); 81 | -------------------------------------------------------------------------------- /tests/fixes.labels.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import $ from "./data/_.fixes"; // FORMS 4 | 5 | describe("Check Fixes $425 labels", () => { 6 | it("$425 labels() check", () => 7 | expect($.$425.labels()).to.be.deep.equal({ 8 | "1a": "1aa", 9 | "2a": "2aa", 10 | "3a": "3aa", 11 | })); 12 | }); 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/fixes.submit.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.fixes'; // FORMS 4 | 5 | describe('Form submit() decoupled callback', () => { 6 | // $L 7 | it('$L.submit() should call onError callback on invalid form', (done) => { 8 | $.$L.submit({ 9 | onError: (form) => { 10 | form.state.options.set({ validateOnChange: true }); 11 | form.$('email').set('value', 'notAnEmailYet'); 12 | 13 | describe('Form $L onError() checks', () => { 14 | it('$L state.options "validateOnChange" should be true', () => 15 | expect(form.state.options.get('validateOnChange')).to.be.true); 16 | 17 | it('$L email value should be equal to "notAnEmailYet"', () => 18 | expect(form.$('email').value).to.be.equal('notAnEmailYet')); 19 | 20 | it('$L email hasError should be true', () => 21 | expect(form.$('email').hasError).to.be.true); 22 | 23 | it('$L form submitted should be 1', () => 24 | expect(form.submitted).to.equal(1)); 25 | 26 | it('$L form isValid should be false', () => 27 | expect(form.isValid).to.be.false); 28 | }); 29 | 30 | done(); 31 | }, 32 | }); 33 | }); 34 | 35 | // $472 36 | describe('$472 submit', () => { 37 | it('$472 submit', (done) => { 38 | $.$472.submit() 39 | .then(() => { 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | // $480 46 | describe('$480 submit', () => { 47 | it('$480 submit', (done) => { 48 | $.$480.submit() 49 | .then((instance) => { 50 | describe('Form $480 submit checks', () => { 51 | it('$480 .isValid should be false.', () => 52 | expect($.$480.isValid).to.be.false); 53 | 54 | it('$480 $(passwordConfirm).isValid should be false.', () => 55 | expect($.$480.$('passwordConfirm').isValid).to.be.false); 56 | 57 | const msg = 'The Password Confirmation field is required when Password Required is true.'; 58 | 59 | it('$480 $(passwordConfirm).error should be equal ' + msg, () => 60 | expect($.$480.$('passwordConfirm').error).to.be.equal(msg)); 61 | }); 62 | done(); 63 | }); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /tests/fixes.types.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.fixes'; // FORMS 4 | 5 | 6 | describe('$L Field types checks', () => { 7 | it('$L email type should be equal to "email"', () => 8 | expect($.$L.$('email').type).to.be.equal('email')); // #415 9 | }); 10 | -------------------------------------------------------------------------------- /tests/fixes.validation.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.fixes'; // FORMS 4 | 5 | // $M 6 | describe('$M Check jobs[0] ', () => { 7 | it('$M Check jobs[0]', (done) => { 8 | $.$M.$('jobs[0]').validate() 9 | .then(({ isValid }) => { 10 | // eslint-disable-next-line 11 | expect(isValid).to.be.false; 12 | done(); 13 | }); 14 | }); 15 | }); 16 | 17 | describe('Check Fixes $B validation', () => { 18 | it('$B people isValid should be false', () => 19 | expect($.$B.$('people').isValid).to.be.false); 20 | 21 | it('$B people hasError should be true', () => 22 | expect($.$B.$('people').hasError).to.be.true); 23 | 24 | it('$B emptyArray isValid should be false', () => 25 | expect($.$B.$('emptyArray').isValid).to.be.false); 26 | 27 | it('$B emptyArray hasError should be true', () => 28 | expect($.$B.$('emptyArray').hasError).to.be.true); 29 | }); 30 | 31 | describe('Check before/after $U validation', () => { 32 | it('$U from hasError should be true', () => 33 | expect($.$U.$('from').hasError).to.be.true); 34 | 35 | it('$U from error message should be equal to "The FROM must be equal or before TO."', () => 36 | expect($.$U.$('from').error).to.be.equal('The FROM must be equal or before TO.')); 37 | 38 | it('$U to hasError should be true', () => 39 | expect($.$U.$('to').hasError).to.be.true); 40 | 41 | it('$U to error message should be equal to "The TO must be after FROM."', () => 42 | expect($.$U.$('to').error).to.be.equal('The TO must be after FROM.')); 43 | }); -------------------------------------------------------------------------------- /tests/flat.props.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.flat'; // FORMS 4 | 5 | describe('Check Flat $A placeholder field prop', () => { 6 | it('$A username placeholder should be equal to "Username Placeholder"', () => 7 | expect($.$A.$('username').placeholder).to.be.equal('Username Placeholder')); 8 | 9 | it('$A username type should be equal to "text"', () => 10 | expect($.$A.$('username').type).to.be.equal('text')); 11 | 12 | it('$A username bindings should be equal to "default"', () => 13 | expect($.$A.$('username').bindings).to.be.equal('default')); 14 | 15 | it('$A devSkills value should be equal to "5" (string)', () => 16 | expect($.$A.$('devSkills').value).to.be.equal('5')); 17 | 18 | it('$A devSkills value should be a string', () => 19 | expect($.$A.$('devSkills').value).to.be.a('string')); 20 | 21 | it('$A devSkills get(value) should be equal to 5 (number)', () => 22 | expect($.$A.$('devSkills').get('value')).to.be.equal(5)); 23 | 24 | it('$A devSkills get(value) should be a number', () => 25 | expect($.$A.$('devSkills').get('value')).to.be.a('number')); 26 | 27 | it('$A devSkills id should start with devSkills--', () => 28 | expect($.$A.$('devSkills').id.substring(0, 11)).to.be.equal('devSkills--')); 29 | }); 30 | 31 | describe('Check Flat $B value field prop', () => { 32 | it('$B username value should be empty string', () => 33 | expect($.$B.$('username').value).to.be.equal('')); 34 | }); 35 | 36 | describe('Check Flat $R extended field prop', () => { 37 | it('$R email should have property newFieldProp', () => 38 | expect($.$R.$('email')).to.have.property('newFieldProp')); 39 | 40 | it('$R email newFieldProp should be true', () => 41 | expect($.$R.$('email').newFieldProp).to.be.true); 42 | 43 | it('$R email autoComplete should be equal "on"', () => 44 | expect($.$R.$('email').autoComplete).to.be.equal('on')); 45 | 46 | it('$R email bind() autoComplete should be equal "on"', () => 47 | expect($.$R.$('email').bind().autoComplete).to.be.equal('on')); 48 | 49 | it('$R email autoComplete should be undefined', () => 50 | expect($.$R.$('emailConfirm').autoComplete).to.be.undefined); 51 | 52 | it('$R email bind() autoComplete should be undefined', () => 53 | expect($.$R.$('emailConfirm').bind().autoComplete).to.be.undefined); 54 | }); 55 | 56 | describe('Check Flat $G prop', () => { 57 | it('$G username error should be null', () => 58 | expect($.$G.errors().username).to.be.null); 59 | }); 60 | -------------------------------------------------------------------------------- /tests/flat.submit.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.flat'; // FORMS 4 | 5 | describe('Form submit() decoupled callback', () => { 6 | // $A 7 | it('$A.submit() should call onSuccess callback on valid form', (done) => { 8 | $.$A.submit({ 9 | onSuccess: (form) => { 10 | expect(form.submitted).to.equal(1); 11 | expect(form.isValid).to.be.true; 12 | done(); 13 | }, 14 | }); 15 | }); 16 | 17 | // $I 18 | it('$I.submit() should call onSuccess callback on valid form', (done) => { 19 | $.$I.submit({ 20 | onSuccess: (form) => { 21 | expect(form.submitted).to.equal(1); 22 | expect(form.isValid).to.be.true; 23 | done(); 24 | }, 25 | }); 26 | }); 27 | 28 | // $N 29 | it('$N.submit() should call onError callback on invalid form', (done) => { 30 | $.$N.submit({ 31 | onError: (form) => { 32 | expect(form.submitted).to.equal(1); 33 | expect(form.isValid).to.be.false; 34 | done(); 35 | }, 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/flat.types.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { expect, assert } from 'chai'; 3 | 4 | import $forms from './data/_.flat'; // FORMS 5 | 6 | 7 | const checkHelperIsObject = helper => 8 | _.each($forms, (form, key) => it(`${key} ${helper}() is object`, () => 9 | assert.isObject(form[helper](), `${key}.${helper}() is not object`))); 10 | 11 | const checkComputedIsBoolean = computed => 12 | _.each($forms, (form, key) => it(`${key} ${computed} is boolean`, () => 13 | assert.isBoolean(form[computed], `${key}.${computed} is not boolean`))); 14 | 15 | const checkComputedIsNumber = computed => 16 | _.each($forms, (form, key) => it(`${key} ${computed} is numbser`, () => 17 | assert.isNumber(form[computed], `${key}.${computed} is not numbser`))); 18 | 19 | 20 | describe('Check validate() returns promise that resolves to boolean', () => { 21 | _.each($forms, (form, key) => ( 22 | it(`${key} validate() is promise that resolves to boolean`, () => { 23 | const promise = form.validate(); 24 | expect(promise).to.be.a('promise'); 25 | return promise.then(result => expect(result.isValid).to.be.a('boolean')); 26 | }) 27 | )); 28 | }); 29 | 30 | describe('Check FORM validate(key) returns promise that resolves to boolean', () => ( 31 | _.each($forms, (form, formKey) => describe(`${formKey} form`, () => ( 32 | form.each(field => ( 33 | it(`validate('${field.path}') is promise that resolves to boolean`, () => { 34 | const promise = form.validate(field.path); 35 | expect(promise).to.be.a('promise'); 36 | return promise.then(result => expect(result.isValid).to.be.a('boolean')); 37 | }) 38 | )) 39 | ))) 40 | )); 41 | 42 | describe('Check FIELD validate(key) returns promise that resolves to boolean', () => ( 43 | _.each($forms, (form, formKey) => describe(`${formKey} form`, () => ( 44 | form.each(field => ( 45 | it(`validate('${field.path}') is promise that resolves to boolean`, () => { 46 | const promise = field.validate(); 47 | expect(promise).to.be.a('promise'); 48 | return promise.then(result => expect(result.isValid).to.be.a('boolean')); 49 | }) 50 | )) 51 | ))) 52 | )); 53 | 54 | describe('Check form helpers returns object', () => { 55 | checkHelperIsObject('types'); 56 | checkHelperIsObject('get'); 57 | checkHelperIsObject('values'); 58 | checkHelperIsObject('errors'); 59 | checkHelperIsObject('labels'); 60 | checkHelperIsObject('placeholders'); 61 | checkHelperIsObject('defaults'); 62 | checkHelperIsObject('initials'); 63 | }); 64 | 65 | describe('Check form computed returns boolean', () => { 66 | checkComputedIsBoolean('validating'); 67 | checkComputedIsBoolean('hasError'); 68 | checkComputedIsBoolean('isDirty'); 69 | checkComputedIsBoolean('isPristine'); 70 | checkComputedIsBoolean('isDefault'); 71 | checkComputedIsBoolean('isValid'); 72 | checkComputedIsBoolean('isEmpty'); 73 | checkComputedIsBoolean('focused'); 74 | checkComputedIsBoolean('touched'); 75 | checkComputedIsBoolean('disabled'); 76 | }); 77 | 78 | 79 | describe('Check form computed returns number', () => { 80 | checkComputedIsNumber('changed'); 81 | checkComputedIsNumber('submitted'); 82 | checkComputedIsNumber('validated'); 83 | checkComputedIsNumber('size'); 84 | }); 85 | -------------------------------------------------------------------------------- /tests/nested.actions.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.nested'; // FORMS 4 | 5 | describe('Form error test', () => { 6 | it('$A select() should throw error', () => 7 | expect(() => $.$A.select('some.test')).to.throw(Error)); 8 | 9 | it('$A get() should throw error', () => 10 | expect(() => $.$A.get('not-allowed-prop')).to.throw(Error)); 11 | 12 | it('$T del() should throw error', () => 13 | expect(() => $.$T.$('notIncrementalFields').del(99)).to.throw(Error)); 14 | }); 15 | 16 | describe('Form $focused test', () => { 17 | it('$T notIncrementalFields $focused should be true', () => 18 | expect($.$T.$('notIncrementalFields').$focused).to.be.true); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/nested.labels.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.nested'; // FORMS 4 | 5 | describe('Check Nested $A Unified Properties (label)', () => { 6 | it('$A user.email label should be equal to "Email"', () => 7 | expect($.$A.$('user.email').label).to.be.equal('Email')); 8 | 9 | it('$A user.emailConfirm label should be equal to "Confirm User Email"', () => 10 | expect($.$A.$('user.emailConfirm').label).to.be.equal('Confirm User Email')); 11 | 12 | it('$A user.emailConfirm default should be equal to "Default Value"', () => 13 | expect($.$A.$('user.emailConfirm').default).to.be.equal('Default Value')); 14 | }); 15 | 16 | describe('Check Nested $I Separated Properties (labels)', () => { 17 | it('$I state.city.places.centralPark label should be equal to "Central Park"', () => 18 | expect($.$I.$('state.city.places.centralPark').label).to.be.equal('Central Park')); 19 | 20 | it('$I state.city.places.statueOfLiberty label should be equal to "Statue of Liberty"', () => 21 | expect($.$I.$('state.city.places.statueOfLiberty').label).to.be.equal('Statue of Liberty')); 22 | 23 | it('$I state.city.places.empireStateBuilding label should be equal to "Empire State Building"', () => 24 | expect($.$I.$('state.city.places.empireStateBuilding').label).to.be.equal('Empire State Building')); 25 | 26 | it('$I state.city.places.brooklynBridge label should be equal to "Brooklyn Bridge"', () => 27 | expect($.$I.$('state.city.places.brooklynBridge').label).to.be.equal('Brooklyn Bridge')); 28 | }); 29 | 30 | describe('Check Nested $O Separated Properties (labels)', () => { 31 | it('$O club label should be equal to "Label Club"', () => 32 | expect($.$O.$('club').label).to.be.equal('Label Club')); 33 | 34 | it('$O club.name label should be equal to "Label Club Name"', () => 35 | expect($.$O.$('club.name').label).to.be.equal('Label Club Name')); 36 | 37 | it('$O club.city label should be equal to "Label Club City"', () => 38 | expect($.$O.$('club.city').label).to.be.equal('Label Club City')); 39 | 40 | it('$O members label should be equal to "Label Members"', () => 41 | expect($.$O.$('members').label).to.be.equal('Label Members')); 42 | 43 | it('$O members[1].firstname label should be equal to "Label Member FirstName"', () => 44 | expect($.$O.$('members[1].firstname').label).to.be.equal('Label Member FirstName')); 45 | 46 | it('$O members[1].hobbies label should be equal to "Label Member Hobby"', () => 47 | expect($.$O.$('members[1].hobbies').label).to.be.equal('Label Member Hobby')); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/nested.performance.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.nested'; // FORMS 4 | 5 | // describe('Form performance', () => { 6 | // 7 | // it('DVR enabled', () => { 8 | // const t0 = Date.now(); 9 | // 10 | // $.$Z.submit(); 11 | // const span = Date.now() - t0; 12 | // expect(span).lessThan(5000); 13 | // }); 14 | // 15 | // }); 16 | -------------------------------------------------------------------------------- /tests/nested.state.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.nested'; // FORMS 4 | 5 | describe('Form state extra', () => { 6 | it('$A state.$extra should have "foo" property', () => 7 | expect($.$A.state.$extra).to.have.property('foo')); 8 | 9 | it('$A state.$extra "foo" prop should be "bar"', () => 10 | expect($.$A.state.extra('foo')).to.be.equal('bar')); 11 | 12 | it('$B state.extra() should be array', () => 13 | expect($.$B.state.extra()).to.be.instanceof(Array)); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/nested.submit.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import $ from './data/_.nested'; // FORMS 4 | 5 | describe('Nested Form Manual submit()', () => { 6 | // $R 7 | it('$R.submit() should call onSuccess callback', (done) => { 8 | $.$R.$('members').submit().then((instance) => { 9 | expect(instance.submitted).to.equal(1); 10 | expect(instance.hasError).to.be.false; 11 | expect(instance.isValid).to.be.true; 12 | done(); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/promises/_.fixes.ts: -------------------------------------------------------------------------------- 1 | import validate from './fixes.validate'; 2 | 3 | export default { validate }; 4 | -------------------------------------------------------------------------------- /tests/promises/_.flat.ts: -------------------------------------------------------------------------------- 1 | import validate from './flat.validate'; 2 | 3 | export default { validate }; 4 | -------------------------------------------------------------------------------- /tests/promises/_.nested.ts: -------------------------------------------------------------------------------- 1 | import validate from './nested.validate'; 2 | 3 | export default { validate }; 4 | -------------------------------------------------------------------------------- /tests/promises/fixes.validate.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | export default ($) => { 4 | describe("Fixes Form validate()", () => { 5 | // $L 6 | it("$L validate() should be false", (done) => { 7 | $.$L.validate().then(({ isValid }) => { 8 | describe("Form $L checks after validate()", () => { 9 | it('$L state.options "validateOnChange" should be true', () => 10 | expect($.$L.state.options.get("validateOnChange")).to.be.true); 11 | 12 | it("$L email hasError should be true", () => 13 | expect($.$L.$("email").hasError).to.be.true); 14 | }); 15 | 16 | expect(isValid).to.be.false; // eslint-disable-line 17 | done(); 18 | }); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /tests/promises/flat.validate.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | export default ($) => { 4 | describe("Flat Form validate()", () => { 5 | // $A 6 | it("$A email validate() should be true", (done) => { 7 | $.$A 8 | .$("email") 9 | .validate() 10 | .then(({ isValid }) => { 11 | expect(isValid).to.be.true; // eslint-disable-line 12 | expect($.$A.errors().email).to.be.null; // eslint-disable-line 13 | expect($.$A.$("email").errors()).to.be.null; // eslint-disable-line 14 | done(); 15 | }); 16 | }); 17 | 18 | // $B 19 | it("$B validate() should be false", (done) => { 20 | $.$B.validate().then(({ isValid }) => { 21 | expect(isValid).to.be.false; // eslint-disable-line 22 | done(); 23 | }); 24 | }); 25 | 26 | // $E 27 | it("$E validate() should be true", (done) => { 28 | $.$E.validate().then(({ isValid }) => { 29 | expect(isValid).to.be.true; // eslint-disable-line 30 | done(); 31 | }); 32 | }); 33 | 34 | // $L 35 | it("$L validate() should be false", (done) => { 36 | $.$L.validate().then(({ isValid }) => { 37 | expect(isValid).to.be.false; // eslint-disable-line 38 | done(); 39 | }); 40 | }); 41 | 42 | // $M 43 | it("$M validate() should be false", (done) => { 44 | $.$M.validate().then(({ isValid }) => { 45 | expect(isValid).to.be.false; // eslint-disable-line 46 | done(); 47 | }); 48 | }); 49 | 50 | // $N 51 | it("$N validate() should be false", (done) => { 52 | $.$N.validate().then(({ isValid }) => { 53 | expect(isValid).to.be.false; // eslint-disable-line 54 | done(); 55 | }); 56 | }); 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /tests/promises/nested.validate.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | export default ($) => { 4 | describe('Nested Form validate()', () => { 5 | // $R 6 | it('$R validate() should be true', (done) => { 7 | $.$R.validate({ showErrors: true }).then(({ isValid, hasError }) => { 8 | expect(isValid).to.be.true; // eslint-disable-line 9 | expect(hasError).to.be.false; // eslint-disable-line 10 | expect($.$R.isValid).to.be.true; // eslint-disable-line 11 | expect($.$R.hasError).to.be.false; // eslint-disable-line 12 | expect($.$R.errors().email).to.be.null; // eslint-disable-line 13 | expect($.$R.$('email').errors()).to.be.null; // eslint-disable-line 14 | done(); 15 | }); 16 | }); 17 | 18 | // $S 19 | it('$S validate() should be false', (done) => { 20 | $.$S.validate().then(({ isValid }) => { 21 | expect(isValid).to.be.false; // eslint-disable-line 22 | done(); 23 | }); 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /tests/test.computed.ts: -------------------------------------------------------------------------------- 1 | import $flat from './data/_.flat'; 2 | import $nested from './data/_.nested'; 3 | 4 | import flat from './computed/_.flat'; 5 | import nested from './computed/_.nested'; 6 | 7 | flat.isValid($flat); 8 | flat.hasError($flat); 9 | flat.isDirty($flat); 10 | flat.isEmpty($flat); 11 | flat.isPristine($flat); 12 | flat.isPristine($flat); 13 | 14 | nested.isValid($nested); 15 | -------------------------------------------------------------------------------- /tests/test.fieldClass.ts: -------------------------------------------------------------------------------- 1 | import { Field } from "../src"; 2 | import { 3 | $OVERRIDE, 4 | $SEPARATED, 5 | $UNIFIED, 6 | CustomField, 7 | OverrideCustomField, 8 | FieldPaths, 9 | } from "./data/forms/forms.fieldClass"; 10 | import { expect } from "chai"; 11 | 12 | describe("FieldClass", () => { 13 | describe("when form has separate definition", () => { 14 | describe("when class is not specified", () => { 15 | it("the field as an instance of Field class", () => { 16 | expect($SEPARATED.$(FieldPaths.standardField)).to.be.an.instanceOf( 17 | Field, 18 | ); 19 | expect( 20 | $SEPARATED.$(FieldPaths.nestedStandardField), 21 | ).to.be.an.instanceOf(Field); 22 | }); 23 | }); 24 | describe("when class is specified", () => { 25 | it("the field is an instance of the specified class", () => { 26 | expect($SEPARATED.$(FieldPaths.customField)).to.be.an.instanceOf( 27 | CustomField, 28 | ); 29 | expect($SEPARATED.$(FieldPaths.nestedCustomField)).to.be.an.instanceOf( 30 | CustomField, 31 | ); 32 | }); 33 | }); 34 | }); 35 | 36 | describe("when form has unified definition", () => { 37 | describe("when class is not specified", () => { 38 | it("the field as an instance of Field class", () => { 39 | expect($UNIFIED.$(FieldPaths.standardField)).to.be.an.instanceOf(Field); 40 | expect($UNIFIED.$(FieldPaths.nestedStandardField)).to.be.an.instanceOf( 41 | Field, 42 | ); 43 | }); 44 | }); 45 | describe("when class is specified", () => { 46 | it("the field is an instance of the specified class", () => { 47 | expect($UNIFIED.$(FieldPaths.customField)).to.be.an.instanceOf( 48 | CustomField, 49 | ); 50 | expect($UNIFIED.$(FieldPaths.nestedCustomField)).to.be.an.instanceOf( 51 | CustomField, 52 | ); 53 | }); 54 | }); 55 | }); 56 | 57 | describe("when form has override of makeField", () => { 58 | it("adheres to the makeField logic", () => { 59 | expect($OVERRIDE.$(FieldPaths.standardField)).to.be.an.instanceOf(Field); 60 | expect($OVERRIDE.$(FieldPaths.nestedStandardField)).to.be.an.instanceOf( 61 | Field, 62 | ); 63 | expect($OVERRIDE.$(FieldPaths.customField)).to.be.an.instanceOf( 64 | CustomField, 65 | ); 66 | expect($OVERRIDE.$(FieldPaths.nestedCustomField)).to.be.an.instanceOf( 67 | OverrideCustomField, 68 | ); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /tests/test.options.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import $flat from "./data/_.flat"; // FORMS 4 | import $fixes from "./data/_.fixes"; // FORMS 5 | 6 | describe("Field values checks", () => { 7 | it('Fixes $A state.options.get(loadingMessage) should be equal to "Custom Loading Message..."', () => 8 | expect($fixes.$A.state.options.get("loadingMessage")).to.be.equal( 9 | "Custom Loading Message..." 10 | )); 11 | }); 12 | 13 | describe("Check Flat $B error prop", () => { 14 | it('Flat $B state.options defaultGenericError should be equal to "Custom Generic Error"', () => 15 | expect($flat.$B.state.options.get("defaultGenericError")).to.be.equal( 16 | "Custom Generic Error" 17 | )); 18 | }); 19 | 20 | describe("Check Flat $I error prop", () => { 21 | it("Flat $I state.options.get(strictUpdate) should be true", () => 22 | expect($flat.$I.state.options.get("strictUpdate")).to.be.true); 23 | 24 | it("Flat $I state.options.get() should be an object", () => 25 | expect($flat.$I.state.options.get()).to.be.an("object")); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/test.promises.ts: -------------------------------------------------------------------------------- 1 | // FORMS 2 | import $flat from "./data/_.flat"; 3 | import $nested from "./data/_.nested"; 4 | import $fixes from "./data/_.fixes"; 5 | 6 | // TESTS 7 | import flat from "./promises/_.flat"; 8 | import nested from "./promises/_.nested"; 9 | import fixes from "./promises/_.fixes"; 10 | 11 | flat.validate($flat); 12 | nested.validate($nested); 13 | fixes.validate($fixes); 14 | -------------------------------------------------------------------------------- /tests/test.set.method.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import $methods from "./data/_.methods"; // FORMS 3 | 4 | 5 | describe("after field set() method checks", () => { 6 | it('disable prop should be true', () => expect($methods.$set.$('test').disabled).to.be.true); 7 | it('deleted prop should be true', () => expect($methods.$set.$('test').deleted).to.be.true); 8 | it('type prop should be true', () => expect($methods.$set.$('test').type).to.be.equal("number")); 9 | it('initial prop should be equal "test"', () => expect($methods.$set.$('test').initial).to.be.equal("test")); 10 | it('default prop should be equal "test"', () => expect($methods.$set.$('test').default).to.be.equal("test")); 11 | it('label prop should be equal "test"', () => expect($methods.$set.$('test').label).to.be.equal("test")); 12 | it('placeholder prop should be equal "test"', () => expect($methods.$set.$('test').placeholder).to.be.equal("test")); 13 | it('related prop should be equal array with "test"', () => expect($methods.$set.$('test').related).to.be.deep.equal(["test"])); 14 | }); -------------------------------------------------------------------------------- /tests/test.umd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MobX React Form (UMD) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /tests/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { hasUnifiedProps, hasSeparatedProps } from '../src/utils' 3 | 4 | describe('utils', () => { 5 | it('Issue #598, hasUnifiedProps()', () => { 6 | const fields = [ 7 | { 8 | name: 'field1' 9 | }, 10 | { 11 | name: 'field2', 12 | label: 'label 2' 13 | } 14 | ] 15 | expect(hasUnifiedProps({fields})).to.be.true 16 | }) 17 | }) -------------------------------------------------------------------------------- /umd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MobX React Form (UMD) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 50 | 51 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const LodashModuleReplacementPlugin = require("lodash-webpack-plugin"); 2 | const { join } = require("path"); 3 | 4 | const MINIFY = process.env.MINIFY === "YES"; 5 | 6 | const rules = [ 7 | { 8 | test: /\.ts$/, 9 | loader: "ts-loader", 10 | include: join(__dirname, "src"), 11 | }, 12 | { 13 | test: /\.json$/, 14 | loader: "json-loader", 15 | }, 16 | ]; 17 | 18 | module.exports = { 19 | mode: MINIFY ? "production" : "development", 20 | devtool: "source-map", 21 | entry: { 22 | MobxReactForm: "./src/index", 23 | MobxReactFormComposer: "./src/composer", 24 | MobxReactFormValidatorVJF: "./src/validators/VJF", 25 | MobxReactFormValidatorDVR: "./src/validators/DVR", 26 | MobxReactFormValidatorSVK: "./src/validators/SVK", 27 | MobxReactFormValidatorYUP: "./src/validators/YUP", 28 | MobxReactFormValidatorJOI: "./src/validators/JOI", 29 | MobxReactFormValidatorZOD: "./src/validators/ZOD", 30 | }, 31 | output: { 32 | path: join(__dirname, "umd"), 33 | filename: MINIFY ? "[name].umd.min.js" : "[name].umd.js", 34 | library: "[name]", 35 | libraryTarget: "umd", 36 | }, 37 | resolve: { 38 | modules: ["node_modules"], 39 | extensions: [".ts", ".json"], 40 | }, 41 | externals: { 42 | mobx: "mobx", 43 | lodash: "_", 44 | }, 45 | module: { rules }, 46 | plugins: [new LodashModuleReplacementPlugin()], 47 | }; 48 | --------------------------------------------------------------------------------