354 lines
13 KiB
TypeScript
354 lines
13 KiB
TypeScript
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();
|
||
});
|
||
});
|