mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-40028 Added improvement to ignore field validation if it is inside a group/section that is hidden (#11362)
* [AAE-40028] Added improvement to ignore field validation if it is inside a group/section that is hidden
This commit is contained in:
@@ -43,6 +43,7 @@ Contains the value and metadata for a field of a [`Form`](../../../lib/process-s
|
||||
| displayText | string | | Displayed text for Hyperlink widgets |
|
||||
| isVisible | boolean | true | Is the field shown on the form? |
|
||||
| visibilityCondition | [`WidgetVisibilityModel`](../../../lib/core/src/lib/form/models/widget-visibility.model.ts) | null | Defines a expression that determines whether the field is visible or not, based on its logical relation to values in other fields |
|
||||
| checkParentVisibilityForValidation | boolean | false | When enabled (and `FormModel.enableParentVisibilityCheck` is true), validation will be skipped if the field's parent container/group/section is hidden, even if the field itself is visible. |
|
||||
| enableFractions | boolean | false | Are numeric values allowed to contain a decimal point? |
|
||||
| currency | string | null | Currency symbol for Amount widgets |
|
||||
| dateDisplayFormat | string | | Date/time display format template |
|
||||
|
||||
@@ -41,6 +41,7 @@ Contains the value and metadata for a form.
|
||||
|isValid | true||is form valid|
|
||||
|processVariables| ProcessVariableModel[] | []|process variables|
|
||||
|variables| FormVariableModel[] | []|variables|
|
||||
|enableParentVisibilityCheck| boolean | false|Enables checking parent container/group/section visibility for validation. When enabled, fields with `checkParentVisibilityForValidation: true` will skip validation if their parent is hidden.|
|
||||
|
||||
## Methods
|
||||
|
||||
@@ -112,3 +113,5 @@ Contains the value and metadata for a form.
|
||||
Changes variable value
|
||||
- `loadInjectedFieldValidators(injectedFieldValidators: FormFieldValidator[]): void`
|
||||
Checks it there are any injectedValidators and adds them to the array of field validators.
|
||||
- `isFieldOrParentHidden(field: FormFieldModel): boolean`
|
||||
Checks if a field or any of its parent containers/groups/sections is hidden. Returns true if the field should skip validation. Only checks parent visibility if `enableParentVisibilityCheck` is true and the field has `checkParentVisibilityForValidation` set to true.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContainerModel } from './container.model';
|
||||
import { ErrorMessageModel } from './error-message.model';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import {
|
||||
@@ -201,6 +202,143 @@ describe('FormFieldValidator', () => {
|
||||
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should skip validation for hidden field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
required: true,
|
||||
value: null
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = null;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden group when opt-in is disabled - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = null;
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden section when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'section1',
|
||||
type: FormFieldTypes.SECTION,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const container = form.fields[0] as ContainerModel;
|
||||
container.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = null;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden section when opt-in is disabled - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'section1',
|
||||
type: FormFieldTypes.SECTION,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const container = form.fields[0] as ContainerModel;
|
||||
container.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = null;
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation when opt-in property defaults to false - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = null;
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation for visible field with invalid value', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
required: true,
|
||||
value: null
|
||||
});
|
||||
field.isVisible = true;
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NumberFieldValidator', () => {
|
||||
@@ -264,6 +402,99 @@ describe('FormFieldValidator', () => {
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should skip validation for hidden field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: 'not-a-number'
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.NUMBER, checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'not-a-number';
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden group when opt-in is disabled - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.NUMBER, checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'not-a-number';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation when opt-in property defaults to false - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.NUMBER }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'not-a-number';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation for visible field with invalid value', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: 'not-a-number'
|
||||
});
|
||||
field.isVisible = true;
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MinLengthFieldValidator', () => {
|
||||
@@ -315,6 +546,101 @@ describe('FormFieldValidator', () => {
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should skip validation for hidden field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: 'ab',
|
||||
minLength: 5
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, minLength: 5, checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'ab';
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden group when opt-in is disabled - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, minLength: 5, checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'ab';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation when opt-in property defaults to false - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, minLength: 5 }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'ab';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation for visible field with invalid value', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: 'ab',
|
||||
minLength: 5
|
||||
});
|
||||
field.isVisible = true;
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MaxLengthFieldValidator', () => {
|
||||
@@ -404,6 +730,101 @@ describe('FormFieldValidator', () => {
|
||||
expect(isSupported).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip validation for hidden field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: '12345',
|
||||
maxLength: 3
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, maxLength: 3, checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '12345';
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden group when opt-in is disabled (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, maxLength: 3, checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '12345';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation when opt-in property defaults to false (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, maxLength: 3 }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '12345';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation for visible field with invalid value', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: '12345',
|
||||
maxLength: 3
|
||||
});
|
||||
field.isVisible = true;
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MinValueFieldValidator', () => {
|
||||
@@ -474,6 +895,101 @@ describe('FormFieldValidator', () => {
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should skip validation for hidden field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: '5',
|
||||
minValue: '10'
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.NUMBER, minValue: '10', checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '5';
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden group when opt-in is disabled (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.NUMBER, minValue: '10', checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '5';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation when opt-in property defaults to false (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.NUMBER, minValue: '10' }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '5';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation for visible field with invalid value', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: '5',
|
||||
minValue: '10'
|
||||
});
|
||||
field.isVisible = true;
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MaxValueFieldValidator', () => {
|
||||
@@ -544,6 +1060,101 @@ describe('FormFieldValidator', () => {
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should skip validation for hidden field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: '15',
|
||||
maxValue: '10'
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.NUMBER, maxValue: '10', checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '15';
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden group when opt-in is disabled (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.NUMBER, maxValue: '10', checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '15';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation when opt-in property defaults to false (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.NUMBER, maxValue: '10' }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '15';
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation for visible field with invalid value', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: '15',
|
||||
maxValue: '10'
|
||||
});
|
||||
field.isVisible = true;
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RegExFieldValidator', () => {
|
||||
@@ -602,6 +1213,102 @@ describe('FormFieldValidator', () => {
|
||||
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should skip validation for hidden field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: 'invalid',
|
||||
regexPattern: '^valid$'
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [{ id: 'field1', type: FormFieldTypes.TEXT, regexPattern: '^valid$', checkParentVisibilityForValidation: true }]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'invalid';
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden group when opt-in is disabled (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [{ id: 'field1', type: FormFieldTypes.TEXT, regexPattern: '^valid$', checkParentVisibilityForValidation: false }]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'invalid';
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation when opt-in property defaults to false (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, regexPattern: '^valid$' }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'invalid';
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation for visible field with invalid value', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
value: 'invalid',
|
||||
regexPattern: '^valid$'
|
||||
});
|
||||
field.isVisible = true;
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FixedValueFieldValidator', () => {
|
||||
@@ -659,6 +1366,139 @@ describe('FormFieldValidator', () => {
|
||||
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should skip validation for hidden field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
value: 'Invalid',
|
||||
options: [
|
||||
{ id: '1', name: 'Option 1' },
|
||||
{ id: '2', name: 'Option 2' }
|
||||
]
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
checkParentVisibilityForValidation: true,
|
||||
options: [
|
||||
{ id: '1', name: 'Option 1' },
|
||||
{ id: '2', name: 'Option 2' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'Invalid';
|
||||
expect(validator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden group when opt-in is disabled (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
checkParentVisibilityForValidation: false,
|
||||
options: [
|
||||
{ id: '1', name: 'Option 1' },
|
||||
{ id: '2', name: 'Option 2' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'Invalid';
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation when opt-in property defaults to false (backward compatible)', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
options: [
|
||||
{ id: '1', name: 'Option 1' },
|
||||
{ id: '2', name: 'Option 2' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = 'Invalid';
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation for visible field with invalid value', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TYPEAHEAD,
|
||||
value: 'Invalid',
|
||||
options: [
|
||||
{ id: '1', name: 'Option 1' },
|
||||
{ id: '2', name: 'Option 2' }
|
||||
]
|
||||
});
|
||||
field.isVisible = true;
|
||||
expect(validator.validate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DecimalFieldValidator', () => {
|
||||
@@ -727,5 +1567,97 @@ describe('FormFieldValidator', () => {
|
||||
|
||||
expect(decimalValidator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for hidden field', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.DECIMAL,
|
||||
value: '1.22',
|
||||
precision: 1
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(decimalValidator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip validation for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.DECIMAL, precision: 1, checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
form.enableParentVisibilityCheck = true;
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '1.22';
|
||||
expect(decimalValidator.validate(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not skip validation for field in hidden group when opt-in is disabled - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.DECIMAL, precision: 1, checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '1.22';
|
||||
expect(decimalValidator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation when opt-in property defaults to false - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.DECIMAL, precision: 1 }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const form = new FormModel(formJson);
|
||||
const group = form.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = form.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
field.value = '1.22';
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(decimalValidator.validate(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not skip validation for visible field with invalid value', () => {
|
||||
const form = new FormModel();
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.DECIMAL,
|
||||
value: '1.22',
|
||||
precision: 1
|
||||
});
|
||||
field.isVisible = true;
|
||||
expect(decimalValidator.validate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,7 +51,7 @@ export class RequiredFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.isVisible) {
|
||||
if (this.isSupported(field) && field.form && !field.form.isFieldOrParentHidden(field)) {
|
||||
if (field.type === FormFieldTypes.RADIO_BUTTONS) {
|
||||
const option = field.options.find((opt) => opt.id === field.value);
|
||||
return !!option;
|
||||
@@ -89,7 +89,7 @@ export class NumberFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.isVisible) {
|
||||
if (this.isSupported(field) && field.form && !field.form.isFieldOrParentHidden(field)) {
|
||||
if (field.value === null || field.value === undefined || field.value === '') {
|
||||
return true;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ export class MinLengthFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value && field.isVisible) {
|
||||
if (this.isSupported(field) && field.value && field.form && !field.form.isFieldOrParentHidden(field)) {
|
||||
if (field.value.length >= field.minLength) {
|
||||
return true;
|
||||
}
|
||||
@@ -139,7 +139,7 @@ export class MaxLengthFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value && field.isVisible) {
|
||||
if (this.isSupported(field) && field.value && field.form && !field.form.isFieldOrParentHidden(field)) {
|
||||
if (field.value.toString().length <= this.getMaxLength(field)) {
|
||||
return true;
|
||||
}
|
||||
@@ -166,7 +166,7 @@ export class MinValueFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value && field.isVisible) {
|
||||
if (this.isSupported(field) && field.value && field.form && !field.form.isFieldOrParentHidden(field)) {
|
||||
const value: number = +field.value;
|
||||
const minValue: number = +field.minValue;
|
||||
|
||||
@@ -190,7 +190,7 @@ export class MaxValueFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value && field.isVisible) {
|
||||
if (this.isSupported(field) && field.value && field.form && !field.form.isFieldOrParentHidden(field)) {
|
||||
const value: number = +field.value;
|
||||
const maxValue: number = +field.maxValue;
|
||||
|
||||
@@ -214,7 +214,7 @@ export class RegExFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.value && field.isVisible) {
|
||||
if (this.isSupported(field) && field.value && field.form && !field.form.isFieldOrParentHidden(field)) {
|
||||
if (field.value.length > 0 && field.value.match(new RegExp('^' + field.regexPattern + '$'))) {
|
||||
return true;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ export class FixedValueFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
if (this.isSupported(field) && field.isVisible) {
|
||||
if (this.isSupported(field) && field.form && !field.form.isFieldOrParentHidden(field)) {
|
||||
if (this.hasStringValue(field) && this.hasOptions(field) && !this.hasValidNameOrValidId(field)) {
|
||||
field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_VALUE';
|
||||
return false;
|
||||
@@ -271,7 +271,7 @@ export class DecimalFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
validate(field: FormFieldModel): boolean {
|
||||
const shouldValidateField = this.isSupported(field) && field.isVisible;
|
||||
const shouldValidateField = this.isSupported(field) && field.form && !field.form.isFieldOrParentHidden(field);
|
||||
|
||||
if (!shouldValidateField) {
|
||||
return true;
|
||||
|
||||
@@ -85,6 +85,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
displayText: string;
|
||||
isVisible: boolean = true;
|
||||
visibilityCondition: WidgetVisibilityModel = null;
|
||||
checkParentVisibilityForValidation: boolean = false;
|
||||
enableFractions: boolean = false;
|
||||
currency: string = null;
|
||||
dateDisplayFormat: string = this.defaultDateFormat;
|
||||
@@ -219,6 +220,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
this.hyperlinkUrl = json.hyperlinkUrl;
|
||||
this.displayText = json.displayText;
|
||||
this.visibilityCondition = formFieldVisibilityConditionHandler.getVisibilityCondition(this.id, json.visibilityCondition, parent);
|
||||
this.checkParentVisibilityForValidation = json.checkParentVisibilityForValidation ?? false;
|
||||
this.enableFractions = json.enableFractions;
|
||||
this.currency = json.currency;
|
||||
this.dateDisplayFormat = json.dateDisplayFormat || this.getDefaultDateFormat(json);
|
||||
|
||||
@@ -807,4 +807,316 @@ describe('FormModel', () => {
|
||||
expect(allFields[3].id).toBe('text-in-section');
|
||||
});
|
||||
});
|
||||
|
||||
describe('FormModel - isFieldOrParentHidden', () => {
|
||||
let form: FormModel;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new FormModel();
|
||||
});
|
||||
|
||||
it('should return true for directly hidden field', () => {
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(form.isFieldOrParentHidden(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for visible field with no hidden parent', () => {
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
field.isVisible = true;
|
||||
expect(form.isFieldOrParentHidden(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for field in hidden group when opt-in is disabled - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const testForm = new FormModel(formJson);
|
||||
const group = testForm.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = testForm.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
expect(testForm.isFieldOrParentHidden(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for field in hidden group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const testForm = new FormModel(formJson);
|
||||
testForm.enableParentVisibilityCheck = true;
|
||||
const group = testForm.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = testForm.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
expect(testForm.isFieldOrParentHidden(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for field in hidden section when opt-in is disabled - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'section1',
|
||||
type: FormFieldTypes.SECTION,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: false }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const testForm = new FormModel(formJson);
|
||||
const container = testForm.fields[0] as ContainerModel;
|
||||
container.field.isVisible = false;
|
||||
const field = testForm.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
expect(testForm.isFieldOrParentHidden(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for field in hidden section - opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'section1',
|
||||
type: FormFieldTypes.SECTION,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const testForm = new FormModel(formJson);
|
||||
testForm.enableParentVisibilityCheck = true;
|
||||
const container = testForm.fields[0] as ContainerModel;
|
||||
container.field.isVisible = false;
|
||||
const field = testForm.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
expect(testForm.isFieldOrParentHidden(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for nested structure - section in group when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
id: 'section1',
|
||||
type: FormFieldTypes.SECTION,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: true }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testForm = new FormModel(formJson);
|
||||
testForm.enableParentVisibilityCheck = true;
|
||||
const group = testForm.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const sectionField = testForm.getFieldById('section1');
|
||||
sectionField.isVisible = true;
|
||||
const field = testForm.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
expect(testForm.isFieldOrParentHidden(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for nested structure - section in section when opt-in is enabled', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'outerSection',
|
||||
type: FormFieldTypes.SECTION,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
id: 'innerSection',
|
||||
type: FormFieldTypes.SECTION,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: true }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testForm = new FormModel(formJson);
|
||||
testForm.enableParentVisibilityCheck = true;
|
||||
const container = testForm.fields[0] as ContainerModel;
|
||||
container.field.isVisible = false;
|
||||
const innerSectionField = testForm.getFieldById('innerSection');
|
||||
innerSectionField.isVisible = true;
|
||||
const field = testForm.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
expect(testForm.isFieldOrParentHidden(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for visible field with visible parents - ContainerModel', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: {
|
||||
1: [
|
||||
{
|
||||
id: 'section1',
|
||||
type: FormFieldTypes.SECTION,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true }] }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testForm = new FormModel(formJson);
|
||||
const group = testForm.fields[0] as ContainerModel;
|
||||
group.field.isVisible = true;
|
||||
const sectionField = testForm.getFieldById('section1');
|
||||
sectionField.isVisible = true;
|
||||
const field = testForm.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
expect(testForm.isFieldOrParentHidden(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for field with no parent - root level', () => {
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
field.isVisible = true;
|
||||
form.fields = [field];
|
||||
expect(form.isFieldOrParentHidden(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for null field', () => {
|
||||
expect(form.isFieldOrParentHidden(null as any)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for undefined field', () => {
|
||||
expect(form.isFieldOrParentHidden(undefined as any)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for field with null form', () => {
|
||||
const fieldWithoutForm = new FormFieldModel(null as any, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
fieldWithoutForm.isVisible = true;
|
||||
const testForm = new FormModel();
|
||||
expect(testForm.isFieldOrParentHidden(fieldWithoutForm)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for form with empty fields array', () => {
|
||||
form.fields = [];
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
field.isVisible = true;
|
||||
expect(form.isFieldOrParentHidden(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should use ID comparison to find parent field', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true, checkParentVisibilityForValidation: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const testForm = new FormModel(formJson);
|
||||
testForm.enableParentVisibilityCheck = true;
|
||||
const group = testForm.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = testForm.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
const fieldWithSameId = new FormFieldModel(testForm, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
checkParentVisibilityForValidation: true
|
||||
});
|
||||
fieldWithSameId.isVisible = true;
|
||||
expect(testForm.isFieldOrParentHidden(fieldWithSameId)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when opt-in property defaults to false - backward compatible', () => {
|
||||
const formJson = {
|
||||
id: 'test-form',
|
||||
name: 'Test Form',
|
||||
fields: [
|
||||
{
|
||||
id: 'group1',
|
||||
type: FormFieldTypes.GROUP,
|
||||
numberOfColumns: 1,
|
||||
fields: { 1: [{ id: 'field1', type: FormFieldTypes.TEXT, required: true }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
const testForm = new FormModel(formJson);
|
||||
const group = testForm.fields[0] as ContainerModel;
|
||||
group.field.isVisible = false;
|
||||
const field = testForm.getFieldById('field1');
|
||||
field.isVisible = true;
|
||||
expect(field.checkParentVisibilityForValidation).toBe(false);
|
||||
expect(testForm.isFieldOrParentHidden(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for directly hidden field - opt-in is enabled', () => {
|
||||
const field = new FormFieldModel(form, {
|
||||
id: 'field1',
|
||||
type: FormFieldTypes.TEXT,
|
||||
checkParentVisibilityForValidation: true
|
||||
});
|
||||
field.isVisible = false;
|
||||
expect(form.isFieldOrParentHidden(field)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import { FormFieldEvent } from '../../../events/form-field.event';
|
||||
import { ValidateFormFieldEvent } from '../../../events/validate-form-field.event';
|
||||
import { ValidateFormEvent } from '../../../events/validate-form.event';
|
||||
import { ContainerModel } from './container.model';
|
||||
import { ContainerColumnModel } from './container-column.model';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { FormValues } from './form-values';
|
||||
@@ -95,6 +96,7 @@ export class FormModel implements ProcessFormModel {
|
||||
isValid = true;
|
||||
processVariables: ProcessVariableModel[] = [];
|
||||
variables: FormVariableModel[] = [];
|
||||
enableParentVisibilityCheck: boolean = false;
|
||||
|
||||
constructor(
|
||||
json?: any,
|
||||
@@ -532,4 +534,126 @@ export class FormModel implements ProcessFormModel {
|
||||
private loadInjectedFieldValidators(injectedFieldValidators: FormFieldValidator[]): void {
|
||||
this.fieldValidators = injectedFieldValidators ? [...FORM_FIELD_VALIDATORS, ...injectedFieldValidators] : [...FORM_FIELD_VALIDATORS];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a field or any of its parent containers/groups/sections is hidden.
|
||||
* Returns true if the field should skip validation (field or parent is hidden).
|
||||
*
|
||||
* Parent visibility is only checked if:
|
||||
* - `enableParentVisibilityCheck` is true
|
||||
* - `field.checkParentVisibilityForValidation` is true (field opt-in enabled)
|
||||
*
|
||||
* @param field The form field to check
|
||||
* @returns true if field or parent is hidden, false otherwise
|
||||
*/
|
||||
isFieldOrParentHidden(field: FormFieldModel): boolean {
|
||||
if (!field) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!field.isVisible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.enableParentVisibilityCheck && field.checkParentVisibilityForValidation) {
|
||||
return this.hasHiddenParent(field);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given field has a hidden parent container/group/section.
|
||||
*
|
||||
* @param targetField The form field to check
|
||||
* @returns true if field has a hidden parent, false otherwise
|
||||
*/
|
||||
private hasHiddenParent(targetField: FormFieldModel): boolean {
|
||||
if (!targetField || !this.fields || this.fields.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const rootElement of this.fields) {
|
||||
const parent = this.findParentInElement(rootElement, targetField);
|
||||
if (parent && !parent.isVisible) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively searches for a field within an element (container/group/section).
|
||||
* Returns the parent element if field is found within it, null otherwise.
|
||||
*
|
||||
* @param element The container/group/section to search in
|
||||
* @param targetField The form field to find
|
||||
* @returns Parent element if field found, null otherwise
|
||||
*/
|
||||
private findParentInElement(element: ContainerModel | FormFieldModel, targetField: FormFieldModel): ContainerModel | FormFieldModel | null {
|
||||
if (!element || !targetField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const columns = this.getColumnsFromElement(element);
|
||||
if (!columns || columns.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.searchFieldsInColumns(columns, element, targetField);
|
||||
}
|
||||
|
||||
private getColumnsFromElement(element: ContainerModel | FormFieldModel): ContainerColumnModel[] | null {
|
||||
if (element instanceof ContainerModel) {
|
||||
return element.field?.columns || null;
|
||||
} else if (element instanceof FormFieldModel && element.type === FormFieldTypes.SECTION) {
|
||||
return element.columns || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private searchFieldsInColumns(
|
||||
columns: ContainerColumnModel[],
|
||||
parentElement: ContainerModel | FormFieldModel,
|
||||
targetField: FormFieldModel
|
||||
): ContainerModel | FormFieldModel | null {
|
||||
for (const column of columns) {
|
||||
if (!column?.fields || column.fields.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = this.searchFieldsInColumn(column.fields, parentElement, targetField);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private searchFieldsInColumn(
|
||||
fields: FormFieldModel[],
|
||||
parentElement: ContainerModel | FormFieldModel,
|
||||
targetField: FormFieldModel
|
||||
): ContainerModel | FormFieldModel | null {
|
||||
for (const field of fields) {
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.id === targetField.id) {
|
||||
return parentElement;
|
||||
}
|
||||
|
||||
if (field.type === FormFieldTypes.SECTION) {
|
||||
const nestedParent = this.findParentInElement(field, targetField);
|
||||
if (nestedParent) {
|
||||
return !parentElement.isVisible ? parentElement : nestedParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user