diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts index 25577ab99f..10f03ace75 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts @@ -16,6 +16,8 @@ */ import { DateFnsUtils } from '../../../../common'; +import { FormRulesEvent } from '../../../events/form-rules.event'; +import { firstValueFrom, map, Subject, take, timeout } from 'rxjs'; import { FormFieldTypes } from './form-field-types'; import { RequiredFieldValidator } from './form-field-validator'; import { FormFieldModel } from './form-field.model'; @@ -1541,6 +1543,17 @@ describe('FormFieldModel', () => { }); describe('add row', () => { + const assignFormRulesEventSubject = (): Subject => { + const formRulesEvent = new Subject(); + (field.form as any).formService = { + formRulesEvent, + validateForm: new Subject(), + validateFormField: new Subject(), + formFieldValueChanged: new Subject() + }; + return formRulesEvent; + }; + it('should add row if allowed by limit param', () => { expect(field.rows.length).toBe(2); @@ -1568,18 +1581,56 @@ describe('FormFieldModel', () => { expect(field.rows.length).toBe(5); }); - it('should call onRepeatableSectionChanged', () => { - spyOn(field.form, 'onRepeatableSectionChanged').and.callThrough(); + it('should call onRepeatableSectionRowCountChanged', () => { + spyOn(field.form, 'onRepeatableSectionRowCountChanged').and.callThrough(); - expect(field.form.onRepeatableSectionChanged).not.toHaveBeenCalled(); + expect(field.form.onRepeatableSectionRowCountChanged).not.toHaveBeenCalled(); field.addRow(field.fields, form); - expect(field.form.onRepeatableSectionChanged).toHaveBeenCalled(); + expect(field.form.onRepeatableSectionRowCountChanged).toHaveBeenCalledWith(field); + }); + + it('should emit onRowCountChanged via formService.formRulesEvent', async () => { + const formRulesEvent = assignFormRulesEventSubject(); + const emissionPromise = firstValueFrom(formRulesEvent); + + field.addRow(field.fields, form); + + const emittedEvent = await emissionPromise; + expect(emittedEvent.type).toBe('onRowCountChanged'); + expect(emittedEvent.field).toBe(field); + }); + + it('should NOT emit onRowCountChanged when add row is not allowed', async () => { + const formRulesEvent = assignFormRulesEventSubject(); + + field.addRow(field.fields, form); + field.addRow(field.fields, form); + field.addRow(field.fields, form); + expect(field.rows.length).toBe(5); + + const emissionPromise = firstValueFrom(formRulesEvent.pipe(timeout(50))); + + field.addRow(field.fields, form); + + await expectAsync(emissionPromise).toBeRejected(); + expect(field.rows.length).toBe(5); }); }); describe('remove row', () => { + const assignFormRulesEventSubject = (): Subject => { + const formRulesEvent = new Subject(); + (field.form as any).formService = { + formRulesEvent, + validateForm: new Subject(), + validateFormField: new Subject(), + formFieldValueChanged: new Subject() + }; + return formRulesEvent; + }; + it('should remove row if target index exists', () => { expect(field.rows.length).toBe(2); @@ -1646,14 +1697,77 @@ describe('FormFieldModel', () => { expect(field.form.onFormFieldChanged).not.toHaveBeenCalled(); }); - it('should call onRepeatableSectionChanged', () => { - spyOn(field.form, 'onRepeatableSectionChanged').and.callThrough(); + it('should call onRepeatableSectionRowCountChanged', () => { + spyOn(field.form, 'onRepeatableSectionRowCountChanged').and.callThrough(); - expect(field.form.onRepeatableSectionChanged).not.toHaveBeenCalled(); + expect(field.form.onRepeatableSectionRowCountChanged).not.toHaveBeenCalled(); field.removeRow(1); - expect(field.form.onRepeatableSectionChanged).toHaveBeenCalled(); + expect(field.form.onRepeatableSectionRowCountChanged).toHaveBeenCalledWith(field); + }); + + it('should emit onRowCountChanged via formService.formRulesEvent', async () => { + const formRulesEvent = assignFormRulesEventSubject(); + const emissionPromise = firstValueFrom(formRulesEvent); + + field.removeRow(1); + + const emittedEvent = await emissionPromise; + expect(emittedEvent.type).toBe('onRowCountChanged'); + expect(emittedEvent.field).toBe(field); + }); + + it('should update form values before emitting onRowCountChanged', async () => { + const formRulesEvent = assignFormRulesEventSubject(); + form.values[field.id] = [ + { + Text0wwp7n: 'mock-1', + Integer0rzkwq: 1 + }, + { + Text0wwp7n: 'mock-2', + Integer0rzkwq: 2 + } + ]; + + const valuesLengthAtEmissionPromise = firstValueFrom( + formRulesEvent.pipe( + take(1), + map(() => form.values[field.id].length) + ) + ); + + field.removeRow(1); + + const valuesLengthAtEmission = await valuesLengthAtEmissionPromise; + expect(valuesLengthAtEmission).toBe(1); + expect(form.values[field.id]).toEqual([ + { + Text0wwp7n: 'mock-1', + Integer0rzkwq: 1 + } + ]); + }); + + it('should emit onRowCountChanged even when form values do not contain section id', async () => { + const formRulesEvent = assignFormRulesEventSubject(); + form.values = {}; + const emissionPromise = firstValueFrom(formRulesEvent); + + field.removeRow(1); + + const emittedEvent = await emissionPromise; + expect(emittedEvent.type).toBe('onRowCountChanged'); + }); + + it('should NOT emit onRowCountChanged when remove row target index does not exist', async () => { + const formRulesEvent = assignFormRulesEventSubject(); + const emissionPromise = firstValueFrom(formRulesEvent.pipe(timeout(50))); + + field.removeRow(2); + + await expectAsync(emissionPromise).toBeRejected(); }); }); 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 61b07ec7fe..d26de6c34a 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 @@ -470,7 +470,7 @@ export class FormFieldModel extends FormWidgetModel { } this.rows.push(this.createRow(fields, form, this.rows.length)); - this.form.onRepeatableSectionChanged(); + this.form.onRepeatableSectionRowCountChanged(this); } private shouldAddRow(): boolean { @@ -484,14 +484,17 @@ export class FormFieldModel extends FormWidgetModel { this.rows.splice(index, 1); this.updateChildrenFieldsRowIndex(); - this.form.onRepeatableSectionChanged(); - if (!this.form.values[this.id]) { - return; + const hasSectionValues = !!this.form.values[this.id]; + if (hasSectionValues) { + this.form.values[this.id].splice(index, 1); } - this.form.values[this.id].splice(index, 1); - this.form.onFormFieldChanged(this); + this.form.onRepeatableSectionRowCountChanged(this); + + if (hasSectionValues) { + this.form.onFormFieldChanged(this); + } } private shouldRemoveRow(index: number): boolean { diff --git a/lib/core/src/lib/form/components/widgets/core/form.model.ts b/lib/core/src/lib/form/components/widgets/core/form.model.ts index a330838c5b..be5d78f5c5 100644 --- a/lib/core/src/lib/form/components/widgets/core/form.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/form.model.ts @@ -16,6 +16,7 @@ */ import { FormFieldEvent } from '../../../events/form-field.event'; +import { FormRulesEvent } from '../../../events/form-rules.event'; import { ValidateFormFieldEvent } from '../../../events/validate-form-field.event'; import { ValidateFormEvent } from '../../../events/validate-form.event'; import { ContainerModel } from './container.model'; @@ -154,6 +155,11 @@ export class FormModel implements ProcessFormModel { this.fieldsCache = this.getFormFields([], true); } + onRepeatableSectionRowCountChanged(sectionField: FormFieldModel): void { + this.onRepeatableSectionChanged(); + this.formService?.formRulesEvent?.next(new FormRulesEvent('onRowCountChanged', new FormFieldEvent(this, sectionField))); + } + /** * Validates entire form and all form fields. */ diff --git a/lib/core/src/lib/form/services/form-validation-service.interface.ts b/lib/core/src/lib/form/services/form-validation-service.interface.ts index 3587f9c717..71091ef049 100644 --- a/lib/core/src/lib/form/services/form-validation-service.interface.ts +++ b/lib/core/src/lib/form/services/form-validation-service.interface.ts @@ -17,6 +17,7 @@ import { Subject } from 'rxjs'; import { FormFieldEvent } from '../events/form-field.event'; +import { FormRulesEvent } from '../events/form-rules.event'; import { ValidateFormFieldEvent } from '../events/validate-form-field.event'; import { ValidateFormEvent } from '../events/validate-form.event'; @@ -24,4 +25,5 @@ export interface FormValidationService { formFieldValueChanged: Subject; validateForm: Subject; validateFormField: Subject; + formRulesEvent?: Subject; }