Files
makelore/tests/unit/composer-attachment-card.test.tsx
2026-07-29 17:22:35 +08:00

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');
});
});