import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, ViewChild } from '@angular/core';
import { By } from '@angular/platform-browser';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
import { SharedModule } from '../../shared.module';
import { TearsheetStepComponent } from '../tearsheet-step/tearsheet-step.component';
import { TearsheetComponent, TearsheetOverflowScroll } from './tearsheet.component';
tearsheet!: TearsheetComponent;
}
+@Component({
+ selector: 'cd-mock-form-step',
+ template: '',
+ standalone: false
+})
+class MockFormStepComponent {
+ formGroup = new FormGroup({
+ parent: new FormGroup({
+ child: new FormControl('', Validators.required)
+ })
+ });
+}
+
+@Component({
+ template: `
+ <cd-tearsheet
+ [steps]="steps"
+ [title]="title"
+ [description]="description"
+ >
+ <cd-tearsheet-step>
+ <cd-mock-form-step #tearsheetStep></cd-mock-form-step>
+ </cd-tearsheet-step>
+ <cd-tearsheet-step>
+ <div>Step 2</div>
+ </cd-tearsheet-step>
+ </cd-tearsheet>
+ `,
+ standalone: false
+})
+class MockFormHostComponent {
+ steps = [
+ { label: 'Step 1', complete: false },
+ { label: 'Step 2', complete: false }
+ ];
+ title = 'Form Host';
+ description = 'Form Host Description';
+
+ @ViewChild(TearsheetComponent)
+ tearsheet!: TearsheetComponent;
+
+ @ViewChild(MockFormStepComponent)
+ formStep!: MockFormStepComponent;
+}
+
describe('TearsheetComponent', () => {
let hostFixture: ComponentFixture<MockHostComponent>;
let hostComponent: MockHostComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [TearsheetComponent, TearsheetStepComponent, MockHostComponent],
+ declarations: [
+ TearsheetComponent,
+ TearsheetStepComponent,
+ MockHostComponent,
+ MockFormStepComponent,
+ MockFormHostComponent
+ ],
imports: [SharedModule],
providers: [
{
});
});
+ describe('nested form validation on next', () => {
+ it('should mark nested controls dirty and touched on next', () => {
+ const formHostFixture = TestBed.createComponent(MockFormHostComponent);
+ formHostFixture.detectChanges();
+ const formHost = formHostFixture.componentInstance;
+
+ const childControl = formHost.formStep.formGroup.get('parent.child');
+ expect(childControl).toBeTruthy();
+ expect(childControl?.dirty).toBe(false);
+ expect(childControl?.touched).toBe(false);
+
+ formHost.tearsheet.onNext();
+
+ expect(childControl?.dirty).toBe(true);
+ expect(childControl?.touched).toBe(true);
+ expect(childControl?.hasError('required')).toBe(true);
+ });
+ });
+
describe('[stepValid] seeding and live sync', () => {
it('should seed step as invalid immediately when stepValid starts false', () => {
hostComponent.step1Valid = false;
TemplateRef,
ViewEncapsulation
} from '@angular/core';
-import { FormBuilder } from '@angular/forms';
+import { AbstractControl, FormArray, FormBuilder, FormGroup } from '@angular/forms';
import { Step } from 'carbon-components-angular';
import { TearsheetStepComponent } from '../tearsheet-step/tearsheet-step.component';
import { ModalCdsService } from '../../services/modal-cds.service';
onNext() {
this.validateStep.emit({ step: this.currentStep });
-
const wrapper = this.stepContents?.toArray()?.[this.currentStep];
- const legacyForm = wrapper?.resolvedFormGroup;
- if (legacyForm) {
- legacyForm.markAllAsTouched();
- legacyForm.updateValueAndValidity({ emitEvent: true });
- this._updateStepInvalid(this.currentStep, legacyForm.invalid);
+ const currentForm = wrapper?.resolvedFormGroup;
+ currentForm?.markAllAsTouched();
+ this.markControlsDirtyAndValidate(currentForm);
+ if (currentForm) {
+ this._updateStepInvalid(this.currentStep, currentForm.invalid);
}
const canAdvance = wrapper ? wrapper.canProceed : true;
const form = wrapper.resolvedFormGroup;
if (!form) return;
form.markAllAsTouched();
- form.updateValueAndValidity({ emitEvent: true });
+ this.markControlsDirtyAndValidate(form);
this._updateStepInvalid(index, form.invalid);
});
this.submitRequested.emit(mergedPayloads);
}
+ private markControlsDirtyAndValidate(control: AbstractControl | null) {
+ if (!control) {
+ return;
+ }
+ if (control instanceof FormGroup || control instanceof FormArray) {
+ Object.values(control.controls).forEach((child) => this.markControlsDirtyAndValidate(child));
+ }
+ control.markAsDirty({ onlySelf: true });
+ control.updateValueAndValidity({ onlySelf: true, emitEvent: true });
+ }
+
closeFullTearsheet() {
this.cdsModalService.show(ConfirmationModalComponent, {
titleText: $localize`Are you sure you want to cancel ?`,