diff --git a/lib/core/src/lib/form/components/form-field/form-field.component.html b/lib/core/src/lib/form/components/form-field/form-field.component.html index 9a3a12f5ae..cf414fbc0d 100644 --- a/lib/core/src/lib/form/components/form-field/form-field.component.html +++ b/lib/core/src/lib/form/components/form-field/form-field.component.html @@ -1,6 +1,5 @@
{ fixture.detectChanges(); const styles = testingUtils.getByCSS('#field-FAKE-TXT-WIDGET-container').styles; - expect(styles.visibility).toEqual('hidden'); expect(styles.display).toEqual('none'); }); @@ -135,7 +134,6 @@ describe('FormFieldComponent', () => { fixture.detectChanges(); const styles = testingUtils.getByCSS('#field-FAKE-TXT-WIDGET-container').styles; - expect(styles.visibility).toEqual('visible'); expect(styles.display).toEqual('block'); }); @@ -147,13 +145,14 @@ describe('FormFieldComponent', () => { component.field = field; fixture.detectChanges(); + let styles = testingUtils.getByCSS('#field-FAKE-TXT-WIDGET-container').styles; - expect(styles.visibility).toEqual('visible'); expect(styles.display).toEqual('block'); + component.field.isVisible = false; fixture.detectChanges(); + styles = testingUtils.getByCSS('#field-FAKE-TXT-WIDGET-container').styles; - expect(styles.visibility).toEqual('hidden'); expect(styles.display).toEqual('none'); }); diff --git a/lib/core/src/lib/form/components/form-renderer.component.html b/lib/core/src/lib/form/components/form-renderer.component.html index 18f65a016b..19d2918172 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.html +++ b/lib/core/src/lib/form/components/form-renderer.component.html @@ -38,15 +38,17 @@
-
+ [style.width.%]="getColumnWidth(currentRootElement)"> + - to be implemented + - +
+ +
-
+
diff --git a/lib/core/src/lib/form/components/form-renderer.component.scss b/lib/core/src/lib/form/components/form-renderer.component.scss index 24d18d0b12..0234f31560 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.scss +++ b/lib/core/src/lib/form/components/form-renderer.component.scss @@ -14,39 +14,37 @@ } .adf-container-widget { - .adf-grid-list-column-view { - @include layout-bp(lt-md) { - display: flow; - } - - display: flex; - align-items: flex-start; - margin-right: -1%; - } - - .adf-grid-list-single-column { - display: flex; - flex-direction: column; - align-items: flex-start; - flex: 1 1 auto; - } - - .adf-grid-list-column-view-item { - width: 100%; - flex-grow: 1; - box-sizing: border-box; - padding-left: 1%; - padding-right: 1%; - } - .adf-grid-list { display: grid; - } - .adf-grid-list-item { - box-sizing: border-box; - padding-left: 3px; - padding-right: 3px; + &-column-view { + @include layout-bp(lt-md) { + display: flow; + } + + display: flex; + margin-right: -1%; + + &-item { + width: 100%; + flex-grow: 1; + box-sizing: border-box; + padding-left: 1%; + padding-right: 1%; + } + } + + &-single-column { + display: flex; + flex-direction: column; + flex: 1 1 auto; + } + + &-item { + box-sizing: border-box; + padding-left: 3px; + padding-right: 3px; + } } @include layout-bp(lt-md) { diff --git a/lib/core/src/lib/form/components/form-renderer.component.spec.ts b/lib/core/src/lib/form/components/form-renderer.component.spec.ts index 528dacd0b7..5ae5f4cef5 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.spec.ts +++ b/lib/core/src/lib/form/components/form-renderer.component.spec.ts @@ -37,6 +37,7 @@ import { formNumberTextJson, formNumberWidgetVisibility, formRequiredNumberWidget, + mockSectionVisibilityForm, multilineWidgetFormVisibilityMock, numberMinMaxForm, numberNotRequiredForm, @@ -44,22 +45,22 @@ import { radioWidgetVisibilityForm, textWidgetVisibility } from './mock/form-renderer.component.mock'; -import { TextWidgetComponent } from './widgets'; +import { FormModel, TextWidgetComponent } from './widgets'; const typeIntoInput = (testingUtils: UnitTestingUtils, selector: string, message: string) => { testingUtils.fillInputByCSS(selector, message); }; -const expectElementToBeHidden = (testingUtils: UnitTestingUtils, selector: string): void => { - const targetElement = testingUtils.getByCSS(selector).nativeElement; +const expectElementToBeHidden = (testingUtils: UnitTestingUtils, fieldId: string): void => { + const targetElement = testingUtils.getByCSS(`#field-${fieldId}-container`).nativeElement; expect(targetElement).toBeTruthy(); - expect(targetElement.style.visibility).toBe('hidden', `${targetElement.id} should be hidden but it is not`); + expect(targetElement.style.display).toBe('none', `${targetElement.id} should be hidden but it is not`); }; -const expectElementToBeVisible = (testingUtils: UnitTestingUtils, selector: string): void => { - const targetElement = testingUtils.getByCSS(selector).nativeElement; +const expectElementToBeVisible = (testingUtils: UnitTestingUtils, fieldId: string, display: string = 'block'): void => { + const targetElement = testingUtils.getByCSS(`#field-${fieldId}-container`).nativeElement; expect(targetElement).toBeTruthy(); - expect(targetElement.style.visibility).not.toBe('hidden', `${targetElement.id} should be visibile but it is not`); + expect(targetElement.style.display).toBe(display, `${targetElement.id} should be visible but it is not`); }; const expectInputElementValueIs = (testingUtils: UnitTestingUtils, selector: string, value: string): void => { @@ -108,13 +109,13 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Text0pqd1u-container'); + expectElementToBeHidden(testingUtils, 'Text0pqd1u'); testingUtils.fillInputByCSS('#Date0hwq20', '2019-11-19'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Text0pqd1u-container'); + expectElementToBeVisible(testingUtils, 'Text0pqd1u'); }); it('Should not be able to see a widget when the visibility condition refers to another fields with specific date', async () => { @@ -122,14 +123,14 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Text0uyqd3-container'); + expectElementToBeVisible(testingUtils, 'Text0uyqd3'); testingUtils.fillInputByCSS('#Date0hwq20', '2019-11-19'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Text0uyqd3-container'); + expectElementToBeHidden(testingUtils, 'Text0uyqd3'); }); it('[C310336] - Should be able to set visibility conditions for Date widget', async () => { @@ -137,14 +138,14 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Text5asd0a-container'); - expectElementToBeHidden(testingUtils, '#field-Date8wbe3d-container'); + expectElementToBeVisible(testingUtils, 'Text5asd0a'); + expectElementToBeHidden(testingUtils, 'Date8wbe3d'); typeIntoInput(testingUtils, '#Text5asd0a', 'Date'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Date8wbe3d-container'); + expectElementToBeVisible(testingUtils, 'Date8wbe3d'); }); }); @@ -154,12 +155,12 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container'); + expectElementToBeHidden(testingUtils, 'Displayvalue0g6092'); typeIntoInput(testingUtils, '#Text0bq3ar', 'DisplayValue'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Displayvalue0g6092-container'); + expectElementToBeVisible(testingUtils, 'Displayvalue0g6092'); expectInputElementValueIs(testingUtils, '#Displayvalue0g6092', 'No field selected'); }); @@ -167,82 +168,82 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(formDisplayValueForm.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-DisplayValueOne-container'); + expectElementToBeHidden(testingUtils, 'DisplayValueOne'); typeIntoInput(testingUtils, '#Text0howrc', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-DisplayValueOne-container'); + expectElementToBeVisible(testingUtils, 'DisplayValueOne'); expectInputElementValueIs(testingUtils, '#DisplayValueOne', 'No field selected'); typeIntoInput(testingUtils, '#Text0howrc', 'aaab'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-DisplayValueOne-container'); + expectElementToBeHidden(testingUtils, 'DisplayValueOne'); }); it('[C309864] - Should be able to see Display value widget when visibility condition refers to another field and form variable', async () => { formRendererComponent.formDefinition = formService.parseForm(formDisplayValueForm.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-DisplayValueVariableField-container'); + expectElementToBeHidden(testingUtils, 'DisplayValueVariableField'); typeIntoInput(testingUtils, '#TextOne', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-DisplayValueVariableField-container'); + expectElementToBeVisible(testingUtils, 'DisplayValueVariableField'); expectInputElementValueIs(testingUtils, '#DisplayValueVariableField', 'No field selected'); typeIntoInput(testingUtils, '#TextOne', 'aaab'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-DisplayValueVariableField-container'); + expectElementToBeHidden(testingUtils, 'DisplayValueVariableField'); }); it('[C309865] - Should be able to see Display value widget when has multiple visibility conditions and next condition operators', async () => { formRendererComponent.formDefinition = formService.parseForm(formDisplayValueCombinedVisibility.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#Text0bq3ar'); - expectElementToBeVisible(testingUtils, '#TextTwo'); - expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container'); + expectElementToBeVisible(testingUtils, 'Text0bq3ar'); + expectElementToBeVisible(testingUtils, 'TextTwo'); + expectElementToBeHidden(testingUtils, 'Displayvalue0g6092'); typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa'); expectInputElementValueIs(testingUtils, '#TextTwo', ''); - expectElementToBeVisible(testingUtils, '#field-Displayvalue0g6092-container'); + expectElementToBeVisible(testingUtils, 'Displayvalue0g6092'); typeIntoInput(testingUtils, '#Text0bq3ar', 'bbb'); fixture.detectChanges(); await fixture.whenStable(); typeIntoInput(testingUtils, '#Text0bq3ar', 'bbb'); expectInputElementValueIs(testingUtils, '#TextTwo', ''); - expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container'); + expectElementToBeHidden(testingUtils, 'Displayvalue0g6092'); typeIntoInput(testingUtils, '#TextTwo', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); typeIntoInput(testingUtils, '#Text0bq3ar', 'bbb'); expectInputElementValueIs(testingUtils, '#TextTwo', 'aaa'); - expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container'); + expectElementToBeHidden(testingUtils, 'Displayvalue0g6092'); typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa'); expectInputElementValueIs(testingUtils, '#TextTwo', 'aaa'); - expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container'); + expectElementToBeHidden(testingUtils, 'Displayvalue0g6092'); typeIntoInput(testingUtils, '#TextTwo', 'bbb'); fixture.detectChanges(); await fixture.whenStable(); typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa'); expectInputElementValueIs(testingUtils, '#TextTwo', 'bbb'); - expectElementToBeVisible(testingUtils, '#field-Displayvalue0g6092-container'); + expectElementToBeVisible(testingUtils, 'Displayvalue0g6092'); }); }); @@ -252,24 +253,24 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#Number1'); - expectElementToBeHidden(testingUtils, '#field-Number2-container'); + expectElementToBeVisible(testingUtils, 'Number1'); + expectElementToBeHidden(testingUtils, 'Number2'); expect(formRendererComponent.formDefinition.isValid).toBe(true, 'Form should be valid by default'); typeIntoInput(testingUtils, '#Number1', '5'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#Number1'); - expectElementToBeVisible(testingUtils, '#field-Number2-container'); + expectElementToBeVisible(testingUtils, 'Number1'); + expectElementToBeVisible(testingUtils, 'Number2'); expect(formRendererComponent.formDefinition.isValid).toBe(true, 'Form should be valid with a valid value'); typeIntoInput(testingUtils, '#Number1', 'az'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#Number1'); - expectElementToBeHidden(testingUtils, '#field-Number2-container'); + expectElementToBeVisible(testingUtils, 'Number1'); + expectElementToBeHidden(testingUtils, 'Number2'); expect(formRendererComponent.formDefinition.isValid).toBe(false, 'Form should be invalid with an invalid value'); }); @@ -278,18 +279,18 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#Text'); - expectElementToBeHidden(testingUtils, '#field-NumberFieldValue-container'); + expectElementToBeVisible(testingUtils, 'Text'); + expectElementToBeHidden(testingUtils, 'NumberFieldValue'); typeIntoInput(testingUtils, '#Text', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-NumberFieldValue-container'); + expectElementToBeVisible(testingUtils, 'NumberFieldValue'); typeIntoInput(testingUtils, '#Text', 'bbb'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-NumberFieldValue-container'); + expectElementToBeHidden(testingUtils, 'NumberFieldValue'); }); it('[C315170] - Should be able to complete a task with a form with required number widgets', async () => { @@ -297,18 +298,18 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Number2-container'); - expectElementToBeVisible(testingUtils, '#Number1'); + expectElementToBeHidden(testingUtils, 'Number2'); + expectElementToBeVisible(testingUtils, 'Number1'); typeIntoInput(testingUtils, '#Number1', '5'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Number2-container'); + expectElementToBeVisible(testingUtils, 'Number2'); typeIntoInput(testingUtils, '#Number1', '123'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Number2-container'); + expectElementToBeHidden(testingUtils, 'Number2'); const errorWidgetText = testingUtils.getByCSS('#field-Number1-container error-widget .adf-error-text').nativeElement; expect(errorWidgetText.textContent).toBe(`FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`); expect(formRendererComponent.formDefinition.isValid).toBe(false, 'Form should not be valid without mandatory field'); @@ -318,8 +319,8 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(formNumberTextJson.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#NumberReq'); - expectElementToBeVisible(testingUtils, '#NumberNotReq'); + expectElementToBeVisible(testingUtils, 'NumberReq'); + expectElementToBeVisible(testingUtils, 'NumberNotReq'); expect(formRendererComponent.formDefinition.isValid).toBe(false, 'Form should be invalid with an empty required value'); typeIntoInput(testingUtils, '#NumberNotReq', '5'); @@ -338,7 +339,13 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(colspanForm.formRepresentation.formDefinition, null, false, true); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container div.adf-grid-list'); + + const numbersContainerGridElement = testingUtils.getByCSS( + `#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container div.adf-grid-list` + ).nativeElement; + expect(numbersContainerGridElement).toBeTruthy(); + expect(numbersContainerGridElement.style.display).not.toBe('hidden'); + const sectionGridElement: HTMLElement[] = testingUtils .getAllByCSS('#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container div .adf-grid-list-item') .map((element) => element.nativeElement); @@ -356,7 +363,13 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(colspanForm.formRepresentation.formDefinition, null, false, false); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container section.adf-grid-list-column-view'); + + const numbersContainerElement = testingUtils.getByCSS( + `#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container section.adf-grid-list-column-view` + ).nativeElement; + expect(numbersContainerElement).toBeTruthy(); + expect(numbersContainerElement.style.display).not.toBe('hidden'); + const sectionGridElement = testingUtils .getAllByCSS('#field-2bc275fb-e113-4d-7d-885f-6e74a7332d40-container section .adf-grid-list-single-column') .map((element) => element.nativeElement); @@ -376,7 +389,10 @@ describe('Form Renderer Component', () => { const twoSpanTextWidgetContainerId = '#field-1ff21afc-7df4-4607-8363-1dc8576e1c8e-container'; const oneSpanTextWidgetContainerId = '#field-f4285ad-g123-1a73-521d-7nm4a7231aul0-container'; - expectElementToBeVisible(testingUtils, `${oneSpanTextWidgetContainerId} section.adf-grid-list-column-view`); + const oneSpanTextWidgetElement = testingUtils.getByCSS(`${oneSpanTextWidgetContainerId} section.adf-grid-list-column-view`).nativeElement; + expect(oneSpanTextWidgetElement).toBeTruthy(); + expect(oneSpanTextWidgetElement.style.display).not.toBe('hidden'); + const sectionGridElement = testingUtils .getAllByCSS(`${oneSpanTextWidgetContainerId} section .adf-grid-list-single-column`) .map((element) => element.nativeElement); @@ -394,7 +410,7 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#Number0x8cbv'); + expectElementToBeVisible(testingUtils, 'Number0x8cbv'); expectElementToBeValid(testingUtils, 'Number0x8cbv'); testingUtils.blurByCSS('#Number0x8cbv'); @@ -434,7 +450,7 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(numberMinMaxForm.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#Number0him2z'); + expectElementToBeVisible(testingUtils, 'Number0him2z'); expectElementToBeValid(testingUtils, 'Number0him2z'); testingUtils.blurByCSS('#Number0him2z'); @@ -475,17 +491,17 @@ describe('Form Renderer Component', () => { await fixture.whenStable(); const inputText = testingUtils.getByCSS('#Text').nativeElement; expect(inputText).not.toBeNull(); - expectElementToBeHidden(testingUtils, '#field-NumberFieldValue-container'); + expectElementToBeHidden(testingUtils, 'NumberFieldValue'); typeIntoInput(testingUtils, '#Text', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-NumberFieldValue-container'); + expectElementToBeVisible(testingUtils, 'NumberFieldValue'); typeIntoInput(testingUtils, '#Text', 'bbb'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-NumberFieldValue-container'); + expectElementToBeHidden(testingUtils, 'NumberFieldValue'); }); it('[C309665] - Should be able to see Number widget when visibility condition refers to another field and form variable', async () => { @@ -494,39 +510,39 @@ describe('Form Renderer Component', () => { await fixture.whenStable(); const inputText = testingUtils.getByCSS('#Text').nativeElement; expect(inputText).not.toBeNull(); - expectElementToBeHidden(testingUtils, '#field-NumberFieldVariable-container'); + expectElementToBeHidden(testingUtils, 'NumberFieldVariable'); typeIntoInput(testingUtils, '#Text', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-NumberFieldVariable-container'); + expectElementToBeVisible(testingUtils, 'NumberFieldVariable'); typeIntoInput(testingUtils, '#Text', 'bbb'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-NumberFieldVariable-container'); + expectElementToBeHidden(testingUtils, 'NumberFieldVariable'); }); it('[C309666] - Should be able to see Number widget when has multiple visibility conditions and next condition operators', async () => { formRendererComponent.formDefinition = formService.parseForm(numberWidgetVisibilityForm.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Number0wxaur-container'); + expectElementToBeHidden(testingUtils, 'Number0wxaur'); typeIntoInput(testingUtils, '#Text0hs0gt', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Number0wxaur-container'); + expectElementToBeVisible(testingUtils, 'Number0wxaur'); typeIntoInput(testingUtils, '#Text0hs0gt', 'bbb'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Number0wxaur-container'); + expectElementToBeHidden(testingUtils, 'Number0wxaur'); typeIntoInput(testingUtils, '#Text0cuqet', 'aaa'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Number0wxaur-container'); + expectElementToBeHidden(testingUtils, 'Number0wxaur'); expectInputElementValueIs(testingUtils, '#Text0hs0gt', 'bbb'); typeIntoInput(testingUtils, '#Text0hs0gt', 'aaa'); @@ -534,7 +550,7 @@ describe('Form Renderer Component', () => { await fixture.whenStable(); expectInputElementValueIs(testingUtils, '#Text0hs0gt', 'aaa'); expectInputElementValueIs(testingUtils, '#Text0cuqet', 'aaa'); - expectElementToBeHidden(testingUtils, '#field-Number0wxaur-container'); + expectElementToBeHidden(testingUtils, 'Number0wxaur'); }); }); @@ -543,26 +559,26 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(textWidgetVisibility.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#textOne'); - expectElementToBeHidden(testingUtils, '#field-textThree-container'); - expectElementToBeHidden(testingUtils, '#field-textTwo-container'); - expectElementToBeVisible(testingUtils, '#field-textFour-container'); + expectElementToBeVisible(testingUtils, 'textOne'); + expectElementToBeHidden(testingUtils, 'textThree'); + expectElementToBeHidden(testingUtils, 'textTwo'); + expectElementToBeVisible(testingUtils, 'textFour'); typeIntoInput(testingUtils, '#textOne', 'Test'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-textOne-container'); - expectElementToBeVisible(testingUtils, '#field-textTwo-container'); - expectElementToBeVisible(testingUtils, '#field-textThree-container'); - expectElementToBeHidden(testingUtils, '#field-textFour-container'); + expectElementToBeVisible(testingUtils, 'textOne'); + expectElementToBeVisible(testingUtils, 'textTwo'); + expectElementToBeVisible(testingUtils, 'textThree'); + expectElementToBeHidden(testingUtils, 'textFour'); typeIntoInput(testingUtils, '#textTwo', 'Test'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-textOne-container'); - expectElementToBeVisible(testingUtils, '#field-textTwo-container'); - expectElementToBeVisible(testingUtils, '#field-textFour-container'); - expectElementToBeHidden(testingUtils, '#field-textThree-container'); + expectElementToBeVisible(testingUtils, 'textOne'); + expectElementToBeVisible(testingUtils, 'textTwo'); + expectElementToBeVisible(testingUtils, 'textFour'); + expectElementToBeHidden(testingUtils, 'textThree'); }); }); @@ -571,13 +587,13 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(radioWidgetVisibilityForm.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Text0cee7g-container'); - expectElementToBeHidden(testingUtils, '#field-Radiobuttons03rkbo-container'); + expectElementToBeVisible(testingUtils, 'Text0cee7g'); + expectElementToBeHidden(testingUtils, 'Radiobuttons03rkbo'); typeIntoInput(testingUtils, '#Text0cee7g', 'Radio'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Radiobuttons03rkbo-container'); + expectElementToBeVisible(testingUtils, 'Radiobuttons03rkbo'); }); }); @@ -587,8 +603,8 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(customWidgetForm.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#Text0vdi18'); - expectElementToBeVisible(testingUtils, '#bananaforevah0k8gui'); + expectElementToBeVisible(testingUtils, 'Text0vdi18'); + expectElementToBeVisible(testingUtils, 'bananaforevah0k8gui'); }); it('Should be able to correctly use visibility in a custom process cloud widget ', async () => { @@ -596,11 +612,11 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(customWidgetFormWithVisibility.formRepresentation.formDefinition); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-bananaforevah0k8gui-container'); + expectElementToBeHidden(testingUtils, 'bananaforevah0k8gui'); typeIntoInput(testingUtils, '#Text0vdi18', 'no'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-bananaforevah0k8gui-container'); + expectElementToBeVisible(testingUtils, 'bananaforevah0k8gui'); }); }); @@ -641,15 +657,15 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Text0id3ic-container'); - expectElementToBeVisible(testingUtils, '#field-Number0yggl7-container'); - expectElementToBeHidden(testingUtils, '#field-Amount0kceqc-container'); + expectElementToBeVisible(testingUtils, 'Text0id3ic'); + expectElementToBeVisible(testingUtils, 'Number0yggl7'); + expectElementToBeHidden(testingUtils, 'Amount0kceqc'); typeIntoInput(testingUtils, '#Text0id3ic', 'text1'); typeIntoInput(testingUtils, '#Number0yggl7', '77'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Amount0kceqc-container'); + expectElementToBeVisible(testingUtils, 'Amount0kceqc'); }); }); @@ -659,19 +675,19 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Checkbox0pr51m-container'); - expectElementToBeVisible(testingUtils, '#field-Checkbox0fp0zf-container'); - expectElementToBeHidden(testingUtils, '#field-Checkbox0lb7ze-container'); + expectElementToBeVisible(testingUtils, 'Checkbox0pr51m'); + expectElementToBeVisible(testingUtils, 'Checkbox0fp0zf'); + expectElementToBeHidden(testingUtils, 'Checkbox0lb7ze'); testingUtils.clickByCSS('#Checkbox0pr51m-input'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Checkbox0lb7ze-container'); + expectElementToBeHidden(testingUtils, 'Checkbox0lb7ze'); testingUtils.clickByCSS('#Checkbox0fp0zf-input'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Checkbox0lb7ze-container'); + expectElementToBeVisible(testingUtils, 'Checkbox0lb7ze'); }); }); @@ -681,18 +697,18 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Text-container'); - expectElementToBeVisible(testingUtils, '#field-MultilineTextId-container'); + expectElementToBeVisible(testingUtils, 'Text'); + expectElementToBeVisible(testingUtils, 'MultilineTextId'); typeIntoInput(testingUtils, '#Text', 'textwrong'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-MultilineTextId-container'); + expectElementToBeHidden(testingUtils, 'MultilineTextId'); typeIntoInput(testingUtils, '#Text', 'text'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-MultilineTextId-container'); + expectElementToBeVisible(testingUtils, 'MultilineTextId'); }); }); @@ -702,18 +718,18 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Text0tzu53-container'); - expectElementToBeHidden(testingUtils, '#field-Displaytext0q4w02-container'); + expectElementToBeVisible(testingUtils, 'Text0tzu53'); + expectElementToBeHidden(testingUtils, 'Displaytext0q4w02'); typeIntoInput(testingUtils, '#Text0tzu53', 'aaa-value'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Displaytext0q4w02-container'); + expectElementToBeVisible(testingUtils, 'Displaytext0q4w02'); typeIntoInput(testingUtils, '#Text0tzu53', 'bbb'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Displaytext0q4w02-container'); + expectElementToBeHidden(testingUtils, 'Displaytext0q4w02'); }); it('[C309870] - Should be able to see Display text widget when visibility condition refers to another field and form variable', async () => { @@ -721,18 +737,18 @@ describe('Form Renderer Component', () => { fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Text0tzu53-container'); - expectElementToBeHidden(testingUtils, '#field-Displaytext8bac2e-container'); + expectElementToBeVisible(testingUtils, 'Text0tzu53'); + expectElementToBeHidden(testingUtils, 'Displaytext8bac2e'); typeIntoInput(testingUtils, '#Text0tzu53', 'aaa-variable'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeVisible(testingUtils, '#field-Displaytext8bac2e-container'); + expectElementToBeVisible(testingUtils, 'Displaytext8bac2e'); typeIntoInput(testingUtils, '#Text0tzu53', 'bbb'); fixture.detectChanges(); await fixture.whenStable(); - expectElementToBeHidden(testingUtils, '#field-Displaytext8bac2e-container'); + expectElementToBeHidden(testingUtils, 'Displaytext8bac2e'); }); }); @@ -746,4 +762,33 @@ describe('Form Renderer Component', () => { expect(decimalInputElement.value).toBeTruthy('10.12'); }); }); + + describe('Section', () => { + it('should be able to set visibility conditions for Section with fields', () => { + const formDefinition = new FormModel(mockSectionVisibilityForm); + const sectionIdSelector = '48e6bfed-b477-4641-86cb-bfa70229a31e'; + const mockTextDisplaySectionId = 'Text0n9o62'; + const mockTestDisplaySectionFieldId = 'Text0nu6os'; + const mockSectionFieldId = 'Text0t23xu'; + + fixture.componentRef.setInput('formDefinition', formDefinition); + fixture.detectChanges(); + + expectElementToBeVisible(testingUtils, mockTestDisplaySectionFieldId); + expectElementToBeVisible(testingUtils, mockTextDisplaySectionId); + + expectElementToBeHidden(testingUtils, sectionIdSelector); + expectElementToBeHidden(testingUtils, mockSectionFieldId); + + typeIntoInput(testingUtils, `#${mockTextDisplaySectionId}`, 'section'); + fixture.detectChanges(); + + expectElementToBeVisible(testingUtils, sectionIdSelector, 'flex'); + + typeIntoInput(testingUtils, `#${mockTestDisplaySectionFieldId}`, 'section text'); + fixture.detectChanges(); + + expectElementToBeVisible(testingUtils, mockSectionFieldId); + }); + }); }); diff --git a/lib/core/src/lib/form/components/form-renderer.component.ts b/lib/core/src/lib/form/components/form-renderer.component.ts index 68b525487c..de3797b466 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.ts +++ b/lib/core/src/lib/form/components/form-renderer.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { JsonPipe, NgClass, NgForOf, NgIf, NgStyle, NgTemplateOutlet, UpperCasePipe } from '@angular/common'; +import { JsonPipe, NgClass, NgForOf, NgIf, NgStyle, NgTemplateOutlet } from '@angular/common'; import { Component, Inject, Injector, Input, OnDestroy, OnInit, Optional, ViewEncapsulation } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; @@ -28,8 +28,8 @@ import { FormService } from '../services/form.service'; import { FormFieldComponent } from './form-field/form-field.component'; import { FORM_FIELD_MODEL_RENDER_MIDDLEWARE, FormFieldModelRenderMiddleware } from './middlewares/middleware'; import { ContainerModel, FormFieldModel, FormModel, TabModel } from './widgets'; -import { FieldStylePipe } from '../pipes/field-style.pipe'; import { HeaderWidgetComponent } from './widgets/header/header.widget'; +import { FormSectionComponent } from './form-section/form-section.component'; @Component({ selector: 'adf-form-renderer', @@ -56,10 +56,9 @@ import { HeaderWidgetComponent } from './widgets/header/header.widget'; MatSlideToggleModule, FormsModule, JsonPipe, - UpperCasePipe, - FieldStylePipe, NgClass, - HeaderWidgetComponent + HeaderWidgetComponent, + FormSectionComponent ], encapsulation: ViewEncapsulation.None }) @@ -154,9 +153,10 @@ export class FormRendererComponent implements OnInit, OnDestroy { * @param container container model * @returns the column width for the given model */ - getColumnWith(container: ContainerModel): string { - const colspan = container ? container.field.colspan : 1; - return (100 / container.field.numberOfColumns) * colspan + ''; + getColumnWidth(container: ContainerModel): string { + const { field } = container; + const colspan = field ? field.colspan : 1; + return (100 / field.numberOfColumns) * colspan + ''; } private runMiddlewareServices(): void { diff --git a/lib/core/src/lib/form/components/form-section/form-section.component.html b/lib/core/src/lib/form/components/form-section/form-section.component.html new file mode 100644 index 0000000000..8d69084307 --- /dev/null +++ b/lib/core/src/lib/form/components/form-section/form-section.component.html @@ -0,0 +1,11 @@ +
+
+
+ +
+
+
diff --git a/lib/core/src/lib/form/components/form-section/form-section.component.scss b/lib/core/src/lib/form/components/form-section/form-section.component.scss new file mode 100644 index 0000000000..8181567b2a --- /dev/null +++ b/lib/core/src/lib/form/components/form-section/form-section.component.scss @@ -0,0 +1,18 @@ +.adf-grid-list { + &-section { + &-single-column { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + } + + &-column-view-item { + width: 100%; + display: flex; + flex-grow: 1; + box-sizing: border-box; + padding-left: 1%; + padding-right: 1%; + } + } +} diff --git a/lib/core/src/lib/form/components/form-section/form-section.component.spec.ts b/lib/core/src/lib/form/components/form-section/form-section.component.spec.ts new file mode 100644 index 0000000000..f7143fb6e6 --- /dev/null +++ b/lib/core/src/lib/form/components/form-section/form-section.component.spec.ts @@ -0,0 +1,62 @@ +/*! + * @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 { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CoreTestingModule, UnitTestingUtils } from '../../../testing'; +import { FormFieldModel, FormModel } from '../widgets'; +import { FormSectionComponent } from './form-section.component'; +import { mockSectionWithFields } from '../mock/form-renderer.component.mock'; + +describe('FormSectionComponent', () => { + let fixture: ComponentFixture; + let component: FormSectionComponent; + let testingUtils: UnitTestingUtils; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [CoreTestingModule] + }); + fixture = TestBed.createComponent(FormSectionComponent); + testingUtils = new UnitTestingUtils(fixture.debugElement); + component = fixture.componentInstance; + }); + + it('should calculate the correct width for section columns', () => { + const numberOfColumns = 3; + const columnField = { colspan: 2 } as FormFieldModel; + + const width = component.getSectionColumnWidth(numberOfColumns, [columnField]); + expect(width).toBe('66.66666666666667'); + }); + + it('should handle columns with no colspan defined', () => { + const numberOfColumns = 3; + const columnField = {} as FormFieldModel; + + const width = component.getSectionColumnWidth(numberOfColumns, [columnField]); + expect(width).toBe('33.333333333333336'); + }); + + it('should display fields inside section', () => { + const sectionField = new FormFieldModel(new FormModel(), mockSectionWithFields); + fixture.componentRef.setInput('field', sectionField); + fixture.detectChanges(); + + const sectionFields = testingUtils.getAllByCSS('.adf-grid-list-section-column-view-item adf-form-field'); + expect(sectionFields.length).toBe(2); + }); +}); diff --git a/lib/core/src/lib/form/components/form-section/form-section.component.ts b/lib/core/src/lib/form/components/form-section/form-section.component.ts new file mode 100644 index 0000000000..3652151a98 --- /dev/null +++ b/lib/core/src/lib/form/components/form-section/form-section.component.ts @@ -0,0 +1,49 @@ +/*! + * @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 { Component, inject, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { WidgetVisibilityService } from '../../services/widget-visibility.service'; +import { FormFieldModel } from '../widgets/core/form-field.model'; +import { FormFieldComponent } from '../form-field/form-field.component'; +import { NgFor } from '@angular/common'; + +@Component({ + selector: 'adf-form-section', + standalone: true, + templateUrl: './form-section.component.html', + encapsulation: ViewEncapsulation.None, + styleUrls: ['./form-section.component.scss'], + imports: [NgFor, FormFieldComponent] +}) +export class FormSectionComponent implements OnInit { + @Input() + field: FormFieldModel = null; + + private readonly visibilityService = inject(WidgetVisibilityService); + + ngOnInit(): void { + this.visibilityService.refreshVisibility(this.field.form); + } + + getSectionColumnWidth(numberOfColumns: number, columnFields: FormFieldModel[]): string { + const firstColumnFieldIndex = 0; + const defaultColspan = 1; + const fieldColspan = columnFields[firstColumnFieldIndex]?.colspan ?? defaultColspan; + + return (100 / numberOfColumns) * fieldColspan + ''; + } +} diff --git a/lib/core/src/lib/form/components/mock/form-renderer.component.mock.ts b/lib/core/src/lib/form/components/mock/form-renderer.component.mock.ts index 05bd6a8168..fb22f95685 100644 --- a/lib/core/src/lib/form/components/mock/form-renderer.component.mock.ts +++ b/lib/core/src/lib/form/components/mock/form-renderer.component.mock.ts @@ -2135,3 +2135,213 @@ export const displayBigDecimalWidgetMock = { } } }; + +export const mockSectionWithFields = { + id: '51058d64-b36c-48f0-97a2-d9bd0398edab', + name: 'Section', + type: 'section', + numberOfColumns: 2, + fields: { + 1: [ + { + id: 'Text07kgd8', + 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: 'Date0opbi8', + name: 'Date', + type: 'date', + readOnly: false, + required: false, + colspan: 1, + rowspan: 1, + placeholder: null, + minValue: null, + maxValue: null, + minDateRangeValue: null, + maxDateRangeValue: null, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + }, + dateDisplayFormat: 'yyyy-MM-dd' + } + ] + }, + colspan: 1 +}; + +export const mockFormWithSimpleSection = { + id: 'form-363114eb-35f6-40d0-9908-8bbbe776c3e6', + name: 'simplest section', + key: 'simplest-section-uzvc7', + description: '', + version: 0, + formDefinition: { + tabs: [], + fields: [ + { + id: 'Group0wuwv7', + name: 'Group', + type: 'group', + tab: null, + params: { + hideHeader: false, + allowCollapse: false, + collapseByDefault: false + }, + numberOfColumns: 1, + fields: { + 1: [mockSectionWithFields] + } + } + ], + outcomes: [], + metadata: {}, + variables: [] + } +}; + +export const mockSectionVisibilityForm = { + id: 'form-65e9f07c-44d9-4469-8f5d-74aba3bd7326', + name: 'section visibility', + key: 'section-visibility-tark1', + description: '', + version: 0, + formDefinition: { + tabs: [], + fields: [ + { + id: 'Group0sfmn9', + name: 'Group', + type: 'group', + tab: null, + params: { + hideHeader: false, + allowCollapse: false, + collapseByDefault: false + }, + numberOfColumns: 1, + fields: { + 1: [ + { + id: '48e6bfed-b477-4641-86cb-bfa70229a31e', + name: 'Section', + type: 'section', + numberOfColumns: 1, + fields: { + 1: [ + { + id: 'Text0t23xu', + name: 'Text', + type: 'text', + readOnly: false, + required: false, + colspan: 1, + rowspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + regexPattern: null, + visibilityCondition: { + leftType: 'field', + leftValue: 'Text0nu6os', + operator: '==', + rightValue: 'section text', + rightType: 'value', + nextConditionOperator: '', + nextCondition: null + }, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ] + }, + colspan: 1, + visibilityCondition: { + leftType: 'field', + leftValue: 'Text0n9o62', + operator: '==', + rightValue: 'section', + rightType: 'value', + nextConditionOperator: '', + nextCondition: null + } + } + ] + } + }, + { + id: 'f89d427e-dc4d-44df-b47d-4f61a1cea21f', + name: 'Label', + type: 'container', + tab: null, + numberOfColumns: 2, + fields: { + 1: [ + { + id: 'Text0n9o62', + name: 'display section', + 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: 'Text0nu6os', + name: 'display section field', + 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 + } + } + ] + } + } + ], + outcomes: [], + metadata: {}, + variables: [] + } +}; diff --git a/lib/core/src/lib/form/components/mock/form.mock.ts b/lib/core/src/lib/form/components/mock/form.mock.ts index 8930700eef..680e3172ff 100644 --- a/lib/core/src/lib/form/components/mock/form.mock.ts +++ b/lib/core/src/lib/form/components/mock/form.mock.ts @@ -1406,3 +1406,283 @@ export const fakeViewerForm = { variables: [] } }; + +export const mockFormWithSections = { + id: 'form-970ddfcf-6d5d-4cbb-ae81-958ade65062c', + name: 'simple section', + key: 'simple-section-z54vk', + description: '', + version: 0, + tabs: [], + fields: [ + { + id: 'Group0bw66i', + name: 'Group', + type: 'group', + tab: null, + params: { + hideHeader: false, + allowCollapse: false, + collapseByDefault: false + }, + numberOfColumns: 1, + fields: { + 1: [ + { + id: '4c0590a9-44e3-415f-9e69-3a3ffe99066b', + name: 'Section', + type: 'section', + numberOfColumns: 4, + fields: { + 1: [ + { + id: 'Text0xiuw4', + name: 'Text in section', + type: 'text', + readOnly: false, + required: false, + colspan: 1, + rowspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + regexPattern: null, + visibilityCondition: { + leftType: 'field', + leftValue: 'Text0bbed8', + operator: '==', + rightValue: 'text inside', + rightType: 'value', + nextConditionOperator: '', + nextCondition: null + }, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ], + 2: [], + 3: [ + { + id: 'dateInsideSection', + name: 'Date', + type: 'date', + readOnly: false, + required: true, + colspan: 2, + rowspan: 1, + placeholder: null, + minValue: null, + maxValue: null, + minDateRangeValue: null, + maxDateRangeValue: null, + visibilityCondition: { + leftType: 'field', + leftValue: 'Text0s7k3m', + operator: '==', + rightValue: 'date', + rightType: 'value', + nextConditionOperator: '', + nextCondition: null + }, + params: { + existingColspan: 1, + maxColspan: 2 + }, + dateDisplayFormat: 'yyyy-MM-dd' + } + ] + }, + colspan: 1 + }, + { + id: '9d0ed068-f6b9-4ddc-ba8b-8ac29b745e1d', + name: 'Section', + type: 'section', + numberOfColumns: 6, + fields: { + 1: [ + { + id: 'Text0dkzym', + name: 'Text', + type: 'text', + readOnly: false, + required: false, + colspan: 2, + rowspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + regexPattern: null, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ], + 2: [ + { + id: 'Text0kn8qb', + name: 'type text outside', + type: 'text', + readOnly: false, + required: false, + colspan: 2, + rowspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + regexPattern: null, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ], + 3: [ + { + id: 'Text0bbed8', + name: 'type text inside', + type: 'text', + readOnly: false, + required: false, + colspan: 2, + rowspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + regexPattern: null, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ] + }, + colspan: 1 + } + ] + } + }, + { + id: 'fc73e4fa-5725-41b2-9185-624d9efd6581', + name: 'Label', + type: 'container', + tab: null, + numberOfColumns: 4, + fields: { + 1: [ + { + id: 'Text0p0v95', + name: 'type section', + 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 + } + }, + { + id: 'Text0s7k3m', + name: 'type date', + 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: 'multilineOutsideSection', + name: 'Multiline text', + type: 'multi-line-text', + readOnly: false, + colspan: 1, + rowspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + regexPattern: null, + required: false, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ], + 3: [ + { + id: 'Text0opb9w', + name: 'Text', + type: 'text', + readOnly: false, + required: false, + colspan: 2, + rowspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + regexPattern: null, + visibilityCondition: { + leftType: 'field', + leftValue: 'Text0kn8qb', + operator: '==', + rightValue: 'text outside', + rightType: 'value', + nextConditionOperator: '', + nextCondition: null + }, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ] + } + } + ], + outcomes: [], + metadata: {}, + variables: [], + rules: { + fields: { + Text0bbed8: { + click: [ + { + actions: [ + { + target: 'field.Text0xiuw4', + payload: { + value: 'updated by form rules!' + } + } + ] + } + ] + } + } + } +}; diff --git a/lib/core/src/lib/form/components/widgets/core/container-column.model.ts b/lib/core/src/lib/form/components/widgets/core/container-column.model.ts index 489e03b210..a301041641 100644 --- a/lib/core/src/lib/form/components/widgets/core/container-column.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/container-column.model.ts @@ -15,16 +15,15 @@ * limitations under the License. */ -/* eslint-disable @angular-eslint/component-selector */ +import { FormFieldModel } from './form-field.model'; export class ContainerColumnModel { - size: number = 12; - fields: any[] = []; + fields: FormFieldModel[] = []; colspan: number = 1; rowspan: number = 1; hasFields(): boolean { - return this.fields && this.fields.length > 0; + return !!this.fields?.length; } } 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 3bc492b042..9211b5dffc 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 @@ -879,6 +879,21 @@ describe('FormFieldModel', () => { expect(field.numberOfColumns).toBe(123); }); + it('should NOT update colspan based on fields colspans for section type', () => { + const form = new FormModel(); + const json = { + type: FormFieldTypes.SECTION, + numberOfColumns: 2, + fields: { + column1: [{ id: 'field1', colspan: 2 }], + column2: [{ id: 'field2', colspan: 3 }] + } + }; + const formField = new FormFieldModel(form, json); + + expect(formField.colspan).toBe(1); + }); + it('should instantiate FormField when has no variable', () => { const form = new FormModel({}); form.json = { 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 4c498f48cb..d724aa207b 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 @@ -282,30 +282,34 @@ export class FormFieldModel extends FormWidgetModel { } private containerFactory(json: any, form: any): void { - this.numberOfColumns = json.numberOfColumns || 1; - - this.fields = json.fields; - + const { numberOfColumns = 1, fields = {} } = json; + this.numberOfColumns = numberOfColumns; + this.fields = fields; this.rowspan = 1; this.colspan = 1; - if (json.fields) { - for (const currentField in json.fields) { - if (Object.prototype.hasOwnProperty.call(json.fields, currentField)) { - const col = new ContainerColumnModel(); - - col.fields = (json.fields[currentField] || []).map((field) => new FormFieldModel(form, field)); - col.rowspan = json.fields[currentField].length; - - col.fields.forEach((colFields: any) => { - this.colspan = colFields.colspan > this.colspan ? colFields.colspan : this.colspan; - }); - - this.rowspan = this.rowspan < col.rowspan ? col.rowspan : this.rowspan; - this.columns.push(col); - } + Object.keys(fields).forEach((currentField) => { + if (!Object.prototype.hasOwnProperty.call(fields, currentField)) { + return; } - } + + const col = new ContainerColumnModel(); + col.fields = (fields[currentField] || []).map((field: any) => new FormFieldModel(form, field)); + col.rowspan = fields[currentField].length; + + if (!FormFieldTypes.isSectionType(this.type)) { + this.updateContainerColspan(col.fields); + } + + this.rowspan = Math.max(this.rowspan, col.rowspan); + this.columns.push(col); + }); + } + + private updateContainerColspan(fields: FormFieldModel[]): void { + fields.forEach((colField: FormFieldModel) => { + this.colspan = Math.max(this.colspan, colField.colspan); + }); } parseValue(json: any): any { diff --git a/lib/core/src/lib/form/components/widgets/core/form.model.spec.ts b/lib/core/src/lib/form/components/widgets/core/form.model.spec.ts index b4cb116731..9c4aa2fa2f 100644 --- a/lib/core/src/lib/form/components/widgets/core/form.model.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form.model.spec.ts @@ -24,7 +24,7 @@ import { FormFieldModel } from './form-field.model'; import { FormOutcomeModel } from './form-outcome.model'; import { FormModel } from './form.model'; import { TabModel } from './tab.model'; -import { fakeMetadataForm, mockDisplayExternalPropertyForm } from '../../mock/form.mock'; +import { fakeMetadataForm, mockDisplayExternalPropertyForm, mockFormWithSections } from '../../mock/form.mock'; import { CoreTestingModule } from '../../../../testing'; import { TestBed } from '@angular/core/testing'; @@ -643,4 +643,68 @@ describe('FormModel', () => { expect(FormFieldTypes.isConstantValueType(displayExternalPropertyWidget.type)).toBeTrue(); expect(displayExternalPropertyWidget.value).toBe('hr'); }); + + describe('getFormFields', () => { + let form: FormModel; + + beforeEach(() => { + form = new FormModel(mockFormWithSections); + }); + + it('should get all form fields (containers, sections, fields)', () => { + const fields = form.getFormFields(); + expect(fields.length).toBe(13); + }); + + it('should filter form fields by type inside sections', () => { + const fields = form.getFormFields([FormFieldTypes.DATE]); + expect(fields.length).toBe(1); + expect(fields[0].id).toBe('dateInsideSection'); + expect(fields[0].type).toBe(FormFieldTypes.DATE); + }); + + it('should filter form fields by type outside sections', () => { + const fields = form.getFormFields([FormFieldTypes.MULTILINE_TEXT]); + expect(fields.length).toBe(1); + expect(fields[0].id).toBe('multilineOutsideSection'); + expect(fields[0].type).toBe(FormFieldTypes.MULTILINE_TEXT); + }); + + it('should filter form fields by multiple types', () => { + const fields = form.getFormFields([FormFieldTypes.DATE, FormFieldTypes.MULTILINE_TEXT]); + expect(fields.length).toBe(2); + expect(fields[0].id).toBe('dateInsideSection'); + expect(fields[1].id).toBe('multilineOutsideSection'); + expect(fields[0].type).toBe(FormFieldTypes.DATE); + expect(fields[1].type).toBe(FormFieldTypes.MULTILINE_TEXT); + }); + + it('should return no fields when filtered by a non-existent type', () => { + const fields = form.getFormFields(['NON_EXISTENT_TYPE']); + expect(fields.length).toBe(0); + }); + + it('should return fields from cache if available', () => { + form.fieldsCache = [new FormFieldModel(form, { type: FormFieldTypes.TEXT }), new FormFieldModel(form, { type: FormFieldTypes.NUMBER })]; + const fields = form.getFormFields(); + expect(fields.length).toBe(2); + expect(fields[0].type).toBe(FormFieldTypes.TEXT); + expect(fields[1].type).toBe(FormFieldTypes.NUMBER); + }); + + it('should return filtered fields from cache if available', () => { + form.fieldsCache = [ + new FormFieldModel(form, { type: FormFieldTypes.TEXT }), + new FormFieldModel(form, { type: FormFieldTypes.AMOUNT }), + new FormFieldModel(form, { type: FormFieldTypes.DATE }), + new FormFieldModel(form, { type: FormFieldTypes.NUMBER }) + ]; + + const fields = form.getFormFields([FormFieldTypes.AMOUNT, FormFieldTypes.DATE, FormFieldTypes.NUMBER]); + expect(fields.length).toBe(3); + expect(fields[0].type).toBe(FormFieldTypes.AMOUNT); + expect(fields[1].type).toBe(FormFieldTypes.DATE); + expect(fields[2].type).toBe(FormFieldTypes.NUMBER); + }); + }); }); 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 42dcb99dde..5dfe1c8b6a 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 @@ -340,28 +340,63 @@ export class FormModel implements ProcessFormModel { return this.fieldsCache.find((field) => field.id === fieldId); } - getFormFields(): FormFieldModel[] { - if (this.fieldsCache.length > 0) { - return this.fieldsCache; - } else { - const formFieldModel: FormFieldModel[] = []; - - for (let i = 0; i < this.fields.length; i++) { - const field = this.fields[i]; - - if (field instanceof ContainerModel) { - formFieldModel.push(field.field); - - field.field.columns.forEach((column) => { - formFieldModel.push(...column.fields); - }); - } else { - formFieldModel.push(field); - } - } - - return formFieldModel; + getFormFields(filterTypes?: string[]): FormFieldModel[] { + if (this.fieldsCache?.length) { + return this.filterFieldsByType(this.fieldsCache, filterTypes); } + + const formFieldModel: FormFieldModel[] = []; + this.processFields(this.fields, formFieldModel); + return this.filterFieldsByType(formFieldModel, filterTypes); + } + + private processFields(fields: (ContainerModel | FormFieldModel)[], formFieldModel: FormFieldModel[]): void { + fields.forEach((field) => { + if (this.isSectionField(field)) { + this.handleSectionField(field, formFieldModel); + } else if (this.isContainerField(field)) { + this.handleContainerField(field, formFieldModel); + } else { + this.handleSingleField(field, formFieldModel); + } + }); + } + + private isContainerField(field: ContainerModel | FormFieldModel): field is ContainerModel { + return field instanceof ContainerModel; + } + + private isSectionField(field: ContainerModel | FormFieldModel): field is FormFieldModel { + return field.type === FormFieldTypes.SECTION; + } + + private handleSectionField(section: FormFieldModel, formFieldModel: FormFieldModel[]): void { + formFieldModel.push(section); + section.columns.forEach((column) => { + this.processFields(column.fields, formFieldModel); + }); + } + + private handleContainerField(container: ContainerModel, formFieldModel: FormFieldModel[]): void { + formFieldModel.push(container.field); + container.field.columns.forEach((column) => { + this.processFields(column.fields, formFieldModel); + }); + } + + private handleSingleField(field: FormFieldModel, formFieldModel: FormFieldModel[]): void { + formFieldModel.push(field); + if (field.fields) { + this.processFields(Object.values(field.fields), formFieldModel); + } + } + + private filterFieldsByType(fields: FormFieldModel[], types?: string[]): FormFieldModel[] { + if (!types?.length) { + return fields; + } + + return fields.filter((field) => types.includes(field?.type)); } markAsInvalid(): void { diff --git a/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.scss b/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.scss index c9b475ef35..5790d8f248 100644 --- a/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.scss +++ b/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.scss @@ -4,4 +4,5 @@ font-weight: var(--adf-readonly-text-font-weight, var(--adf-form-label-font-weight, var(--theme-font-weight))); color: var(--adf-readonly-text-color, var(--adf-form-label-color, var(--theme-text-color))); line-height: normal; + word-break: break-word; } diff --git a/lib/process-services/src/lib/form/form.component.visibility.spec.ts b/lib/process-services/src/lib/form/form.component.visibility.spec.ts index c0f4dfaf7f..52127862bb 100644 --- a/lib/process-services/src/lib/form/form.component.visibility.spec.ts +++ b/lib/process-services/src/lib/form/form.component.visibility.spec.ts @@ -152,12 +152,12 @@ describe('FormComponent UI and visibility', () => { fixture.detectChanges(); const firstEl = fixture.debugElement.query(By.css('#field-country-container')); - expect(firstEl.nativeElement.style.visibility).toBe('hidden'); + expect(firstEl.nativeElement.style.display).toBe('none'); const secondEl = fixture.debugElement.query(By.css('#name')); expect(secondEl).not.toBeNull(); expect(secondEl).toBeDefined(); - expect(fixture.nativeElement.querySelector('#field-name-container').style.visibility).not.toBe('hidden'); + expect(fixture.nativeElement.querySelector('#field-name-container').style.display).toBe('block'); }); it('should hide the field based on the previous one', () => { @@ -171,10 +171,10 @@ describe('FormComponent UI and visibility', () => { const firstEl = fixture.debugElement.query(By.css('#name')); expect(firstEl).not.toBeNull(); expect(firstEl).toBeDefined(); - expect(fixture.nativeElement.querySelector('#field-name-container').style.visibility).not.toBe('hidden'); + expect(fixture.nativeElement.querySelector('#field-name-container').style.display).toBe('block'); const secondEl = fixture.debugElement.query(By.css('#field-country-container')); - expect(secondEl.nativeElement.style.visibility).toBe('hidden'); + expect(secondEl.nativeElement.style.display).toBe('none'); }); it('should show the hidden field when the visibility condition change to true', () => { @@ -186,10 +186,10 @@ describe('FormComponent UI and visibility', () => { fixture.detectChanges(); let firstEl = fixture.debugElement.query(By.css('#field-country-container')); - expect(firstEl.nativeElement.style.visibility).toBe('hidden'); + expect(firstEl.nativeElement.style.display).toBe('none'); const secondEl = fixture.debugElement.query(By.css('#field-name-container')); - expect(secondEl.nativeElement.style.visibility).not.toBe('hidden'); + expect(secondEl.nativeElement.style.display).toBe('block'); const inputElement = fixture.nativeElement.querySelector('#name'); inputElement.value = 'italy'; @@ -197,7 +197,7 @@ describe('FormComponent UI and visibility', () => { fixture.detectChanges(); firstEl = fixture.debugElement.query(By.css('#field-country-container')); - expect(firstEl.nativeElement.style.visibility).not.toBe('hidden'); + expect(firstEl.nativeElement.style.display).toBe('block'); }); }); diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts index 91257294e7..897b17ecf4 100644 --- a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts +++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts @@ -511,9 +511,9 @@ describe('TaskFormComponent', () => { fixture.detectChanges(); await fixture.whenStable(); const inputThreeContainer = fixture.nativeElement.querySelector('#field-text3-container'); - expect(inputThreeContainer.style.visibility).toBe('hidden'); + expect(inputThreeContainer.style.display).toBe('none'); const completeOutcomeButton: HTMLButtonElement = fixture.nativeElement.querySelector('#adf-form-complete'); - expect(completeOutcomeButton.style.visibility).not.toBe('hidden'); + expect(completeOutcomeButton.style.display).not.toBe('none'); completeOutcomeButton.click(); fixture.detectChanges(); });