import { describe, it, expect, vi, beforeEach } from 'vitest' describe('Assets Index Page', () => { beforeEach(() => { vi.clearAllMocks() }) describe('filtering', () => { it('builds query string from filters', () => { const filters = { q: 'laptop', category_id: 'cat-123', location_id: 'loc-456', condition_id: undefined, limit: 24, offset: 0 } const params = new URLSearchParams() if (filters.q) params.set('q', filters.q) if (filters.category_id) params.set('category_id', filters.category_id) if (filters.location_id) params.set('location_id', filters.location_id) if (filters.condition_id) params.set('condition_id', filters.condition_id) params.set('limit', String(filters.limit)) params.set('offset', String(filters.offset)) const queryString = params.toString() expect(queryString).toContain('q=laptop') expect(queryString).toContain('category_id=cat-123') expect(queryString).toContain('location_id=loc-456') expect(queryString).toContain('limit=24') expect(queryString).toContain('offset=0') expect(queryString).not.toContain('condition_id') }) it('clears all filters', () => { const filters = { q: 'search term', category_id: 'cat-123', location_id: 'loc-456', condition_id: 'cond-789', offset: 24 } // Clear filters function filters.q = '' filters.category_id = undefined as string | undefined filters.location_id = undefined as string | undefined filters.condition_id = undefined as string | undefined filters.offset = 0 expect(filters.q).toBe('') expect(filters.category_id).toBeUndefined() expect(filters.location_id).toBeUndefined() expect(filters.condition_id).toBeUndefined() expect(filters.offset).toBe(0) }) it('debounces search input', async () => { vi.useFakeTimers() let searchValue = '' let filterQ = '' let filterOffset = 24 const handleSearch = (val: string) => { searchValue = val setTimeout(() => { filterQ = val filterOffset = 0 }, 300) } handleSearch('test') expect(searchValue).toBe('test') expect(filterQ).toBe('') vi.advanceTimersByTime(300) expect(filterQ).toBe('test') expect(filterOffset).toBe(0) vi.useRealTimers() }) }) describe('pagination', () => { it('calculates current page from offset', () => { const testCases = [ { offset: 0, limit: 24, expected: 1 }, { offset: 24, limit: 24, expected: 2 }, { offset: 48, limit: 24, expected: 3 }, { offset: 0, limit: 10, expected: 1 }, { offset: 10, limit: 10, expected: 2 } ] testCases.forEach(({ offset, limit, expected }) => { const page = Math.floor(offset / limit) + 1 expect(page).toBe(expected) }) }) it('calculates offset from page number', () => { const testCases = [ { page: 1, limit: 24, expected: 0 }, { page: 2, limit: 24, expected: 24 }, { page: 3, limit: 24, expected: 48 }, { page: 1, limit: 10, expected: 0 }, { page: 5, limit: 10, expected: 40 } ] testCases.forEach(({ page, limit, expected }) => { const offset = (page - 1) * limit expect(offset).toBe(expected) }) }) it('calculates total pages from total items', () => { const testCases = [ { total: 100, limit: 24, expected: 5 }, { total: 24, limit: 24, expected: 1 }, { total: 25, limit: 24, expected: 2 }, { total: 0, limit: 24, expected: 0 }, { total: 50, limit: 10, expected: 5 } ] testCases.forEach(({ total, limit, expected }) => { const totalPages = Math.ceil(total / limit) expect(totalPages).toBe(expected) }) }) it('shows correct range in footer', () => { const page = 2 const limit = 24 const total = 100 const start = ((page - 1) * limit) + 1 const end = Math.min(page * limit, total) expect(start).toBe(25) expect(end).toBe(48) }) }) describe('short ID generation', () => { it('generates short ID from asset ID', () => { const getShortId = (assetId: string): string => { return `ATC-${assetId.slice(0, 4).toUpperCase()}` } expect(getShortId('abc123def456')).toBe('ATC-ABC1') expect(getShortId('xyz789')).toBe('ATC-XYZ7') expect(getShortId('1234567890')).toBe('ATC-1234') }) }) describe('asset selection', () => { it('toggles individual asset selection', () => { const selectedAssets: string[] = [] const toggleAssetSelection = (assetId: string) => { const index = selectedAssets.indexOf(assetId) if (index === -1) { selectedAssets.push(assetId) } else { selectedAssets.splice(index, 1) } } toggleAssetSelection('asset-1') expect(selectedAssets).toContain('asset-1') toggleAssetSelection('asset-2') expect(selectedAssets).toContain('asset-1') expect(selectedAssets).toContain('asset-2') toggleAssetSelection('asset-1') expect(selectedAssets).not.toContain('asset-1') expect(selectedAssets).toContain('asset-2') }) it('selects all assets', () => { const assets = [ { id: 'asset-1' }, { id: 'asset-2' }, { id: 'asset-3' } ] let selectedAssets: string[] = [] expect(selectedAssets).toEqual([]) selectedAssets = assets.map(a => a.id) expect(selectedAssets).toEqual(['asset-1', 'asset-2', 'asset-3']) }) it('deselects all assets', () => { let selectedAssets = ['asset-1', 'asset-2', 'asset-3'] expect(selectedAssets).toHaveLength(3) selectedAssets = [] expect(selectedAssets).toEqual([]) }) it('determines if all are selected', () => { const assets = [{ id: 'asset-1' }, { id: 'asset-2' }] let selectedAssets = ['asset-1', 'asset-2'] let allSelected = assets.length > 0 && selectedAssets.length === assets.length expect(allSelected).toBe(true) selectedAssets = ['asset-1'] allSelected = assets.length > 0 && selectedAssets.length === assets.length expect(allSelected).toBe(false) selectedAssets = [] allSelected = assets.length > 0 && selectedAssets.length === assets.length expect(allSelected).toBe(false) }) }) describe('filter options mapping', () => { it('maps categories to select options', () => { const categories = [ { id: 'cat-1', name: 'Electronics' }, { id: 'cat-2', name: 'Books' } ] const categoryOptions = [ { label: 'Uncategorized', value: 'uncategorized' }, ...categories.map(c => ({ label: c.name, value: c.id })) ] expect(categoryOptions).toEqual([ { label: 'Uncategorized', value: 'uncategorized' }, { label: 'Electronics', value: 'cat-1' }, { label: 'Books', value: 'cat-2' } ]) }) it('maps locations to hierarchical select options', () => { const locations = [ { id: 'home', name: 'Home' }, { id: 'bedroom', name: 'Bedroom', parent_id: 'home' }, { id: 'closet', name: 'Closet', parent_id: 'bedroom' } ] interface LocationTreeNode { location: { id: string, name: string, parent_id?: string } children: LocationTreeNode[] } const childrenMap = new Map() locations.forEach((location) => { const parentId = location.parent_id || undefined if (!childrenMap.has(parentId)) { childrenMap.set(parentId, []) } childrenMap.get(parentId)!.push(location) }) const buildTree = (parentId: string | undefined): LocationTreeNode[] => { const children = childrenMap.get(parentId) || [] return children .sort((a, b) => a.name.localeCompare(b.name)) .map(location => ({ location, children: buildTree(location.id) })) } const flattenTree = (nodes: LocationTreeNode[], depth = 0): { label: string, value: string }[] => { return nodes.flatMap((node) => { const indent = depth > 0 ? `${'\u00A0'.repeat(depth * 2)}└ ` : '' return [ { label: `${indent}${node.location.name}`, value: node.location.id }, ...flattenTree(node.children, depth + 1) ] }) } const locationOptions = flattenTree(buildTree(undefined)) expect(locationOptions).toEqual([ { label: 'Home', value: 'home' }, { label: '\u00A0\u00A0└ Bedroom', value: 'bedroom' }, { label: '\u00A0\u00A0\u00A0\u00A0└ Closet', value: 'closet' } ]) }) it('maps conditions to select options', () => { const conditions = [ { id: 'cond-1', label: 'New' }, { id: 'cond-2', label: 'Used' } ] const conditionOptions = conditions.map(c => ({ label: c.label, value: c.id })) expect(conditionOptions).toEqual([ { label: 'New', value: 'cond-1' }, { label: 'Used', value: 'cond-2' } ]) }) it('handles empty arrays gracefully', () => { const categories: { name: string, id: string }[] = [] const categoryOptions = [ { label: 'Uncategorized', value: 'uncategorized' }, ...(categories?.map(c => ({ label: c.name, value: c.id })) || []) ] expect(categoryOptions).toEqual([{ label: 'Uncategorized', value: 'uncategorized' }]) }) }) describe('navigation after import', () => { it('navigates to edit page after successful import', () => { const mockRouterPush = vi.fn() const assetId = 'new-asset-123' const onImported = (id: string) => { mockRouterPush(`/assets/${id}/edit`) } onImported(assetId) expect(mockRouterPush).toHaveBeenCalledWith('/assets/new-asset-123/edit') }) }) describe('row actions', () => { it('navigates when dropdown actions are selected', () => { const routerPush = vi.fn() const asset = { id: 'asset-123' } const items = [[ { label: 'View', onSelect: () => routerPush(`/assets/${asset.id}`) }, { label: 'Edit', onSelect: () => routerPush(`/assets/${asset.id}/edit`) } ]] items[0]![0]!.onSelect() items[0]![1]!.onSelect() expect(routerPush).toHaveBeenNthCalledWith(1, '/assets/asset-123') expect(routerPush).toHaveBeenNthCalledWith(2, '/assets/asset-123/edit') }) }) describe('loading and empty states', () => { it('shows loading state when status is pending', () => { const status = 'pending' expect(status).toBe('pending') }) it('shows empty state when no assets', () => { const assetsResponse = { assets: [], total: 0 } expect(assetsResponse.assets.length).toBe(0) }) it('shows assets table when data is loaded', () => { const assetsResponse = { assets: [{ id: 'asset-1', name: 'Test Asset' }], total: 1 } expect(assetsResponse.assets.length).toBeGreaterThan(0) }) }) })