feat: add CatalogService and ProductDetailPageComponent with initial unit tests

This commit is contained in:
2026-07-01 11:16:19 -03:00
parent 3ad29f959d
commit ae0690a6f4
3 changed files with 15 additions and 44 deletions

View File

@@ -35,14 +35,9 @@ export class CatalogService {
); );
} }
getProducto(id: number, defaultVariantId?: number): Observable<ProductDetail> { getProducto(id: number): Observable<ProductDetail> {
const params =
defaultVariantId === undefined
? undefined
: new HttpParams({ fromObject: { default_variant: defaultVariantId } });
return this.http return this.http
.get<ApiResponse<ProductDetail>>(`${this.tenantApiUrl}/productos/${id}`, { params }) .get<ApiResponse<ProductDetail>>(`${this.tenantApiUrl}/productos/${id}`)
.pipe(map((response) => response.data)); .pipe(map((response) => response.data));
} }

View File

@@ -31,7 +31,6 @@ describe('ProductDetailPageComponent', () => {
}; };
let paramMapSubject: BehaviorSubject<any>; let paramMapSubject: BehaviorSubject<any>;
let queryParamMapSubject: BehaviorSubject<any>;
let catalogServiceStub: any; let catalogServiceStub: any;
let routerStub: any; let routerStub: any;
@@ -49,7 +48,6 @@ describe('ProductDetailPageComponent', () => {
beforeEach(() => { beforeEach(() => {
vi.restoreAllMocks(); vi.restoreAllMocks();
paramMapSubject = new BehaviorSubject(convertToParamMap({ id: '1' })); paramMapSubject = new BehaviorSubject(convertToParamMap({ id: '1' }));
queryParamMapSubject = new BehaviorSubject(convertToParamMap({}));
catalogServiceStub = { catalogServiceStub = {
getProducto: vi.fn().mockReturnValue(of(mockProduct)) getProducto: vi.fn().mockReturnValue(of(mockProduct))
}; };
@@ -65,8 +63,7 @@ describe('ProductDetailPageComponent', () => {
{ {
provide: ActivatedRoute, provide: ActivatedRoute,
useValue: { useValue: {
paramMap: paramMapSubject.asObservable(), paramMap: paramMapSubject.asObservable()
queryParamMap: queryParamMapSubject.asObservable()
} }
}, },
{ {
@@ -88,7 +85,7 @@ describe('ProductDetailPageComponent', () => {
const element = fixture.nativeElement as HTMLElement; const element = fixture.nativeElement as HTMLElement;
expect(catalogServiceStub.getProducto).toHaveBeenCalledWith(1, undefined); expect(catalogServiceStub.getProducto).toHaveBeenCalledWith(1);
expect(element.querySelector('app-product-carousel')).not.toBeNull(); expect(element.querySelector('app-product-carousel')).not.toBeNull();
expect(element.querySelector('.product-detail__title')?.textContent).toContain('Auriculares Bluetooth'); expect(element.querySelector('.product-detail__title')?.textContent).toContain('Auriculares Bluetooth');
@@ -108,25 +105,6 @@ describe('ProductDetailPageComponent', () => {
expect(element.textContent).toContain('No pudimos cargar los detalles del producto.'); expect(element.textContent).toContain('No pudimos cargar los detalles del producto.');
}); });
it('passes default_variant from the query params when loading the product', async () => {
queryParamMapSubject.next(convertToParamMap({ default_variant: '123' }));
await configureTestingModule();
const fixture = TestBed.createComponent(ProductDetailPageComponent);
fixture.detectChanges();
expect(catalogServiceStub.getProducto).toHaveBeenCalledWith(1, 123);
});
it('ignores an invalid default_variant query param', async () => {
queryParamMapSubject.next(convertToParamMap({ default_variant: 'abc' }));
await configureTestingModule();
const fixture = TestBed.createComponent(ProductDetailPageComponent);
fixture.detectChanges();
expect(catalogServiceStub.getProducto).toHaveBeenCalledWith(1, undefined);
});
it('should use default variant images if present', async () => { it('should use default variant images if present', async () => {
const detailProduct: ProductDetail = { const detailProduct: ProductDetail = {

View File

@@ -13,7 +13,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { CommonModule, isPlatformBrowser } from '@angular/common'; import { CommonModule, isPlatformBrowser } from '@angular/common';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { combineLatest, Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CatalogService } from '../../../../core/services/catalog/catalog.service'; import { CatalogService } from '../../../../core/services/catalog/catalog.service';
import { import {
@@ -97,17 +97,15 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.routeSub = combineLatest([this.route.paramMap, this.route.queryParamMap]).subscribe( this.routeSub = this.route.paramMap.subscribe((params) => {
([params, queryParams]) => { const id = this.parseIntegerParam(params.get('id'));
const id = this.parseIntegerParam(params.get('id')); if (id === null) {
if (id === null) { this.error.set('ID de producto inválido');
this.error.set('ID de producto inválido'); return;
return;
}
this.loadProduct(id, this.parseIntegerParam(queryParams.get('default_variant')) ?? undefined);
} }
);
this.loadProduct(id);
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@@ -117,13 +115,13 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
this.clearMeasurementTimer(); this.clearMeasurementTimer();
} }
private loadProduct(id: number, defaultVariantId?: number): void { private loadProduct(id: number): void {
this.loading.set(true); this.loading.set(true);
this.error.set(null); this.error.set(null);
this.product.set(null); this.product.set(null);
this.productSub?.unsubscribe(); this.productSub?.unsubscribe();
this.productSub = this.catalogService.getProducto(id, defaultVariantId).subscribe({ this.productSub = this.catalogService.getProducto(id).subscribe({
next: (prod) => { next: (prod) => {
this.product.set(prod); this.product.set(prod);
this.quantity.set(1); this.quantity.set(1);