/* @vitest-environment jsdom */ import { fireEvent, render, screen, within } from '@testing-library/react'; import type { ImgHTMLAttributes } from 'react'; import { describe, expect, it, vi } from 'vitest'; import type { CanvasLayer } from './ImageCanvasEditorTypes'; import type { UiAssetExtractionState } from './ImageCanvasUiAssetExtractionModel'; import { ImageCanvasUiAssetExtractionOverlayView } from './ImageCanvasUiAssetExtractionOverlayView'; type ResolvedAssetImageMockProps = Omit< ImgHTMLAttributes, 'src' > & { src?: string | null; objectKey?: string | null; fallbackSrc?: string | null; refreshKey?: string | number | null; }; vi.mock('../ResolvedAssetImage', () => ({ ResolvedAssetImage: ({ src, objectKey, fallbackSrc, refreshKey, ...props }: ResolvedAssetImageMockProps) => ( ), })); function createLayer(overrides: Partial = {}): CanvasLayer { return { id: 'layer-source', resourceId: 'resource-source', title: '源图', src: 'data:image/png;base64,source', x: 100, y: 120, width: 320, height: 240, originalWidth: 640, originalHeight: 480, zIndex: 4, sourceType: 'uploaded', ...overrides, }; } function createState( overrides: Partial = {}, ): UiAssetExtractionState { return { sourceLayerId: 'layer-source', tool: 'rect', marks: [], draftMark: null, status: 'idle', ...overrides, }; } describe('ImageCanvasUiAssetExtractionOverlayView', () => { it('renders quick edit tools vertically on the right without extraction actions', () => { const handleToolChange = vi.fn(); render( , ); const toolbar = screen.getByRole('toolbar', { name: '快速编辑框选工具', }); expect(toolbar.className).toContain( 'image-canvas-editor__ui-extraction-toolbar--right', ); expect(within(toolbar).queryByText('框选素材')).toBeNull(); expect(within(toolbar).queryByText('0')).toBeNull(); expect( within(toolbar).queryByRole('button', { name: '提取' }), ).toBeNull(); expect( within(toolbar).getByRole('button', { name: '矩形框选' }), ).toBeTruthy(); expect( within(toolbar).getByRole('button', { name: '椭圆框选' }), ).toBeTruthy(); expect( within(toolbar) .getByRole('button', { name: '矩形框选' }) .getAttribute('aria-pressed'), ).toBe('true'); fireEvent.click( within(toolbar).getByRole('button', { name: '矩形框选' }), ); fireEvent.click( within(toolbar).getByRole('button', { name: '画笔自由框选' }), ); expect(handleToolChange).toHaveBeenNthCalledWith(1, null); expect(handleToolChange).toHaveBeenNthCalledWith(2, 'brush'); }); it('renders UI extraction tools on the right and a bottom composer with previews and gpt-image-2 pricing', () => { const marks = Array.from({ length: 7 }, (_, index) => ({ id: `mark-${index + 1}`, tool: 'rect' as const, x: index * 8, y: index * 6, width: 48, height: 36, })); render( , ); expect( screen.getByRole('toolbar', { name: 'UI素材框选工具' }).className, ).toContain('image-canvas-editor__ui-extraction-toolbar--right'); const panel = screen.getByRole('dialog', { name: 'UI素材提取' }); expect(panel.className).toContain( 'image-canvas-editor__ui-extraction-panel', ); expect( within(panel).getByText('使用框选工具框选你希望从画面中提取的素材'), ).toBeTruthy(); expect(within(panel).getAllByLabelText(/框选区域预览/u)).toHaveLength(7); expect(within(panel).getByLabelText('计划规格').textContent).toBe('1:1·2K'); expect(within(panel).getByLabelText('固定模型').textContent).toContain('gpt-image-2'); expect( within(panel).queryByRole('button', { name: '取消框选' }), ).toBeNull(); expect(within(panel).getByRole('button', { name: '提取' }).textContent).toContain('5泥点'); }); it('crops mark previews from the same source coordinates as the red selection', () => { render( , ); const preview = screen.getByLabelText('框选区域预览 1'); const previewImage = within(preview).getByRole('img', { name: '源图框选预览 1', }) as HTMLImageElement; expect(previewImage.getAttribute('src')).toBe('data:image/png;base64,ui'); expect(previewImage.style.width).toBe('800%'); expect(previewImage.style.height).toBe('800%'); expect(previewImage.style.left).toBe('-200%'); expect(previewImage.style.top).toBe('-200%'); }); it('reuses the source layer signed read-url cache for private crop previews', () => { render( , ); const preview = screen.getByLabelText('框选区域预览 1'); const previewImage = within(preview).getByRole('img', { name: '源图框选预览 1', }); expect(previewImage.getAttribute('data-object-key')).toBe( 'generated-editor-assets/project-1/source.png', ); expect(previewImage.getAttribute('data-refresh-key')).toBe( 'task-private', ); expect(previewImage.getAttribute('data-fallback-src')).toBe(''); }); it('shows numbered labels for quick edit red selections', () => { const { container } = render( , ); const labels = Array.from( container.querySelectorAll( '.image-canvas-editor__ui-extraction-mark-label', ), ).map((node) => node.textContent); const labelCircles = Array.from( container.querySelectorAll( '.image-canvas-editor__ui-extraction-mark-label circle', ), ).map((node) => node.getAttribute('r')); expect(labels).toEqual(['1', '2']); expect(labelCircles).toEqual(['12', '12']); }); });