utopia-ui/lib/src/Components/Profile/ItemFunctions.spec.tsx
Anton Tranelis 649efe551d
refactor(lib): implement server-response-first pattern (#322)
* use server response for local state updates

* fix formatting

* refactor: comprehensive server-response-first pattern implementation

## Major Changes

### LayerProps ID Required
- Made `LayerProps.id` required (was optional)
- All layers now guaranteed to have server-provided UUID
- Enables reliable layer ID mapping from server responses

### useSelectPosition Hook Refactored
- Added reusable `handleApiOperation` helper function
- Refactored `itemUpdatePosition`, `itemUpdateParent`, `linkItem`
- All functions now use server response + layer ID mapping
- Consistent error handling and toast management

### itemFunctions.ts Complete Refactor
- **submitNewItem**: Server response with layer mapping
- **linkItem**: Server response preserves layer object
- **unlinkItem**: Same pattern as linkItem
- **handleDelete**: Simplified error handling
- **onUpdateItem**: Complex function refactored for both update/create branches

### Benefits
-  Eliminates race conditions from manual state construction
-  Server response as single source of truth for all updates
-  Consistent error handling across all API operations
-  Items no longer disappear from map after updates
-  Type-safe layer ID mapping

### Testing
- Updated ItemFunctions.spec.tsx with new toast patterns
- Added required layer IDs to test objects
- All 19 tests passing (3 skipped)
- ESLint clean

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix linting

* fix: resolve TypeScript undefined data errors

- Add non-null assertions for result.data in conditional blocks
- TypeScript now properly recognizes data is defined after success check
- All linting and TypeScript errors resolved

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fixed examples

* remove unneccessary uuid generation

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-20 15:03:30 +02:00

105 lines
2.9 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, vi } from 'vitest'
import { linkItem } from './itemFunctions'
import type { Item } from '#types/Item'
const toastErrorMock: (t: string) => void = vi.fn()
const toastSuccessMock: (t: string) => void = vi.fn()
const toastLoadingMock: (t: string) => number = vi.fn(() => 123)
const toastUpdateMock: (id: number, options: any) => void = vi.fn()
vi.mock('react-toastify', () => ({
toast: {
error: (t: string) => toastErrorMock(t),
success: (t: string) => toastSuccessMock(t),
loading: (t: string) => toastLoadingMock(t),
update: (id: number, options: any) => toastUpdateMock(id, options),
},
}))
describe('linkItem', () => {
const id = 'some-id'
let updateApi: (item: Partial<Item>) => Promise<Item> = vi.fn()
const item: Item = {
layer: {
id: 'test-layer-id',
api: {
updateItem: (item) => updateApi(item),
getItems: vi.fn(),
},
name: '',
menuIcon: '',
menuColor: '',
menuText: '',
markerIcon: {
image: '',
},
markerShape: 'square',
markerDefaultColor: '',
itemType: {
name: 'Test Item Type',
show_name_input: true,
show_profile_button: false,
show_start_end: true,
show_start_end_input: true,
show_text: true,
show_text_input: true,
custom_text: 'This is a custom text for the item type.',
profileTemplate: [
{ collection: 'users', id: null, item: {} },
{ collection: 'posts', id: '123', item: {} },
],
offers_and_needs: true,
icon_as_labels: {},
relations: true,
template: 'default',
questlog: false,
},
},
id: '',
name: '',
}
const updateItem = vi.fn()
beforeEach(() => {
updateApi = vi.fn()
vi.clearAllMocks()
})
describe('api rejects', () => {
it('toasts an error', async () => {
updateApi = vi.fn().mockRejectedValue('autsch')
await linkItem(id, item, updateItem)
expect(toastUpdateMock).toHaveBeenCalledWith(123, expect.objectContaining({ type: 'error' }))
expect(updateItem).not.toHaveBeenCalled()
expect(toastSuccessMock).not.toHaveBeenCalled()
})
})
describe('api resolves', () => {
it('toasts success and calls updateItem()', async () => {
const serverResponse = {
...item,
layer: 'test-layer-id',
relations: [{ items_id: item.id, related_items_id: id }],
}
updateApi = vi.fn().mockResolvedValue(serverResponse)
await linkItem(id, item, updateItem)
expect(toastUpdateMock).toHaveBeenCalledWith(
123,
expect.objectContaining({ type: 'success' }),
)
expect(updateItem).toHaveBeenCalledWith(
expect.objectContaining({
...serverResponse,
layer: item.layer,
}),
)
})
})
})