fix(cart): refresh state after expiration
This commit is contained in:
@@ -175,6 +175,63 @@ describe('CartComponent', () => {
|
|||||||
expect(removeItem).not.toHaveBeenCalled();
|
expect(removeItem).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('notifies the user and refreshes the cart when a mutation reports expiration', async () => {
|
||||||
|
const expirationMessage = 'La reserva de stock venció. Usá el carrito activo para continuar.';
|
||||||
|
const removeItem = vi.fn().mockReturnValue(
|
||||||
|
throwError(() => ({
|
||||||
|
error: {
|
||||||
|
code: 'stock_reservation.expired',
|
||||||
|
message: expirationMessage,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
const loadCart = vi.fn().mockReturnValue(of({}));
|
||||||
|
const danger = vi.fn();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CartComponent],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CartService,
|
||||||
|
useValue: {
|
||||||
|
cart: signal(null).asReadonly(),
|
||||||
|
loadCart,
|
||||||
|
updateItemQuantity: vi.fn(),
|
||||||
|
removeItem,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ModalService,
|
||||||
|
useValue: { openConfirmDelete: vi.fn().mockReturnValue(of(true)) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ToastService,
|
||||||
|
useValue: { success: vi.fn(), info: vi.fn(), danger },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(CartComponent);
|
||||||
|
fixture.componentRef.setInput('items', [
|
||||||
|
{
|
||||||
|
cartItemId: 10,
|
||||||
|
imageUrl: null,
|
||||||
|
product: 'Producto vencido',
|
||||||
|
originalPrice: null,
|
||||||
|
discountedPrice: 1000,
|
||||||
|
discountPercentage: null,
|
||||||
|
attributes: [],
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('remove');
|
||||||
|
|
||||||
|
expect(danger).toHaveBeenCalledWith(expirationMessage);
|
||||||
|
expect(loadCart).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
it('optimistically updates quantity and rolls back on error', async () => {
|
it('optimistically updates quantity and rolls back on error', async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
const updateItemQuantity = vi.fn().mockReturnValue(throwError(() => new Error('Error')));
|
const updateItemQuantity = vi.fn().mockReturnValue(throwError(() => new Error('Error')));
|
||||||
|
|||||||
@@ -98,11 +98,12 @@ export class CartComponent {
|
|||||||
this.clearOverride(update.cartItemId);
|
this.clearOverride(update.cartItemId);
|
||||||
},
|
},
|
||||||
error: (err: HttpErrorResponse) => {
|
error: (err: HttpErrorResponse) => {
|
||||||
console.error('Error updating cart quantity', err);
|
|
||||||
const msg =
|
|
||||||
err.error?.message || 'Error al actualizar la cantidad del producto.';
|
|
||||||
this.toastService.danger(msg);
|
|
||||||
this.clearOverride(update.cartItemId);
|
this.clearOverride(update.cartItemId);
|
||||||
|
this.handleMutationError(
|
||||||
|
err,
|
||||||
|
'Error al actualizar la cantidad del producto.',
|
||||||
|
'Error updating cart quantity',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
catchError(() => EMPTY),
|
catchError(() => EMPTY),
|
||||||
@@ -205,9 +206,12 @@ export class CartComponent {
|
|||||||
this.toastService.success(response.message || 'Variante actualizada.');
|
this.toastService.success(response.message || 'Variante actualizada.');
|
||||||
},
|
},
|
||||||
error: (error: HttpErrorResponse) => {
|
error: (error: HttpErrorResponse) => {
|
||||||
console.error('Error updating cart item variant', error);
|
|
||||||
this.clearVariantOverride(cartItemId);
|
this.clearVariantOverride(cartItemId);
|
||||||
this.toastService.danger(error.error?.message || 'No se pudo actualizar la variante.');
|
this.handleMutationError(
|
||||||
|
error,
|
||||||
|
'No se pudo actualizar la variante.',
|
||||||
|
'Error updating cart item variant',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -305,10 +309,29 @@ export class CartComponent {
|
|||||||
this.toastService.info(msg);
|
this.toastService.info(msg);
|
||||||
},
|
},
|
||||||
error: (err: HttpErrorResponse) => {
|
error: (err: HttpErrorResponse) => {
|
||||||
console.error('Error removing item from cart', err);
|
this.handleMutationError(
|
||||||
const msg = err.error?.message || 'Error al eliminar el producto del carrito.';
|
err,
|
||||||
this.toastService.danger(msg);
|
'Error al eliminar el producto del carrito.',
|
||||||
|
'Error removing item from cart',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleMutationError(
|
||||||
|
error: HttpErrorResponse,
|
||||||
|
fallbackMessage: string,
|
||||||
|
logMessage: string,
|
||||||
|
): void {
|
||||||
|
console.error(logMessage, error);
|
||||||
|
this.toastService.danger(error.error?.message || fallbackMessage);
|
||||||
|
|
||||||
|
if (error.error?.code !== 'stock_reservation.expired') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cartService.loadCart().subscribe({
|
||||||
|
error: (refreshError) => console.error('Error refreshing expired cart', refreshError),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user