saveinmed/marketplace/src/stores/cartStore.test.ts
Tiago Yamamoto 285f586371 fix: cart price display showing cents as reais (R19 instead of R.19)
Root cause: Cart.tsx used formatCurrency(819) -> '819,00' instead of formatCents(819) -> 'R$ 8,19'

Changes:
- Cart.tsx: Replace formatCurrency with formatCents at all 5 price display points
- Cart.tsx: Add debug logging (logPrice) to trace price values
- cartStore.ts: Add debug logging for addItem, updateQuantity, etc.
- cartStore.ts: Document unitPrice as CENTS in interface
- cartStore.test.ts: Add 7 new price conversion tests for cents handling

All 95 tests pass.
2025-12-26 23:08:55 -03:00

326 lines
11 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest'
import { useCartStore, selectGroupedCart, selectCartSummary, CartItem } from './cartStore'
const mockItem: Omit<CartItem, 'quantity'> = {
id: 'prod-1',
name: 'Produto Teste',
activeIngredient: 'Amoxicilina',
lab: 'EMS',
batch: 'L1001',
expiry: '2025-12',
vendorId: 'vendor-1',
vendorName: 'Distribuidora Norte',
unitPrice: 25.00
}
const mockItem2: Omit<CartItem, 'quantity'> = {
id: 'prod-2',
name: 'Produto Teste 2',
activeIngredient: 'Dipirona',
lab: 'Eurofarma',
batch: 'L2002',
expiry: '2026-01',
vendorId: 'vendor-2',
vendorName: 'Distribuidora Sul',
unitPrice: 15.00
}
describe('cartStore', () => {
beforeEach(() => {
// Reset the store before each test
useCartStore.setState({ items: [] })
})
describe('addItem', () => {
it('should add an item to the cart', () => {
const store = useCartStore.getState()
store.addItem(mockItem)
const state = useCartStore.getState()
expect(state.items).toHaveLength(1)
expect(state.items[0]).toMatchObject({ ...mockItem, quantity: 1 })
})
it('should add item with custom quantity', () => {
const store = useCartStore.getState()
store.addItem(mockItem, 5)
const state = useCartStore.getState()
expect(state.items[0].quantity).toBe(5)
})
it('should increment quantity if item already exists', () => {
const store = useCartStore.getState()
store.addItem(mockItem, 2)
store.addItem(mockItem, 3)
const state = useCartStore.getState()
expect(state.items).toHaveLength(1)
expect(state.items[0].quantity).toBe(5)
})
})
describe('updateQuantity', () => {
it('should update quantity of existing item', () => {
useCartStore.getState().addItem(mockItem, 2)
useCartStore.getState().updateQuantity('prod-1', 10)
const state = useCartStore.getState()
expect(state.items[0].quantity).toBe(10)
})
it('should not affect other items', () => {
useCartStore.getState().addItem(mockItem, 2)
useCartStore.getState().addItem(mockItem2, 3)
useCartStore.getState().updateQuantity('prod-1', 10)
const state = useCartStore.getState()
expect(state.items[0].quantity).toBe(10)
expect(state.items[1].quantity).toBe(3)
})
})
describe('removeItem', () => {
it('should remove item from cart', () => {
useCartStore.getState().addItem(mockItem)
useCartStore.getState().addItem(mockItem2)
useCartStore.getState().removeItem('prod-1')
const state = useCartStore.getState()
expect(state.items).toHaveLength(1)
expect(state.items[0].id).toBe('prod-2')
})
it('should handle removing non-existent item', () => {
useCartStore.getState().addItem(mockItem)
useCartStore.getState().removeItem('non-existent')
const state = useCartStore.getState()
expect(state.items).toHaveLength(1)
})
})
describe('clearVendor', () => {
it('should remove all items from a specific vendor', () => {
const mockItemSameVendor: Omit<CartItem, 'quantity'> = {
...mockItem,
id: 'prod-3',
name: 'Outro Produto'
}
useCartStore.getState().addItem(mockItem)
useCartStore.getState().addItem(mockItemSameVendor)
useCartStore.getState().addItem(mockItem2)
useCartStore.getState().clearVendor('vendor-1')
const state = useCartStore.getState()
expect(state.items).toHaveLength(1)
expect(state.items[0].vendorId).toBe('vendor-2')
})
})
describe('clearAll', () => {
it('should remove all items from cart', () => {
useCartStore.getState().addItem(mockItem)
useCartStore.getState().addItem(mockItem2)
useCartStore.getState().clearAll()
const state = useCartStore.getState()
expect(state.items).toHaveLength(0)
})
})
})
describe('selectors', () => {
beforeEach(() => {
useCartStore.setState({ items: [] })
})
describe('selectGroupedCart', () => {
it('should group items by vendor', () => {
useCartStore.getState().addItem(mockItem, 2)
useCartStore.getState().addItem(mockItem2, 3)
const state = useCartStore.getState()
const groups = selectGroupedCart(state)
expect(Object.keys(groups)).toHaveLength(2)
expect(groups['vendor-1'].items).toHaveLength(1)
expect(groups['vendor-2'].items).toHaveLength(1)
})
it('should calculate vendor totals correctly', () => {
useCartStore.getState().addItem(mockItem, 2) // 2 * 25 = 50
useCartStore.getState().addItem(mockItem2, 3) // 3 * 15 = 45
const state = useCartStore.getState()
const groups = selectGroupedCart(state)
expect(groups['vendor-1'].total).toBe(50)
expect(groups['vendor-2'].total).toBe(45)
})
it('should return empty object for empty cart', () => {
const state = useCartStore.getState()
const groups = selectGroupedCart(state)
expect(Object.keys(groups)).toHaveLength(0)
})
})
describe('selectCartSummary', () => {
it('should calculate total items correctly', () => {
useCartStore.getState().addItem(mockItem, 2)
useCartStore.getState().addItem(mockItem2, 3)
const state = useCartStore.getState()
const summary = selectCartSummary(state)
expect(summary.totalItems).toBe(5)
})
it('should calculate total value correctly', () => {
useCartStore.getState().addItem(mockItem, 2) // 2 * 25 = 50
useCartStore.getState().addItem(mockItem2, 3) // 3 * 15 = 45
const state = useCartStore.getState()
const summary = selectCartSummary(state)
expect(summary.totalValue).toBe(95)
})
it('should return zeros for empty cart', () => {
const state = useCartStore.getState()
const summary = selectCartSummary(state)
expect(summary.totalItems).toBe(0)
expect(summary.totalValue).toBe(0)
})
})
})
/**
* PRICE CONVERSION TESTS
* These tests verify that prices are handled correctly in CENTS
* The API returns price_cents (e.g., 819 = R$ 8,19)
*/
describe('price conversion (cents)', () => {
const mockItemCents: Omit<CartItem, 'quantity'> = {
id: 'prod-cents-1',
name: 'Paracetamol 750mg',
activeIngredient: 'Paracetamol',
lab: 'EMS',
batch: 'L2025840',
expiry: '2027-09-15',
vendorId: 'vendor-anapolis',
vendorName: 'Anápolis/GO',
unitPrice: 819 // 819 cents = R$ 8,19
}
const mockItemCents2: Omit<CartItem, 'quantity'> = {
id: 'prod-cents-2',
name: 'Dipirona 500mg',
activeIngredient: 'Dipirona',
lab: 'Medley',
batch: 'L2025123',
expiry: '2026-06-30',
vendorId: 'vendor-anapolis',
vendorName: 'Anápolis/GO',
unitPrice: 1299 // 1299 cents = R$ 12,99
}
beforeEach(() => {
useCartStore.setState({ items: [] })
})
describe('storing prices in cents', () => {
it('should store unitPrice as cents (819 = R$ 8,19)', () => {
useCartStore.getState().addItem(mockItemCents)
const state = useCartStore.getState()
expect(state.items[0].unitPrice).toBe(819)
// Verify it's NOT stored as reais (8.19)
expect(state.items[0].unitPrice).not.toBe(8.19)
})
it('should preserve cents precision for subtotal calculation', () => {
useCartStore.getState().addItem(mockItemCents, 3)
const state = useCartStore.getState()
const item = state.items[0]
const subtotal = item.quantity * item.unitPrice
// 3 * 819 = 2457 cents = R$ 24,57
expect(subtotal).toBe(2457)
expect(subtotal / 100).toBe(24.57)
})
})
describe('cents calculations in selectors', () => {
it('should calculate vendor total in cents', () => {
// 819 cents * 2 = 1638 cents
useCartStore.getState().addItem(mockItemCents, 2)
// 1299 cents * 1 = 1299 cents
useCartStore.getState().addItem(mockItemCents2, 1)
const state = useCartStore.getState()
const groups = selectGroupedCart(state)
// Total: 1638 + 1299 = 2937 cents = R$ 29,37
expect(groups['vendor-anapolis'].total).toBe(2937)
expect(groups['vendor-anapolis'].total / 100).toBeCloseTo(29.37)
})
it('should calculate cart summary total in cents', () => {
useCartStore.getState().addItem(mockItemCents, 2) // 1638 cents
useCartStore.getState().addItem(mockItemCents2, 1) // 1299 cents
const state = useCartStore.getState()
const summary = selectCartSummary(state)
expect(summary.totalItems).toBe(3)
expect(summary.totalValue).toBe(2937) // cents
expect(summary.totalValue / 100).toBeCloseTo(29.37) // reais
})
})
describe('edge cases for cents', () => {
it('should handle single unit correctly', () => {
useCartStore.getState().addItem(mockItemCents, 1)
const state = useCartStore.getState()
const summary = selectCartSummary(state)
expect(summary.totalValue).toBe(819)
expect(summary.totalValue / 100).toBe(8.19)
})
it('should handle large quantities without overflow', () => {
useCartStore.getState().addItem(mockItemCents, 1000)
const state = useCartStore.getState()
const summary = selectCartSummary(state)
// 819 * 1000 = 819000 cents = R$ 8,190.00
expect(summary.totalValue).toBe(819000)
expect(summary.totalValue / 100).toBe(8190)
})
it('should handle small cent values (R$ 0,01)', () => {
const cheapItem: Omit<CartItem, 'quantity'> = {
...mockItemCents,
id: 'cheap-1',
unitPrice: 1 // 1 cent = R$ 0,01
}
useCartStore.getState().addItem(cheapItem, 10)
const state = useCartStore.getState()
const summary = selectCartSummary(state)
expect(summary.totalValue).toBe(10) // 10 cents
expect(summary.totalValue / 100).toBe(0.10)
})
})
})