mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
* 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>
105 lines
2.9 KiB
TypeScript
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,
|
|
}),
|
|
)
|
|
})
|
|
})
|
|
})
|