feat(cart): integrate confirmation modals for item removal in cart component
This commit is contained in:
144
src/app/shared/components/cart/cart.component.spec.ts
Normal file
144
src/app/shared/components/cart/cart.component.spec.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import '@angular/compiler';
|
||||
import { signal } from '@angular/core';
|
||||
import { TestBed, getTestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import {
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting
|
||||
} from '@angular/platform-browser/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { CartService } from '../../../core/services/cart/cart.service';
|
||||
import { ModalService } from '../../../core/services/modal.service';
|
||||
import { ToastService } from '../../../core/services/toast.service';
|
||||
import { CartComponent } from './cart.component';
|
||||
|
||||
describe('CartComponent', () => {
|
||||
beforeAll(() => {
|
||||
try {
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting()
|
||||
);
|
||||
} catch {
|
||||
// Test environment may already be initialized by another setup entrypoint.
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('opens a confirm delete modal before removing an item', async () => {
|
||||
const removeItem = vi.fn().mockReturnValue(of({ message: 'Producto eliminado.' }));
|
||||
const openConfirmDelete = vi.fn().mockReturnValue(of(true));
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CartComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: CartService,
|
||||
useValue: {
|
||||
cart: signal(null).asReadonly(),
|
||||
updateItemQuantity: vi.fn(),
|
||||
removeItem
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: ModalService,
|
||||
useValue: {
|
||||
openConfirmDelete
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: ToastService,
|
||||
useValue: {
|
||||
success: vi.fn(),
|
||||
info: vi.fn(),
|
||||
danger: vi.fn()
|
||||
}
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(CartComponent);
|
||||
fixture.componentRef.setInput('items', [
|
||||
{
|
||||
productVariantId: 10,
|
||||
imageUrl: null,
|
||||
product: 'Producto de prueba',
|
||||
originalPrice: null,
|
||||
discountedPrice: 1000,
|
||||
discountPercentage: null,
|
||||
attributes: [],
|
||||
quantity: 1
|
||||
}
|
||||
]);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('remove');
|
||||
|
||||
expect(openConfirmDelete).toHaveBeenCalledWith({
|
||||
title: 'Eliminar producto',
|
||||
content:
|
||||
'Se eliminara "Producto de prueba" del carrito. Esta accion no se puede deshacer.',
|
||||
confirmLabel: 'Eliminar',
|
||||
cancelLabel: 'Cancelar'
|
||||
});
|
||||
expect(removeItem).toHaveBeenCalledWith(10);
|
||||
});
|
||||
|
||||
it('does not remove the item when the delete confirmation is cancelled', async () => {
|
||||
const removeItem = vi.fn().mockReturnValue(of({}));
|
||||
const openConfirmDelete = vi.fn().mockReturnValue(of(false));
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CartComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: CartService,
|
||||
useValue: {
|
||||
cart: signal(null).asReadonly(),
|
||||
updateItemQuantity: vi.fn(),
|
||||
removeItem
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: ModalService,
|
||||
useValue: {
|
||||
openConfirmDelete
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: ToastService,
|
||||
useValue: {
|
||||
success: vi.fn(),
|
||||
info: vi.fn(),
|
||||
danger: vi.fn()
|
||||
}
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(CartComponent);
|
||||
fixture.componentRef.setInput('items', [
|
||||
{
|
||||
productVariantId: 10,
|
||||
imageUrl: null,
|
||||
product: 'Producto de prueba',
|
||||
originalPrice: null,
|
||||
discountedPrice: 1000,
|
||||
discountPercentage: null,
|
||||
attributes: [],
|
||||
quantity: 1
|
||||
}
|
||||
]);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('remove');
|
||||
|
||||
expect(openConfirmDelete).toHaveBeenCalled();
|
||||
expect(removeItem).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { Subject, EMPTY } from 'rxjs';
|
||||
import { catchError, debounceTime, groupBy, mergeMap, switchMap, tap } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { CartItemAttribute, CartItemComponent } from '../cart-item/cart-item.component';
|
||||
import { ModalService } from '../../../core/services/modal.service';
|
||||
import { CartService } from '../../../core/services/cart/cart.service';
|
||||
import { ToastService } from '../../../core/services/toast.service';
|
||||
|
||||
@@ -28,6 +29,7 @@ export interface CartItemMock {
|
||||
})
|
||||
export class CartComponent {
|
||||
private readonly cartService = inject(CartService);
|
||||
private readonly modalService = inject(ModalService);
|
||||
private readonly toastService = inject(ToastService);
|
||||
private readonly quantityUpdates$ = new Subject<{ productVariantId: number; quantity: number }>();
|
||||
|
||||
@@ -88,36 +90,22 @@ export class CartComponent {
|
||||
}
|
||||
|
||||
protected onItemRemove(index: number): void {
|
||||
const mockItem = this.items()[index];
|
||||
const productVariantId = mockItem?.productVariantId;
|
||||
if (productVariantId) {
|
||||
this.cartService.removeItem(productVariantId).subscribe({
|
||||
next: (res) => {
|
||||
const msg = res.message || 'Producto eliminado del carrito.';
|
||||
this.toastService.success(msg);
|
||||
},
|
||||
error: (err: HttpErrorResponse) => {
|
||||
console.error('Error removing item from cart', err);
|
||||
const msg = err.error?.message || 'Error al eliminar el producto del carrito.';
|
||||
this.toastService.danger(msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const item = this.cartService.cart()?.items[index];
|
||||
if (item) {
|
||||
this.cartService.removeItem(item.product_variant_id).subscribe({
|
||||
next: (res) => {
|
||||
const msg = res.message || 'Producto eliminado del carrito.';
|
||||
this.toastService.success(msg);
|
||||
},
|
||||
error: (err: HttpErrorResponse) => {
|
||||
console.error('Error removing item from cart', err);
|
||||
const msg = err.error?.message || 'Error al eliminar el producto del carrito.';
|
||||
this.toastService.danger(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
const target = this.resolveRemoveTarget(index);
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalService.openConfirmDelete({
|
||||
title: 'Eliminar producto',
|
||||
content: `Se eliminara "${target.productName}" del carrito. Esta accion no se puede deshacer.`,
|
||||
confirmLabel: 'Eliminar',
|
||||
cancelLabel: 'Cancelar'
|
||||
}).subscribe((confirmed) => {
|
||||
if (confirmed) {
|
||||
this.removeItem(target.productVariantId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected readonly formattedSubtotal = computed(() => this.formatCurrency(this.subtotal()));
|
||||
@@ -130,5 +118,44 @@ export class CartComponent {
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||
return `$ ${parts.join(',')}`;
|
||||
}
|
||||
|
||||
private resolveRemoveTarget(
|
||||
index: number
|
||||
): { productVariantId: number; productName: string } | null {
|
||||
const mockItem = this.items()[index];
|
||||
const productVariantId = mockItem?.productVariantId;
|
||||
|
||||
if (productVariantId) {
|
||||
return {
|
||||
productVariantId,
|
||||
productName: mockItem.product
|
||||
};
|
||||
}
|
||||
|
||||
const item = this.cartService.cart()?.items[index];
|
||||
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
productVariantId: item.product_variant_id,
|
||||
productName: item.product?.nombre ?? 'este producto'
|
||||
};
|
||||
}
|
||||
|
||||
private removeItem(productVariantId: number): void {
|
||||
this.cartService.removeItem(productVariantId).subscribe({
|
||||
next: (res) => {
|
||||
const msg = res.message || 'Producto eliminado del carrito.';
|
||||
this.toastService.info(msg);
|
||||
},
|
||||
error: (err: HttpErrorResponse) => {
|
||||
console.error('Error removing item from cart', err);
|
||||
const msg = err.error?.message || 'Error al eliminar el producto del carrito.';
|
||||
this.toastService.danger(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user