feat: add unit tests for tenant, guarantor, lease card, dashboard, session, theme, and workspace services
This commit is contained in:
1 parent
74a77f2d2b
commit
181410e28f
8 files changed
+514
-2
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { SessionService } from '../../services/session.service';
|
||||
import { WorkspaceService } from '../../services/workspace.service';
|
||||
import { DashboardPage } from './dashboard-page';
|
||||
|
||||
describe('DashboardPage', () => {
|
||||
const http = { get: vi.fn() };
|
||||
const router = { navigateByUrl: vi.fn() };
|
||||
const session = { username: vi.fn(() => 'camille'), email: vi.fn(() => 'camille@example.com') };
|
||||
const workspace = { apartments: vi.fn(() => []), loadApartments: vi.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: HttpClient, useValue: http },
|
||||
{ provide: Router, useValue: router },
|
||||
{ provide: SessionService, useValue: session },
|
||||
{ provide: WorkspaceService, useValue: workspace },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the workspace and displays the user initial', () => {
|
||||
const fixture = TestBed.createComponent(DashboardPage);
|
||||
|
||||
expect(workspace.loadApartments).toHaveBeenCalled();
|
||||
expect(fixture.componentInstance.userInitial()).toBe('C');
|
||||
});
|
||||
|
||||
it('shows the backend response or connection error', () => {
|
||||
const fixture = TestBed.createComponent(DashboardPage);
|
||||
const component = fixture.componentInstance;
|
||||
http.get.mockReturnValueOnce(of('Hello'));
|
||||
component.checkConnection();
|
||||
http.get.mockReturnValueOnce(throwError(() => ({ statusText: 'Offline' })));
|
||||
component.checkConnection();
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith('/api/hello', { responseType: 'text' });
|
||||
expect(component.helloMessage()).toBe('Failed to connect to backend: Offline');
|
||||
});
|
||||
|
||||
it('navigates to apartments', () => {
|
||||
const fixture = TestBed.createComponent(DashboardPage);
|
||||
|
||||
fixture.componentInstance.showApartments();
|
||||
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/apartments');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { vi } from 'vitest';
|
||||
import { GuarantorForm } from './guarantor-form';
|
||||
|
||||
describe('GuarantorForm', () => {
|
||||
it('marks an invalid form as touched without submitting it', () => {
|
||||
const fixture = TestBed.createComponent(GuarantorForm);
|
||||
const markAllAsTouched = vi.fn();
|
||||
const submitted = vi.fn();
|
||||
fixture.componentInstance.submitted.subscribe(submitted);
|
||||
|
||||
fixture.componentInstance.save({ invalid: true, control: { markAllAsTouched } } as never);
|
||||
|
||||
expect(markAllAsTouched).toHaveBeenCalled();
|
||||
expect(submitted).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits trimmed data and resets the form after saving', () => {
|
||||
const fixture = TestBed.createComponent(GuarantorForm);
|
||||
const component = fixture.componentInstance;
|
||||
const submitted = vi.fn();
|
||||
const resetForm = vi.fn();
|
||||
component.submitted.subscribe(submitted);
|
||||
component.firstName.set(' Alex ');
|
||||
component.lastName.set(' Durand ');
|
||||
component.email.set(' alex@example.com ');
|
||||
component.phone.set(' 0600000000 ');
|
||||
component.address.set(' 1 rue de Paris ');
|
||||
|
||||
component.save({ invalid: false, resetForm } as never);
|
||||
|
||||
expect(submitted).toHaveBeenCalledWith({
|
||||
firstName: 'Alex', lastName: 'Durand', email: 'alex@example.com', phone: '0600000000', address: '1 rue de Paris',
|
||||
});
|
||||
expect(component.firstName()).toBe('');
|
||||
expect(resetForm).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { LeaseCard } from './lease-card';
|
||||
|
||||
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: '' }]);
|
||||
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: [], startDate: '', endDate: '', monthlyRent: 0, monthlyCharges: 0, securityDeposit: 0, 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é');
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { vi } from 'vitest';
|
||||
import { TenantForm } from './tenant-form';
|
||||
|
||||
describe('TenantForm', () => {
|
||||
@@ -15,4 +16,21 @@ describe('TenantForm', () => {
|
||||
|
||||
expect(firstName).toBe('Camille');
|
||||
});
|
||||
|
||||
it('loads an existing tenant and leaves invalid data untouched', () => {
|
||||
const fixture = TestBed.createComponent(TenantForm);
|
||||
fixture.componentRef.setInput('tenant', {
|
||||
id: 'tenant-1', firstName: 'Camille', lastName: 'Martin', email: 'camille@example.com', phone: '0600000000', address: '1 rue de Paris',
|
||||
});
|
||||
fixture.detectChanges();
|
||||
const markAllAsTouched = vi.fn();
|
||||
const submitted = vi.fn();
|
||||
fixture.componentInstance.submitted.subscribe(submitted);
|
||||
|
||||
fixture.componentInstance.save({ invalid: true, control: { markAllAsTouched } } as never);
|
||||
|
||||
expect(fixture.componentInstance.firstName()).toBe('Camille');
|
||||
expect(markAllAsTouched).toHaveBeenCalled();
|
||||
expect(submitted).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { SessionService } from './session.service';
|
||||
|
||||
describe('SessionService', () => {
|
||||
const http = { get: vi.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
TestBed.configureTestingModule({ providers: [SessionService, { provide: HttpClient, useValue: http }] });
|
||||
});
|
||||
|
||||
it('stores an authenticated user and applies its defaults', () => {
|
||||
const service = TestBed.inject(SessionService);
|
||||
http.get.mockReturnValueOnce(of({ authenticated: true, username: 'Camille', email: 'camille@example.com' }));
|
||||
service.refresh();
|
||||
expect(service.username()).toBe('Camille');
|
||||
expect(service.email()).toBe('camille@example.com');
|
||||
|
||||
http.get.mockReturnValueOnce(of({ authenticated: true }));
|
||||
service.refresh();
|
||||
expect(service.username()).toBe('User');
|
||||
expect(service.email()).toBe('');
|
||||
});
|
||||
|
||||
it('clears the session for an anonymous or failed request', () => {
|
||||
const service = TestBed.inject(SessionService);
|
||||
http.get.mockReturnValueOnce(of({ authenticated: false }));
|
||||
service.refresh();
|
||||
expect(service.isLoggedIn()).toBe(false);
|
||||
|
||||
http.get.mockReturnValueOnce(throwError(() => new Error('offline')));
|
||||
service.refresh();
|
||||
expect(http.get).toHaveBeenCalledWith('/api/user');
|
||||
expect(service.username()).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { vi } from 'vitest';
|
||||
import { ThemeService } from './theme.service';
|
||||
|
||||
describe('ThemeService', () => {
|
||||
it('persists explicit and system preferences', () => {
|
||||
const listener = vi.fn();
|
||||
vi.stubGlobal('matchMedia', vi.fn(() => ({ matches: true, addEventListener: listener })));
|
||||
localStorage.setItem('lmnp-theme', 'system');
|
||||
const service = TestBed.inject(ThemeService);
|
||||
TestBed.tick();
|
||||
|
||||
expect(document.documentElement.dataset['theme']).toBe('dark');
|
||||
service.setPreference('light');
|
||||
TestBed.tick();
|
||||
expect(localStorage.getItem('lmnp-theme')).toBe('light');
|
||||
expect(document.documentElement.dataset['themePreference']).toBe('light');
|
||||
expect(listener).toHaveBeenCalledWith('change', expect.any(Function));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { ApartmentService } from './apartment.service';
|
||||
import { LeaseService } from './lease.service';
|
||||
import { TenantService } from './tenant.service';
|
||||
import { WorkspaceService } from './workspace.service';
|
||||
|
||||
describe('WorkspaceService', () => {
|
||||
const apartment = { id: 'apartment-1', name: 'T2', address: '1 rue de Paris', purchasePrice: 250000, description: '' };
|
||||
const tenant = { id: 'tenant-1', firstName: 'Camille', lastName: 'Martin', email: 'camille@example.com', phone: '', address: '' };
|
||||
const lease = { id: 'lease-1', apartmentId: 'apartment-1', tenantIds: ['tenant-1'], guarantors: [], startDate: '2026-01-01', endDate: '2027-01-01', monthlyRent: 800, monthlyCharges: 50, securityDeposit: 800, status: 'ACTIVE' as const };
|
||||
const apartments = { getApartments: vi.fn(), createApartment: vi.fn(), updateApartment: vi.fn(), deleteApartment: vi.fn() };
|
||||
const tenants = { getTenants: vi.fn(), createTenant: vi.fn(), updateTenant: vi.fn(), deleteTenant: vi.fn(), uploadAttachment: vi.fn(), downloadAttachment: vi.fn() };
|
||||
const leases = { getLeases: vi.fn(), createLease: vi.fn(), deleteLease: vi.fn(), downloadPdf: vi.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
apartments.getApartments.mockReturnValue(of([apartment]));
|
||||
apartments.createApartment.mockReturnValue(of(apartment));
|
||||
apartments.updateApartment.mockReturnValue(of(apartment));
|
||||
apartments.deleteApartment.mockReturnValue(of(undefined));
|
||||
tenants.getTenants.mockReturnValue(of([tenant]));
|
||||
tenants.createTenant.mockReturnValue(of(tenant));
|
||||
tenants.updateTenant.mockReturnValue(of(tenant));
|
||||
tenants.deleteTenant.mockReturnValue(of(undefined));
|
||||
tenants.uploadAttachment.mockReturnValue(of({ id: 'attachment-1', fileName: 'id.pdf' }));
|
||||
tenants.downloadAttachment.mockReturnValue(of(new Blob(['identity'])));
|
||||
leases.getLeases.mockReturnValue(of([lease]));
|
||||
leases.createLease.mockReturnValue(of(lease));
|
||||
leases.deleteLease.mockReturnValue(of(undefined));
|
||||
leases.downloadPdf.mockReturnValue(of(new Blob(['pdf'])));
|
||||
vi.stubGlobal('confirm', vi.fn(() => true));
|
||||
vi.stubGlobal('URL', { createObjectURL: vi.fn(() => 'blob:test'), revokeObjectURL: vi.fn() });
|
||||
vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {});
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
WorkspaceService,
|
||||
{ provide: ApartmentService, useValue: apartments },
|
||||
{ provide: TenantService, useValue: tenants },
|
||||
{ provide: LeaseService, useValue: leases },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
|
||||
it('loads, creates, updates, and deletes every workspace resource', () => {
|
||||
const service = TestBed.inject(WorkspaceService);
|
||||
service.loadApartments();
|
||||
service.saveApartment(apartment);
|
||||
service.saveApartment(apartment, apartment.id);
|
||||
service.deleteApartment(apartment.id);
|
||||
service.loadTenants();
|
||||
service.saveTenant(tenant);
|
||||
service.saveTenant(tenant, tenant.id);
|
||||
service.deleteTenant(tenant.id);
|
||||
service.loadLeases();
|
||||
service.saveLease(lease);
|
||||
service.deleteLease(lease.id);
|
||||
|
||||
expect(service.apartments()).toEqual([apartment]);
|
||||
expect(service.tenants()).toEqual([tenant]);
|
||||
expect(service.leases()).toEqual([lease]);
|
||||
expect(apartments.updateApartment).toHaveBeenCalledWith('apartment-1', apartment);
|
||||
expect(tenants.updateTenant).toHaveBeenCalledWith('tenant-1', tenant);
|
||||
expect(leases.deleteLease).toHaveBeenCalledWith('lease-1');
|
||||
});
|
||||
|
||||
it('only deletes confirmed resources and handles attachment downloads', () => {
|
||||
const service = TestBed.inject(WorkspaceService);
|
||||
const confirmation = vi.mocked(confirm);
|
||||
confirmation.mockReturnValueOnce(false);
|
||||
service.deleteApartment(apartment.id);
|
||||
service.deleteTenant(tenant.id);
|
||||
service.deleteLease(lease.id);
|
||||
service.uploadTenantAttachment('tenant-1', new File(['id'], 'id.pdf'));
|
||||
service.downloadTenantAttachment('tenant-1', { id: 'attachment-1', fileName: 'id.pdf' });
|
||||
service.downloadLeasePdf(lease);
|
||||
service.downloadLeasePdf({ ...lease, id: undefined });
|
||||
|
||||
expect(apartments.deleteApartment).not.toHaveBeenCalled();
|
||||
expect(tenants.deleteTenant).toHaveBeenCalledWith('tenant-1');
|
||||
expect(leases.deleteLease).toHaveBeenCalledWith('lease-1');
|
||||
expect(URL.createObjectURL).toHaveBeenCalledTimes(2);
|
||||
expect(URL.revokeObjectURL).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('displays a server attachment error or its fallback', () => {
|
||||
const service = TestBed.inject(WorkspaceService);
|
||||
tenants.uploadAttachment.mockReturnValueOnce(throwError(() => ({ error: { message: 'Format refusé' } })));
|
||||
service.uploadTenantAttachment('tenant-1', new File(['id'], 'id.pdf'));
|
||||
expect(service.tenantAttachmentError()).toBe('Format refusé');
|
||||
|
||||
tenants.uploadAttachment.mockReturnValueOnce(throwError(() => new Error('offline')));
|
||||
service.uploadTenantAttachment('tenant-1', new File(['id'], 'id.pdf'));
|
||||
expect(service.tenantAttachmentError()).toBe('Impossible d’envoyer cette pièce jointe.');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user