refactor: address loading review findings (#18)
This commit is contained in:
1 parent
ec3319494b
commit
0f68a91c5d
26 files changed
+607
-364
No files matched your search
@@ -27,17 +27,6 @@
|
||||
background: var(--surface);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.primary-action mat-spinner {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
@if (error()) {
|
||||
<div class="alert-error" role="alert">{{ error() }}</div>
|
||||
}
|
||||
<fieldset [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<p class="visually-hidden" role="status" aria-live="polite">
|
||||
{{ saving() ? 'Enregistrement en cours.' : '' }}
|
||||
</p>
|
||||
<fieldset class="busy-fieldset" [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<div class="form-grid">
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Nom de l'appartement</mat-label
|
||||
@@ -77,7 +80,7 @@
|
||||
<div class="form-actions">
|
||||
<button mat-button type="button" (click)="closed.emit()">Annuler</button
|
||||
><button mat-flat-button type="submit" class="primary-action">
|
||||
<span aria-live="polite">
|
||||
<span>
|
||||
@if (saving()) {
|
||||
<mat-spinner diameter="18" aria-hidden="true" /> Enregistrement…
|
||||
} @else {
|
||||
|
||||
@@ -58,6 +58,7 @@ describe('ApartmentForm', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.querySelector('fieldset').disabled).toBe(true);
|
||||
expect(fixture.nativeElement.querySelector('[role=status]').closest('fieldset')).toBeNull();
|
||||
expect(fixture.nativeElement.textContent).toContain('Enregistrement…');
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Impossible d’enregistrer cet appartement.',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { Apartment } from '../../services/apartment.service';
|
||||
import { LoadState } from '../../services/workspace.service';
|
||||
import { LoadState } from '../../services/workspace-resource';
|
||||
|
||||
@Component({
|
||||
selector: 'app-apartment-list',
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
} @else {
|
||||
<app-apartment-form
|
||||
[apartment]="editingApartment()"
|
||||
[saving]="workspace.apartmentSaving()"
|
||||
[saveError]="workspace.apartmentSaveError()"
|
||||
[saving]="workspace.apartmentSaving(editingApartment()?.id)"
|
||||
[saveError]="workspace.apartmentSaveError(editingApartment()?.id)"
|
||||
(closed)="closeForm()"
|
||||
(saved)="saveApartment($event)"
|
||||
/>
|
||||
|
||||
@@ -9,8 +9,9 @@ describe('ApartmentsPage', () => {
|
||||
const workspace = {
|
||||
apartments: signal<Apartment[]>([]),
|
||||
apartmentLoadState: signal({ loaded: false, loading: true, error: '' }),
|
||||
apartmentSaving: signal(false),
|
||||
apartmentSaveError: signal(''),
|
||||
apartmentSaving: vi.fn(() => false),
|
||||
apartmentSaveError: vi.fn(() => ''),
|
||||
clearApartmentSaveError: vi.fn(),
|
||||
deletingApartmentIds: signal<ReadonlySet<string>>(new Set()),
|
||||
apartmentDeleteErrors: signal<Record<string, string>>({}),
|
||||
loadApartments: () => undefined,
|
||||
|
||||
@@ -20,13 +20,13 @@ export class ApartmentsPage {
|
||||
}
|
||||
|
||||
openAddForm(): void {
|
||||
this.workspace.apartmentSaveError.set('');
|
||||
this.workspace.clearApartmentSaveError();
|
||||
this.editingApartment.set(null);
|
||||
this.showForm.set(true);
|
||||
}
|
||||
|
||||
openEditForm(apartment: Apartment): void {
|
||||
this.workspace.apartmentSaveError.set('');
|
||||
this.workspace.clearApartmentSaveError(apartment.id);
|
||||
this.editingApartment.set(apartment);
|
||||
this.showForm.set(true);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,30 @@ import { LeaseCard } from './lease-card';
|
||||
describe('LeaseCard', () => {
|
||||
it('resolves apartment, tenants, and every status label', () => {
|
||||
const fixture = TestBed.createComponent(LeaseCard);
|
||||
fixture.componentRef.setInput('apartments', [{ id: 'apartment-1', name: 'T2', address: '', purchasePrice: 0, description: '' }]);
|
||||
fixture.componentRef.setInput('apartments', [
|
||||
{ id: 'apartment-1', name: 'T2', address: '', purchasePrice: 0, description: '' },
|
||||
]);
|
||||
fixture.componentRef.setInput('tenants', [
|
||||
{ id: 'tenant-1', firstName: 'Camille', lastName: 'Martin', email: '', phone: '', address: '' },
|
||||
{
|
||||
id: 'tenant-1',
|
||||
firstName: 'Camille',
|
||||
lastName: 'Martin',
|
||||
email: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
},
|
||||
{ id: 'tenant-2', firstName: 'Alex', lastName: 'Durand', email: '', phone: '', address: '' },
|
||||
]);
|
||||
fixture.componentRef.setInput('lease', {
|
||||
apartmentId: 'apartment-1', tenantIds: ['tenant-1', 'missing', 'tenant-2'], guarantors: [], startDate: '', endDate: '', monthlyRent: 0, monthlyCharges: 0, securityDeposit: 0, status: 'DRAFT',
|
||||
apartmentId: 'apartment-1',
|
||||
tenantIds: ['tenant-1', 'missing', 'tenant-2'],
|
||||
guarantors: [],
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
monthlyRent: 0,
|
||||
monthlyCharges: 0,
|
||||
securityDeposit: 0,
|
||||
status: 'DRAFT',
|
||||
});
|
||||
const component = fixture.componentInstance;
|
||||
|
||||
@@ -19,8 +36,41 @@ describe('LeaseCard', () => {
|
||||
expect(component.statusLabel()).toBe('Bail en préparation');
|
||||
fixture.componentRef.setInput('lease', { ...component.lease(), status: 'ACTIVE' });
|
||||
expect(component.statusLabel()).toBe('Bail actif');
|
||||
fixture.componentRef.setInput('lease', { ...component.lease(), apartmentId: 'missing', status: 'ARCHIVED' });
|
||||
fixture.componentRef.setInput('lease', {
|
||||
...component.lease(),
|
||||
apartmentId: 'missing',
|
||||
status: 'ARCHIVED',
|
||||
});
|
||||
expect(component.apartmentName()).toBe('Bien inconnu');
|
||||
expect(component.statusLabel()).toBe('Bail archivé');
|
||||
});
|
||||
|
||||
it('shows local Bail PDF download progress and failure', () => {
|
||||
const fixture = TestBed.createComponent(LeaseCard);
|
||||
fixture.componentRef.setInput('apartments', []);
|
||||
fixture.componentRef.setInput('tenants', []);
|
||||
fixture.componentRef.setInput('lease', {
|
||||
id: 'lease-1',
|
||||
apartmentId: 'apartment-1',
|
||||
tenantIds: [],
|
||||
guarantors: [],
|
||||
startDate: '2026-01-01',
|
||||
endDate: '2027-01-01',
|
||||
monthlyRent: 800,
|
||||
monthlyCharges: 50,
|
||||
securityDeposit: 800,
|
||||
status: 'ACTIVE',
|
||||
});
|
||||
fixture.componentRef.setInput('downloading', true);
|
||||
fixture.detectChanges();
|
||||
|
||||
const download = fixture.nativeElement.querySelector('.card-actions button');
|
||||
expect(download.disabled).toBe(true);
|
||||
expect(download.textContent).toContain('Téléchargement…');
|
||||
|
||||
fixture.componentRef.setInput('downloading', false);
|
||||
fixture.componentRef.setInput('downloadError', 'Impossible de télécharger ce bail.');
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.textContent).toContain('Impossible de télécharger ce bail.');
|
||||
});
|
||||
});
|
||||
@@ -28,17 +28,6 @@
|
||||
border-radius: 22px;
|
||||
background: var(--surface);
|
||||
}
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.primary-action mat-spinner {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.guarantor-section {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,10 @@
|
||||
@if (error()) {
|
||||
<div class="alert-error" role="alert">{{ error() }}</div>
|
||||
}
|
||||
<fieldset [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<p class="visually-hidden" role="status" aria-live="polite">
|
||||
{{ saving() ? 'Enregistrement en cours.' : '' }}
|
||||
</p>
|
||||
<fieldset class="busy-fieldset" [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<section class="form-section">
|
||||
<div class="section-heading">
|
||||
<span>01</span>
|
||||
@@ -143,7 +146,7 @@
|
||||
</section>
|
||||
</fieldset>
|
||||
</form>
|
||||
<fieldset [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<fieldset class="busy-fieldset" [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<section class="guarantor-section">
|
||||
<div class="section-heading">
|
||||
<span>03</span>
|
||||
@@ -171,7 +174,7 @@
|
||||
class="primary-action"
|
||||
[disabled]="saving()"
|
||||
>
|
||||
<span aria-live="polite">
|
||||
<span>
|
||||
@if (saving()) {
|
||||
<mat-spinner diameter="18" aria-hidden="true" /> Enregistrement…
|
||||
} @else {
|
||||
|
||||
@@ -60,8 +60,8 @@
|
||||
[tenants]="workspace.tenants()"
|
||||
[deleting]="!!lease.id && workspace.deletingLeaseIds().has(lease.id)"
|
||||
[deleteError]="lease.id ? (workspace.leaseDeleteErrors()[lease.id] ?? '') : ''"
|
||||
[downloading]="!!lease.id && workspace.downloadingLeaseIds().has(lease.id)"
|
||||
[downloadError]="lease.id ? (workspace.leaseDownloadErrors()[lease.id] ?? '') : ''"
|
||||
[downloading]="!!lease.id && workspace.leaseDownloading(lease.id)"
|
||||
[downloadError]="lease.id ? workspace.leaseDownloadError(lease.id) : ''"
|
||||
(pdfRequested)="workspace.downloadLeasePdf($event)"
|
||||
(deleteRequested)="workspace.deleteLease($event)"
|
||||
/>
|
||||
|
||||
@@ -17,8 +17,11 @@ describe('LeasesPage', () => {
|
||||
saveLease: vi.fn(),
|
||||
deleteLease: vi.fn(),
|
||||
downloadLeasePdf: vi.fn(),
|
||||
leaseSaving: signal(false),
|
||||
leaseSaveError: signal(''),
|
||||
leaseSaving: vi.fn(() => false),
|
||||
leaseSaveError: vi.fn(() => ''),
|
||||
clearLeaseSaveError: vi.fn(),
|
||||
leaseDownloading: vi.fn(() => false),
|
||||
leaseDownloadError: vi.fn(() => ''),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -41,7 +41,7 @@ export class LeasesPage {
|
||||
}
|
||||
|
||||
openForm(): void {
|
||||
this.workspace.leaseSaveError.set('');
|
||||
this.workspace.clearLeaseSaveError();
|
||||
this.showForm.set(true);
|
||||
}
|
||||
closeForm(): void {
|
||||
|
||||
@@ -41,6 +41,23 @@ describe('TenantCard', () => {
|
||||
expect(fixture.nativeElement.textContent).toContain('Envoi : 50 %');
|
||||
});
|
||||
|
||||
it('falls back to indeterminate upload feedback when no total is available', () => {
|
||||
const fixture = TestBed.createComponent(TenantCard);
|
||||
fixture.componentRef.setInput('tenant', {
|
||||
id: 'tenant-1',
|
||||
firstName: 'Camille',
|
||||
lastName: 'Martin',
|
||||
email: 'camille@example.com',
|
||||
phone: '',
|
||||
address: '',
|
||||
});
|
||||
fixture.componentRef.setInput('uploading', true);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('Envoi…');
|
||||
expect(fixture.nativeElement.querySelector('mat-progress-bar')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('shows progress only on the selected attachment download', () => {
|
||||
const fixture = TestBed.createComponent(TenantCard);
|
||||
fixture.componentRef.setInput('tenant', {
|
||||
|
||||
@@ -26,12 +26,6 @@
|
||||
border-radius: 22px;
|
||||
background: var(--surface);
|
||||
}
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.alert-error {
|
||||
margin-bottom: 18px;
|
||||
padding: 13px 15px;
|
||||
@@ -40,11 +34,6 @@ fieldset {
|
||||
background: var(--danger-soft);
|
||||
color: var(--danger);
|
||||
}
|
||||
.primary-action mat-spinner {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.form-grid {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
@if (saveError()) {
|
||||
<div class="alert-error" role="alert">{{ saveError() }}</div>
|
||||
}
|
||||
<fieldset [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<p class="visually-hidden" role="status" aria-live="polite">
|
||||
{{ saving() ? 'Enregistrement en cours.' : '' }}
|
||||
</p>
|
||||
<fieldset class="busy-fieldset" [disabled]="saving()" [attr.inert]="saving() ? '' : null">
|
||||
<div class="form-grid">
|
||||
<mat-form-field appearance="outline"
|
||||
><mat-label>Prénom</mat-label
|
||||
@@ -79,7 +82,7 @@
|
||||
<div class="form-actions">
|
||||
<button mat-button type="button" (click)="cancelled.emit()">Annuler</button
|
||||
><button mat-flat-button type="submit" class="primary-action">
|
||||
<span aria-live="polite">
|
||||
<span>
|
||||
@if (saving()) {
|
||||
<mat-spinner diameter="18" aria-hidden="true" /> Enregistrement…
|
||||
} @else {
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
[attachmentError]="
|
||||
tenant.id ? (workspace.tenantAttachmentErrors()[tenant.id] ?? '') : ''
|
||||
"
|
||||
[uploading]="workspace.uploadingTenantIds().has(tenant.id!)"
|
||||
[uploading]="tenant.id ? workspace.uploadingTenantIds().has(tenant.id) : false"
|
||||
[uploadProgress]="
|
||||
tenant.id ? (workspace.tenantUploadProgress()[tenant.id] ?? null) : null
|
||||
"
|
||||
@@ -83,8 +83,8 @@
|
||||
} @else {
|
||||
<app-tenant-form
|
||||
[tenant]="editingTenant()"
|
||||
[saving]="workspace.tenantSaving()"
|
||||
[saveError]="workspace.tenantSaveError()"
|
||||
[saving]="workspace.tenantSaving(editingTenant()?.id)"
|
||||
[saveError]="workspace.tenantSaveError(editingTenant()?.id)"
|
||||
(cancelled)="closeForm()"
|
||||
(submitted)="saveTenant($event)"
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { signal } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { TenantsPage } from './tenants-page';
|
||||
import { WorkspaceService } from '../../services/workspace.service';
|
||||
@@ -9,7 +8,7 @@ describe('TenantsPage', () => {
|
||||
providers: [
|
||||
{
|
||||
provide: WorkspaceService,
|
||||
useValue: { loadTenants: () => undefined, tenantSaveError: signal('') },
|
||||
useValue: { loadTenants: () => undefined, clearTenantSaveError: () => undefined },
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -23,12 +23,12 @@ export class TenantsPage {
|
||||
}
|
||||
|
||||
openAddForm(): void {
|
||||
this.workspace.tenantSaveError.set('');
|
||||
this.workspace.clearTenantSaveError();
|
||||
this.editingTenant.set(null);
|
||||
this.showForm.set(true);
|
||||
}
|
||||
openEditForm(tenant: Tenant): void {
|
||||
this.workspace.tenantSaveError.set('');
|
||||
this.workspace.clearTenantSaveError(tenant.id);
|
||||
this.editingTenant.set(tenant);
|
||||
this.showForm.set(true);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user