diff --git a/src/Framework/Framework/Resources/Scripts/tests/stateManagement.data.ts b/src/Framework/Framework/Resources/Scripts/tests/stateManagement.data.ts index 95e55e9a48..32c04813a4 100644 --- a/src/Framework/Framework/Resources/Scripts/tests/stateManagement.data.ts +++ b/src/Framework/Framework/Resources/Scripts/tests/stateManagement.data.ts @@ -115,6 +115,16 @@ initDotvvm({ "C": 4, "D": 8, } + }, + tValidated: { + type: "object", + properties: { + RegexValidated: { + validationRules: [ + { ruleName: "regularExpression", errorMessage: "Must have even length", parameters: ["^(..)+$"] } + ] + } + } } } }) diff --git a/src/Framework/Framework/Resources/Scripts/tests/validation.test.ts b/src/Framework/Framework/Resources/Scripts/tests/validation.test.ts index 1e11d688d4..f0226ba970 100644 --- a/src/Framework/Framework/Resources/Scripts/tests/validation.test.ts +++ b/src/Framework/Framework/Resources/Scripts/tests/validation.test.ts @@ -3,6 +3,12 @@ import { globalValidationObject as validation, ValidationErrorDescriptor } from import { createComplexObservableSubViewmodel, createComplexObservableViewmodel, ObservableHierarchy, ObservableSubHierarchy } from "./observableHierarchies" import { getErrors } from "../validation/error" import { setLogger } from "../utils/logging"; +import { runClientSideValidation } from '../validation/validation' +import dotvvm from '../dotvvm-root' +import { getStateManager } from "../dotvvm-base"; +import { StateManager } from "../state-manager"; + +require("./stateManagement.data") describe("DotVVM.Validation - public API", () => { @@ -319,6 +325,21 @@ describe("DotVVM.Validation - public API", () => { }) }); + +describe("DotVVM.Validation - view model validation", () => { + const s = getStateManager() as StateManager + test("Validated object in dynamic", () => { + dotvvm.updateState(x => ({...x, Dynamic: { something: "abc", validatedObj: { $type: "tValidated", RegexValidated: "abcd" } } })) + s.doUpdateNow() + runClientSideValidation(s.stateObservable, {} as any) + expect(dotvvm.validation.errors).toHaveLength(0) + s.patchState({ Dynamic: { validatedObj: { RegexValidated: "abcde" }}}) + s.doUpdateNow() + runClientSideValidation(s.stateObservable, {} as any) + expect(dotvvm.validation.errors).toHaveLength(1) + }) +}) + function SetupComplexObservableViewmodelWithErrorsOnProp1AndProp21() { validation.removeErrors("/"); const vm = createComplexObservableViewmodel(); diff --git a/src/Framework/Framework/Resources/Scripts/validation/validation.ts b/src/Framework/Framework/Resources/Scripts/validation/validation.ts index 6c62f2b541..64d707d909 100644 --- a/src/Framework/Framework/Resources/Scripts/validation/validation.ts +++ b/src/Framework/Framework/Resources/Scripts/validation/validation.ts @@ -66,7 +66,7 @@ const createValidationHandler = (pathFunction: (context: KnockoutBindingContext) } }) -const runClientSideValidation = (validationTarget: any, options: PostbackOptions) => { +export const runClientSideValidation = (validationTarget: any, options: PostbackOptions) => { watchAndTriggerValidationErrorChanged(options, () => { @@ -171,6 +171,9 @@ function validateViewModel(viewModel: any, path: string): void { } function validateRecursive(observable: KnockoutObservable, propertyValue: any, type: TypeDefinition, propertyPath: string) { + if (compileConstants.debug && !ko.isObservable(observable)) { + throw Error(`Property ${propertyPath} isn't a knockout observable and cannot be validated.`) + } const lastSetError: CoerceResult = (observable as any)[lastSetErrorSymbol]; if (lastSetError && lastSetError.isError) { ValidationError.attach(lastSetError.message, propertyPath, observable); @@ -203,7 +206,7 @@ function validateRecursive(observable: KnockoutObservable, propertyValue: a validateViewModel(propertyValue, propertyPath); } else { for (const k of keys(propertyValue)) { - validateRecursive(ko.unwrap(propertyValue[k]), propertyValue[k], { type: "dynamic" }, propertyPath + "/" + k); + validateRecursive(propertyValue[k], ko.unwrap(propertyValue[k]), { type: "dynamic" }, propertyPath + "/" + k); } } }