feat(cart): implement optimistic quantity updates with rollback on error
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting
|
||||
} from '@angular/platform-browser/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { CartService } from '../../../core/services/cart/cart.service';
|
||||
@@ -141,4 +141,136 @@ describe('CartComponent', () => {
|
||||
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 = {
|
||||
productVariantId: 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 = {
|
||||
productVariantId: 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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user