sector => clean archi et test vert
This commit is contained in:
@@ -15,6 +15,8 @@ import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-r
|
|||||||
import { PbProfileRepository } from '@app/infrastructure/profiles/pb-profile.repository';
|
import { PbProfileRepository } from '@app/infrastructure/profiles/pb-profile.repository';
|
||||||
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
|
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
|
||||||
import { PbProjectRepository } from '@app/infrastructure/projects/pb-project.repository';
|
import { PbProjectRepository } from '@app/infrastructure/projects/pb-project.repository';
|
||||||
|
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
|
||||||
|
import { PbSectorRepository } from '@app/infrastructure/sectors/pb-sector.repository';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
@@ -31,6 +33,7 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provideHttpClient(withFetch()),
|
provideHttpClient(withFetch()),
|
||||||
{ provide: PROFILE_REPOSITORY_TOKEN, useExisting: PbProfileRepository },
|
{ provide: PROFILE_REPOSITORY_TOKEN, useExisting: PbProfileRepository },
|
||||||
{ provide: PROJECT_REPOSITORY_TOKEN, useExisting: PbProjectRepository },
|
{ provide: PROJECT_REPOSITORY_TOKEN, useExisting: PbProjectRepository },
|
||||||
|
{ provide: SECTOR_REPOSITORY_TOKEN, useExisting: PbSectorRepository },
|
||||||
provideToastr({
|
provideToastr({
|
||||||
timeOut: 10000,
|
timeOut: 10000,
|
||||||
positionClass: 'toast-top-right',
|
positionClass: 'toast-top-right',
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { SectorService } from './sector.service';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
describe('SectorService', () => {
|
|
||||||
let service: SectorService;
|
|
||||||
|
|
||||||
const routerSpy = {
|
|
||||||
navigate: jest.fn(),
|
|
||||||
navigateByUrl: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [
|
|
||||||
{ provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation
|
|
||||||
],
|
|
||||||
imports: [],
|
|
||||||
});
|
|
||||||
service = TestBed.inject(SectorService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import PocketBase from 'pocketbase';
|
|
||||||
import { environment } from '@env/environment.development';
|
|
||||||
import { from } from 'rxjs';
|
|
||||||
import { Sector } from '@app/shared/models/sector';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class SectorService {
|
|
||||||
get sectors() {
|
|
||||||
const pb = new PocketBase(environment.baseUrl);
|
|
||||||
return from(
|
|
||||||
pb.collection<Sector>('secteur').getFullList({
|
|
||||||
sort: 'nom',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSectorById(id: string) {
|
|
||||||
const pb = new PocketBase(environment.baseUrl);
|
|
||||||
return from(pb.collection<Sector>('secteur').getOne<Sector>(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
src/app/domain/sectors/dto/create-sector.dto.ts
Normal file
6
src/app/domain/sectors/dto/create-sector.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export interface CreateSectorDto {
|
||||||
|
id: string;
|
||||||
|
created: string;
|
||||||
|
updated: string;
|
||||||
|
nom: string;
|
||||||
|
}
|
||||||
7
src/app/domain/sectors/sector.repository.ts
Normal file
7
src/app/domain/sectors/sector.repository.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
|
||||||
|
export interface SectorRepository {
|
||||||
|
list(): Observable<Sector[]>;
|
||||||
|
getOne(sectorId: string): Observable<Sector>;
|
||||||
|
}
|
||||||
25
src/app/infrastructure/sectors/pb-sector.repository.ts
Normal file
25
src/app/infrastructure/sectors/pb-sector.repository.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { SectorRepository } from '@app/domain/sectors/sector.repository';
|
||||||
|
import { from, Observable } from 'rxjs';
|
||||||
|
import { environment } from '@env/environment';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class PbSectorRepository implements SectorRepository {
|
||||||
|
private pb = new PocketBase(environment.baseUrl);
|
||||||
|
|
||||||
|
getOne(sectorId: string): Observable<Sector> {
|
||||||
|
return from(this.pb.collection<Sector>('secteur').getOne<Sector>(sectorId));
|
||||||
|
}
|
||||||
|
|
||||||
|
list(): Observable<Sector[]> {
|
||||||
|
return from(
|
||||||
|
this.pb.collection<Sector>('secteur').getFullList({
|
||||||
|
sort: 'nom',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
import { SectorRepository } from '@app/domain/sectors/sector.repository';
|
||||||
|
|
||||||
|
export const SECTOR_REPOSITORY_TOKEN = new InjectionToken<SectorRepository>('SectorRepository');
|
||||||
@@ -2,17 +2,19 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { ProfileDetailComponent } from './profile-detail.component';
|
import { ProfileDetailComponent } from './profile-detail.component';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
|
||||||
import { ToastrService } from 'ngx-toastr';
|
|
||||||
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
|
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
|
||||||
import { ProjectRepository } from '@app/domain/projects/project.repository';
|
import { ProjectRepository } from '@app/domain/projects/project.repository';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { Project } from '@app/domain/projects/project.model';
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
import { SectorRepository } from '@app/domain/sectors/sector.repository';
|
||||||
|
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
|
||||||
|
|
||||||
describe('ProfileDetailComponent', () => {
|
describe('ProfileDetailComponent', () => {
|
||||||
let component: ProfileDetailComponent;
|
let component: ProfileDetailComponent;
|
||||||
let fixture: ComponentFixture<ProfileDetailComponent>;
|
let fixture: ComponentFixture<ProfileDetailComponent>;
|
||||||
let mockProjectRepository: jest.Mocked<ProjectRepository>;
|
let mockProjectRepository: jest.Mocked<ProjectRepository>;
|
||||||
|
let mockSectorRepo: SectorRepository;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockProjectRepository = {
|
mockProjectRepository = {
|
||||||
@@ -22,11 +24,17 @@ describe('ProfileDetailComponent', () => {
|
|||||||
update: jest.fn().mockReturnValue(of({} as Project)),
|
update: jest.fn().mockReturnValue(of({} as Project)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mockSectorRepo = {
|
||||||
|
list: jest.fn(),
|
||||||
|
getOne: jest.fn().mockReturnValue(of({} as Sector)),
|
||||||
|
};
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [ProfileDetailComponent],
|
imports: [ProfileDetailComponent],
|
||||||
providers: [
|
providers: [
|
||||||
provideRouter([]),
|
provideRouter([]),
|
||||||
{ provide: PROJECT_REPOSITORY_TOKEN, useValue: mockProjectRepository },
|
{ provide: PROJECT_REPOSITORY_TOKEN, useValue: mockProjectRepository },
|
||||||
|
{ provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepo },
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@if (sector != undefined) {
|
@if (sector() != undefined) {
|
||||||
<div class="flex flex-wrap space-x-2 items-center space-y-1">
|
<div class="flex flex-wrap space-x-2 items-center space-y-1">
|
||||||
@for (chip of sector.nom.split('-'); track chip) {
|
@for (chip of sector().noms; track chip) {
|
||||||
<small
|
<small
|
||||||
class="rounded-full bg-indigo-400 hover:bg-indigo-700 text-xs text-white py-1.5 px-3 font-semibold"
|
class="rounded-full bg-indigo-400 hover:bg-indigo-700 text-xs text-white py-1.5 px-3 font-semibold"
|
||||||
>{{ chip | titlecase }}</small
|
>{{ chip | titlecase }}</small
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { ChipsComponent } from './chips.component';
|
import { ChipsComponent } from './chips.component';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
import { SectorRepository } from '@app/domain/sectors/sector.repository';
|
||||||
|
|
||||||
describe('ChipsComponent', () => {
|
describe('ChipsComponent', () => {
|
||||||
let component: ChipsComponent;
|
let component: ChipsComponent;
|
||||||
let fixture: ComponentFixture<ChipsComponent>;
|
let fixture: ComponentFixture<ChipsComponent>;
|
||||||
|
|
||||||
|
let mockSectorRepo: SectorRepository;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
mockSectorRepo = {
|
||||||
|
list: jest.fn(),
|
||||||
|
getOne: jest.fn().mockReturnValue(of({} as Sector)),
|
||||||
|
};
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [ChipsComponent],
|
imports: [ChipsComponent],
|
||||||
|
providers: [
|
||||||
|
provideRouter([]),
|
||||||
|
{ provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepo },
|
||||||
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ChipsComponent);
|
fixture = TestBed.createComponent(ChipsComponent);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Component, inject, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { Sector } from '@app/shared/models/sector';
|
|
||||||
import { TitleCasePipe } from '@angular/common';
|
import { TitleCasePipe } from '@angular/common';
|
||||||
import { SectorService } from '@app/core/services/sector/sector.service';
|
|
||||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { SectorFacade } from '@app/ui/sectors/sector.facade';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-chips',
|
selector: 'app-chips',
|
||||||
@@ -15,12 +14,12 @@ import { UntilDestroy } from '@ngneat/until-destroy';
|
|||||||
export class ChipsComponent implements OnInit {
|
export class ChipsComponent implements OnInit {
|
||||||
@Input({ required: true }) sectorId: string | null = null;
|
@Input({ required: true }) sectorId: string | null = null;
|
||||||
|
|
||||||
protected sectorService = inject(SectorService);
|
private readonly sectorFacade = new SectorFacade();
|
||||||
|
protected sector = this.sectorFacade.sector;
|
||||||
protected sector: Sector | undefined = undefined;
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.sectorId)
|
if (this.sectorId) {
|
||||||
this.sectorService.getSectorById(this.sectorId).subscribe((value) => (this.sector = value));
|
this.sectorFacade.loadOne(this.sectorId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
@if (user != undefined) {
|
|
||||||
<a
|
|
||||||
[routerLink]="[user.username ? user.username : user.id]"
|
|
||||||
[state]="{ user, profile }"
|
|
||||||
class="cursor-pointer"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="items-center bg-gray-50 rounded-lg shadow sm:flex dark:bg-gray-800 dark:border-gray-700 cursor-pointer"
|
|
||||||
>
|
|
||||||
<div class="sm:w-max w-full flex items-center justify-center">
|
|
||||||
@if (user.avatar) {
|
|
||||||
<img
|
|
||||||
class="max-w-xl rounded-lg max-h-64 object-cover sm:rounded-none sm:rounded-l-lg"
|
|
||||||
src="{{ environment.baseUrl }}/api/files/users/{{ user.id }}/{{ user.avatar }}"
|
|
||||||
alt="{{ user.username }}"
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
} @else {
|
|
||||||
<img
|
|
||||||
class="max-w-xl rounded-lg max-h-64 sm:rounded-none sm:rounded-l-lg"
|
|
||||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{ user.username }}"
|
|
||||||
alt="{{ user.username }}"
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-5 flex flex-col items-center space-y-2">
|
|
||||||
@if (profile.estVerifier) {
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
class="size-6 text-purple-800"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M8.603 3.799A4.49 4.49 0 0 1 12 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 0 1 3.498 1.307 4.491 4.491 0 0 1 1.307 3.497A4.49 4.49 0 0 1 21.75 12a4.49 4.49 0 0 1-1.549 3.397 4.491 4.491 0 0 1-1.307 3.497 4.491 4.491 0 0 1-3.497 1.307A4.49 4.49 0 0 1 12 21.75a4.49 4.49 0 0 1-3.397-1.549 4.49 4.49 0 0 1-3.498-1.306 4.491 4.491 0 0 1-1.307-3.498A4.49 4.49 0 0 1 2.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 0 1 1.307-3.497 4.49 4.49 0 0 1 3.497-1.307Zm7.007 6.387a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (user.name) {
|
|
||||||
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
|
|
||||||
{{ user.name }}
|
|
||||||
</h3>
|
|
||||||
} @else if (user.username) {
|
|
||||||
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
|
|
||||||
{{ user.username }}
|
|
||||||
</h3>
|
|
||||||
} @else {
|
|
||||||
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
|
|
||||||
Non mentionné
|
|
||||||
</h3>
|
|
||||||
}
|
|
||||||
<span class="text-gray-500 dark:text-gray-400">{{ profile.profession }}</span>
|
|
||||||
|
|
||||||
<app-chips [sectorId]="profile.secteur" />
|
|
||||||
|
|
||||||
<app-reseaux [reseaux]="profile.reseaux" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { HorizentalProfileItemComponent } from './horizental-profile-item.component';
|
|
||||||
|
|
||||||
describe('HorizentalProfileItemComponent', () => {
|
|
||||||
let component: HorizentalProfileItemComponent;
|
|
||||||
let fixture: ComponentFixture<HorizentalProfileItemComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [HorizentalProfileItemComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(HorizentalProfileItemComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Component, inject, Input, OnInit } from '@angular/core';
|
|
||||||
import { Router, RouterLink } from '@angular/router';
|
|
||||||
import { UserService } from '@app/core/services/user/user.service';
|
|
||||||
import { User } from '@app/shared/models/user';
|
|
||||||
import { ChipsComponent } from '@app/shared/components/chips/chips.component';
|
|
||||||
import { ReseauxComponent } from '@app/shared/components/reseaux/reseaux.component';
|
|
||||||
import { environment } from '@env/environment';
|
|
||||||
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-horizental-profile-item',
|
|
||||||
standalone: true,
|
|
||||||
imports: [RouterLink, ChipsComponent, ReseauxComponent],
|
|
||||||
templateUrl: './horizental-profile-item.component.html',
|
|
||||||
styleUrl: './horizental-profile-item.component.scss',
|
|
||||||
})
|
|
||||||
export class HorizentalProfileItemComponent implements OnInit {
|
|
||||||
@Input({ required: true }) profile: ProfileViewModel = {} as ProfileViewModel;
|
|
||||||
protected router = inject(Router);
|
|
||||||
protected userService = inject(UserService);
|
|
||||||
|
|
||||||
protected user: User | undefined = undefined;
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.userService
|
|
||||||
.getUserById(this.profile.utilisateur)
|
|
||||||
.subscribe((value) => (this.user = value));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly environment = environment;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<section class="bg-white dark:bg-gray-900">
|
|
||||||
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 lg:px-6">
|
|
||||||
<div class="grid gap-8 mb-6 lg:mb-16 md:grid-cols-2">
|
|
||||||
@for (profile of profiles; track profile.id) {
|
|
||||||
<app-horizental-profile-item [profile]="profile" />
|
|
||||||
} @empty {
|
|
||||||
<p>Aucun profile trouvée</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { HorizentalProfileListComponent } from './horizental-profile-list.component';
|
|
||||||
|
|
||||||
describe('HorizentalProfileListComponent', () => {
|
|
||||||
let component: HorizentalProfileListComponent;
|
|
||||||
let fixture: ComponentFixture<HorizentalProfileListComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [HorizentalProfileListComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(HorizentalProfileListComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
|
||||||
import { HorizentalProfileItemComponent } from '@app/shared/components/horizental-profile-item/horizental-profile-item.component';
|
|
||||||
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-horizental-profile-list',
|
|
||||||
standalone: true,
|
|
||||||
imports: [HorizentalProfileItemComponent],
|
|
||||||
templateUrl: './horizental-profile-list.component.html',
|
|
||||||
styleUrl: './horizental-profile-list.component.scss',
|
|
||||||
})
|
|
||||||
export class HorizentalProfileListComponent {
|
|
||||||
@Input({ required: true }) profiles: ProfileViewModel[] = [];
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,9 @@ import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-r
|
|||||||
import { ProfileRepository } from '@app/domain/profiles/profile.repository';
|
import { ProfileRepository } from '@app/domain/profiles/profile.repository';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { Profile } from '@app/domain/profiles/profile.model';
|
import { Profile } from '@app/domain/profiles/profile.model';
|
||||||
|
import { SectorRepository } from '@app/domain/sectors/sector.repository';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
|
||||||
|
|
||||||
describe('MyProfileUpdateFormComponent', () => {
|
describe('MyProfileUpdateFormComponent', () => {
|
||||||
let component: MyProfileUpdateFormComponent;
|
let component: MyProfileUpdateFormComponent;
|
||||||
@@ -15,6 +18,7 @@ describe('MyProfileUpdateFormComponent', () => {
|
|||||||
|
|
||||||
let mockToastrService: Partial<ToastrService>;
|
let mockToastrService: Partial<ToastrService>;
|
||||||
let mockProfileRepo: ProfileRepository;
|
let mockProfileRepo: ProfileRepository;
|
||||||
|
let mockSectorRepo: SectorRepository;
|
||||||
|
|
||||||
const mockProfileData = {
|
const mockProfileData = {
|
||||||
profession: '',
|
profession: '',
|
||||||
@@ -38,6 +42,11 @@ describe('MyProfileUpdateFormComponent', () => {
|
|||||||
getByUserId: jest.fn().mockReturnValue(of({} as Profile)),
|
getByUserId: jest.fn().mockReturnValue(of({} as Profile)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mockSectorRepo = {
|
||||||
|
list: jest.fn().mockReturnValue(of([])),
|
||||||
|
getOne: jest.fn().mockReturnValue(of({} as Sector)),
|
||||||
|
};
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [MyProfileUpdateFormComponent],
|
imports: [MyProfileUpdateFormComponent],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -45,6 +54,7 @@ describe('MyProfileUpdateFormComponent', () => {
|
|||||||
provideRouter([]),
|
provideRouter([]),
|
||||||
{ provide: ToastrService, useValue: mockToastrService },
|
{ provide: ToastrService, useValue: mockToastrService },
|
||||||
{ provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepo },
|
{ provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepo },
|
||||||
|
{ provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepo },
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, effect, inject, Input, OnInit, signal } from '@angular/core';
|
import { Component, effect, inject, Input, OnInit } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
FormBuilder,
|
FormBuilder,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -8,8 +8,6 @@ import {
|
|||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
import { NgClass } from '@angular/common';
|
import { NgClass } from '@angular/common';
|
||||||
import { SectorService } from '@app/core/services/sector/sector.service';
|
|
||||||
import { Sector } from '@app/shared/models/sector';
|
|
||||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||||
import { MyProfileUpdateCvFormComponent } from '@app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component';
|
import { MyProfileUpdateCvFormComponent } from '@app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
@@ -17,6 +15,7 @@ import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
|
|||||||
import { ProfileFacade } from '@app/ui/profiles/profile.facade';
|
import { ProfileFacade } from '@app/ui/profiles/profile.facade';
|
||||||
import { ActionType } from '@app/domain/action-type.util';
|
import { ActionType } from '@app/domain/action-type.util';
|
||||||
import { Profile } from '@app/domain/profiles/profile.model';
|
import { Profile } from '@app/domain/profiles/profile.model';
|
||||||
|
import { SectorFacade } from '@app/ui/sectors/sector.facade';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-my-profile-update-form',
|
selector: 'app-my-profile-update-form',
|
||||||
@@ -31,15 +30,19 @@ export class MyProfileUpdateFormComponent implements OnInit {
|
|||||||
|
|
||||||
@Input({ required: true }) profile: ProfileViewModel = {} as ProfileViewModel;
|
@Input({ required: true }) profile: ProfileViewModel = {} as ProfileViewModel;
|
||||||
private readonly formBuilder = inject(FormBuilder);
|
private readonly formBuilder = inject(FormBuilder);
|
||||||
protected readonly sectorService = inject(SectorService);
|
|
||||||
protected readonly authService = inject(AuthService);
|
protected readonly authService = inject(AuthService);
|
||||||
profileForm!: FormGroup;
|
profileForm!: FormGroup;
|
||||||
protected sectors = signal<Sector[]>([]);
|
|
||||||
|
|
||||||
private readonly profileFacade = new ProfileFacade();
|
private readonly profileFacade = new ProfileFacade();
|
||||||
protected readonly loading = this.profileFacade.loading;
|
protected readonly loading = this.profileFacade.loading;
|
||||||
protected readonly error = this.profileFacade.error;
|
protected readonly error = this.profileFacade.error;
|
||||||
|
|
||||||
|
private readonly sectorFacade = new SectorFacade();
|
||||||
|
protected sectors = this.sectorFacade.sectors;
|
||||||
|
protected sector = this.sectorFacade.sector;
|
||||||
|
protected readonly sectorLoading = this.sectorFacade.loading;
|
||||||
|
protected readonly sectorError = this.sectorFacade.error;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
switch (this.loading().action) {
|
switch (this.loading().action) {
|
||||||
@@ -58,6 +61,12 @@ export class MyProfileUpdateFormComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
switch (this.sectorLoading().action) {
|
||||||
|
case ActionType.READ:
|
||||||
|
if (!this.sectorLoading() && !this.sectorError().hasError) {
|
||||||
|
this.profileForm.get('secteur')!.setValue(this.sector().id);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,12 +104,10 @@ export class MyProfileUpdateFormComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.profile.secteur) {
|
if (this.profile.secteur) {
|
||||||
this.sectorService
|
this.sectorFacade.loadOne(this.profile.secteur);
|
||||||
.getSectorById(this.profile.secteur)
|
|
||||||
.subscribe((value) => this.profileForm.get('secteur')!.setValue(value.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sectorService.sectors.subscribe((value) => this.sectors.set(value));
|
this.sectorFacade.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { VerticalProfileListComponent } from './vertical-profile-list.component';
|
import { VerticalProfileListComponent } from './vertical-profile-list.component';
|
||||||
import { Router } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
describe('VerticalProfileListComponent', () => {
|
describe('VerticalProfileListComponent', () => {
|
||||||
let component: VerticalProfileListComponent;
|
let component: VerticalProfileListComponent;
|
||||||
let fixture: ComponentFixture<VerticalProfileListComponent>;
|
let fixture: ComponentFixture<VerticalProfileListComponent>;
|
||||||
|
|
||||||
const routerSpy = {
|
|
||||||
navigate: jest.fn(),
|
|
||||||
navigateByUrl: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [VerticalProfileListComponent],
|
imports: [VerticalProfileListComponent],
|
||||||
providers: [{ provide: Router, useValue: routerSpy }],
|
providers: [provideRouter([])],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(VerticalProfileListComponent);
|
fixture = TestBed.createComponent(VerticalProfileListComponent);
|
||||||
|
|||||||
15
src/app/testing/domain/sectors/fake-sector.repository.ts
Normal file
15
src/app/testing/domain/sectors/fake-sector.repository.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { SectorRepository } from '@app/domain/sectors/sector.repository';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
import { fakeSectors } from '@app/testing/sector.mock';
|
||||||
|
|
||||||
|
export class FakeSectorRepository implements SectorRepository {
|
||||||
|
getOne(sectorId: string): Observable<Sector> {
|
||||||
|
const sector = fakeSectors.find((s) => s.id === sectorId) ?? ({} as Sector);
|
||||||
|
return of(sector);
|
||||||
|
}
|
||||||
|
|
||||||
|
list(): Observable<Sector[]> {
|
||||||
|
return of(fakeSectors);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { PbProfileRepository } from '@app/infrastructure/profiles/pb-profile.rep
|
|||||||
import { Profile } from '@app/domain/profiles/profile.model';
|
import { Profile } from '@app/domain/profiles/profile.model';
|
||||||
import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto';
|
import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto';
|
||||||
import PocketBase from 'pocketbase';
|
import PocketBase from 'pocketbase';
|
||||||
|
import { mockProfiles } from '@app/testing/profile.mock';
|
||||||
|
|
||||||
jest.mock('pocketbase'); // on mock le module PocketBase
|
jest.mock('pocketbase'); // on mock le module PocketBase
|
||||||
|
|
||||||
@@ -36,29 +37,12 @@ describe('PbProfileRepository', () => {
|
|||||||
// 🔹 TEST : list()
|
// 🔹 TEST : list()
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
it('devrait appeler pb.collection("profiles").getFullList() avec un tri par profession', (done) => {
|
it('devrait appeler pb.collection("profiles").getFullList() avec un tri par profession', (done) => {
|
||||||
const fakeProfiles: Profile[] = [
|
mockCollection.getFullList.mockResolvedValue(mockProfiles);
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
created: '',
|
|
||||||
updated: '',
|
|
||||||
profession: 'Développeur',
|
|
||||||
utilisateur: 'u001',
|
|
||||||
estVerifier: false,
|
|
||||||
secteur: 'Informatique',
|
|
||||||
reseaux: {} as JSON,
|
|
||||||
bio: 'Bio test',
|
|
||||||
cv: '',
|
|
||||||
projets: [],
|
|
||||||
apropos: 'À propos...',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
mockCollection.getFullList.mockResolvedValue(fakeProfiles);
|
|
||||||
|
|
||||||
repo.list().subscribe((result) => {
|
repo.list().subscribe((result) => {
|
||||||
expect(mockPocketBase.collection).toHaveBeenCalledWith('profiles');
|
expect(mockPocketBase.collection).toHaveBeenCalledWith('profiles');
|
||||||
expect(mockCollection.getFullList).toHaveBeenCalledWith({ sort: 'profession' });
|
expect(mockCollection.getFullList).toHaveBeenCalledWith({ sort: 'profession' });
|
||||||
expect(result).toEqual(fakeProfiles);
|
expect(result).toEqual(mockProfiles);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -66,29 +50,14 @@ describe('PbProfileRepository', () => {
|
|||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// 🔹 TEST : getByUserId()
|
// 🔹 TEST : getByUserId()
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
it('devrait appeler pb.collection("profiles").getFirstListItem() avec le bon filtre utilisateur', (done) => {
|
it('devrait appeler pb.collection("profiles").getFirstListItem() avec le bon filtre utilisateur', () => {
|
||||||
const userId = 'user_001';
|
const userId = '1';
|
||||||
const fakeProfile: Profile = {
|
|
||||||
id: 'p001',
|
|
||||||
created: '',
|
|
||||||
updated: '',
|
|
||||||
profession: 'Designer',
|
|
||||||
utilisateur: userId,
|
|
||||||
estVerifier: true,
|
|
||||||
secteur: 'Création',
|
|
||||||
reseaux: {} as JSON,
|
|
||||||
bio: 'Bio',
|
|
||||||
cv: '',
|
|
||||||
projets: [],
|
|
||||||
apropos: 'À propos...',
|
|
||||||
};
|
|
||||||
|
|
||||||
mockCollection.getFirstListItem.mockResolvedValue(fakeProfile);
|
mockCollection.getFirstListItem.mockResolvedValue(mockProfiles);
|
||||||
|
|
||||||
repo.getByUserId(userId).subscribe((result) => {
|
repo.getByUserId(userId).subscribe((result) => {
|
||||||
expect(mockCollection.getFirstListItem).toHaveBeenCalledWith(`utilisateur="${userId}"`);
|
expect(mockCollection.getFirstListItem).toHaveBeenCalledWith(`utilisateur="${userId}"`);
|
||||||
expect(result).toEqual(fakeProfile);
|
expect(result).toEqual(mockProfiles[0]);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,7 +97,7 @@ describe('PbProfileRepository', () => {
|
|||||||
// 🔹 TEST : update()
|
// 🔹 TEST : update()
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
it('devrait appeler pb.collection("profiles").update() avec ID et données partielle', (done) => {
|
it('devrait appeler pb.collection("profiles").update() avec ID et données partielle', (done) => {
|
||||||
const id = 'p002';
|
const id = '1';
|
||||||
const data = { bio: 'Bio mise à jour' };
|
const data = { bio: 'Bio mise à jour' };
|
||||||
const updatedProfile: Profile = {
|
const updatedProfile: Profile = {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { PbProjectRepository } from '@app/infrastructure/projects/pb-project.rep
|
|||||||
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
import { Project } from '@app/domain/projects/project.model';
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
import PocketBase from 'pocketbase';
|
import PocketBase from 'pocketbase';
|
||||||
|
import { fakeProjects } from '@app/testing/project.mock';
|
||||||
|
|
||||||
jest.mock('pocketbase'); // on mock le module entier
|
jest.mock('pocketbase'); // on mock le module entier
|
||||||
|
|
||||||
@@ -63,19 +64,7 @@ describe('PbProjectRepository', () => {
|
|||||||
// 🔹 TEST : list()
|
// 🔹 TEST : list()
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
it('devrait appeler pb.collection("projets").getFullList() avec le filtre utilisateur', (done) => {
|
it('devrait appeler pb.collection("projets").getFullList() avec le filtre utilisateur', (done) => {
|
||||||
const userId = 'user_001';
|
const userId = '1';
|
||||||
const fakeProjects: Project[] = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
created: '',
|
|
||||||
updated: '',
|
|
||||||
nom: 'P1',
|
|
||||||
lien: '',
|
|
||||||
description: '',
|
|
||||||
fichier: [],
|
|
||||||
utilisateur: userId,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
mockCollection.getFullList.mockResolvedValue(fakeProjects);
|
mockCollection.getFullList.mockResolvedValue(fakeProjects);
|
||||||
|
|
||||||
@@ -92,7 +81,7 @@ describe('PbProjectRepository', () => {
|
|||||||
// 🔹 TEST : get()
|
// 🔹 TEST : get()
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
it('devrait appeler pb.collection("projets").getOne() avec le bon ID', (done) => {
|
it('devrait appeler pb.collection("projets").getOne() avec le bon ID', (done) => {
|
||||||
const id = 'p123';
|
const id = '1';
|
||||||
const fakeProject: Project = {
|
const fakeProject: Project = {
|
||||||
id,
|
id,
|
||||||
created: '',
|
created: '',
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { FakeSectorRepository } from '@app/testing/domain/sectors/fake-sector.repository';
|
||||||
|
import { PbSectorRepository } from '@app/infrastructure/sectors/pb-sector.repository';
|
||||||
|
import { fakeSectors } from '@app/testing/sector.mock';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
jest.mock('pocketbase'); // on mock le module entier
|
||||||
|
|
||||||
|
describe('SectorRepository', () => {
|
||||||
|
let sectorRepo: FakeSectorRepository;
|
||||||
|
let mockCollection: any;
|
||||||
|
let mockPocketBase: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Création d’un faux client PocketBase avec des méthodes mockées
|
||||||
|
mockPocketBase = {
|
||||||
|
collection: jest.fn().mockReturnValue({
|
||||||
|
getFullList: jest.fn(),
|
||||||
|
getOne: jest.fn(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 👇 On remplace la classe importée par notre version mockée
|
||||||
|
(PocketBase as jest.Mock).mockImplementation(() => mockPocketBase);
|
||||||
|
|
||||||
|
// on récupère une "collection" simulée
|
||||||
|
mockCollection = mockPocketBase.collection('sectors');
|
||||||
|
|
||||||
|
// on instancie le repository à tester
|
||||||
|
sectorRepo = new PbSectorRepository();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : list()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("sectors").getFullList() ', () => {
|
||||||
|
mockCollection.getFullList.mockResolvedValue(fakeSectors);
|
||||||
|
|
||||||
|
sectorRepo.list().subscribe((result) => {
|
||||||
|
expect(mockPocketBase.collection).toHaveBeenCalledWith('sectors');
|
||||||
|
expect(result).toEqual(fakeSectors);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : getOne()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("sectors").getFirstListItem() ', () => {
|
||||||
|
const sectorId = 'sector_001';
|
||||||
|
|
||||||
|
mockCollection.getOne.mockResolvedValue(fakeSectors.find((s) => s.id === sectorId));
|
||||||
|
|
||||||
|
sectorRepo.getOne(sectorId).subscribe((result) => {
|
||||||
|
expect(mockCollection.getFirstListItem).toHaveBeenCalledWith(`sectors="${sectorId}"`);
|
||||||
|
expect(result).toEqual(fakeSectors[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
34
src/app/testing/sector.mock.ts
Normal file
34
src/app/testing/sector.mock.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
|
||||||
|
export const fakeSectors: Sector[] = [
|
||||||
|
{
|
||||||
|
id: 'sector_001',
|
||||||
|
created: '2025-01-01T10:00:00Z',
|
||||||
|
updated: '2025-01-05T14:00:00Z',
|
||||||
|
nom: 'Informatique',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sector_002',
|
||||||
|
created: '2025-01-02T11:00:00Z',
|
||||||
|
updated: '2025-01-06T15:00:00Z',
|
||||||
|
nom: 'Design graphique',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sector_003',
|
||||||
|
created: '2025-01-03T12:00:00Z',
|
||||||
|
updated: '2025-01-07T16:00:00Z',
|
||||||
|
nom: 'Marketing digital',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sector_004',
|
||||||
|
created: '2025-01-04T13:00:00Z',
|
||||||
|
updated: '2025-01-08T17:00:00Z',
|
||||||
|
nom: 'Finance et comptabilité',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sector_005',
|
||||||
|
created: '2025-01-05T14:00:00Z',
|
||||||
|
updated: '2025-01-09T18:00:00Z',
|
||||||
|
nom: 'Ressources humaines',
|
||||||
|
},
|
||||||
|
];
|
||||||
46
src/app/testing/ui/sectors/sector.facade.spec.ts
Normal file
46
src/app/testing/ui/sectors/sector.facade.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { SectorFacade } from '@app/ui/sectors/sector.facade';
|
||||||
|
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
|
||||||
|
import { FakeSectorRepository } from '@app/testing/domain/sectors/fake-sector.repository';
|
||||||
|
import { fakeSectors } from '@app/testing/sector.mock';
|
||||||
|
|
||||||
|
describe('SectorFacade', () => {
|
||||||
|
let sectorFacade: SectorFacade;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
SectorFacade,
|
||||||
|
{ provide: SECTOR_REPOSITORY_TOKEN, useClass: FakeSectorRepository },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
sectorFacade = TestBed.inject(SectorFacade);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : Chargement des secteurs (load)
|
||||||
|
// ------------------------------------------
|
||||||
|
it("devrait charger les secteurs d'activité ", () => {
|
||||||
|
sectorFacade.load();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(sectorFacade.sectors().length).toBe(fakeSectors.length);
|
||||||
|
expect(sectorFacade.loading().isLoading).toBe(false);
|
||||||
|
expect(sectorFacade.error().hasError).toBe(false);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : Chargement d’un secteur unique
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait charger un secteur par son ID', () => {
|
||||||
|
const sectorId = '1';
|
||||||
|
sectorFacade.loadOne(sectorId);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(sectorFacade.sector()).toBe(fakeSectors.find((s) => s.id === sectorId));
|
||||||
|
expect(sectorFacade.error().hasError).toBe(false);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
30
src/app/testing/ui/sectors/sector.presenter.spec.ts
Normal file
30
src/app/testing/ui/sectors/sector.presenter.spec.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { SectorPresenter } from '@app/ui/sectors/sector.presenter';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
import { fakeSectors } from '@app/testing/sector.mock';
|
||||||
|
|
||||||
|
describe('SectorPresenter', () => {
|
||||||
|
let presenter: SectorPresenter;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
presenter = new SectorPresenter();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait transformer un Sector en SectorPresenterModel', () => {
|
||||||
|
const sector: Sector = fakeSectors[0];
|
||||||
|
|
||||||
|
const viewModel = presenter.toViewModel(sector);
|
||||||
|
|
||||||
|
expect(viewModel).toEqual({
|
||||||
|
id: sector.id,
|
||||||
|
nom: sector.nom,
|
||||||
|
noms: ['Informatique'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait transformer un tableau complet', () => {
|
||||||
|
const result = presenter.toViewModels(fakeSectors);
|
||||||
|
|
||||||
|
expect(result.length).toBe(5);
|
||||||
|
expect(result[0].nom).toBe(fakeSectors[0].nom);
|
||||||
|
});
|
||||||
|
});
|
||||||
20
src/app/testing/usecase/sectors/get-sector.usecase.spec.ts
Normal file
20
src/app/testing/usecase/sectors/get-sector.usecase.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { FakeSectorRepository } from '@app/testing/domain/sectors/fake-sector.repository';
|
||||||
|
import { fakeSectors } from '@app/testing/sector.mock';
|
||||||
|
import { GetSectorUseCase } from '@app/usecase/sectors/get-sector.usecase';
|
||||||
|
|
||||||
|
describe('ListSectorUsecase', () => {
|
||||||
|
let useCase: GetSectorUseCase;
|
||||||
|
let repo: FakeSectorRepository;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
repo = new FakeSectorRepository();
|
||||||
|
useCase = new GetSectorUseCase(repo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('doit retourne la liste des secteurs ', () => {
|
||||||
|
const sectorId = '1';
|
||||||
|
useCase.execute(sectorId).subscribe((sector) => {
|
||||||
|
expect(sector).toBe(fakeSectors.find((s) => s.id === sectorId));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
19
src/app/testing/usecase/sectors/list-sector.usecase.spec.ts
Normal file
19
src/app/testing/usecase/sectors/list-sector.usecase.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { ListSectorUsecase } from '@app/usecase/sectors/list-sector.usecase';
|
||||||
|
import { FakeSectorRepository } from '@app/testing/domain/sectors/fake-sector.repository';
|
||||||
|
import { fakeSectors } from '@app/testing/sector.mock';
|
||||||
|
|
||||||
|
describe('ListSectorUsecase', () => {
|
||||||
|
let useCase: ListSectorUsecase;
|
||||||
|
let repo: FakeSectorRepository;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
repo = new FakeSectorRepository();
|
||||||
|
useCase = new ListSectorUsecase(repo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('doit retourne la liste des secteurs ', () => {
|
||||||
|
useCase.execute().subscribe((sectors) => {
|
||||||
|
expect(sectors.length).toBe(fakeSectors.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -35,10 +35,7 @@ export class ProjectFacade {
|
|||||||
private readonly projectPresenter = new ProjectPresenter();
|
private readonly projectPresenter = new ProjectPresenter();
|
||||||
|
|
||||||
load(userId: string) {
|
load(userId: string) {
|
||||||
this.loading.set({
|
this.handleError(ActionType.READ, false, null, true);
|
||||||
action: ActionType.READ,
|
|
||||||
isLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.listUseCase.execute(userId).subscribe({
|
this.listUseCase.execute(userId).subscribe({
|
||||||
next: (projects: Project[]) => {
|
next: (projects: Project[]) => {
|
||||||
@@ -53,10 +50,7 @@ export class ProjectFacade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadOne(projectId: string) {
|
loadOne(projectId: string) {
|
||||||
this.loading.set({
|
this.handleError(ActionType.READ, false, null, true);
|
||||||
action: ActionType.READ,
|
|
||||||
isLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.getUseCase.execute(projectId).subscribe({
|
this.getUseCase.execute(projectId).subscribe({
|
||||||
next: (project: Project) => {
|
next: (project: Project) => {
|
||||||
@@ -70,10 +64,7 @@ export class ProjectFacade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create(projectDto: CreateProjectDto) {
|
create(projectDto: CreateProjectDto) {
|
||||||
this.loading.set({
|
this.handleError(ActionType.CREATE, false, null, true);
|
||||||
action: ActionType.CREATE,
|
|
||||||
isLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.createUseCase.execute(projectDto).subscribe({
|
this.createUseCase.execute(projectDto).subscribe({
|
||||||
next: (project: Project) => {
|
next: (project: Project) => {
|
||||||
@@ -88,10 +79,7 @@ export class ProjectFacade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(userId: string, data: any) {
|
update(userId: string, data: any) {
|
||||||
this.loading.set({
|
this.handleError(ActionType.UPDATE, false, null, true);
|
||||||
action: ActionType.UPDATE,
|
|
||||||
isLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.UpdateUseCase.execute(userId, data).subscribe({
|
this.UpdateUseCase.execute(userId, data).subscribe({
|
||||||
next: (project: Project) => {
|
next: (project: Project) => {
|
||||||
|
|||||||
65
src/app/ui/sectors/sector.facade.ts
Normal file
65
src/app/ui/sectors/sector.facade.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { inject, signal } from '@angular/core';
|
||||||
|
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
|
||||||
|
import { ListSectorUsecase } from '@app/usecase/sectors/list-sector.usecase';
|
||||||
|
import { GetSectorUseCase } from '@app/usecase/sectors/get-sector.usecase';
|
||||||
|
import { ActionType } from '@app/domain/action-type.util';
|
||||||
|
import { LoaderAction } from '@app/domain/loader-action.util';
|
||||||
|
import { ErrorResponse } from '@app/domain/error-response.util';
|
||||||
|
import { SectorPresenterModel } from '@app/ui/sectors/sector.presenter.model';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
import { SectorPresenter } from '@app/ui/sectors/sector.presenter';
|
||||||
|
|
||||||
|
export class SectorFacade {
|
||||||
|
private readonly sectorRepo = inject(SECTOR_REPOSITORY_TOKEN);
|
||||||
|
|
||||||
|
private readonly listSectorUseCase = new ListSectorUsecase(this.sectorRepo);
|
||||||
|
private readonly getSectorUseCase = new GetSectorUseCase(this.sectorRepo);
|
||||||
|
|
||||||
|
readonly sectors = signal<SectorPresenterModel[]>([]);
|
||||||
|
readonly sector = signal<SectorPresenterModel>({} as SectorPresenterModel);
|
||||||
|
|
||||||
|
readonly loading = signal<LoaderAction>({ isLoading: false, action: ActionType.NONE });
|
||||||
|
readonly error = signal<ErrorResponse>({
|
||||||
|
action: ActionType.NONE,
|
||||||
|
hasError: false,
|
||||||
|
message: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
private readonly sectorPresenter = new SectorPresenter();
|
||||||
|
|
||||||
|
load() {
|
||||||
|
this.handleError(ActionType.READ, false, null, true);
|
||||||
|
this.listSectorUseCase.execute().subscribe({
|
||||||
|
next: (sectors: Sector[]) => {
|
||||||
|
this.sectors.set(this.sectorPresenter.toViewModels(sectors));
|
||||||
|
this.handleError(ActionType.READ, false, null, false);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.handleError(ActionType.READ, false, err, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOne(sectorId: string) {
|
||||||
|
this.handleError(ActionType.READ, false, null, true);
|
||||||
|
this.getSectorUseCase.execute(sectorId).subscribe({
|
||||||
|
next: (sector: Sector) => {
|
||||||
|
this.sector.set(this.sectorPresenter.toViewModel(sector));
|
||||||
|
this.handleError(ActionType.READ, false, null, false);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.handleError(ActionType.READ, false, err, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(
|
||||||
|
action: ActionType = ActionType.NONE,
|
||||||
|
hasError: boolean,
|
||||||
|
message: string | null = null,
|
||||||
|
isLoading = false
|
||||||
|
) {
|
||||||
|
this.error.set({ action, hasError, message });
|
||||||
|
this.loading.set({ action, isLoading });
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/app/ui/sectors/sector.presenter.model.ts
Normal file
5
src/app/ui/sectors/sector.presenter.model.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface SectorPresenterModel {
|
||||||
|
id: string;
|
||||||
|
nom: string;
|
||||||
|
noms?: string[];
|
||||||
|
}
|
||||||
17
src/app/ui/sectors/sector.presenter.ts
Normal file
17
src/app/ui/sectors/sector.presenter.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { SectorPresenterModel } from '@app/ui/sectors/sector.presenter.model';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
|
||||||
|
export class SectorPresenter {
|
||||||
|
toViewModel(sector: Sector): SectorPresenterModel {
|
||||||
|
const names = sector.nom ? sector.nom.split('-') : [];
|
||||||
|
return {
|
||||||
|
id: sector.id,
|
||||||
|
nom: sector.nom,
|
||||||
|
noms: names,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toViewModels(sectors: Sector[]): SectorPresenterModel[] {
|
||||||
|
return sectors.map(this.toViewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/app/usecase/sectors/get-sector.usecase.ts
Normal file
11
src/app/usecase/sectors/get-sector.usecase.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { SectorRepository } from '@app/domain/sectors/sector.repository';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
|
||||||
|
export class GetSectorUseCase {
|
||||||
|
constructor(private readonly sectorRepository: SectorRepository) {}
|
||||||
|
|
||||||
|
execute(sectorId: string): Observable<Sector> {
|
||||||
|
return this.sectorRepository.getOne(sectorId);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/app/usecase/sectors/list-sector.usecase.ts
Normal file
11
src/app/usecase/sectors/list-sector.usecase.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { SectorRepository } from '@app/domain/sectors/sector.repository';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Sector } from '@app/domain/sectors/sector.model';
|
||||||
|
|
||||||
|
export class ListSectorUsecase {
|
||||||
|
constructor(private readonly sectorRepository: SectorRepository) {}
|
||||||
|
|
||||||
|
execute(): Observable<Sector[]> {
|
||||||
|
return this.sectorRepository.list();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user