From de3457903882764b18da4298f371990b40d20510 Mon Sep 17 00:00:00 2001 From: Darren Thornton <6361057+dthornton-hyl@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:25:15 -0600 Subject: [PATCH] AAE-42156 Update text and multiline fields to show custom regex validation message. (#11670) * AAE-42156 Custom RegExp Validation message. * code review update - variable rename --- .../core/custom-validation-message.token.ts | 21 ++ .../widgets/core/form-field-validator.spec.ts | 44 ++++ .../widgets/core/form-field-validator.ts | 7 +- .../widgets/core/form-field.model.ts | 3 + .../lib/form/components/widgets/core/index.ts | 1 + .../multiline-text.widget.spec.ts | 195 ++++++++++++++++++ .../multiline-text/multiline-text.widget.ts | 26 ++- .../widgets/text/text.widget.spec.ts | 195 ++++++++++++++++++ .../components/widgets/text/text.widget.ts | 22 +- .../model/formFieldRepresentation.ts | 1 + 10 files changed, 511 insertions(+), 4 deletions(-) create mode 100644 lib/core/src/lib/form/components/widgets/core/custom-validation-message.token.ts diff --git a/lib/core/src/lib/form/components/widgets/core/custom-validation-message.token.ts b/lib/core/src/lib/form/components/widgets/core/custom-validation-message.token.ts new file mode 100644 index 0000000000..27bc953c57 --- /dev/null +++ b/lib/core/src/lib/form/components/widgets/core/custom-validation-message.token.ts @@ -0,0 +1,21 @@ +/*! + * @license + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InjectionToken } from '@angular/core'; +import { Observable } from 'rxjs'; + +export const ADF_CUSTOM_MESSAGE = new InjectionToken | boolean>('adf-custom-validation-message'); diff --git a/lib/core/src/lib/form/components/widgets/core/form-field-validator.spec.ts b/lib/core/src/lib/form/components/widgets/core/form-field-validator.spec.ts index 2b3c21a19d..ac9756de2b 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field-validator.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field-validator.spec.ts @@ -1309,6 +1309,50 @@ describe('FormFieldValidator', () => { field.isVisible = true; expect(validator.validate(field)).toBe(false); }); + + it('should use default message when ADF_CUSTOM_MESSAGE is off (enableCustomValidationMessage false on field)', () => { + const form = new FormModel(); + const field = new FormFieldModel(form, { + id: 'field1', + type: FormFieldTypes.TEXT, + value: 'invalid', + regexPattern: '^valid$', + customValidationMessage: 'My custom error' + }); + field.enableCustomValidationMessage = false; + field.isVisible = true; + expect(validator.validate(field)).toBe(false); + expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.INVALID_VALUE'); + }); + + it('should use custom message when ADF_CUSTOM_MESSAGE is on and customValidationMessage is set (set by widget on field)', () => { + const form = new FormModel(); + const field = new FormFieldModel(form, { + id: 'field1', + type: FormFieldTypes.TEXT, + value: 'invalid', + regexPattern: '^valid$', + customValidationMessage: 'My custom error' + }); + field.enableCustomValidationMessage = true; + field.isVisible = true; + expect(validator.validate(field)).toBe(false); + expect(field.validationSummary.message).toBe('My custom error'); + }); + + it('should use default message when ADF_CUSTOM_MESSAGE is on but customValidationMessage is not set', () => { + const form = new FormModel(); + const field = new FormFieldModel(form, { + id: 'field1', + type: FormFieldTypes.TEXT, + value: 'invalid', + regexPattern: '^valid$' + }); + field.enableCustomValidationMessage = true; + field.isVisible = true; + expect(validator.validate(field)).toBe(false); + expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.INVALID_VALUE'); + }); }); describe('FixedValueFieldValidator', () => { diff --git a/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts b/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts index 4fb03766cd..0aa6680960 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts @@ -218,7 +218,12 @@ export class RegExFieldValidator implements FormFieldValidator { if (field.value.length > 0 && field.value.match(new RegExp('^' + field.regexPattern + '$'))) { return true; } - field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_VALUE'; + + if (field.enableCustomValidationMessage === true && !!field.customValidationMessage) { + field.validationSummary.message = field.customValidationMessage; + } else { + field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_VALUE'; + } return false; } return true; diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts index 7a6f9e8927..df27122eb9 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts @@ -71,6 +71,8 @@ export class FormFieldModel extends FormWidgetModel { precision: number; dynamicDateRangeSelection: boolean; regexPattern: string; + customValidationMessage?: string; + enableCustomValidationMessage?: boolean; options: FormFieldOption[] = []; restUrl: string; roles: string[]; @@ -211,6 +213,7 @@ export class FormFieldModel extends FormWidgetModel { this.maxDateRangeValue = json.maxDateRangeValue; this.dynamicDateRangeSelection = json.dynamicDateRangeSelection; this.regexPattern = json.regexPattern; + this.customValidationMessage = json.customValidationMessage; this.options = this.parseOptions(json.options, json.optionType); this.emptyOption = this.getEmptyOption(this.options); this.hasEmptyValue = json?.hasEmptyValue ?? !!this.emptyOption; diff --git a/lib/core/src/lib/form/components/widgets/core/index.ts b/lib/core/src/lib/form/components/widgets/core/index.ts index e36dec85cc..31297dc914 100644 --- a/lib/core/src/lib/form/components/widgets/core/index.ts +++ b/lib/core/src/lib/form/components/widgets/core/index.ts @@ -32,6 +32,7 @@ export * from './tab.model'; export * from './form-outcome.model'; export * from './form-outcome-event.model'; export * from './form-field-validator'; +export * from './custom-validation-message.token'; export * from './content-link.model'; export * from './error-message.model'; export * from './external-content'; diff --git a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts index e0b9a66e7c..abb846e264 100644 --- a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts @@ -23,6 +23,8 @@ import { FormFieldTypes } from '../core/form-field-types'; import { MultilineTextWidgetComponentComponent } from './multiline-text.widget'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UnitTestingUtils } from '../../../../testing/unit-testing-utils'; +import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; +import { of, Subject } from 'rxjs'; describe('MultilineTextWidgetComponentComponent', () => { let loader: HarnessLoader; @@ -104,3 +106,196 @@ describe('MultilineTextWidgetComponentComponent', () => { }); }); }); + +describe('MultilineTextWidgetComponentComponent - ADF_CUSTOM_MESSAGE', () => { + let widget: MultilineTextWidgetComponentComponent; + let fixture: ComponentFixture; + let loader: HarnessLoader; + let testingUtils: UnitTestingUtils; + + describe('when provided as plain boolean', () => { + describe('set to true', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MultilineTextWidgetComponentComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: true }] + }); + fixture = TestBed.createComponent(MultilineTextWidgetComponentComponent); + widget = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + }); + + it('should set enableCustomValidationMessage to true on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'multiline-id', + name: 'multiline-name', + type: FormFieldTypes.MULTILINE_TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeTrue(); + }); + + it('should display custom validation message when regex validation fails', async () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'multiline-id', + name: 'multiline-name', + value: '', + type: FormFieldTypes.MULTILINE_TEXT, + readOnly: false, + regexPattern: '^[0-9]+$', + customValidationMessage: 'Only numbers are allowed' + }); + fixture.detectChanges(); + + await testingUtils.fillMatInput('invalid text'); + expect(widget.field.isValid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('Only numbers are allowed'); + }); + }); + + describe('set to false', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MultilineTextWidgetComponentComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: false }] + }); + fixture = TestBed.createComponent(MultilineTextWidgetComponentComponent); + widget = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + }); + + it('should set enableCustomValidationMessage to false on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'multiline-id', + name: 'multiline-name', + type: FormFieldTypes.MULTILINE_TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeFalse(); + }); + + it('should display default validation message when regex validation fails even if customValidationMessage is set', async () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'multiline-id', + name: 'multiline-name', + value: '', + type: FormFieldTypes.MULTILINE_TEXT, + readOnly: false, + regexPattern: '^[0-9]+$', + customValidationMessage: 'Only numbers are allowed' + }); + fixture.detectChanges(); + + await testingUtils.fillMatInput('invalid text'); + expect(widget.field.isValid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.INVALID_VALUE'); + }); + }); + }); + + describe('when provided as observable', () => { + describe('emitting true', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MultilineTextWidgetComponentComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: of(true) }] + }); + fixture = TestBed.createComponent(MultilineTextWidgetComponentComponent); + widget = fixture.componentInstance; + }); + + it('should set enableCustomValidationMessage to true on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'multiline-id', + name: 'multiline-name', + type: FormFieldTypes.MULTILINE_TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeTrue(); + }); + }); + + describe('emitting false', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MultilineTextWidgetComponentComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: of(false) }] + }); + fixture = TestBed.createComponent(MultilineTextWidgetComponentComponent); + widget = fixture.componentInstance; + }); + + it('should set enableCustomValidationMessage to false on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'multiline-id', + name: 'multiline-name', + type: FormFieldTypes.MULTILINE_TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeFalse(); + }); + }); + + describe('when field is not set', () => { + it('should not throw when observable emits after field is cleared', () => { + const subject = new Subject(); + TestBed.configureTestingModule({ + imports: [MultilineTextWidgetComponentComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: subject }] + }); + fixture = TestBed.createComponent(MultilineTextWidgetComponentComponent); + widget = fixture.componentInstance; + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'multiline-id', + name: 'multiline-name', + type: FormFieldTypes.MULTILINE_TEXT + }); + fixture.detectChanges(); + + widget.field = undefined as any; + expect(() => subject.next(true)).not.toThrow(); + }); + }); + }); + + describe('when not provided', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MultilineTextWidgetComponentComponent] + }); + fixture = TestBed.createComponent(MultilineTextWidgetComponentComponent); + widget = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + }); + + it('should default enableCustomValidationMessage to false on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'multiline-id', + name: 'multiline-name', + type: FormFieldTypes.MULTILINE_TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeFalse(); + }); + + it('should display default validation message when regex validation fails', async () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'multiline-id', + name: 'multiline-name', + value: '', + type: FormFieldTypes.MULTILINE_TEXT, + readOnly: false, + regexPattern: '^[0-9]+$', + customValidationMessage: 'Only numbers are allowed' + }); + fixture.detectChanges(); + + await testingUtils.fillMatInput('invalid text'); + expect(widget.field.isValid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.INVALID_VALUE'); + }); + }); +}); diff --git a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts index 11334b1af1..541706b69a 100644 --- a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts +++ b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts @@ -18,11 +18,14 @@ /* eslint-disable @angular-eslint/component-selector */ import { NgIf } from '@angular/common'; -import { Component, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { TranslatePipe } from '@ngx-translate/core'; +import { isObservable } from 'rxjs'; +import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; import { ErrorWidgetComponent } from '../error/error.component'; import { WidgetComponent } from '../widget.component'; @@ -44,4 +47,23 @@ import { WidgetComponent } from '../widget.component'; imports: [MatFormFieldModule, NgIf, TranslatePipe, MatInputModule, FormsModule, ErrorWidgetComponent], encapsulation: ViewEncapsulation.None }) -export class MultilineTextWidgetComponentComponent extends WidgetComponent {} +export class MultilineTextWidgetComponentComponent extends WidgetComponent implements OnInit { + private readonly destroyRef = inject(DestroyRef); + private readonly enableCustomMessage = inject(ADF_CUSTOM_MESSAGE, { optional: true }); + + ngOnInit(): void { + if (this.enableCustomMessage != null) { + if (isObservable(this.enableCustomMessage)) { + this.enableCustomMessage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((enabled: boolean) => { + if (this.field) { + this.field.enableCustomValidationMessage = enabled ?? false; + } + }); + } else { + this.field.enableCustomValidationMessage = this.enableCustomMessage; + } + } else { + this.field.enableCustomValidationMessage = false; + } + } +} diff --git a/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts b/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts index 0da9a0f4aa..341b6d8d15 100644 --- a/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts @@ -24,6 +24,8 @@ import { TextWidgetComponent } from './text.widget'; import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { UnitTestingUtils } from '../../../../testing/unit-testing-utils'; +import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; +import { of, Subject } from 'rxjs'; describe('TextWidgetComponent', () => { const form = new FormModel({ taskId: 'fake-task-id' }); @@ -504,3 +506,196 @@ describe('TextWidgetComponent', () => { }); }); }); + +describe('TextWidgetComponent - ADF_CUSTOM_MESSAGE', () => { + let widget: TextWidgetComponent; + let fixture: ComponentFixture; + let loader: HarnessLoader; + let testingUtils: UnitTestingUtils; + + describe('when provided as plain boolean', () => { + describe('set to true', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TextWidgetComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: true }] + }); + fixture = TestBed.createComponent(TextWidgetComponent); + widget = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + }); + + it('should set enableCustomValidationMessage to true on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'text-id', + name: 'text-name', + type: FormFieldTypes.TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeTrue(); + }); + + it('should display custom validation message when regex validation fails', async () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'text-id', + name: 'text-name', + value: '', + type: FormFieldTypes.TEXT, + readOnly: false, + regexPattern: '^[0-9]+$', + customValidationMessage: 'Only numbers are allowed' + }); + fixture.detectChanges(); + + await testingUtils.fillMatInput('invalid text'); + expect(widget.field.isValid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('Only numbers are allowed'); + }); + }); + + describe('set to false', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TextWidgetComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: false }] + }); + fixture = TestBed.createComponent(TextWidgetComponent); + widget = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + }); + + it('should set enableCustomValidationMessage to false on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'text-id', + name: 'text-name', + type: FormFieldTypes.TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeFalse(); + }); + + it('should display default validation message when regex validation fails even if customValidationMessage is set', async () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'text-id', + name: 'text-name', + value: '', + type: FormFieldTypes.TEXT, + readOnly: false, + regexPattern: '^[0-9]+$', + customValidationMessage: 'Only numbers are allowed' + }); + fixture.detectChanges(); + + await testingUtils.fillMatInput('invalid text'); + expect(widget.field.isValid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.INVALID_VALUE'); + }); + }); + }); + + describe('when provided as observable', () => { + describe('emitting true', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TextWidgetComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: of(true) }] + }); + fixture = TestBed.createComponent(TextWidgetComponent); + widget = fixture.componentInstance; + }); + + it('should set enableCustomValidationMessage to true on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'text-id', + name: 'text-name', + type: FormFieldTypes.TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeTrue(); + }); + }); + + describe('emitting false', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TextWidgetComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: of(false) }] + }); + fixture = TestBed.createComponent(TextWidgetComponent); + widget = fixture.componentInstance; + }); + + it('should set enableCustomValidationMessage to false on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'text-id', + name: 'text-name', + type: FormFieldTypes.TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeFalse(); + }); + }); + + describe('when field is not set', () => { + it('should not throw when observable emits after field is cleared', () => { + const subject = new Subject(); + TestBed.configureTestingModule({ + imports: [TextWidgetComponent], + providers: [{ provide: ADF_CUSTOM_MESSAGE, useValue: subject }] + }); + fixture = TestBed.createComponent(TextWidgetComponent); + widget = fixture.componentInstance; + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'text-id', + name: 'text-name', + type: FormFieldTypes.TEXT + }); + fixture.detectChanges(); + + widget.field = undefined as any; + expect(() => subject.next(true)).not.toThrow(); + }); + }); + }); + + describe('when not provided', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TextWidgetComponent] + }); + fixture = TestBed.createComponent(TextWidgetComponent); + widget = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + }); + + it('should default enableCustomValidationMessage to false on the field', () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'text-id', + name: 'text-name', + type: FormFieldTypes.TEXT + }); + fixture.detectChanges(); + expect(widget.field.enableCustomValidationMessage).toBeFalse(); + }); + + it('should display default validation message when regex validation fails', async () => { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { + id: 'text-id', + name: 'text-name', + value: '', + type: FormFieldTypes.TEXT, + readOnly: false, + regexPattern: '^[0-9]+$', + customValidationMessage: 'Only numbers are allowed' + }); + fixture.detectChanges(); + + await testingUtils.fillMatInput('invalid text'); + expect(widget.field.isValid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.INVALID_VALUE'); + }); + }); +}); diff --git a/lib/core/src/lib/form/components/widgets/text/text.widget.ts b/lib/core/src/lib/form/components/widgets/text/text.widget.ts index cf1dbe1859..ef6ddbb515 100644 --- a/lib/core/src/lib/form/components/widgets/text/text.widget.ts +++ b/lib/core/src/lib/form/components/widgets/text/text.widget.ts @@ -18,11 +18,14 @@ /* eslint-disable @angular-eslint/component-selector */ import { NgIf, NgTemplateOutlet } from '@angular/common'; -import { Component, Directive, inject, InjectionToken, Input, OnInit, TemplateRef, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, Directive, inject, InjectionToken, Input, OnInit, TemplateRef, ViewEncapsulation } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { TranslatePipe } from '@ngx-translate/core'; +import { isObservable } from 'rxjs'; +import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; import { ErrorWidgetComponent } from '../error/error.component'; import { WidgetComponent } from '../widget.component'; import { InputMaskDirective } from './text-mask.component'; @@ -69,7 +72,24 @@ export class TextWidgetComponent extends WidgetComponent implements OnInit { isMaskReversed: boolean; fieldStatusTemplate = inject(FIELD_STATUS_TEMPLATE, { optional: true }); + private readonly destroyRef = inject(DestroyRef); + private readonly enableCustomMessage = inject(ADF_CUSTOM_MESSAGE, { optional: true }); + ngOnInit() { + if (this.enableCustomMessage != null) { + if (isObservable(this.enableCustomMessage)) { + this.enableCustomMessage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((enabled: boolean) => { + if (this.field) { + this.field.enableCustomValidationMessage = enabled ?? false; + } + }); + } else { + this.field.enableCustomValidationMessage = this.enableCustomMessage; + } + } else { + this.field.enableCustomValidationMessage = false; + } + if (this.field.params) { this.mask = this.field.params['inputMask']; this.placeholder = diff --git a/lib/js-api/src/api/activiti-rest-api/model/formFieldRepresentation.ts b/lib/js-api/src/api/activiti-rest-api/model/formFieldRepresentation.ts index 5f88ce30fc..609968c71b 100644 --- a/lib/js-api/src/api/activiti-rest-api/model/formFieldRepresentation.ts +++ b/lib/js-api/src/api/activiti-rest-api/model/formFieldRepresentation.ts @@ -42,6 +42,7 @@ export interface FormFieldRepresentation { placeholder?: string; readOnly?: boolean; regexPattern?: string; + customValidationMessage?: string; required?: boolean; restIdProperty?: string; restLabelProperty?: string;