AAE-27107 Improve repeatable section widget (#11336)

* AAE-27107 Improve logic

* AAE-27107 Add tests

* AAE-27107 Fix logic

* AAE-27107 Fix test

* AAE-27107 Fix updateForm logic
This commit is contained in:
Diogo Bastos
2025-11-11 10:09:03 +00:00
committed by GitHub
parent 54f20b7b6e
commit 0d670dd333
11 changed files with 385 additions and 199 deletions
@@ -81,8 +81,24 @@
<div
class="adf-grid-list-container"
[class.adf-grid-list-container-multiple]="hasMultipleRows"
>
<h4 class="adf-grid-list-container-label">{{ 'FORM.FORM_RENDERER.ROW_LABEL' | translate: {number: rowIndex + 1} }}</h4>
[class.adf-grid-list-container-disabled]="currentRootElement.field.readOnly"
>
<div class="adf-grid-list-row">
<span class="adf-grid-list-row-label">{{ 'FORM.FORM_RENDERER.ROW_LABEL' | translate: {number: rowIndex + 1} }}</span>
@let shouldDisplayRemoveRowButton = !currentRootElement.field.rows[rowIndex].isInitial || (currentRootElement.field.rows[rowIndex].isInitial && currentRootElement.field.params.allowInitialRowsDelete);
@if (shouldDisplayRemoveRowButton) {
<button
mat-icon-button
class="adf-grid-list-row-remove-button"
[disabled]="currentRootElement.field.readOnly"
[matTooltip]="'FORM.FORM_RENDERER.DELETE_ROW' | translate"
(click)="displayDialogToRemoveRow(currentRootElement.field, rowIndex)"
>
<mat-icon>close</mat-icon>
</button>
}
</div>
<section class="adf-grid-list-column-view">
@for (column of row.columns; track $index) {
<div
@@ -100,17 +116,6 @@
}
</div>
}
@let shouldDisplayRemoveRowButton = !currentRootElement.field.rows[rowIndex].isInitial || (currentRootElement.field.rows[rowIndex].isInitial && currentRootElement.field.params.allowInitialRowsDelete);
@if (shouldDisplayRemoveRowButton) {
<button
mat-icon-button
class="adf-grid-list-remove-row-button"
(click)="displayDialogToRemoveRow(currentRootElement.field, rowIndex)"
>
<mat-icon>close</mat-icon>
</button>
}
</section>
</div>
}
@@ -98,28 +98,48 @@
padding-right: 3px;
}
&-remove-row-button {
margin-top: 20px;
&-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
#{ms.$mat-icon} {
&-remove-button {
padding: 0;
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
justify-content: center;
#{ms.$mat-icon} {
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
}
}
}
&-container {
padding: 0 10px;
&-label {
margin: 5px 0 5px -10px;
}
padding: 0 15px;
&-multiple {
border-bottom: 1px solid rgba(0, 0, 0, 0.54);
margin-bottom: 25px;
}
&-disabled {
&.adf-grid-list-container-multiple {
border-bottom: 1px solid
var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
}
.adf-grid-list-row-label {
color: var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
cursor: default;
}
}
}
}
@@ -921,7 +921,7 @@ describe('Form Renderer Component', () => {
expect(row).toBeTruthy();
const removeRowButton = testingUtils.getByCSS(
'#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-remove-row-button'
'#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-row-remove-button'
);
expect(removeRowButton).toBeTruthy();
@@ -938,7 +938,7 @@ describe('Form Renderer Component', () => {
expect(row).toBeTruthy();
const removeRowButton = testingUtils.getByCSS(
'#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-remove-row-button'
'#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-row-remove-button'
);
expect(removeRowButton).toBeFalsy();
@@ -32,6 +32,7 @@ import { FormSectionComponent } from './form-section/form-section.component';
import { DecimalRenderMiddlewareService } from './middlewares/decimal-middleware.service';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogComponent } from '../../../lib/dialogs/confirm-dialog/confirm.dialog';
import { MatTooltipModule } from '@angular/material/tooltip';
@Component({
selector: 'adf-form-renderer',
@@ -63,7 +64,8 @@ import { ConfirmDialogComponent } from '../../../lib/dialogs/confirm-dialog/conf
NgClass,
HeaderWidgetComponent,
FormSectionComponent,
RepeatWidgetComponent
RepeatWidgetComponent,
MatTooltipModule
],
encapsulation: ViewEncapsulation.None
})
@@ -1272,61 +1272,121 @@ describe('FormFieldModel', () => {
let form: FormModel;
let field: FormFieldModel;
const fields = {
'1': [
{
id: 'Text0wwp7n',
name: 'Text',
type: 'text',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
'2': [
{
id: 'Integer0rzkwq',
name: 'Integer',
type: 'integer',
readOnly: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minValue: null,
maxValue: null,
required: false,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
]
};
const fieldsDisabled = {
'1': [
{
id: 'Text0wwp7n',
name: 'Text',
type: 'text',
readOnly: true,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
'2': [
{
id: 'Integer0rzkwq',
name: 'Integer',
type: 'integer',
readOnly: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minValue: null,
maxValue: null,
required: false,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
]
};
const json = {
id: 'RepeatableSection0tbw2y',
name: 'Repeatable Section',
type: 'repeatable-section',
tab: null,
params: {
initialNumberOfRows: 2,
allowInitialRowsDelete: true,
maxNumberOfRows: 5
},
numberOfColumns: 2,
fields
};
const jsonDisabled = {
id: 'RepeatableSection0tbw2y',
name: 'Repeatable Section',
type: 'repeatable-section',
tab: null,
params: {
initialNumberOfRows: 2,
allowInitialRowsDelete: true,
maxNumberOfRows: 5
},
numberOfColumns: 2,
fields: fieldsDisabled
};
beforeEach(() => {
form = new FormModel();
field = new FormFieldModel(form, {
id: 'RepeatableSection0tbw2y',
name: 'Repeatable Section',
type: 'repeatable-section',
tab: null,
params: {
initialNumberOfRows: 2,
allowInitialRowsDelete: true,
newRowsLimit: 3
},
numberOfColumns: 2,
fields: {
'1': [
{
id: 'Text0wwp7n',
name: 'Text',
type: 'text',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
'2': [
{
id: 'Integer0rzkwq',
name: 'Integer',
type: 'integer',
readOnly: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minValue: null,
maxValue: null,
required: false,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
]
}
});
field = new FormFieldModel(form, json);
});
describe('add row', () => {
@@ -1408,5 +1468,64 @@ describe('FormFieldModel', () => {
expect(form.values[field.id]).toEqual(formValues.removeState);
});
});
describe('disabled state', () => {
const textWidgetId = 'Text0wwp7n';
/**
*
* @param expectation expectation function
*/
function checkChildrenWidgets(expectation: any) {
for (const row of field.rows) {
for (const column of row.columns) {
for (const child of column.fields) {
expectation(child);
}
}
}
}
it('should make all children fields disabled if repeatable section is disabled', () => {
field.readOnly = true;
checkChildrenWidgets((child) => {
expect(child.readOnly).toBeTruthy();
});
});
it('should allow for initial disabled children widgets if repeatable section is enabled', () => {
field = new FormFieldModel(form, jsonDisabled);
checkChildrenWidgets((child) => {
if (child.id.startsWith(textWidgetId)) {
expect(child.readOnly).toBeTruthy();
return;
}
expect(child.readOnly).toBeFalsy();
});
});
it('should keep initial disabled children widgets if repeatable section is re-enabled', () => {
field = new FormFieldModel(form, jsonDisabled);
field.rows[0].columns[0].fields[0].readOnly = true;
field.readOnly = true;
checkChildrenWidgets((child) => {
expect(child.readOnly).toBeTruthy();
});
field.readOnly = false;
checkChildrenWidgets((child) => {
if (child.id.startsWith(textWidgetId)) {
expect(child.readOnly).toBeTruthy();
return;
}
expect(child.readOnly).toBeFalsy();
});
});
});
});
});
@@ -137,6 +137,12 @@ export class FormFieldModel extends FormWidgetModel {
set readOnly(readOnly: boolean) {
this._readOnly = readOnly;
if (this.type === FormFieldTypes.REPEATABLE_SECTION) {
this.updateRepeatableSectionReadOnlyState(readOnly);
return;
}
this.updateForm();
}
@@ -312,7 +318,7 @@ export class FormFieldModel extends FormWidgetModel {
}
const col = new ContainerColumnModel();
col.fields = (fields[currentField] || []).map((field: any) => new FormFieldModel(form, field));
col.fields = (fields[currentField] || []).map((field: any) => new FormFieldModel(form, field, this.setupParentConfig(field)));
col.rowspan = fields[currentField].length;
if (!FormFieldTypes.isSectionType(this.type)) {
@@ -324,6 +330,18 @@ export class FormFieldModel extends FormWidgetModel {
});
}
private setupParentConfig(field: any) {
if (this.parent) {
return {
...this.parent,
uid: this.getUniqueId(field, this.parent.uid.split(ROW_ID_PREFIX)[1]),
value: this.parent.value?.[field.id]
};
}
return undefined;
}
private repeatableSectionFactory(json: any, form: any): void {
const { numberOfColumns = 1, params, value, fields = {} } = json;
@@ -333,26 +351,26 @@ export class FormFieldModel extends FormWidgetModel {
this.colspan = 1;
this.rows = [];
for (let i = 0; i < this.getNumberOfRows(params.initialNumberOfRows, params.newRowsLimit, value); i++) {
for (let i = 0; i < this.getNumberOfRows(params.initialNumberOfRows, params.maxNumberOfRows, value); i++) {
this.rows.push(this.createRow(fields, form, i, value?.[i], i < params?.initialNumberOfRows));
}
this.columns = this.rows[0].columns;
}
private getNumberOfRows(initialNrRows: number = 1, rowsLimit?: number, value?: any) {
return value?.length && !!rowsLimit ? Math.min(value?.length, initialNrRows + rowsLimit) : (value?.length ?? initialNrRows);
private getNumberOfRows(initialNrRows: number = 1, maxNrRows?: number, value?: any) {
return value?.length ? (maxNrRows ? Math.min(value.length, maxNrRows) : value.length) : initialNrRows;
}
private createRow(fields: any, form: any, index: number, value?: any, isInitial: boolean = false) {
const row = new ContainerRowModel(isInitial);
row.columns.push(...this.createColumns(fields, form, index, value));
row.columns.push(...this.createColumns(fields, form, row.id, index, value));
return row;
}
private createColumns(fields: any, form: any, index?: number, value?: any) {
private createColumns(fields: any, form: any, rowId: string, index?: number, value?: any) {
const columns: ContainerColumnModel[] = [];
Object.keys(fields).forEach((currentField) => {
@@ -365,10 +383,10 @@ export class FormFieldModel extends FormWidgetModel {
(field: any) =>
new FormFieldModel(form, field, {
id: this.id,
uid: this.getUniqueId(field),
uid: this.getUniqueId(field, rowId),
fields: this.fields,
rowIndex: index ?? 0,
value: value?.[field.id]
value: field.type === FormFieldTypes.SECTION ? value : value?.[field.id]
})
);
col.rowspan = fields[currentField].length;
@@ -384,8 +402,34 @@ export class FormFieldModel extends FormWidgetModel {
return columns;
}
private getUniqueId(field: FormFieldModel): string {
return field.id + ROW_ID_PREFIX + window.crypto.getRandomValues(new Uint32Array(1))[0].toString();
private updateRepeatableSectionReadOnlyState(state: boolean) {
for (const row of this.rows) {
for (const column of row.columns) {
for (const field of column.fields) {
if (field.type === FormFieldTypes.SECTION) {
this.updateInnerSectionReadOnlyState(field, state);
}
field.readOnly = this.getRepeatableSectionFieldReadOnlyState(field, state);
}
}
}
}
private updateInnerSectionReadOnlyState(section: FormFieldModel, state: boolean) {
for (const column of section.columns) {
for (const field of column.fields) {
field.readOnly = this.getRepeatableSectionFieldReadOnlyState(field, state);
}
}
}
private getRepeatableSectionFieldReadOnlyState(field: FormFieldModel, state: boolean): boolean {
return state || field.json.readOnly;
}
private getUniqueId(field: FormFieldModel, rowId: string): string {
return field.id + ROW_ID_PREFIX + rowId;
}
private updateChildrenFieldsRowIndex() {
@@ -400,11 +444,23 @@ export class FormFieldModel extends FormWidgetModel {
private createInitialValue(fields: any) {
return Object.keys(fields)
.map((currentField) => (fields[currentField] || []).map((field) => field.id))
.flat(1)
.map((currentField) => (fields[currentField] || []).map((field) => this.getFieldId(field)))
.flat(2)
.reduce((acc, curr) => ((acc[curr] = null), acc), {});
}
private getFieldId(field: any) {
if (field.type === FormFieldTypes.SECTION) {
const fields = field.fields;
return Object.keys(fields)
.map((currentField) => (fields[currentField] || []).map((e) => e.id))
.flat(1);
}
return field.id;
}
private updateContainerColspan(fields: FormFieldModel[]): void {
fields.forEach((colField: FormFieldModel) => {
this.colspan = Math.max(this.colspan, colField.colspan);
@@ -420,7 +476,7 @@ export class FormFieldModel extends FormWidgetModel {
}
private shouldAddRow(): boolean {
return !this.params.newRowsLimit || this.rows.length < this.params.initialNumberOfRows + this.params.newRowsLimit;
return !this.params.maxNumberOfRows || this.rows.length < this.params.maxNumberOfRows;
}
removeRow(index: number) {
@@ -526,7 +582,7 @@ export class FormFieldModel extends FormWidgetModel {
}
if (this.isCheckboxField(json)) {
return json.value === 'true' || json.value === true;
return json.value === 'true' || json.value === true || initialValue === true || initialValue === 'true';
}
return value;
@@ -537,16 +593,22 @@ export class FormFieldModel extends FormWidgetModel {
return;
}
const formValue = this.getFormValue();
if (this.parent) {
this.updateRepeatableSectionValue();
return;
this.updateRepeatableSectionValue(formValue);
} else {
this.updateValue(formValue);
}
this.form.onFormFieldChanged(this);
}
getFormValue() {
switch (this.type) {
case FormFieldTypes.DROPDOWN: {
if (!this.value) {
this.form.values[this.id] = null;
break;
return null;
}
/*
@@ -554,63 +616,57 @@ export class FormFieldModel extends FormWidgetModel {
but saving back as object: { id: <id>, name: <name> }
*/
if (Array.isArray(this.value)) {
this.form.values[this.id] = this.value;
break;
return this.value;
}
if (typeof this.value === 'string') {
if (this.value === 'empty' || this.value === '') {
this.form.values[this.id] = null;
break;
return null;
}
const matchingOption: FormFieldOption = this.options.find((opt) => opt.id === this.value);
this.form.values[this.id] = matchingOption || null;
return matchingOption || null;
}
if (typeof this.value === 'object') {
if (this.value.id === 'empty' || this.value.id === '') {
this.form.values[this.id] = null;
break;
return null;
}
const matchingOption: FormFieldOption = this.options.find((opt) => opt.id === this.value.id);
this.form.values[this.id] = matchingOption;
return matchingOption;
}
break;
return null;
}
case FormFieldTypes.RADIO_BUTTONS: {
const radioButton: FormFieldOption = this.options.find((opt) => opt.id === this.value);
if (this.optionType === 'rest') {
this.form.values[this.id] = radioButton
? { ...radioButton, options: this.options }
: { id: null, name: null, options: this.options };
} else {
this.form.values[this.id] = radioButton ? { ...radioButton } : null;
return radioButton ? { ...radioButton, options: this.options } : { id: null, name: null, options: this.options };
}
break;
return radioButton ? { ...radioButton } : null;
}
case FormFieldTypes.UPLOAD: {
this.form.hasUpload = true;
if (this.value && this.value.length > 0) {
this.form.values[this.id] = Array.isArray(this.value) ? this.value.map((elem) => elem.id).join(',') : [this.value];
} else {
this.form.values[this.id] = null;
return Array.isArray(this.value) ? this.value.map((elem) => elem.id).join(',') : [this.value];
}
break;
return null;
}
case FormFieldTypes.TYPEAHEAD: {
const typeAheadEntry: FormFieldOption[] = this.options.filter((opt) => opt.id === this.value || opt.name === this.value);
if (typeAheadEntry.length > 0) {
this.form.values[this.id] = typeAheadEntry[0];
} else if (this.options.length > 0) {
this.form.values[this.id] = null;
return typeAheadEntry[0];
}
break;
return null;
}
case FormFieldTypes.DATE: {
if (typeof this.value === 'string' && this.value === 'today') {
@@ -618,6 +674,7 @@ export class FormFieldModel extends FormWidgetModel {
}
let dateValue;
try {
dateValue = DateFnsUtils.parseDate(this.value, this.dateDisplayFormat);
} catch {
@@ -626,12 +683,13 @@ export class FormFieldModel extends FormWidgetModel {
if (isValidDate(dateValue)) {
const datePart = DateFnsUtils.formatDate(dateValue, 'yyyy-MM-dd');
this.form.values[this.id] = `${datePart}T00:00:00.000Z`;
} else {
this.form.values[this.id] = null;
this._value = this.value;
return `${datePart}T00:00:00.000Z`;
}
break;
this._value = this.value;
return null;
}
case FormFieldTypes.DATETIME: {
if (typeof this.value === 'string' && this.value === 'now') {
@@ -641,39 +699,32 @@ export class FormFieldModel extends FormWidgetModel {
const dateTimeValue = this.value !== null ? DateFnsUtils.getDate(this.value) : null;
if (isValidDate(dateTimeValue)) {
this.form.values[this.id] = dateTimeValue.toISOString();
} else {
this.form.values[this.id] = null;
this._value = this.value;
return dateTimeValue.toISOString();
}
break;
this._value = this.value;
return null;
}
case FormFieldTypes.NUMBER: {
this.form.values[this.id] = this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10);
break;
return this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10);
}
case FormFieldTypes.AMOUNT: {
this.form.values[this.id] = this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10);
break;
return this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10);
}
case FormFieldTypes.DECIMAL: {
this.form.values[this.id] = parseFloat(this.value);
break;
return parseFloat(this.value);
}
case FormFieldTypes.BOOLEAN: {
this.form.values[this.id] = this.value !== null && this.value !== undefined ? this.value : false;
break;
return this.value !== null && this.value !== undefined ? this.value : false;
}
case FormFieldTypes.PEOPLE: {
this.form.values[this.id] = this.value ? this.value : null;
break;
return this.value ? this.value : null;
}
case FormFieldTypes.FUNCTIONAL_GROUP: {
this.form.values[this.id] = this.value ? this.value : null;
break;
return this.value ? this.value : null;
}
case FormFieldTypes.REPEATABLE_SECTION: {
this.form.values[this.id] = this.value ? this.value : [];
this.repeatableSectionFactory(
{
...this.json,
@@ -681,18 +732,31 @@ export class FormFieldModel extends FormWidgetModel {
},
this.form
);
break;
return this.value ? this.value : this.form.values[this.id];
}
default:
if (this.shouldUpdateFormValues(this.type)) {
this.form.values[this.id] = this.value;
return this.value;
}
}
this.form.onFormFieldChanged(this);
return undefined;
}
}
private updateRepeatableSectionValue() {
private updateValue(value: any) {
if (value === undefined) {
return;
}
this.form.values[this.id] = value;
}
private updateRepeatableSectionValue(value: string) {
if (this.type === FormFieldTypes.SECTION) {
return;
}
if (!this.form.values[this.parent.id]) {
this.form.values[this.parent.id] = [];
}
@@ -701,9 +765,7 @@ export class FormFieldModel extends FormWidgetModel {
this.form.values[this.parent.id][this.parent.rowIndex] = this.createInitialValue(this.parent.fields);
}
this.form.values[this.parent.id][this.parent.rowIndex][this.id.split(ROW_ID_PREFIX)[0]] = this.value;
this.form.onFormFieldChanged(this);
this.form.values[this.parent.id][this.parent.rowIndex][this.id.split(ROW_ID_PREFIX)[0]] = value;
}
/**
@@ -1,4 +1,4 @@
<div [style]="element | adfFieldStyle" class="adf-container-widget-repeat">
<div [style]="element | adfFieldStyle" class="adf-container-widget-repeat" [class.adf-readonly]="element.field.readOnly">
<h4
id="container-repeat"
class="adf-container-widget-repeat__text"
@@ -11,19 +11,19 @@
<ng-content />
@if (!isEditor) {
@let shouldDisplayAddRowButton = !element.field.params.newRowsLimit || (element.field.params.newRowsLimit > getAddedRowsCount());
@let shouldDisplayAddRowButton = !element.field.params.maxNumberOfRows || (element.field.params.maxNumberOfRows > element.field.rows.length);
@if (shouldDisplayAddRowButton) {
<button
mat-button
color="primary"
class="adf-container-widget-row-action"
[disabled]="element.field.readOnly"
(click)="addRow()"
>
<mat-icon>add</mat-icon> {{ 'FORM.FIELD.REPEATABLE_SECTION.ADD_ROW' | translate }}
</button>
} @else {
@let rowLimit = element.field.params.initialNumberOfRows + (element.field.params.newRowsLimit ?? 0);
<span class="adf-container-widget-row-action adf-container-widget-row-limit">{{ 'FORM.FIELD.REPEATABLE_SECTION.ROW_LIMIT_REACHED' | translate: { limit: rowLimit } }}</span>
<span class="adf-container-widget-row-action adf-container-widget-row-limit">{{ 'FORM.FIELD.REPEATABLE_SECTION.ROW_LIMIT_REACHED' | translate: { limit: element.field.params.maxNumberOfRows } }}</span>
}
}
</div>
@@ -8,10 +8,6 @@
font-weight: var(--adf-header-font-weight);
color: var(--adf-header-color);
line-height: normal;
&.adf-collapsible {
cursor: pointer;
}
}
&-row-action {
@@ -23,3 +19,18 @@
font-size: 12px;
}
}
.adf-readonly {
.adf-container-widget {
&-repeat__text {
color: var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
border-bottom: 1px solid var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
cursor: default;
}
&-row-limit {
color: var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
font-size: 12px;
}
}
}
@@ -30,11 +30,11 @@ describe('RepeatWidgetComponent', () => {
/**
*
* @param initialNumberOfRows initial number of rows
* @param newRowsLimit limit for additional rows
* @param maxNumberOfRows maximum number of rows
* @param allowInitialRowsDelete should allow deleting rows
* @returns repeatable section json based on params
*/
function getFormFieldJson(initialNumberOfRows: number = 2, newRowsLimit?: number, allowInitialRowsDelete: boolean = true) {
function getFormFieldJson(initialNumberOfRows: number = 2, maxNumberOfRows?: number, allowInitialRowsDelete: boolean = true) {
return {
id: 'RepeatableSection0tbw2y',
name: 'Repeatable Section',
@@ -43,7 +43,7 @@ describe('RepeatWidgetComponent', () => {
params: {
initialNumberOfRows,
allowInitialRowsDelete,
newRowsLimit
maxNumberOfRows
},
numberOfColumns: 2,
fields: {
@@ -126,7 +126,7 @@ describe('RepeatWidgetComponent', () => {
});
it('should display add row button if limit is defined but not reached', () => {
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1)));
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 3)));
fixture.detectChanges();
@@ -135,8 +135,7 @@ describe('RepeatWidgetComponent', () => {
});
it('should NOT display add row button if limit is defined and reached', () => {
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1)));
spyOn(component, 'getAddedRowsCount').and.returnValue(1);
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 2)));
fixture.detectChanges();
@@ -145,8 +144,7 @@ describe('RepeatWidgetComponent', () => {
});
it('should display row limit if limit has been reached', () => {
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1)));
spyOn(component, 'getAddedRowsCount').and.returnValue(1);
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 2)));
fixture.detectChanges();
@@ -165,31 +163,5 @@ describe('RepeatWidgetComponent', () => {
testingUtils.clickByCSS('button.adf-container-widget-row-action');
expect(component.addRow).toHaveBeenCalled();
});
describe('getAddedRowsCount', () => {
it('should get correct rows count if initial rows are allowed to be deleted', () => {
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1)));
fixture.detectChanges();
expect(component.getAddedRowsCount()).toBe(0);
component.addRow();
fixture.detectChanges();
expect(component.getAddedRowsCount()).toBe(1);
});
it('should get correct rows count if initial rows are NOT allowed to be deleted', () => {
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1, false)));
expect(component.getAddedRowsCount()).toBe(0);
component.addRow();
expect(component.getAddedRowsCount()).toBe(1);
});
});
});
});
@@ -36,10 +36,4 @@ export class RepeatWidgetComponent {
addRow() {
this.element.field.addRow(this.element.json.fields, this.element.form);
}
getAddedRowsCount(): number {
return this.element.json.params.allowInitialRowsDelete
? this.element.field.rows.length - this.element.json.params.initialNumberOfRows
: this.element.field.rows.filter((row) => !row.isInitial).length;
}
}
+1
View File
@@ -78,6 +78,7 @@
"FORM_RENDERER": {
"NAMELESS_TASK": "Nameless task",
"ROW_LABEL": "Row {{ number }}",
"DELETE_ROW": "Delete row",
"REMOVE_ROW_DIALOG": {
"TITLE": "Delete the row",
"MESSAGE": "Are you sure you want to delete this row?",