Files
lmnp-organizer/frontend/src/app/components/lease-card/lease-card.spec.ts
T

354 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
});
});