diff --git a/apps/ai-game-creator-shell/tests/proportionalResize.test.ts b/apps/ai-game-creator-shell/tests/proportionalResize.test.ts index bf73c9dc0..518bb5dcc 100644 --- a/apps/ai-game-creator-shell/tests/proportionalResize.test.ts +++ b/apps/ai-game-creator-shell/tests/proportionalResize.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; import { - resolveProportionalResize, + resizePageRect, resolveProportionalResizeAxis, -} from '../src/view/ui-editor/components/preview/proportionalResize'; +} from '../src/features/ui-editor/nodeTransformGeometry'; describe('proportional resize', () => { it('selects the axis with the greater relative size change', () => { @@ -17,23 +17,25 @@ describe('proportional resize', () => { it('uses the supplied locked axis instead of the larger candidate size', () => { expect( - resolveProportionalResize({ - startSize: [100, 100], - size: [150, 90], - delta: [50, -10], - axis: 'horizontal', - }), - ).toEqual({ axis: 'horizontal', size: [150, 150] }); + resizePageRect( + { min: [0, 0], max: [100, 100] }, + 'se', + [50, -10], + true, + 'horizontal', + ), + ).toEqual({ min: [0, 0], max: [150, 150] }); }); it('uses the locked vertical axis for an opposite-sign diagonal drag', () => { expect( - resolveProportionalResize({ - startSize: [100, 100], - size: [110, 50], - delta: [10, -50], - axis: 'vertical', - }), - ).toEqual({ axis: 'vertical', size: [50, 50] }); + resizePageRect( + { min: [0, 0], max: [100, 100] }, + 'se', + [10, -50], + true, + 'vertical', + ), + ).toEqual({ min: [0, 0], max: [50, 50] }); }); }); diff --git a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts index 115df60ec..425862c39 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts @@ -25,12 +25,22 @@ vi.mock('../src/components/modal/ThemedModal', () => ({ import { invoke } from '@tauri-apps/api/core'; +import type { Node as UiNode } from '../src/features/ui-editor/types/Node'; +import type { State } from '../src/features/ui-editor/types/State'; import type { UiDesignStateSnapshot, UiDesignStateStore, } from '../src/features/ui-editor/uiDesignStateStore'; import UiEditorPage from '../src/view/ui-editor'; -import { useUiEditorPage } from '../src/view/ui-editor/useUiEditorPage'; +import { useUiEditorSession } from '../src/view/ui-editor/useUiEditorPage'; + +class TestResizeObserver { + constructor(_callback: ResizeObserverCallback) {} + observe() {} + disconnect() {} +} + +vi.stubGlobal('ResizeObserver', TestResizeObserver); const EMPTY_SNAPSHOT: UiDesignStateSnapshot = { revision: 0, @@ -42,6 +52,98 @@ const EMPTY_SNAPSHOT: UiDesignStateSnapshot = { }, }; +function node(id: string, children: UiNode[] = []): UiNode { + return { + id, + layout: { + transform: { + anchor_min: [0, 0], + anchor_max: [1, 1], + offset_min: [0, 0], + offset_max: [0, 0], + }, + custom_minimum_size: [0, 0], + size_flags_horizontal: 1, + size_flags_vertical: 1, + size_flags_stretch_ratio: 1, + container: 'None', + }, + metadata: { + name: id, + description: '', + layout_status: 'NoProblem', + components_status: 'NoProblem', + allow_llm_edit_layout: true, + allow_llm_edit_component: true, + source: 'System', + }, + components: [], + children, + }; +} + +function stateWithPages(pageIds: string[]): State { + return { + ui_design_images: Object.fromEntries( + pageIds.map((id) => [ + id, + { + metadata: { + name: id, + description: '', + role: 'Page', + slave_to: null, + }, + path: `assets/${id}.png`, + pixel_size: [320, 180], + pixels_per_unit: 1, + }, + ]), + ), + ui_trees: pageIds.map((id) => ({ + src_ui_design: id, + root: node(`${id}-root`), + })), + sprite_assets: { + panel: { + asset_id: 'panel', + metadata: { name: 'Panel', asset_type: '' }, + path: 'assets/panel.png', + pixel_size: [32, 32], + pixels_per_unit: 1, + border: { left: 0, right: 0, top: 0, bottom: 0 }, + }, + }, + font_assets: { + body: { + asset_id: 'body', + metadata: { + family_name: '测试字体', + face_name: 'Regular', + weight: 400, + italic: false, + format: 'TrueType', + source_file_name: 'body.ttf', + }, + path: 'assets/fonts/body.ttf', + content_sha256: 'a'.repeat(64), + }, + }, + }; +} + +async function renderLoadedSession(state: State) { + const stateStore: UiDesignStateStore = { + load: vi.fn().mockResolvedValue({ revision: 0, state }), + save: vi.fn(), + }; + const hook = renderHook(() => + useUiEditorSession('/tmp/ui-editor', 'ui-resource', stateStore), + ); + await waitFor(() => expect(hook.result.current.save.isLoading).toBe(false)); + return hook; +} + describe('UiEditorPage', () => { it('uses the durable Tauri store for a project UI resource', async () => { vi.mocked(invoke) @@ -155,242 +257,140 @@ describe('UiEditorPage', () => { expect(screen.getByText('素材导入器')).toBeTruthy(); }); - it('switches from a selected node to the sprite inspector', () => { - const { result } = renderHook(() => useUiEditorPage('/tmp/ui-editor')); + it('switches from a selected node to the sprite inspector', async () => { + const { result } = await renderLoadedSession(stateWithPages(['page'])); + const rootId = 'page-root'; act(() => { - result.current.editor.addDesignImages([ - { - id: 'page', - image: { - metadata: { - name: 'Page', - description: '', - role: 'Page', - slave_to: null, - }, - path: 'assets/page.png', - pixel_size: [320, 180], - pixels_per_unit: 1, - }, - }, - ]); - result.current.editor.addSpriteAssets([ - { - asset_id: 'panel', - metadata: { name: 'Panel', asset_type: '' }, - path: 'assets/panel.png', - pixel_size: [32, 32], - pixels_per_unit: 1, - border: { left: 0, right: 0, top: 0, bottom: 0 }, - }, - ]); - result.current.editor.addFontAssets([ - { - asset_id: 'body', - metadata: { - family_name: '测试字体', - face_name: 'Regular', - weight: 400, - italic: false, - format: 'TrueType', - source_file_name: 'body.ttf', - }, - path: 'assets/fonts/body.ttf', - content_sha256: 'a'.repeat(64), - }, - ]); + result.current.input.selectDesignImage('page'); + result.current.input.selectNode(rootId); }); - const rootId = result.current.editor.state.ui_trees[0]!.root.id; - act(() => { - result.current.selectDesignImage('page'); - result.current.selectNode(rootId); - }); - expect(result.current.selectedNodeId).toBe(rootId); + expect(result.current.canvas.selectedNodeId).toBe(rootId); - act(() => result.current.selectSprite('panel')); - expect(result.current.selectedNodeId).toBeNull(); - expect(result.current.selectedSpriteId).toBe('panel'); + act(() => result.current.input.selectSprite('panel')); + expect(result.current.canvas.selectedNodeId).toBeNull(); + expect(result.current.inspector.selectedSpriteId).toBe('panel'); - act(() => result.current.selectFont('body')); - expect(result.current.selectedFontId).toBe('body'); + act(() => result.current.input.selectFont('body')); + expect(result.current.inspector.selectedFontId).toBe('body'); - act(() => result.current.selectDesignImage('page')); - expect(result.current.selectedFontId).toBeNull(); + act(() => result.current.input.selectDesignImage('page')); + expect(result.current.inspector.selectedFontId).toBeNull(); - act(() => result.current.selectFont('body')); - act(() => result.current.selectSprite('panel')); - expect(result.current.selectedFontId).toBeNull(); + act(() => result.current.input.selectFont('body')); + act(() => result.current.input.selectSprite('panel')); + expect(result.current.inspector.selectedFontId).toBeNull(); - act(() => result.current.selectFont('body')); - act(() => result.current.selectNode(rootId)); - expect(result.current.selectedFontId).toBeNull(); + act(() => result.current.input.selectFont('body')); + act(() => result.current.input.selectNode(rootId)); + expect(result.current.inspector.selectedFontId).toBeNull(); }); - it('keeps node preview visibility transient and attached to global node IDs', () => { - const { result } = renderHook(() => useUiEditorPage('/tmp/ui-editor')); - act(() => { - result.current.editor.addDesignImages([ - { - id: 'page-a', - image: { - metadata: { - name: 'Page A', - description: '', - role: 'Page', - slave_to: null, - }, - path: 'assets/page-a.png', - pixel_size: [320, 180], - pixels_per_unit: 1, - }, - }, - { - id: 'page-b', - image: { - metadata: { - name: 'Page B', - description: '', - role: 'Page', - slave_to: null, - }, - path: 'assets/page-b.png', - pixel_size: [320, 180], - pixels_per_unit: 1, - }, - }, - ]); - }); - - act(() => result.current.selectDesignImage('page-a')); - const rootId = result.current.treeForActiveImage!.root.id; + it('keeps node preview visibility transient and attached to global node IDs', async () => { + const { result } = await renderLoadedSession( + stateWithPages(['page-a', 'page-b']), + ); + const rootId = 'page-a-root'; let insertedNodeId: string | undefined; act(() => { - const inserted = result.current.insertNode(rootId); + const inserted = result.current.input.insertNode(rootId, 'page-a'); if (inserted?.ok) insertedNodeId = inserted.value; }); - const stateBeforeToggle = JSON.stringify(result.current.editor.state); + const stateBeforeToggle = JSON.stringify(result.current.canvas.tree); act(() => { - result.current.toggleNodePreviewVisibility(rootId); + result.current.canvas.toggleNodePreviewVisibility(rootId); }); - expect(result.current.isNodePreviewVisible(rootId)).toBe(false); - expect(result.current.hiddenNodeIds.has(rootId)).toBe(true); - expect(JSON.stringify(result.current.editor.state)).toBe(stateBeforeToggle); + expect(result.current.canvas.isNodePreviewVisible(rootId)).toBe(false); + expect(result.current.canvas.hiddenNodeIds.has(rootId)).toBe(true); + expect(JSON.stringify(result.current.canvas.tree)).toBe(stateBeforeToggle); - act(() => result.current.selectDesignImage('page-b')); - expect(result.current.hiddenNodeIds.has(rootId)).toBe(true); + act(() => result.current.input.selectDesignImage('page-b')); + expect(result.current.canvas.hiddenNodeIds.has(rootId)).toBe(true); - act(() => result.current.selectDesignImage('page-a')); - expect(result.current.hiddenNodeIds.has(rootId)).toBe(true); + act(() => result.current.input.selectDesignImage('page-a')); + expect(result.current.canvas.hiddenNodeIds.has(rootId)).toBe(true); expect(insertedNodeId).toBeTruthy(); - act(() => result.current.toggleNodePreviewVisibility(insertedNodeId!)); - expect(result.current.hiddenNodeIds.has(insertedNodeId!)).toBe(true); + act(() => + result.current.canvas.toggleNodePreviewVisibility(insertedNodeId!), + ); + expect(result.current.canvas.hiddenNodeIds.has(insertedNodeId!)).toBe(true); - act(() => result.current.deleteNode(insertedNodeId!)); - expect(result.current.hiddenNodeIds.has(insertedNodeId!)).toBe(false); + act(() => result.current.input.deleteNode(insertedNodeId!, 'page-a')); + expect(result.current.canvas.hiddenNodeIds.has(insertedNodeId!)).toBe( + false, + ); - act(() => result.current.clearState()); - expect(result.current.hiddenNodeIds.size).toBe(0); + act(() => result.current.dialogs.clearState()); + expect(result.current.canvas.hiddenNodeIds.size).toBe(0); }); - it('keeps Inspector status highlighting separate from node navigation', () => { - const { result } = renderHook(() => useUiEditorPage('/tmp/ui-editor')); - act(() => { - result.current.editor.addDesignImages([ - { - id: 'page', - image: { - metadata: { - name: 'Page', - description: '', - role: 'Page', - slave_to: null, - }, - path: 'assets/page.png', - pixel_size: [320, 180], - pixels_per_unit: 1, - }, - }, - ]); - result.current.selectDesignImage('page'); - }); - const rootId = result.current.treeForActiveImage!.root.id; + it('keeps Inspector status highlighting separate from node navigation', async () => { + const { result } = await renderLoadedSession(stateWithPages(['page'])); + const rootId = 'page-root'; let otherNodeId: string | undefined; act(() => { - otherNodeId = result.current.insertNode(rootId)?.value; - result.current.selectNode(rootId); - result.current.highlightStatusField('layout_status'); + otherNodeId = result.current.input.insertNode(rootId, 'page')?.value; + result.current.input.selectNode(rootId); + result.current.input.highlightStatusField('layout_status'); }); - expect(result.current.highlightedStatusField).toMatchObject({ + expect(result.current.inspector.highlightedStatusField).toMatchObject({ field: 'layout_status', requestId: 1, }); - act(() => result.current.highlightStatusField('layout_status')); - expect(result.current.highlightedStatusField).toMatchObject({ + act(() => result.current.input.highlightStatusField('layout_status')); + expect(result.current.inspector.highlightedStatusField).toMatchObject({ field: 'layout_status', requestId: 2, }); - act(() => result.current.selectNode(otherNodeId!)); - expect(result.current.highlightedStatusField).toBeNull(); + act(() => result.current.input.selectNode(rootId)); + expect(result.current.inspector.highlightedStatusField).toBeNull(); act(() => { - result.current.highlightStatusField('components_status'); - result.current.setNodeMetadata({ components_status: 'NoProblem' }); + result.current.input.highlightStatusField('components_status'); + result.current.inspector.setNodeMetadata({ + components_status: 'NoProblem', + }); }); - expect(result.current.highlightedStatusField).toBeNull(); + expect(result.current.inspector.highlightedStatusField).toBeNull(); }); - it('shares exclusive child visibility between tree actions and final preview state', () => { - const { result } = renderHook(() => useUiEditorPage('/tmp/ui-editor')); - act(() => { - result.current.editor.addDesignImages([ - { - id: 'page', - image: { - metadata: { - name: 'Page', - description: '', - role: 'Page', - slave_to: null, - }, - path: 'assets/page.png', - pixel_size: [320, 180], - pixels_per_unit: 1, - }, - }, - ]); - result.current.selectDesignImage('page'); - }); - const rootId = result.current.treeForActiveImage!.root.id; + it('shares exclusive child visibility between tree actions and final preview state', async () => { + const { result } = await renderLoadedSession(stateWithPages(['page'])); + const rootId = 'page-root'; let parentId: string | undefined; let childAId: string | undefined; let childBId: string | undefined; let childBDescendantId: string | undefined; act(() => { - parentId = result.current.insertNode(rootId)?.value; - childAId = result.current.insertNode(parentId!)?.value; - childBId = result.current.insertNode(parentId!)?.value; - childBDescendantId = result.current.insertNode(childBId!)?.value; + parentId = result.current.input.insertNode(rootId, 'page')?.value; + childAId = result.current.input.insertNode(parentId!, 'page')?.value; + childBId = result.current.input.insertNode(parentId!, 'page')?.value; + childBDescendantId = result.current.input.insertNode( + childBId!, + 'page', + )?.value; }); - act(() => result.current.selectNode(parentId!)); - act(() => result.current.setNodeChildrenDisplayMode('Exclusive')); + act(() => result.current.input.selectNode(parentId!)); + act(() => result.current.inspector.setNodeChildrenDisplayMode('Exclusive')); expect(parentId && childAId && childBId).toBeTruthy(); - expect(result.current.isNodePreviewVisible(childAId!)).toBe(true); - expect(result.current.isNodePreviewVisible(childBId!)).toBe(false); - expect(result.current.isNodePreviewVisible(childBDescendantId!)).toBe( - false, - ); + expect(result.current.canvas.isNodePreviewVisible(childAId!)).toBe(true); + expect(result.current.canvas.isNodePreviewVisible(childBId!)).toBe(false); + expect( + result.current.canvas.isNodePreviewVisible(childBDescendantId!), + ).toBe(false); - act(() => result.current.toggleNodePreviewVisibility(childAId!)); - expect(result.current.isNodePreviewVisible(childAId!)).toBe(false); - expect(result.current.isNodePreviewVisible(childBId!)).toBe(false); + act(() => result.current.canvas.toggleNodePreviewVisibility(childAId!)); + expect(result.current.canvas.isNodePreviewVisible(childAId!)).toBe(false); + expect(result.current.canvas.isNodePreviewVisible(childBId!)).toBe(false); - act(() => result.current.toggleNodePreviewVisibility(childBId!)); - expect(result.current.isNodePreviewVisible(childAId!)).toBe(false); - expect(result.current.isNodePreviewVisible(childBId!)).toBe(true); - expect(result.current.isNodePreviewVisible(childBDescendantId!)).toBe(true); + act(() => result.current.canvas.toggleNodePreviewVisibility(childBId!)); + expect(result.current.canvas.isNodePreviewVisible(childAId!)).toBe(false); + expect(result.current.canvas.isNodePreviewVisible(childBId!)).toBe(true); + expect( + result.current.canvas.isNodePreviewVisible(childBDescendantId!), + ).toBe(true); }); it('shows a page-level save failure and allows a retry', async () => { @@ -410,7 +410,7 @@ describe('UiEditorPage', () => { fireEvent.click(await screen.findByRole('button', { name: '仍然保存' })); expect((await screen.findByRole('alert')).textContent).toContain( - '保存失败,请稍后重试。', + '临时存储不可用', ); expect(screen.getByRole('button', { name: '保存' })).toBeTruthy(); expect(stateStore.save).toHaveBeenCalledTimes(1); diff --git a/apps/ai-game-creator-shell/tests/useNodeTransformInteraction.test.tsx b/apps/ai-game-creator-shell/tests/useNodeTransformInteraction.test.tsx index f2a246549..2b79ab689 100644 --- a/apps/ai-game-creator-shell/tests/useNodeTransformInteraction.test.tsx +++ b/apps/ai-game-creator-shell/tests/useNodeTransformInteraction.test.tsx @@ -6,7 +6,7 @@ import { describe, expect, it, vi } from 'vitest'; import type { Node as UiNode } from '../src/features/ui-editor/types/Node'; import type { UITree } from '../src/features/ui-editor/types/UITree'; import { useNodeTransformInteraction } from '../src/view/ui-editor/components/preview/useNodeTransformInteraction'; -import type { UiEditorPageController } from '../src/view/ui-editor/useUiEditorPage'; +import type { UiEditorCanvasProjection } from '../src/view/ui-editor/useUiEditorPage'; function node(id: string, children: UiNode[] = []): UiNode { return { @@ -72,11 +72,11 @@ function pointerEvent( } as never; } -function controller() { +function canvas() { return { selectNode: vi.fn(), updateNodeTransform: vi.fn(), - } as unknown as UiEditorPageController; + } as Pick; } function renderInteraction({ @@ -88,13 +88,13 @@ function renderInteraction({ scale?: number; tree?: UITree | null; } = {}) { - const pageController = controller(); + const canvasProjection = canvas(); const viewportRef = { current: { scale } }; const hook = renderHook( ({ imageId, currentTree }) => useNodeTransformInteraction({ activeImageId: imageId, - controller: pageController, + canvas: canvasProjection, logicalSize: { width: 320, height: 180 }, spaceHeld: false, tree: currentTree, @@ -102,12 +102,12 @@ function renderInteraction({ }), { initialProps: { imageId: activeImageId, currentTree: tree } }, ); - return { ...hook, pageController, viewportRef }; + return { ...hook, canvasProjection, viewportRef }; } describe('useNodeTransformInteraction', () => { it('makes drag and resize mutually exclusive, then permits the next gesture', () => { - const { result, pageController } = renderInteraction(); + const { result, canvasProjection } = renderInteraction(); const dragTarget = gestureTarget(); const resizeTarget = gestureTarget(); @@ -125,7 +125,7 @@ describe('useNodeTransformInteraction', () => { }); expect(resizeTarget.setPointerCapture).not.toHaveBeenCalled(); - expect(pageController.updateNodeTransform).toHaveBeenCalledTimes(1); + expect(canvasProjection.updateNodeTransform).toHaveBeenCalledTimes(1); act(() => { result.current.onNodePointerUp(pointerEvent(dragTarget, 1, 12, 8)); @@ -139,7 +139,7 @@ describe('useNodeTransformInteraction', () => { }); it('cancels a gesture when its tree changes and ignores its later events', () => { - const { result, rerender, pageController } = renderInteraction(); + const { result, rerender, canvasProjection } = renderInteraction(); const target = gestureTarget(); act(() => { @@ -156,11 +156,11 @@ describe('useNodeTransformInteraction', () => { }); expect(target.releasePointerCapture).toHaveBeenCalledWith(1); - expect(pageController.updateNodeTransform).not.toHaveBeenCalled(); + expect(canvasProjection.updateNodeTransform).not.toHaveBeenCalled(); }); it('cancels instead of persisting a non-finite transform', () => { - const { result, pageController, viewportRef } = renderInteraction(); + const { result, canvasProjection, viewportRef } = renderInteraction(); const target = gestureTarget(); act(() => { @@ -170,7 +170,7 @@ describe('useNodeTransformInteraction', () => { }); expect(target.releasePointerCapture).toHaveBeenCalledWith(1); - expect(pageController.updateNodeTransform).not.toHaveBeenCalled(); + expect(canvasProjection.updateNodeTransform).not.toHaveBeenCalled(); }); it('releases a captured pointer when the preview unmounts', () => { diff --git a/apps/ai-game-creator-shell/vite.config.ts b/apps/ai-game-creator-shell/vite.config.ts index c4944c507..f616247b6 100644 --- a/apps/ai-game-creator-shell/vite.config.ts +++ b/apps/ai-game-creator-shell/vite.config.ts @@ -68,20 +68,39 @@ export default defineConfig({ }, ], resolve: { - alias: { - '@genarrative/image-canvas-core': resolve( - repoRoot, - 'packages/image-canvas-core/src/index.ts', - ), - '@genarrative/image-canvas-react/styles.css': resolve( - repoRoot, - 'packages/image-canvas-react/src/styles.css', - ), - '@genarrative/image-canvas-react': resolve( - repoRoot, - 'packages/image-canvas-react/src/index.ts', - ), - }, + alias: [ + // v1.35.0 publishes this bundle but its package metadata points Vite at + // an incomplete source entrypoint. Resolve only the bare module to the + // published ESM artifact; its stylesheet remains a valid subpath import. + { + find: /^@cubone\/react-file-manager$/, + replacement: resolve( + appRoot, + 'node_modules/@cubone/react-file-manager/dist/react-file-manager.es.js', + ), + }, + { + find: '@genarrative/image-canvas-core', + replacement: resolve( + repoRoot, + 'packages/image-canvas-core/src/index.ts', + ), + }, + { + find: '@genarrative/image-canvas-react/styles.css', + replacement: resolve( + repoRoot, + 'packages/image-canvas-react/src/styles.css', + ), + }, + { + find: '@genarrative/image-canvas-react', + replacement: resolve( + repoRoot, + 'packages/image-canvas-react/src/index.ts', + ), + }, + ], dedupe: ['react', 'react-dom'], }, server: { diff --git a/vite.config.ts b/vite.config.ts index eb77fc01f..0f9d4cd54 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -553,21 +553,37 @@ export default defineConfig(({ mode }) => { 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY), }, resolve: { - alias: { - '@': path.resolve(__dirname, '.'), - '@genarrative/image-canvas-core': path.resolve( - __dirname, - 'packages/image-canvas-core/src/index.ts', - ), - '@genarrative/image-canvas-react/styles.css': path.resolve( - __dirname, - 'packages/image-canvas-react/src/styles.css', - ), - '@genarrative/image-canvas-react': path.resolve( - __dirname, - 'packages/image-canvas-react/src/index.ts', - ), - }, + alias: [ + { + find: /^@cubone\/react-file-manager$/, + replacement: path.resolve( + __dirname, + 'apps/ai-game-creator-shell/node_modules/@cubone/react-file-manager/dist/react-file-manager.es.js', + ), + }, + { find: '@', replacement: path.resolve(__dirname, '.') }, + { + find: '@genarrative/image-canvas-core', + replacement: path.resolve( + __dirname, + 'packages/image-canvas-core/src/index.ts', + ), + }, + { + find: '@genarrative/image-canvas-react/styles.css', + replacement: path.resolve( + __dirname, + 'packages/image-canvas-react/src/styles.css', + ), + }, + { + find: '@genarrative/image-canvas-react', + replacement: path.resolve( + __dirname, + 'packages/image-canvas-react/src/index.ts', + ), + }, + ], dedupe: ['react', 'react-dom'], }, optimizeDeps: { diff --git a/vitest.config.ts b/vitest.config.ts index 531c4b115..5f2ac0ec2 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,32 +4,57 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ resolve: { - alias: { + alias: [ + { + find: /^@cubone\/react-file-manager$/, + replacement: path.resolve( + __dirname, + 'apps/ai-game-creator-shell/node_modules/@cubone/react-file-manager/dist/react-file-manager.es.js', + ), + }, // Keep shared components' application-root imports resolvable in tests. - '@': path.resolve(__dirname, '.'), - '@genarrative/image-canvas-core': path.resolve( - __dirname, - 'packages/image-canvas-core/src/index.ts', - ), - '@genarrative/image-canvas-react': path.resolve( - __dirname, - 'packages/image-canvas-react/src/index.ts', - ), + { find: '@', replacement: path.resolve(__dirname, '.') }, + { + find: '@genarrative/image-canvas-core', + replacement: path.resolve( + __dirname, + 'packages/image-canvas-core/src/index.ts', + ), + }, + { + find: '@genarrative/image-canvas-react', + replacement: path.resolve( + __dirname, + 'packages/image-canvas-react/src/index.ts', + ), + }, // The standalone AI shell owns its Tauri guest dependencies. Root tests // use inert aliases so the root H5 package keeps the HostBridge boundary. - '@tauri-apps/plugin-clipboard-manager': path.resolve( - __dirname, - 'apps/ai-game-creator-shell/tests/tauri-plugin-clipboard-manager.mock.ts', - ), - '@tauri-apps/plugin-opener': path.resolve( - __dirname, - 'apps/ai-game-creator-shell/tests/tauri-plugin-opener.mock.ts', - ), + { + find: '@tauri-apps/plugin-clipboard-manager', + replacement: path.resolve( + __dirname, + 'apps/ai-game-creator-shell/tests/tauri-plugin-clipboard-manager.mock.ts', + ), + }, + { + find: '@tauri-apps/plugin-opener', + replacement: path.resolve( + __dirname, + 'apps/ai-game-creator-shell/tests/tauri-plugin-opener.mock.ts', + ), + }, // The AI shell has its own install for standalone Tauri builds. Keep root // Vitest on a single React renderer even after that scoped install exists. - react: path.resolve(__dirname, 'node_modules/react'), - 'react-dom': path.resolve(__dirname, 'node_modules/react-dom'), - }, + { + find: 'react', + replacement: path.resolve(__dirname, 'node_modules/react'), + }, + { + find: 'react-dom', + replacement: path.resolve(__dirname, 'node_modules/react-dom'), + }, + ], dedupe: ['react', 'react-dom', 'zustand'], }, test: {