From 70060f65ec82a42c845be34fcb33c54141be89e0 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 21 Aug 2026 14:19:28 -0300 Subject: [PATCH] feat(cart): notify catalog availability changes --- .../core/services/cart/cart.service.spec.ts | 27 +++++++++++++++++++ src/app/core/services/cart/cart.service.ts | 5 ++++ .../catalog/catalog-availability.service.ts | 15 +++++++++++ 3 files changed, 47 insertions(+) create mode 100644 src/app/core/services/catalog/catalog-availability.service.ts diff --git a/src/app/core/services/cart/cart.service.spec.ts b/src/app/core/services/cart/cart.service.spec.ts index 7b1d7c2..0a43a80 100644 --- a/src/app/core/services/cart/cart.service.spec.ts +++ b/src/app/core/services/cart/cart.service.spec.ts @@ -7,11 +7,13 @@ import { CartService } from './cart.service'; import { TenantService } from '../tenant.service'; import { Cart } from './cart.interface'; import { LOADING_MODE } from '../global-loading/loading-mode'; +import { CatalogAvailabilityService } from '../catalog/catalog-availability.service'; describe('CartService', () => { let service: CartService; let httpMock: HttpTestingController; let tenantServiceMock: any; + let catalogAvailabilityService: CatalogAvailabilityService; const mockCart: Cart = { id: 123, @@ -53,6 +55,7 @@ describe('CartService', () => { }); service = TestBed.inject(CartService); + catalogAvailabilityService = TestBed.inject(CatalogAvailabilityService); httpMock = TestBed.inject(HttpTestingController); }); @@ -101,6 +104,9 @@ describe('CartService', () => { }); it('should add item and update signal', () => { + const availabilityChanged = vi.fn(); + catalogAvailabilityService.availabilityChanged$.subscribe(availabilityChanged); + service.addItem(5, 10, 2).subscribe((res) => { expect(res.data).toEqual(mockCart); expect(service.cart()).toEqual(mockCart); @@ -115,9 +121,13 @@ describe('CartService', () => { }); expect(req.request.withCredentials).toBe(true); req.flush({ data: mockCart }); + expect(availabilityChanged).toHaveBeenCalledOnce(); }); it('should update item quantity and update signal', () => { + const availabilityChanged = vi.fn(); + catalogAvailabilityService.availabilityChanged$.subscribe(availabilityChanged); + const updatedCart = { ...mockCart, subtotal: '30.00' }; updatedCart.items[0].cantidad = 3; @@ -131,9 +141,13 @@ describe('CartService', () => { expect(req.request.body).toEqual({ cantidad: 3 }); expect(req.request.withCredentials).toBe(true); req.flush({ data: updatedCart }); + expect(availabilityChanged).toHaveBeenCalledOnce(); }); it('should remove item and update signal', () => { + const availabilityChanged = vi.fn(); + catalogAvailabilityService.availabilityChanged$.subscribe(availabilityChanged); + const emptyCart: Cart = { id: 123, tenant_codigo: 'acme', @@ -151,5 +165,18 @@ describe('CartService', () => { expect(req.request.method).toBe('DELETE'); expect(req.request.withCredentials).toBe(true); req.flush({ data: emptyCart }); + expect(availabilityChanged).toHaveBeenCalledOnce(); + }); + + it('does not notify an availability change when a cart mutation fails', () => { + const availabilityChanged = vi.fn(); + catalogAvailabilityService.availabilityChanged$.subscribe(availabilityChanged); + + service.removeItem(10).subscribe({ error: vi.fn() }); + + const req = httpMock.expectOne('http://api.test/tenants/acme/cart/items/10'); + req.flush({ message: 'Error' }, { status: 500, statusText: 'Server Error' }); + + expect(availabilityChanged).not.toHaveBeenCalled(); }); }); diff --git a/src/app/core/services/cart/cart.service.ts b/src/app/core/services/cart/cart.service.ts index 750105f..5f5c42d 100644 --- a/src/app/core/services/cart/cart.service.ts +++ b/src/app/core/services/cart/cart.service.ts @@ -3,6 +3,7 @@ import { catchError, map, Observable, tap } from 'rxjs'; import { ApiResponse } from '../api-response.interface'; import { BaseApiService } from '../base-api.service'; +import { CatalogAvailabilityService } from '../catalog/catalog-availability.service'; import { TenantService } from '../tenant.service'; import { Cart } from './cart.interface'; @@ -10,6 +11,7 @@ import { Cart } from './cart.interface'; providedIn: 'root', }) export class CartService extends BaseApiService { + private readonly catalogAvailabilityService = inject(CatalogAvailabilityService); private readonly tenantService = inject(TenantService); private readonly cartState = signal(null); @@ -60,6 +62,7 @@ export class CartService extends BaseApiService { .pipe( tap((response) => { this.cartState.set(response.data); + this.catalogAvailabilityService.notifyAvailabilityChanged(); this.isUpdatingState.set(false); }), catchError((error) => { @@ -93,6 +96,7 @@ export class CartService extends BaseApiService { .pipe( tap((response) => { this.cartState.set(response.data); + this.catalogAvailabilityService.notifyAvailabilityChanged(); this.isUpdatingState.set(false); }), catchError((error) => { @@ -111,6 +115,7 @@ export class CartService extends BaseApiService { .pipe( tap((response) => { this.cartState.set(response.data); + this.catalogAvailabilityService.notifyAvailabilityChanged(); this.isUpdatingState.set(false); }), catchError((error) => { diff --git a/src/app/core/services/catalog/catalog-availability.service.ts b/src/app/core/services/catalog/catalog-availability.service.ts new file mode 100644 index 0000000..e13f8ea --- /dev/null +++ b/src/app/core/services/catalog/catalog-availability.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class CatalogAvailabilityService { + private readonly availabilityChangedSubject = new Subject(); + + readonly availabilityChanged$: Observable = this.availabilityChangedSubject.asObservable(); + + notifyAvailabilityChanged(): void { + this.availabilityChangedSubject.next(); + } +}