diff --git a/src/app/core/services/catalog/catalog-availability.spec.ts b/src/app/core/services/catalog/catalog-availability.spec.ts new file mode 100644 index 0000000..5432b11 --- /dev/null +++ b/src/app/core/services/catalog/catalog-availability.spec.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest'; + +import { CatalogAvailability } from './catalog.interface'; +import { + allowsCatalogAction, + combineCatalogAvailability, + createCatalogAvailability, + maximumCatalogQuantity, +} from './catalog-availability'; + +describe('catalog availability', () => { + it('represents hidden items without irrelevant actions or quantities', () => { + const availability = createCatalogAvailability(0); + + expect(availability).toEqual({ + state: 'hidden', + reasons: [ + { + code: 'out_of_stock', + message: 'Este producto no tiene stock disponible.', + }, + ], + }); + expect(allowsCatalogAction(availability, 'buy_now')).toBe(false); + expect(maximumCatalogQuantity(availability)).toBe(0); + }); + + it('intersects product and variant actions and quantities', () => { + const product: CatalogAvailability = { + state: 'visible', + maximum_quantity: 3, + allowed_actions: ['select_variant', 'change_quantity'], + reasons: [{ code: 'limited', message: 'Corregí la selección.' }], + }; + const variant: CatalogAvailability = { + state: 'visible', + maximum_quantity: 2, + allowed_actions: ['change_quantity', 'add_to_cart'], + reasons: [], + }; + + expect(combineCatalogAvailability(product, variant)).toEqual({ + state: 'visible', + maximum_quantity: 2, + allowed_actions: ['change_quantity'], + reasons: [{ code: 'limited', message: 'Corregí la selección.' }], + }); + }); + + it('lets a hidden decision win composition', () => { + const availability = combineCatalogAvailability( + createCatalogAvailability(5), + createCatalogAvailability(0), + ); + + expect(availability.state).toBe('hidden'); + expect(allowsCatalogAction(availability, 'add_to_cart')).toBe(false); + }); +}); diff --git a/src/app/features/store/pages/product-detail-page/product-detail-page.component.spec.ts b/src/app/features/store/pages/product-detail-page/product-detail-page.component.spec.ts index 47fe911..b428622 100644 --- a/src/app/features/store/pages/product-detail-page/product-detail-page.component.spec.ts +++ b/src/app/features/store/pages/product-detail-page/product-detail-page.component.spec.ts @@ -193,6 +193,21 @@ describe('ProductDetailPageComponent', () => { expect(fixture.componentInstance['selectedVariantMax']()).toBe(4); }); + it('stops presenting a product that becomes hidden during an availability refresh', async () => { + catalogServiceStub.getCatalogItem.mockReturnValue( + throwError(() => new HttpErrorResponse({ status: 404 })), + ); + await configureTestingModule(); + const fixture = TestBed.createComponent(ProductDetailPageComponent); + fixture.detectChanges(); + + TestBed.inject(CatalogAvailabilityService).notifyAvailabilityChanged(); + fixture.detectChanges(); + + expect(fixture.componentInstance['product']()).toBeNull(); + expect(fixture.nativeElement.textContent).toContain('Este producto ya no está disponible.'); + }); + it('shows error message if the resolver cannot load the product', async () => { resolveProductError(PRODUCT_DETAIL_ERROR_MESSAGE); await configureTestingModule(); diff --git a/src/app/shared/components/product-list/product-list.component.spec.ts b/src/app/shared/components/product-list/product-list.component.spec.ts index 35b12db..4a145ad 100644 --- a/src/app/shared/components/product-list/product-list.component.spec.ts +++ b/src/app/shared/components/product-list/product-list.component.spec.ts @@ -59,6 +59,20 @@ describe('ProductListComponent', () => { ) { const getVariantOptions = vi.fn().mockReturnValue( of({ + variants: [ + { + id: 401, + precio: '10000.00', + availability: createCatalogAvailability(1), + values: { tipo: '1', sector: '2', fila: '3', asiento: '4' }, + }, + { + id: 402, + precio: '12000.00', + availability: createCatalogAvailability(1), + values: { tipo: '1', sector: '2', fila: '3', asiento: '5' }, + }, + ], selectors: ['tipo', 'sector', 'fila', 'asiento'].map((key, index) => ({ key, label: key, @@ -76,7 +90,12 @@ describe('ProductListComponent', () => { await TestBed.configureTestingModule({ imports: [ProductListComponent], providers: [ - { provide: CatalogService, useValue: { getVariantOptions } }, + { + provide: CatalogService, + useValue: { + withoutLoading: () => ({ getVariantOptions }), + }, + }, { provide: CartService, useValue: {} }, ], }).compileComponents(); diff --git a/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.spec.ts b/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.spec.ts index e23f0db..3d2bd7a 100644 --- a/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.spec.ts +++ b/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.spec.ts @@ -59,21 +59,15 @@ describe('ProductVerticalWithCartCardComponent', () => { it('shows the backend availability message with the reusable tooltip', async () => { const fixture = await createComponent(); fixture.componentRef.setInput('availability', { - subject: 'product', + state: 'visible', maximum_quantity: 0, - restrictions: [ + reasons: [ { code: 'user_quota_reached', message: 'Alcanzaste el cupo máximo permitido para este producto.', }, ], - capabilities: { - display: true, - select_variant: false, - change_quantity: false, - add_to_cart: false, - buy_now: false, - }, + allowed_actions: [], }); fixture.detectChanges(); diff --git a/src/app/shared/components/variant-selector/variant-selector.component.spec.ts b/src/app/shared/components/variant-selector/variant-selector.component.spec.ts index 4fecc0c..298d25f 100644 --- a/src/app/shared/components/variant-selector/variant-selector.component.spec.ts +++ b/src/app/shared/components/variant-selector/variant-selector.component.spec.ts @@ -92,7 +92,7 @@ describe('VariantSelectorComponent', () => { { id: 2, values: { talle: 'M' }, - availability: { capabilities: { display: true, select_variant: false } }, + availability: { state: 'visible', maximum_quantity: 0, allowed_actions: [], reasons: [] }, }, ]); fixture.detectChanges();