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.
This commit is contained in:
nessar committed 2026-08-17 14:18:16 +02:00
1 parent 5d2d85aa8c
commit 878da155bd
564 files changed
+61467 -47179

No files matched your search

@@ -4,244 +4,244 @@ import { ApartmentForm } from './apartment-form';
import { emptyApartmentLegalProfile } from '../../services/apartment.service';
describe('ApartmentForm', () => {
const completeLegalProfile = {
taxIdentifier: '751150001234',
buildingType: 'APARTMENT_BUILDING' as const,
ownershipRegime: 'Copropriété',
constructionPeriod: '1990_2005' as const,
habitableSurface: 42,
equipment: 'Cuisine équipée',
heating: 'Individuel électrique',
hotWater: 'Ballon électrique',
energyRating: 'D' as const,
estimatedAnnualEnergyCost: 850,
estimatedAnnualEnergyCostReferenceYear: 2021,
destination: 'Habitation',
privateAccessories: 'Cave',
commonAreas: 'Local vélos',
internetAccess: 'Fibre',
departmentCode: '75',
};
const completeLegalProfile = {
taxIdentifier: '751150001234',
buildingType: 'APARTMENT_BUILDING' as const,
ownershipRegime: 'Copropriété',
constructionPeriod: '1990_2005' as const,
habitableSurface: 42,
equipment: 'Cuisine équipée',
heating: 'Individuel électrique',
hotWater: 'Ballon électrique',
energyRating: 'D' as const,
estimatedAnnualEnergyCost: 850,
estimatedAnnualEnergyCostReferenceYear: 2021,
destination: 'Habitation',
privateAccessories: 'Cave',
commonAreas: 'Local vélos',
internetAccess: 'Fibre',
departmentCode: '75',
};
it('initializes its fields from the apartment being edited', () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.componentRef.setInput('apartment', {
id: 'apt-1',
name: 'T2 Paris 15',
address: '45 rue des Entrepreneurs, 75015 Paris',
purchasePrice: 250000,
description: 'Cinquième étage',
privateAreas: [],
legalProfile: completeLegalProfile,
});
fixture.detectChanges();
it('initializes its fields from the apartment being edited', () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.componentRef.setInput('apartment', {
id: 'apt-1',
name: 'T2 Paris 15',
address: '45 rue des Entrepreneurs, 75015 Paris',
purchasePrice: 250000,
description: 'Cinquième étage',
privateAreas: [],
legalProfile: completeLegalProfile,
});
fixture.detectChanges();
const form = fixture.componentInstance;
expect(form.apartmentModel().name).toBe('T2 Paris 15');
expect(form.apartmentModel().purchasePrice).toBe(250000);
expect(form.apartmentModel().legalProfile).toEqual(completeLegalProfile);
});
const form = fixture.componentInstance;
expect(form.apartmentModel().name).toBe('T2 Paris 15');
expect(form.apartmentModel().purchasePrice).toBe(250000);
expect(form.apartmentModel().legalProfile).toEqual(completeLegalProfile);
});
it('shows a validation error instead of emitting an incomplete apartment', () => {
const fixture = TestBed.createComponent(ApartmentForm);
const form = fixture.componentInstance;
const saved = vi.fn();
form.saved.subscribe(saved);
it('shows a validation error instead of emitting an incomplete apartment', () => {
const fixture = TestBed.createComponent(ApartmentForm);
const form = fixture.componentInstance;
const saved = vi.fn();
form.saved.subscribe(saved);
form.save();
form.save();
expect(form.error()).toContain('Veuillez remplir tous les champs obligatoires');
expect(saved).not.toHaveBeenCalled();
});
expect(form.error()).toContain('Veuillez remplir tous les champs obligatoires');
expect(saved).not.toHaveBeenCalled();
});
it('rejects whitespace-only fields before the backend does', () => {
const fixture = TestBed.createComponent(ApartmentForm);
const form = fixture.componentInstance;
const saved = vi.fn();
form.saved.subscribe(saved);
form.apartmentModel.set({
name: ' ',
address: ' ',
purchasePrice: 250000,
description: '',
privateAreas: [],
legalProfile: emptyApartmentLegalProfile(),
});
it('rejects whitespace-only fields before the backend does', () => {
const fixture = TestBed.createComponent(ApartmentForm);
const form = fixture.componentInstance;
const saved = vi.fn();
form.saved.subscribe(saved);
form.apartmentModel.set({
name: ' ',
address: ' ',
purchasePrice: 250000,
description: '',
privateAreas: [],
legalProfile: emptyApartmentLegalProfile(),
});
form.save();
form.save();
expect(form.error()).toContain('Veuillez remplir tous les champs obligatoires');
expect(saved).not.toHaveBeenCalled();
});
expect(form.error()).toContain('Veuillez remplir tous les champs obligatoires');
expect(saved).not.toHaveBeenCalled();
});
it('emits a trimmed apartment draft when valid', () => {
const fixture = TestBed.createComponent(ApartmentForm);
const form = fixture.componentInstance;
const saved = vi.fn();
form.saved.subscribe(saved);
form.apartmentModel.set({
name: ' T2 Paris 15 ',
address: ' 45 rue des Entrepreneurs ',
purchasePrice: 250000,
description: ' Cinquième étage ',
privateAreas: [],
legalProfile: {
...completeLegalProfile,
taxIdentifier: ' 751150001234 ',
equipment: ' Cuisine équipée ',
departmentCode: ' 2a ',
},
});
it('emits a trimmed apartment draft when valid', () => {
const fixture = TestBed.createComponent(ApartmentForm);
const form = fixture.componentInstance;
const saved = vi.fn();
form.saved.subscribe(saved);
form.apartmentModel.set({
name: ' T2 Paris 15 ',
address: ' 45 rue des Entrepreneurs ',
purchasePrice: 250000,
description: ' Cinquième étage ',
privateAreas: [],
legalProfile: {
...completeLegalProfile,
taxIdentifier: ' 751150001234 ',
equipment: ' Cuisine équipée ',
departmentCode: ' 2a ',
},
});
form.save();
form.save();
expect(saved).toHaveBeenCalledWith({
id: undefined,
name: 'T2 Paris 15',
address: '45 rue des Entrepreneurs',
purchasePrice: 250000,
description: 'Cinquième étage',
privateAreas: [],
legalProfile: {
...completeLegalProfile,
taxIdentifier: '751150001234',
equipment: 'Cuisine équipée',
departmentCode: '2A',
},
});
});
expect(saved).toHaveBeenCalledWith({
id: undefined,
name: 'T2 Paris 15',
address: '45 rue des Entrepreneurs',
purchasePrice: 250000,
description: 'Cinquième étage',
privateAreas: [],
legalProfile: {
...completeLegalProfile,
taxIdentifier: '751150001234',
equipment: 'Cuisine équipée',
departmentCode: '2A',
},
});
});
it('disables the whole form and preserves a save error while pending', () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.componentRef.setInput('saving', true);
fixture.componentRef.setInput('saveError', 'Impossible d’enregistrer cet appartement.');
fixture.detectChanges();
it('disables the whole form and preserves a save error while pending', () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.componentRef.setInput('saving', true);
fixture.componentRef.setInput('saveError', 'Impossible d’enregistrer cet appartement.');
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.',
);
});
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.',
);
});
it('preserves entered values after a save fails', async () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.detectChanges();
await fixture.whenStable();
const form = fixture.componentInstance;
form.apartmentModel.set({
name: 'T2 rénové',
address: '45 rue des Entrepreneurs',
purchasePrice: 265000,
description: '',
privateAreas: [],
legalProfile: emptyApartmentLegalProfile(),
});
fixture.componentRef.setInput('saveError', 'Impossible d’enregistrer cet appartement.');
fixture.detectChanges();
await fixture.whenStable();
it('preserves entered values after a save fails', async () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.detectChanges();
await fixture.whenStable();
const form = fixture.componentInstance;
form.apartmentModel.set({
name: 'T2 rénové',
address: '45 rue des Entrepreneurs',
purchasePrice: 265000,
description: '',
privateAreas: [],
legalProfile: emptyApartmentLegalProfile(),
});
fixture.componentRef.setInput('saveError', 'Impossible d’enregistrer cet appartement.');
fixture.detectChanges();
await fixture.whenStable();
expect(fixture.nativeElement.querySelector('input#name').value).toBe('T2 rénové');
expect(fixture.nativeElement.querySelector('input#address').value).toBe(
'45 rue des Entrepreneurs',
);
expect(fixture.nativeElement.textContent).toContain(
'Impossible d’enregistrer cet appartement.',
);
});
expect(fixture.nativeElement.querySelector('input#name').value).toBe('T2 rénové');
expect(fixture.nativeElement.querySelector('input#address').value).toBe(
'45 rue des Entrepreneurs',
);
expect(fixture.nativeElement.textContent).toContain(
'Impossible d’enregistrer cet appartement.',
);
});
it('rejects negative legal-profile numbers', () => {
const fixture = TestBed.createComponent(ApartmentForm);
const form = fixture.componentInstance;
const saved = vi.fn();
form.saved.subscribe(saved);
form.apartmentModel.set({
name: 'T2',
address: '1 rue de Paris',
purchasePrice: 250000,
description: '',
privateAreas: [],
legalProfile: { ...emptyApartmentLegalProfile(), habitableSurface: -1 },
});
it('rejects negative legal-profile numbers', () => {
const fixture = TestBed.createComponent(ApartmentForm);
const form = fixture.componentInstance;
const saved = vi.fn();
form.saved.subscribe(saved);
form.apartmentModel.set({
name: 'T2',
address: '1 rue de Paris',
purchasePrice: 250000,
description: '',
privateAreas: [],
legalProfile: { ...emptyApartmentLegalProfile(), habitableSurface: -1 },
});
form.save();
form.save();
expect(form.error()).toContain('surface habitable');
expect(saved).not.toHaveBeenCalled();
});
expect(form.error()).toContain('surface habitable');
expect(saved).not.toHaveBeenCalled();
});
it('turns an emptied nullable number field back into null', async () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.detectChanges();
await fixture.whenStable();
const surfaceInput: HTMLInputElement =
fixture.nativeElement.querySelector('input#habitableSurface');
it('turns an emptied nullable number field back into null', async () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.detectChanges();
await fixture.whenStable();
const surfaceInput: HTMLInputElement =
fixture.nativeElement.querySelector('input#habitableSurface');
surfaceInput.value = '42';
surfaceInput.dispatchEvent(new Event('input'));
surfaceInput.value = '';
surfaceInput.dispatchEvent(new Event('input'));
await fixture.whenStable();
surfaceInput.value = '42';
surfaceInput.dispatchEvent(new Event('input'));
surfaceInput.value = '';
surfaceInput.dispatchEvent(new Event('input'));
await fixture.whenStable();
expect(fixture.componentInstance.apartmentModel().legalProfile.habitableSurface).toBeNull();
});
expect(fixture.componentInstance.apartmentModel().legalProfile.habitableSurface).toBeNull();
});
it('asks for the energy reference year when an estimated cost is provided', async () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.componentRef.setInput('apartment', {
id: 'apartment-1',
name: 'T2',
address: '1 rue de Paris',
purchasePrice: 250000,
description: '',
privateAreas: [],
legalProfile: {
...emptyApartmentLegalProfile(),
estimatedAnnualEnergyCost: 850,
},
});
fixture.detectChanges();
await fixture.whenStable();
const saved = vi.fn();
fixture.componentInstance.saved.subscribe(saved);
it('asks for the energy reference year when an estimated cost is provided', async () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.componentRef.setInput('apartment', {
id: 'apartment-1',
name: 'T2',
address: '1 rue de Paris',
purchasePrice: 250000,
description: '',
privateAreas: [],
legalProfile: {
...emptyApartmentLegalProfile(),
estimatedAnnualEnergyCost: 850,
},
});
fixture.detectChanges();
await fixture.whenStable();
const saved = vi.fn();
fixture.componentInstance.saved.subscribe(saved);
fixture.nativeElement.querySelector('button[type=submit]').click();
fixture.detectChanges();
fixture.nativeElement.querySelector('button[type=submit]').click();
fixture.detectChanges();
expect(saved).not.toHaveBeenCalled();
expect(fixture.nativeElement.textContent).toContain(
"Renseignez l'année de référence du coût énergétique.",
);
});
expect(saved).not.toHaveBeenCalled();
expect(fixture.nativeElement.textContent).toContain(
"Renseignez l'année de référence du coût énergétique.",
);
});
it('clears an energy reference year that has no estimated cost', async () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.componentRef.setInput('apartment', {
id: 'apartment-1',
name: 'T2',
address: '1 rue de Paris',
purchasePrice: 250000,
description: '',
privateAreas: [],
legalProfile: {
...emptyApartmentLegalProfile(),
estimatedAnnualEnergyCostReferenceYear: 2021,
},
});
fixture.detectChanges();
await fixture.whenStable();
const saved = vi.fn();
fixture.componentInstance.saved.subscribe(saved);
it('clears an energy reference year that has no estimated cost', async () => {
const fixture = TestBed.createComponent(ApartmentForm);
fixture.componentRef.setInput('apartment', {
id: 'apartment-1',
name: 'T2',
address: '1 rue de Paris',
purchasePrice: 250000,
description: '',
privateAreas: [],
legalProfile: {
...emptyApartmentLegalProfile(),
estimatedAnnualEnergyCostReferenceYear: 2021,
},
});
fixture.detectChanges();
await fixture.whenStable();
const saved = vi.fn();
fixture.componentInstance.saved.subscribe(saved);
fixture.nativeElement.querySelector('button[type=submit]').click();
fixture.nativeElement.querySelector('button[type=submit]').click();
expect(saved).toHaveBeenCalledWith(
expect.objectContaining({
legalProfile: expect.objectContaining({
estimatedAnnualEnergyCostReferenceYear: null,
}),
}),
);
});
expect(saved).toHaveBeenCalledWith(
expect.objectContaining({
legalProfile: expect.objectContaining({
estimatedAnnualEnergyCostReferenceYear: null,
}),
}),
);
});
});