310 lines
8.8 KiB
TypeScript
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();
|
|
});
|
|
});
|