import { fireEvent, render, screen } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import { ComposerAttachmentCard } from '@/pages/Chat/ComposerAttachmentCard'; import { applyProcessedImage, type ComposerAttachment, } from '@/pages/Chat/composer-attachments'; function createAttachment( overrides: Partial = {}, ): ComposerAttachment { return { id: 'attachment-1', displayName: 'photo.avif', source: 'user-upload', originalFile: new File(['original'], 'photo.avif', { type: 'image/avif' }), original: { blob: new Blob(['original'], { type: 'image/avif' }), mimeType: 'image/avif', bytes: 8_294_400, width: 3840, height: 2160, previewUrl: 'blob:original', transportFileName: 'photo.avif', }, compressed: { blob: new Blob(['compressed'], { type: 'image/webp' }), mimeType: 'image/webp', bytes: 500_000, width: 1930, height: 1086, previewUrl: 'blob:compressed', transportFileName: 'photo.webp', }, selectedVariant: 'compressed', status: 'compressed', ...overrides, }; } describe('ComposerAttachmentCard', () => { it('shows both dimensions/bytes and the selected preview', () => { render( , ); expect(screen.getByTestId('opencode-composer-image-preview')) .toHaveAttribute('src', 'blob:compressed'); expect(screen.getByText(/3840\u00d72160/)).toBeInTheDocument(); expect(screen.getByText(/1930\u00d71086/)).toBeInTheDocument(); expect(screen.getByTestId('opencode-attachment-token-estimate')) .toHaveTextContent('\u7ea62049 Token'); expect(screen.getByText('\u4f7f\u7528\u539f\u56fe')).toBeInTheDocument(); expect(screen.getByText('\u4f7f\u7528\u538b\u7f29\u56fe')).toBeInTheDocument(); }); it('requests original/compressed selection without deleting the attachment', () => { const onSelectVariant = vi.fn(); render( , ); fireEvent.click(screen.getByTestId('opencode-attachment-use-original')); fireEvent.click(screen.getByTestId('opencode-attachment-use-compressed')); expect(onSelectVariant).toHaveBeenNthCalledWith(1, 'original'); expect(onSelectVariant).toHaveBeenNthCalledWith(2, 'compressed'); expect(screen.getByTestId('opencode-composer-attachment')).toBeInTheDocument(); }); it('does not estimate a same-named Qwen model from another provider', () => { render( , ); expect(screen.queryByTestId('opencode-attachment-token-estimate')) .not.toBeInTheDocument(); }); it.each([ ['failed', '\u56fe\u7247\u538b\u7f29\u5931\u8d25\uff0c\u53d1\u9001\u65f6\u5c06\u4f7f\u7528\u539f\u56fe\u3002'], ['skipped', 'GIF \u52a8\u753b\u4e0d\u4f1a\u81ea\u52a8\u538b\u7f29\uff0c\u53d1\u9001\u65f6\u5c06\u4f7f\u7528\u539f\u56fe\u3002'], ] as const)('shows %s warning without a meaningless toggle', (status, warning) => { render( , ); expect(screen.getByTestId('opencode-attachment-processing-status')) .toHaveTextContent(warning); expect(screen.queryByTestId('opencode-attachment-use-original')) .not.toBeInTheDocument(); expect(screen.queryByTestId('opencode-attachment-use-compressed')) .not.toBeInTheDocument(); }); it('shows decoded original dimensions after later image processing fails', () => { const attachment = createAttachment({ status: 'processing', compressed: undefined, selectedVariant: 'original', original: { ...createAttachment().original, width: undefined, height: undefined, }, }); const processed = applyProcessedImage(attachment, { status: 'failed', original: { blob: attachment.originalFile, mimeType: 'image/avif', bytes: attachment.originalFile.size, width: 3000, height: 2000, }, defaultVariant: 'original', warning: '图片压缩失败,发送时将使用原图。', }); render( , ); expect(screen.getByText(/3000\u00d72000/)).toBeInTheDocument(); expect(screen.getByTestId('opencode-attachment-processing-status')) .toHaveTextContent('图片压缩失败,发送时将使用原图。'); }); it('bounds a long status while retaining its complete accessible content', () => { const warning = `\u56fe\u7247\u5904\u7406\u4e2d${'\u5f88\u957f'.repeat(80)}`; render( , ); const status = screen.getByTestId('opencode-attachment-processing-status'); expect(status).toHaveTextContent(warning); expect(status).toHaveAttribute('title', warning); expect(status).toHaveAttribute('aria-label', warning); expect(status).toHaveClass('line-clamp-2', 'break-words'); }); });