import { TestBed } from '@angular/core/testing'; import { vi } from 'vitest'; import { LeaseCard } from './lease-card'; import { emptyApartmentLegalProfile } from '../../services/apartment.service'; import { emptyLeaseLegalTerms } from '../../services/lease.service'; import { provideHttpClient } from '@angular/common/http'; import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { WorkspaceService } from '../../services/workspace.service'; 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: '', legalProfile: emptyApartmentLegalProfile(), }, ]); fixture.componentRef.setInput('tenants', [ { 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: [], specialClauses: [], legalTerms: emptyLeaseLegalTerms(), leaseType: 'STANDARD', startDate: '', endDate: '', monthlyRent: 0, monthlyCharges: 0, securityDeposit: 0, dueDay: 5, chargeMethod: 'FLAT_RATE', indexFlatRate: false, status: 'DRAFT', }); const component = fixture.componentInstance; expect(component.apartmentName()).toBe('T2'); expect(component.tenantNames()).toBe('Camille Martin, Alex Durand'); 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', }); 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: [], specialClauses: [], legalTerms: emptyLeaseLegalTerms(), leaseType: 'STANDARD', startDate: '2026-07-19', endDate: '2027-07-19', monthlyRent: 800, monthlyCharges: 50, securityDeposit: 800, dueDay: 5, chargeMethod: 'FLAT_RATE', indexFlatRate: false, status: 'ACTIVE', }); fixture.componentRef.setInput('downloading', true); fixture.componentRef.setInput('revision', { number: 3, templateVersion: '2026.10', sourceFingerprint: 'source', sha256: 'sha256', generatedAt: '2026-07-19T10:00:00Z', authorId: 'owner-1', annexes: [ { category: 'NOTICE', documentType: null, sourceId: '', version: 'FR-NOTICE-2023-03', fileName: 'notice-information.pdf', sha256: 'notice-sha256', establishedOn: null, expiresOn: null, sourceUploadedAt: null, }, { category: 'APARTMENT_DOCUMENT', documentType: 'DPE', sourceId: 'document-1', version: '', fileName: 'dpe.pdf', sha256: 'dpe-sha256', establishedOn: '2026-07-19', expiresOn: '2036-07-19', sourceUploadedAt: '2026-07-19T10:00:00Z', }, ], warningCodes: ['DPE_EXPIRED', 'CO_OWNERSHIP_RULES_EXTRACT_MISSING'], warnings: [ { code: 'DPE_EXPIRED', ruleVersion: '2026-07-01' }, { code: 'CO_OWNERSHIP_RULES_EXTRACT_MISSING', ruleVersion: '2026-07-01' }, ], }); fixture.componentRef.setInput('dossierDownloading', 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.'); expect(fixture.nativeElement.textContent).toContain('Révision 3 · modèle 2026.10'); expect(fixture.nativeElement.textContent).toContain('Notice d’information'); expect(fixture.nativeElement.textContent).toContain( 'Diagnostic de performance énergétique (DPE)', ); expect(fixture.nativeElement.textContent).toContain('dpe-sha256'); expect(fixture.nativeElement.textContent).toContain('19 juillet 2026'); expect(fixture.nativeElement.textContent).not.toContain('18 juillet 2026'); expect(fixture.nativeElement.textContent).toContain( 'Le DPE présent dans le dossier est expiré.', ); expect(fixture.nativeElement.textContent).toContain( 'L’extrait du règlement de copropriété manque au dossier.', ); expect(fixture.nativeElement.textContent).toContain('Version de règle : 2026-07-01'); expect(fixture.nativeElement.textContent).not.toContain('DPE_EXPIRED'); const dossier: HTMLButtonElement = fixture.nativeElement.querySelector('.dossier-download'); expect(dossier.disabled).toBe(true); expect(dossier.textContent).toContain('Préparation du dossier…'); expect(download.textContent).toContain('Télécharger le bail'); fixture.componentRef.setInput('downloadError', ''); fixture.componentRef.setInput( 'dossierDownloadError', 'Impossible de télécharger le dossier complet du bail.', ); fixture.detectChanges(); expect(fixture.nativeElement.textContent).toContain( 'Impossible de télécharger le dossier complet du bail.', ); }); it('offers draft editing and dedicated finalization actions', () => { 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: [], specialClauses: [], legalTerms: emptyLeaseLegalTerms(), leaseType: 'STANDARD', startDate: '2026-01-01', endDate: '2027-01-01', monthlyRent: 800, monthlyCharges: 50, securityDeposit: 800, dueDay: 5, chargeMethod: 'FLAT_RATE', indexFlatRate: false, status: 'DRAFT', }); fixture.detectChanges(); const requested = vi.fn(); fixture.componentInstance.finalizationRequested.subscribe(requested); const text = fixture.nativeElement.textContent.replace(/\s+/g, ' '); expect(text).toContain('Période initiale 1 janvier 2026 → 1 janvier 2027 · bail reconductible'); expect(text).toContain('Générer / télécharger le bail'); expect(text).toContain('Modifier'); expect(text).toContain('Supprimer'); expect(text).toContain('Finaliser le bail'); expect(text).not.toContain('État des lieux d’entrée'); expect(text).not.toContain('Activer'); const finalization = [...fixture.nativeElement.querySelectorAll('button')].find((button) => button.textContent.includes('Finaliser le bail'), ); finalization.click(); expect(requested).toHaveBeenCalledWith('lease-1'); fixture.componentRef.setInput('revision', { number: 1, templateVersion: 'FR-MEUBLE-2025-01', sourceFingerprint: 'source', sha256: 'sha256', generatedAt: '2026-01-01T10:00:00Z', authorId: 'owner-1', annexes: [], warningCodes: [], warnings: [], }); fixture.detectChanges(); const conditionReportRequested = vi.fn(); fixture.componentInstance.conditionReportRequested.subscribe(conditionReportRequested); const entryReport = [...fixture.nativeElement.querySelectorAll('button')].find((button) => button.textContent.includes('État des lieux d’entrée'), ); entryReport.click(); expect(conditionReportRequested).toHaveBeenCalledWith({ leaseId: 'lease-1', type: 'ENTRY' }); fixture.componentRef.setInput('lease', { ...fixture.componentInstance.lease(), status: 'ACTIVE', }); fixture.detectChanges(); expect(fixture.nativeElement.textContent).not.toContain('Modifier'); expect(fixture.nativeElement.textContent).not.toContain('Supprimer'); expect(fixture.nativeElement.textContent).not.toContain('Finaliser le bail'); }); it('manages an active single lease and keeps read access for colocation and archives', () => { const fixture = TestBed.createComponent(LeaseCard); fixture.componentRef.setInput('apartments', []); fixture.componentRef.setInput('tenants', []); fixture.componentRef.setInput('lease', { id: 'lease-1', apartmentId: 'apartment-1', tenantIds: ['tenant-1'], guarantors: [], specialClauses: [], legalTerms: emptyLeaseLegalTerms(), leaseType: 'STANDARD', startDate: '2026-01-01', endDate: '2028-12-31', monthlyRent: 800, monthlyCharges: 50, securityDeposit: 800, dueDay: 5, chargeMethod: 'FLAT_RATE', indexFlatRate: false, status: 'ACTIVE', }); const requested = vi.fn(); fixture.componentInstance.leaseEndRequested.subscribe(requested); fixture.detectChanges(); const action: HTMLButtonElement = fixture.nativeElement.querySelector('.lease-end-action'); expect(action.textContent).toContain('Gérer la fin du bail'); action.click(); expect(requested).toHaveBeenCalledWith('lease-1'); expect(fixture.nativeElement.textContent).not.toContain('Archiver'); fixture.componentRef.setInput('lease', { ...fixture.componentInstance.lease(), tenantIds: ['tenant-1', 'tenant-2'], }); fixture.detectChanges(); const colocationAction: HTMLButtonElement = fixture.nativeElement.querySelector('.lease-end-action'); expect(colocationAction.textContent).toContain('Voir la fin du bail'); expect(fixture.nativeElement.textContent).toContain( 'La fin de bail en colocation n’est pas encore prise en charge', ); fixture.componentRef.setInput('lease', { ...fixture.componentInstance.lease(), status: 'ARCHIVED', }); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('.lease-end-action').textContent).toContain( 'Voir la fin du bail', ); }); it('offers an IRL preview only for an active lease with the revision clause enabled', () => { TestBed.configureTestingModule({ providers: [ provideHttpClient(), provideHttpClientTesting(), { provide: WorkspaceService, useValue: { acceptRentRevisionApplication: vi.fn() } }, ], }); const http = TestBed.inject(HttpTestingController); const initialLegalTerms = emptyLeaseLegalTerms(); const legalTerms = { ...initialLegalTerms, rentRevision: { ...initialLegalTerms.rentRevision, enabled: true }, }; 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: [], specialClauses: [], legalTerms, leaseType: 'STANDARD', startDate: '2026-07-19', endDate: '2027-07-19', monthlyRent: 800, monthlyCharges: 50, securityDeposit: 800, dueDay: 5, chargeMethod: 'FLAT_RATE', indexFlatRate: false, status: 'ACTIVE', }); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('app-rent-revision-preview')).toBeNull(); http.expectNone('/api/leases/lease-1/rent-revisions'); const openRevision: HTMLButtonElement = fixture.nativeElement.querySelector('.open-rent-revision'); expect(openRevision.getAttribute('aria-expanded')).toBe('false'); openRevision.click(); fixture.detectChanges(); expect(openRevision.getAttribute('aria-expanded')).toBe('true'); expect(fixture.nativeElement.querySelector('app-rent-revision-preview')).not.toBeNull(); http.expectOne('/api/leases/lease-1/rent-revisions').flush([]); fixture.nativeElement.querySelector('.preview-submit').click(); http.expectOne('/api/leases/lease-1/rent-revisions/preview').flush({}); fixture.componentRef.setInput('lease', { ...fixture.componentInstance.lease(), status: 'DRAFT', }); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('app-rent-revision-preview')).toBeNull(); http.verify(); }); });