diff --git a/extensions/resource-deployment/src/test/ui/validation/validations.test.ts b/extensions/resource-deployment/src/test/ui/validation/validations.test.ts index c6511f5d04..42e8e76bab 100644 --- a/extensions/resource-deployment/src/test/ui/validation/validations.test.ts +++ b/extensions/resource-deployment/src/test/ui/validation/validations.test.ts @@ -12,10 +12,9 @@ import { InputValueType } from '../../../ui/modelViewUtils'; import { createValidation, GreaterThanOrEqualsValidation, IntegerValidation, LessThanOrEqualsValidation, RegexValidation, validateInputBoxComponent, Validation, ValidationType } from '../../../ui/validation/validations'; const inputBox = { - updateProperty(key: string, value: any) { } + validationErrorMessage: '' }; -let inputBoxStub: sinon.SinonStub; -const validationMessage = 'The field value is not valid'; + const testValidations = [ { type: ValidationType.IsInteger, @@ -42,7 +41,6 @@ let onValidityChangedEmitter: vscode.EventEmitter; describe('Validation', () => { beforeEach('validation setup', () => { sinon.restore(); //cleanup all previously defined sinon mocks - inputBoxStub = sinon.stub(inputBox, 'updateProperty').resolves(); onValidityChangedEmitter = new vscode.EventEmitter(); // recreate for every test so that any previous subscriptions on the event are cleared out. }); describe('createValidation and validate input Box', () => { @@ -50,7 +48,6 @@ describe('Validation', () => { it(`validationType: ${testObj.type}`, async () => { const validation = createValidation( testObj, - async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage), async () => undefined, async (_varName: string) => undefined, (_variableName) => onValidityChangedEmitter.event, @@ -62,9 +59,7 @@ describe('Validation', () => { case ValidationType.LessThanOrEqualsTo: should(validation).be.instanceOf(LessThanOrEqualsValidation); break; case ValidationType.GreaterThanOrEqualsTo: should(validation).be.instanceOf(GreaterThanOrEqualsValidation); break; } - should(await validateInputBoxComponent(inputBox, [validation])).be.true(); // undefined and '' values are valid so validation should return true. This allows for fields that are not required - should(inputBoxStub.calledOnce).be.true(); - should(inputBoxStub.getCall(0).args[1]).be.undefined(); + should(await validateInputBoxComponent(inputBox, [validation])).be.true(`Call to validate should be true`); // undefined and '' values are valid so validation should return true. This allows for fields that are not required }); }); }); @@ -87,7 +82,6 @@ describe('Validation', () => { const validationDescription = `value: ${displayTestValue} was not an integer`; const validation = new IntegerValidation( { type: ValidationType.IsInteger, description: validationDescription }, - async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage), async () => testObj.value ); await testValidation(validation, testObj, validationDescription); @@ -114,7 +108,6 @@ describe('Validation', () => { const validationDescription = `value:${displayTestValue} did not match the regex:/${testRegex}/`; const validation = new RegexValidation( { type: ValidationType.IsInteger, description: validationDescription, regex: testRegex }, - async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage), async () => testOb.value ); await testValidation(validation, testOb, validationDescription); @@ -173,7 +166,6 @@ describe('Validation', () => { const validationDescription = `${displayTestValue} did not test as <= ${displayTargetValue}`; const validation = new LessThanOrEqualsValidation( { type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName }, - async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage), async () => testObj.value, async (_variableName: string) => testObj.targetValue, (_variableName) => onValidityChangedEmitter.event, @@ -228,7 +220,6 @@ describe('Validation', () => { const validationDescription = `${displayTestValue} did not test as >= ${displayTargetValue}`; const validation = new GreaterThanOrEqualsValidation( { type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName }, - async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage), async () => testObj.value, async (_variableName: string) => testObj.targetValue, (_variableName) => onValidityChangedEmitter.event, diff --git a/extensions/resource-deployment/src/ui/modelViewUtils.ts b/extensions/resource-deployment/src/ui/modelViewUtils.ts index b41a7dbf61..f843aefa37 100644 --- a/extensions/resource-deployment/src/ui/modelViewUtils.ts +++ b/extensions/resource-deployment/src/ui/modelViewUtils.ts @@ -539,13 +539,6 @@ async function processField(context: FieldContext): Promise { //populate the fieldValidations objects for each field based on the information from the fieldInfo context.fieldValidations = context.fieldInfo.validations?.map((validation => createValidation( validation, - async (isValid: boolean) => { - const inputBox = (context.inputComponents[context.fieldInfo.variableName || context.fieldInfo.label].component); - const validationMessage = (isValid) ? '' : validation.description; - if (inputBox.validationErrorMessage !== validationMessage) { // unset validationErrorMessage if it is set - await inputBox.updateProperty('validationErrorMessage', validationMessage); - } - }, () => context.inputComponents[context.fieldInfo.variableName || context.fieldInfo.label].getValue(), // callback to fetch the value of this field, and return the default value if the field value is undefined (variable: string) => context.inputComponents[variable].getValue(), // callback to fetch the value of a variable corresponding to any field already defined. (targetVariable: string) => (context.inputComponents[targetVariable].component).onValidityChanged, diff --git a/extensions/resource-deployment/src/ui/validation/validations.ts b/extensions/resource-deployment/src/ui/validation/validations.ts index a349c859c6..a0965f984f 100644 --- a/extensions/resource-deployment/src/ui/validation/validations.ts +++ b/extensions/resource-deployment/src/ui/validation/validations.ts @@ -55,9 +55,6 @@ export abstract class Validation { get description(): string { return this._description; } - protected get onValidation(): OnValidation { - return this._onValidation; - } // gets the validation result for this validation object abstract validate(): Promise; @@ -69,15 +66,15 @@ export abstract class Validation { return this._targetValueGetter!(variable); } - constructor(validation: ValidationInfo, protected _onValidation: OnValidation, protected _valueGetter: ValueGetter, protected _targetValueGetter?: TargetValueGetter, protected _onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, protected _onNewDisposableCreated?: (disposable: vscode.Disposable) => void) { + constructor(validation: ValidationInfo, protected _valueGetter: ValueGetter, protected _targetValueGetter?: TargetValueGetter, protected _onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, protected _onNewDisposableCreated?: (disposable: vscode.Disposable) => void) { this._description = validation.description; } } export class IntegerValidation extends Validation { - constructor(validation: IntegerValidationInfo, onValidation: OnValidation, valueGetter: ValueGetter) { - super(validation, onValidation, valueGetter); + constructor(validation: IntegerValidationInfo, valueGetter: ValueGetter) { + super(validation, valueGetter); } private async isIntegerOrEmptyOrUndefined(): Promise { @@ -91,7 +88,6 @@ export class IntegerValidation extends Validation { async validate(): Promise { const isValid = await this.isIntegerOrEmptyOrUndefined(); - await this.onValidation(isValid); return { valid: isValid, message: isValid ? undefined : this.description @@ -106,8 +102,8 @@ export class RegexValidation extends Validation { return this._regex; } - constructor(validation: RegexValidationInfo, validationMessageUpdater: OnValidation, valueGetter: ValueGetter) { - super(validation, validationMessageUpdater, valueGetter); + constructor(validation: RegexValidationInfo, valueGetter: ValueGetter) { + super(validation, valueGetter); throwUnless(validation.regex !== undefined); this._regex = (typeof validation.regex === 'string') ? new RegExp(validation.regex) : validation.regex; } @@ -115,7 +111,6 @@ export class RegexValidation extends Validation { async validate(): Promise { const value = (await this.getValue())?.toString(); const isValid = isUndefinedOrEmpty(value) ? true : this.regex.test(value!); - await this.onValidation(isValid); return { valid: isValid, message: isValid ? undefined : this.description @@ -136,8 +131,8 @@ export abstract class Comparison extends Validation { this._onNewDisposableCreated!(onValidityChanged(isValid => onTargetValidityChangedAction(isValid))); } - constructor(validation: ComparisonValidationInfo, onValidation: OnValidation, valueGetter: ValueGetter, targetValueGetter: TargetValueGetter, onTargetValidityChangedGetter: OnTargetValidityChangedGetter, onNewDisposableCreated: (disposable: vscode.Disposable) => void) { - super(validation, onValidation, valueGetter, targetValueGetter, onTargetValidityChangedGetter, onNewDisposableCreated); + constructor(validation: ComparisonValidationInfo, valueGetter: ValueGetter, targetValueGetter: TargetValueGetter, onTargetValidityChangedGetter: OnTargetValidityChangedGetter, onNewDisposableCreated: (disposable: vscode.Disposable) => void) { + super(validation, valueGetter, targetValueGetter, onTargetValidityChangedGetter, onNewDisposableCreated); throwUnless(validation.target !== undefined); this._target = validation.target; } @@ -157,7 +152,6 @@ export abstract class Comparison extends Validation { async validate(): Promise { this.validateOnTargetValidityChange(); const isValid = await this.isComparisonSuccessful(); - await this.onValidation(isValid); return { valid: isValid, message: isValid ? undefined : this.description @@ -181,22 +175,29 @@ export class GreaterThanOrEqualsValidation extends Comparison { } } -export function createValidation(validation: ValidationInfo, onValidation: OnValidation, valueGetter: ValueGetter, targetValueGetter?: TargetValueGetter, onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, onDisposableCreated?: (disposable: vscode.Disposable) => void): Validation { +export function createValidation(validation: ValidationInfo, valueGetter: ValueGetter, targetValueGetter?: TargetValueGetter, onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, onDisposableCreated?: (disposable: vscode.Disposable) => void): Validation { switch (validation.type) { - case ValidationType.Regex: return new RegexValidation(validation, onValidation, valueGetter); - case ValidationType.IsInteger: return new IntegerValidation(validation, onValidation, valueGetter); - case ValidationType.LessThanOrEqualsTo: return new LessThanOrEqualsValidation(validation, onValidation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!); - case ValidationType.GreaterThanOrEqualsTo: return new GreaterThanOrEqualsValidation(validation, onValidation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!); + case ValidationType.Regex: return new RegexValidation(validation, valueGetter); + case ValidationType.IsInteger: return new IntegerValidation(validation, valueGetter); + case ValidationType.LessThanOrEqualsTo: return new LessThanOrEqualsValidation(validation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!); + case ValidationType.GreaterThanOrEqualsTo: return new GreaterThanOrEqualsValidation(validation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!); default: throw new Error(`unknown validation type:${validation.type}`); //dev error } } export async function validateInputBoxComponent(component: azdata.InputBoxComponent, validations: Validation[] = []): Promise { + let valid = true; + let message = ''; for (const validation of validations) { const result = await validation.validate(); if (!result.valid) { - return false; //bail out on first failure, remaining validations are processed after this one has been fixed by the user. + valid = false; + message = validation.description; + break; //bail out on first failure, remaining validations are processed after this one has been fixed by the user. } } - return true; + if ((component.validationErrorMessage ?? '') !== message) { // Update the message if needed + component.validationErrorMessage = message; + } + return valid; }