182 lines
6.0 KiB
TypeScript
182 lines
6.0 KiB
TypeScript
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> = {},
|
|
): 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(
|
|
<ComposerAttachmentCard
|
|
attachment={createAttachment()}
|
|
modelRef="niancode-user-models/qwen3.7-plus"
|
|
onSelectVariant={vi.fn()}
|
|
onRemove={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<ComposerAttachmentCard
|
|
attachment={createAttachment()}
|
|
modelRef="niancode-user-models/qwen3.7-plus"
|
|
onSelectVariant={onSelectVariant}
|
|
onRemove={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<ComposerAttachmentCard
|
|
attachment={createAttachment()}
|
|
modelRef="openai/qwen3.7-plus"
|
|
onSelectVariant={vi.fn()}
|
|
onRemove={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<ComposerAttachmentCard
|
|
attachment={createAttachment({
|
|
status,
|
|
warning,
|
|
compressed: undefined,
|
|
selectedVariant: 'original',
|
|
})}
|
|
modelRef="niancode-user-models/qwen3.7-plus"
|
|
onSelectVariant={vi.fn()}
|
|
onRemove={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<ComposerAttachmentCard
|
|
attachment={processed}
|
|
modelRef="niancode-user-models/qwen3.7-plus"
|
|
onSelectVariant={vi.fn()}
|
|
onRemove={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<ComposerAttachmentCard
|
|
attachment={createAttachment({
|
|
status: 'processing',
|
|
warning,
|
|
compressed: undefined,
|
|
selectedVariant: 'original',
|
|
})}
|
|
modelRef="niancode-user-models/qwen3.7-plus"
|
|
onSelectVariant={vi.fn()}
|
|
onRemove={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
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');
|
|
});
|
|
});
|