Files
lmnp-organizer/frontend/src/app/components/lease-finalization-form/lease-finalization-form.spec.ts
T
nessar 878da155bd chore: add husky pre-commit hook and lint-staged configuration
- Created a pre-commit hook in .husky to run lint-staged.
- Added .lintstagedrc.mjs to format frontend files with Prettier and apply Spotless to backend Java files.
- Introduced .prettierignore to exclude specific directories from formatting.
2026-08-17 14:18:16 +02:00

283 lines
9.8 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 { FinalizationRevision } from '../../services/lease-finalization.service';
import { LeaseFinalizationForm } from './lease-finalization-form';
function eventFor(target: EventTarget): Event {
const event = new Event('change');
Object.defineProperty(event, 'target', { value: target });
return event;
}
describe('LeaseFinalizationForm', () => {
const revision: FinalizationRevision = {
number: 4,
templateVersion: '2026.10',
sourceFingerprint: 'source',
sha256: 'sha256',
generatedAt: '2026-07-19T10:00:00Z',
authorId: 'owner-1',
furnitureInventory: {
templateVersion: 'FR-MEUBLE-2025-01',
lines: [
{
id: 'line-1',
category: null,
presence: null,
equipmentKind: 'NONE',
privateAreaId: 'area-1',
designation: 'Chaise',
quantity: 1,
description: '',
},
],
},
privateAreas: [
{ id: 'area-1', family: 'PIECE_PRINCIPALE', subtype: 'LIVING_ROOM', label: 'Séjour' },
],
annexes: [],
warningCodes: [],
warnings: [],
};
function createForm(warningCodes: string[] = []) {
const fixture = TestBed.createComponent(LeaseFinalizationForm);
fixture.componentRef.setInput('revision', revision);
fixture.componentRef.setInput('warningCodes', warningCodes);
fixture.componentRef.setInput(
'warnings',
warningCodes.map((code) => ({ code, ruleVersion: '2026-07-01' })),
);
fixture.detectChanges();
return fixture;
}
it('emits the exact civil date string and confirmations', () => {
const fixture = createForm([
'SECURITY_DEPOSIT_ABOVE_TWO_MONTHS',
'IRL_REVISION_WITH_F_OR_G_DPE',
'FUTURE_WARNING',
]);
const component = fixture.componentInstance;
const file = new File(['lease'], 'bail-signe.pdf', { type: 'application/pdf' });
const requested = vi.fn();
component.finalizationRequested.subscribe(requested);
component.signedLease.set(file);
component.lastSignatureDate.set('2026-07-19');
component.allPartiesSigned.set(true);
component.revisionMatches.set(true);
component.discrepancyAnswer.set(false);
component.setWarning('SECURITY_DEPOSIT_ABOVE_TWO_MONTHS', true);
component.setWarning('IRL_REVISION_WITH_F_OR_G_DPE', true);
component.setWarning('FUTURE_WARNING', true);
component.submit();
expect(requested).toHaveBeenCalledWith({
signedLease: file,
revisionNumber: 4,
lastSignatureDate: '2026-07-19',
allPartiesSigned: true,
revisionMatches: true,
confirmedWarningCodes: [
'SECURITY_DEPOSIT_ABOVE_TWO_MONTHS',
'IRL_REVISION_WITH_F_OR_G_DPE',
'FUTURE_WARNING',
],
entryInventoryDiscrepancies: false,
inventoryCorrections: [],
allInventoryCorrectionsInitialed: false,
});
});
it('uses actionable French warning labels without exposing raw codes', () => {
const fixture = createForm([
'SECURITY_DEPOSIT_ABOVE_TWO_MONTHS',
'IRL_REVISION_WITH_F_OR_G_DPE',
'FUTURE_WARNING',
]);
const text = fixture.nativeElement.textContent;
expect(text).toContain('dépasse deux mois de loyer hors charges');
expect(text).toContain('confirme forcer ce montant');
expect(text).toContain('classé F ou G');
expect(text).toContain('maintenir la clause de révision IRL');
expect(text).toContain('avertissement légal signalé');
expect(text).not.toContain('SECURITY_DEPOSIT_ABOVE_TWO_MONTHS');
expect(text).not.toContain('FUTURE_WARNING');
expect(text).not.toContain('IRL_REVISION_WITH_F_OR_G_DPE');
expect(text).toContain('Version de règle : 2026-07-01');
});
it('explains every document warning precisely in French', () => {
const warningCodes = [
'DPE_MISSING',
'DPE_EXPIRED',
'DIAGNOSTIC_EXPIRED',
'CO_OWNERSHIP_RULES_EXTRACT_MISSING',
'PRIOR_RENTAL_AUTHORIZATION_MISSING',
];
const fixture = createForm(warningCodes);
const text = fixture.nativeElement.textContent;
expect(text).toContain('DPE est absent du dossier');
expect(text).toContain('DPE présent dans le dossier est expiré');
expect(text).toContain('diagnostic autre que le DPE est expiré');
expect(text).toContain('extrait du règlement de copropriété manque');
expect(text).toContain('autorisation préalable de mise en location manque');
for (const code of warningCodes) expect(text).not.toContain(code);
});
it('blocks an incomplete submission with an actionable error', () => {
const fixture = createForm(['SECURITY_DEPOSIT_ABOVE_TWO_MONTHS']);
const requested = vi.fn();
fixture.componentInstance.finalizationRequested.subscribe(requested);
fixture.componentInstance.submit();
fixture.detectChanges();
expect(requested).not.toHaveBeenCalled();
expect(fixture.nativeElement.textContent).toContain('Sélectionnez le bail signé');
});
it('blocks finalization while a required guarantee deed is missing', () => {
const fixture = createForm();
fixture.componentRef.setInput('guaranteesComplete', false);
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('button[type=submit]').disabled).toBe(true);
expect(fixture.nativeElement.textContent).toContain('Téléversez tous les actes');
});
it('sends an initialed structured inventory correction after a yes answer', () => {
const fixture = createForm();
const component = fixture.componentInstance;
const file = new File(['lease'], 'bail-signe.pdf', { type: 'application/pdf' });
const requested = vi.fn();
component.finalizationRequested.subscribe(requested);
component.signedLease.set(file);
component.lastSignatureDate.set('2026-07-19');
component.allPartiesSigned.set(true);
component.revisionMatches.set(true);
component.discrepancyAnswer.set(true);
component.inventoryCorrections.set([
{
id: 'correction-1',
inventoryLineId: null,
privateAreaId: 'area-1',
designation: 'Chaise',
quantity: 0,
description: 'Absente',
},
]);
component.allInventoryCorrectionsInitialed.set(true);
component.submit();
expect(requested).toHaveBeenCalledWith(
expect.objectContaining({
entryInventoryDiscrepancies: true,
inventoryCorrections: [
expect.objectContaining({ id: 'correction-1', quantity: 0 }),
],
allInventoryCorrectionsInitialed: true,
}),
);
});
it('prefills known entry discrepancies with readable inventory choices', () => {
const fixture = createForm();
fixture.componentRef.setInput('suggestedInventoryCorrections', [
{
id: 'entry-line-1',
inventoryLineId: 'line-1',
privateAreaId: 'area-1',
designation: 'Chaise',
quantity: 0,
description: 'Chaise manquante',
},
]);
fixture.componentInstance.answerYes();
fixture.detectChanges();
expect(fixture.componentInstance.inventoryCorrections()).toEqual([
expect.objectContaining({ inventoryLineId: 'line-1', quantity: 0 }),
]);
expect(fixture.nativeElement.textContent).toContain('Chaise');
expect(fixture.nativeElement.textContent).toContain('Séjour');
expect(fixture.nativeElement.textContent).not.toContain('ID d’espace privatif');
});
it('accepts only a PDF upload, captures the civil date, and toggles warning confirmations', () => {
const fixture = createForm(['DPE_MISSING']);
const component = fixture.componentInstance;
const input = document.createElement('input');
const invalid = new File(['text'], 'notes.txt', { type: 'text/plain' });
Object.defineProperty(input, 'files', {
value: { item: () => invalid },
configurable: true,
});
component.selectSignedLease({ target: input } as unknown as Event);
expect(component.signedLease()).toBeNull();
expect(component.validationError()).toContain('format PDF');
const pdf = new File(['pdf'], 'bail.pdf', { type: 'application/pdf' });
Object.defineProperty(input, 'files', { value: { item: () => pdf }, configurable: true });
component.selectSignedLease({ target: input } as unknown as Event);
const date = document.createElement('input');
date.value = '2026-07-20';
component.setLastSignatureDate(eventFor(date));
component.setWarning('DPE_MISSING', true);
component.setWarning('DPE_MISSING', false);
expect(component.signedLease()).toBe(pdf);
expect(component.lastSignatureDate()).toBe('2026-07-20');
expect(component.confirmedWarningCodes().size).toBe(0);
expect(component.warningLabel('DPE_MISSING')).toContain('DPE');
});
it('edits a handwritten inventory correction from the revision snapshot', () => {
const fixture = createForm();
const component = fixture.componentInstance;
const requested = vi.fn();
component.finalizationRequested.subscribe(requested);
component.signedLease.set(new File(['pdf'], 'bail.pdf', { type: 'application/pdf' }));
component.lastSignatureDate.set('2026-07-20');
component.allPartiesSigned.set(true);
component.revisionMatches.set(true);
const dialog = fixture.nativeElement.querySelector('dialog');
Object.defineProperty(dialog, 'close', { value: vi.fn() });
component.answerYes();
const select = document.createElement('select');
select.append(new Option('Chaise', 'line-1'));
select.value = 'line-1';
component.selectInventoryLine(0, eventFor(select));
const description = document.createElement('textarea');
description.value = 'Pied rayé';
component.setCorrection(0, 'description', eventFor(description));
component.setCorrection(0, 'inventoryLineId', eventFor(document.createElement('input')));
component.continueWithCorrections();
expect(component.validationError()).toContain('correction');
component.selectInventoryLine(0, eventFor(select));
component.allInventoryCorrectionsInitialed.set(true);
component.continueWithCorrections();
expect(requested).toHaveBeenCalledWith(
expect.objectContaining({
entryInventoryDiscrepancies: true,
inventoryCorrections: [
expect.objectContaining({
inventoryLineId: 'line-1',
description: 'Pied rayé',
}),
],
}),
);
component.removeCorrection(0);
expect(component.inventoryCorrections()).toEqual([]);
});
});