Files
shopit-front/src/app/shared/components/cart/cart.component.spec.ts

310 lines
8.8 KiB
TypeScript

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, throwError } 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('shows an empty cart message when there are no items', async () => {
await TestBed.configureTestingModule({
imports: [CartComponent],
providers: [
{
provide: CartService,
useValue: {
cart: signal(null).asReadonly(),
updateItemQuantity: vi.fn(),
removeItem: vi.fn(),
},
},
{
provide: ModalService,
useValue: {},
},
{
provide: ToastService,
useValue: {
success: vi.fn(),
info: vi.fn(),
danger: vi.fn(),
},
},
],
}).compileComponents();
const fixture = TestBed.createComponent(CartComponent);
fixture.detectChanges();
const emptyMessage = fixture.debugElement.query(By.css('.cart-empty-message'));
expect(emptyMessage).not.toBeNull();
expect(emptyMessage.nativeElement.textContent.trim()).toBe('El carrito está vacío');
expect(fixture.debugElement.query(By.css('app-cart-item'))).toBeNull();
expect(fixture.debugElement.query(By.css('.cart-footer'))).toBeNull();
});
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', [
{
cartItemId: 10,
imageUrl: null,
product: 'Producto de prueba',
originalPrice: null,
discountedPrice: 1000,
discountPercentage: null,
attributes: [],
quantity: 1,
},
]);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.cart-item-media'))).toBeNull();
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', [
{
cartItemId: 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();
});
it('optimistically updates quantity and rolls back on error', async () => {
vi.useFakeTimers();
const updateItemQuantity = vi.fn().mockReturnValue(throwError(() => new Error('Error')));
const danger = vi.fn();
await TestBed.configureTestingModule({
imports: [CartComponent],
providers: [
{
provide: CartService,
useValue: {
cart: signal(null).asReadonly(),
updateItemQuantity,
removeItem: vi.fn(),
},
},
{
provide: ModalService,
useValue: {},
},
{
provide: ToastService,
useValue: {
success: vi.fn(),
info: vi.fn(),
danger,
},
},
],
}).compileComponents();
const fixture = TestBed.createComponent(CartComponent);
const component = fixture.componentInstance;
const item = {
cartItemId: 10,
imageUrl: null,
product: 'Producto de prueba',
originalPrice: null,
discountedPrice: 1000,
discountPercentage: null,
attributes: [],
quantity: 1,
};
fixture.componentRef.setInput('items', [item]);
fixture.detectChanges();
// Trigger quantity change to 3
fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('quantityChange', 3);
fixture.detectChanges();
// Optimistic update should be active immediately in local getter
expect((component as any).getItemQuantity(item)).toBe(3);
// Wait for the debounce time (1000ms)
vi.advanceTimersByTime(1000);
fixture.detectChanges();
// After failure, it should roll back to original quantity (1)
expect((component as any).getItemQuantity(item)).toBe(1);
expect(danger).toHaveBeenCalled();
vi.useRealTimers();
});
it('optimistically updates quantity and clears override on success', async () => {
vi.useFakeTimers();
const updateItemQuantity = vi.fn().mockReturnValue(of({ message: 'Success', data: {} }));
const success = vi.fn();
await TestBed.configureTestingModule({
imports: [CartComponent],
providers: [
{
provide: CartService,
useValue: {
cart: signal(null).asReadonly(),
updateItemQuantity,
removeItem: vi.fn(),
},
},
{
provide: ModalService,
useValue: {},
},
{
provide: ToastService,
useValue: {
success,
info: vi.fn(),
danger: vi.fn(),
},
},
],
}).compileComponents();
const fixture = TestBed.createComponent(CartComponent);
const component = fixture.componentInstance;
const item = {
cartItemId: 10,
imageUrl: null,
product: 'Producto de prueba',
originalPrice: null,
discountedPrice: 1000,
discountPercentage: null,
attributes: [],
quantity: 1,
};
fixture.componentRef.setInput('items', [item]);
fixture.detectChanges();
// Trigger quantity change to 3
fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('quantityChange', 3);
fixture.detectChanges();
// Optimistic update should be active immediately in local getter
expect((component as any).getItemQuantity(item)).toBe(3);
// Wait for the debounce time (1000ms)
vi.advanceTimersByTime(1000);
fixture.detectChanges();
// After success, it should clear override and use input quantity (which is 1 since we didn't update items input here)
expect((component as any).getItemQuantity(item)).toBe(1);
expect(success).toHaveBeenCalled();
vi.useRealTimers();
});
});