Validate private areas across apartment form (#44)
This commit is contained in:
1 parent
b9f5540394
commit
72d3f0ccfa
9 files changed
+323
-44
No files matched your search
@@ -102,7 +102,9 @@
|
||||
<app-apartment-legal-profile-form [legalProfileForm]="apartmentForm.legalProfile" />
|
||||
<app-private-area-editor
|
||||
[areas]="apartmentModel().privateAreas"
|
||||
[serverErrors]="saveFieldErrors()"
|
||||
(areasChanged)="setPrivateAreas($event)"
|
||||
(fieldChanged)="fieldChanged.emit($event)"
|
||||
/>
|
||||
|
||||
<div class="form-actions">
|
||||
|
||||
@@ -94,6 +94,68 @@ describe('ApartmentForm', () => {
|
||||
expect(saved).toHaveBeenCalledWith(expect.objectContaining({ purchasePrice: 0.5 }));
|
||||
});
|
||||
|
||||
it('keeps invalid private-area cards visible, shows their inline error, and focuses the label', async () => {
|
||||
const fixture = TestBed.createComponent(ApartmentForm);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const form = fixture.componentInstance;
|
||||
const saved = vi.fn();
|
||||
form.saved.subscribe(saved);
|
||||
form.apartmentModel.set({
|
||||
name: 'Studio',
|
||||
address: '1 rue du Test',
|
||||
purchasePrice: 1,
|
||||
description: '',
|
||||
privateAreas: [
|
||||
{ id: 'area-1', family: 'PIECE_PRINCIPALE', subtype: 'OTHER', label: ' ' },
|
||||
],
|
||||
legalProfile: emptyApartmentLegalProfile(),
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
form.save();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(saved).not.toHaveBeenCalled();
|
||||
expect(form.error()).toContain('Renseignez le libellé de chaque espace privatif.');
|
||||
expect(fixture.nativeElement.querySelectorAll('mat-card')).toHaveLength(1);
|
||||
expect(fixture.nativeElement.textContent).toContain('Le libellé est obligatoire.');
|
||||
expect(document.activeElement).toBe(
|
||||
fixture.nativeElement.querySelector('.private-area-label'),
|
||||
);
|
||||
});
|
||||
|
||||
it('forwards only the edited private-area server error path', async () => {
|
||||
const fixture = TestBed.createComponent(ApartmentForm);
|
||||
fixture.componentRef.setInput('saveFieldErrors', [
|
||||
{ field: 'privateAreas[0].label', code: 'not_blank' },
|
||||
{ field: 'privateAreas[0].subtypeCompatibleWithFamily', code: 'assert_true' },
|
||||
]);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const changed = vi.fn();
|
||||
fixture.componentInstance.fieldChanged.subscribe(changed);
|
||||
fixture.componentInstance.apartmentModel.set({
|
||||
name: 'Studio',
|
||||
address: '1 rue du Test',
|
||||
purchasePrice: 1,
|
||||
description: '',
|
||||
privateAreas: [
|
||||
{ id: 'area-1', family: 'PIECE_PRINCIPALE', subtype: 'OTHER', label: 'Séjour' },
|
||||
],
|
||||
legalProfile: emptyApartmentLegalProfile(),
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.nativeElement
|
||||
.querySelector('.private-area-label')
|
||||
.dispatchEvent(new Event('input'));
|
||||
|
||||
expect(changed).toHaveBeenCalledTimes(1);
|
||||
expect(changed).toHaveBeenCalledWith('privateAreas[0].label');
|
||||
});
|
||||
|
||||
it('shows backend core errors beside their controls and clears one when it is edited', async () => {
|
||||
const fixture = TestBed.createComponent(ApartmentForm);
|
||||
const form = fixture.componentInstance;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, effect, input, output, signal } from '@angular/core';
|
||||
import { Component, effect, input, output, signal, viewChild } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormRoot, FormField, form, max, min, required } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -49,6 +49,7 @@ export class ApartmentForm {
|
||||
readonly closed = output<void>();
|
||||
readonly saved = output<Apartment>();
|
||||
readonly fieldChanged = output<string>();
|
||||
readonly privateAreaEditor = viewChild(PrivateAreaEditor);
|
||||
|
||||
readonly apartmentModel = signal<Apartment>({
|
||||
name: '',
|
||||
@@ -91,13 +92,21 @@ export class ApartmentForm {
|
||||
save(): void {
|
||||
if (this.saving()) return;
|
||||
this.apartmentForm().markAsTouched();
|
||||
const privateAreasValid = this.privateAreaEditor()?.validate() ?? true;
|
||||
if (
|
||||
this.apartmentForm().invalid() ||
|
||||
!this.apartmentModel().name.trim() ||
|
||||
!this.apartmentModel().address.trim()
|
||||
!this.apartmentModel().address.trim() ||
|
||||
!privateAreasValid
|
||||
) {
|
||||
const firstError = this.apartmentForm().errorSummary()[0];
|
||||
this.error.set(firstError?.message ?? this.coreFieldError() ?? nameRequiredError);
|
||||
this.error.set(
|
||||
firstError?.message ??
|
||||
this.coreFieldError() ??
|
||||
this.privateAreaEditor()?.validationMessage() ??
|
||||
nameRequiredError,
|
||||
);
|
||||
if (!privateAreasValid) this.privateAreaEditor()?.focusFirstInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user