Makelore 2.0 initial clean snapshot
This commit is contained in:
335
tests/unit/chat-command-message.test.ts
Normal file
335
tests/unit/chat-command-message.test.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
findRedoUserMessage,
|
||||
findUndoableUserMessage,
|
||||
getRestorableUserText,
|
||||
getStoredAttachmentsForRestore,
|
||||
} from '@/pages/Chat/chat-command-message';
|
||||
import { normalizeOpencodeSessionMessages } from '@/lib/opencode-message';
|
||||
import type { RawMessage } from '@/types/chat';
|
||||
|
||||
const messages: RawMessage[] = [
|
||||
{
|
||||
id: 'msg_user_1',
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: 'Restore this text' },
|
||||
{ type: 'text', text: 'Synthetic path context', synthetic: true },
|
||||
{
|
||||
id: 'part_image',
|
||||
type: 'image',
|
||||
mimeType: 'image/png',
|
||||
url: 'data:image/png;base64,QUFB',
|
||||
},
|
||||
],
|
||||
_attachedFiles: [
|
||||
{
|
||||
fileName: 'screen.png',
|
||||
mimeType: 'image/png',
|
||||
fileSize: 3,
|
||||
preview: null,
|
||||
source: 'message-ref',
|
||||
},
|
||||
{
|
||||
fileName: 'notes.md',
|
||||
mimeType: 'text/markdown',
|
||||
fileSize: 0,
|
||||
preview: null,
|
||||
filePath: 'D:/repo/notes.md',
|
||||
source: 'message-ref',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'msg_assistant_1',
|
||||
role: 'assistant',
|
||||
content: [{ type: 'text', text: 'Done' }],
|
||||
},
|
||||
{
|
||||
id: 'msg_user_2',
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: 'Second request' }],
|
||||
},
|
||||
];
|
||||
|
||||
describe('chat command message helpers', () => {
|
||||
it('restores only non-synthetic text', () => {
|
||||
expect(getRestorableUserText(messages[0])).toBe('Restore this text');
|
||||
});
|
||||
|
||||
it('finds undo before the revert cursor and fails closed when the cursor is absent', () => {
|
||||
expect(findUndoableUserMessage(messages, undefined)?.id).toBe('msg_user_2');
|
||||
expect(findUndoableUserMessage(messages, 'msg_user_2')?.id).toBe('msg_user_1');
|
||||
expect(findUndoableUserMessage(messages, 'missing')).toBeNull();
|
||||
expect(findUndoableUserMessage(
|
||||
[messages[1], { id: 'msg_system', role: 'system', content: 'notice' }],
|
||||
undefined,
|
||||
)).toBeNull();
|
||||
});
|
||||
|
||||
it('restores a normalized safe command invocation rather than hidden template text', () => {
|
||||
const commandMessage: RawMessage = {
|
||||
id: 'msg_command',
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: '/Review raw arguments ' },
|
||||
{ type: 'text', text: 'Bounded selected path context', synthetic: true },
|
||||
],
|
||||
};
|
||||
expect(findUndoableUserMessage([commandMessage], undefined)?.id).toBe('msg_command');
|
||||
expect(getRestorableUserText(commandMessage)).toBe('/Review raw arguments');
|
||||
});
|
||||
|
||||
it('finds the next user for redo and returns null when unrevert is next', () => {
|
||||
expect(findRedoUserMessage(messages, 'msg_user_1')?.id).toBe('msg_user_2');
|
||||
expect(findRedoUserMessage(messages, 'msg_user_2')).toBeNull();
|
||||
expect(findRedoUserMessage(messages, 'missing')).toBeNull();
|
||||
});
|
||||
|
||||
it('pairs stored images with a validated data url and leaves path files path-only', () => {
|
||||
expect(getStoredAttachmentsForRestore(messages[0])).toEqual([
|
||||
{
|
||||
file: messages[0]._attachedFiles?.[0],
|
||||
imageDataUrl: 'data:image/png;base64,QUFB',
|
||||
},
|
||||
{
|
||||
file: messages[0]._attachedFiles?.[1],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('never treats a remote image url as restorable bytes', () => {
|
||||
const remote: RawMessage = {
|
||||
id: 'msg_remote',
|
||||
role: 'user',
|
||||
content: [{
|
||||
type: 'image',
|
||||
mimeType: 'image/png',
|
||||
url: 'https://runtime.invalid/private.png',
|
||||
}],
|
||||
_attachedFiles: [{
|
||||
fileName: 'private.png',
|
||||
mimeType: 'image/png',
|
||||
fileSize: 0,
|
||||
preview: null,
|
||||
}],
|
||||
};
|
||||
expect(getStoredAttachmentsForRestore(remote)).toEqual([
|
||||
{ file: remote._attachedFiles?.[0] },
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves remote and embedded image positions when MIME types repeat', () => {
|
||||
const interleaved: RawMessage = {
|
||||
id: 'msg_interleaved',
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
id: 'part_remote',
|
||||
type: 'image',
|
||||
mimeType: 'image/png',
|
||||
url: 'https://runtime.invalid/remote.png',
|
||||
},
|
||||
{
|
||||
id: 'part_embedded',
|
||||
type: 'image',
|
||||
mimeType: 'image/png',
|
||||
url: 'data:image/png;base64,QUFB',
|
||||
},
|
||||
],
|
||||
_attachedFiles: [
|
||||
{
|
||||
fileName: 'remote.png',
|
||||
mimeType: 'image/png',
|
||||
fileSize: 0,
|
||||
preview: null,
|
||||
runtimeUrl: 'https://runtime.invalid/remote.png',
|
||||
source: 'runtime-media',
|
||||
},
|
||||
{
|
||||
fileName: 'embedded.png',
|
||||
mimeType: 'image/png',
|
||||
fileSize: 3,
|
||||
preview: null,
|
||||
source: 'message-ref',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStoredAttachmentsForRestore(interleaved)).toEqual([
|
||||
{ file: interleaved._attachedFiles?.[0] },
|
||||
{
|
||||
file: interleaved._attachedFiles?.[1],
|
||||
imageDataUrl: 'data:image/png;base64,QUFB',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('consumes the preview-backed image position before pairing the next embedded image', () => {
|
||||
const previewThenEmbedded: RawMessage = {
|
||||
id: 'msg_preview_then_embedded',
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'image',
|
||||
mimeType: 'image/png',
|
||||
url: 'https://runtime.invalid/preview-backed.png',
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
mimeType: 'image/png',
|
||||
url: 'data:image/png;base64,QkJC',
|
||||
},
|
||||
],
|
||||
_attachedFiles: [
|
||||
{
|
||||
fileName: 'preview-backed.png',
|
||||
mimeType: 'image/png',
|
||||
fileSize: 3,
|
||||
preview: 'data:image/png;base64,QUFB',
|
||||
},
|
||||
{
|
||||
fileName: 'embedded.png',
|
||||
mimeType: 'image/png',
|
||||
fileSize: 3,
|
||||
preview: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStoredAttachmentsForRestore(previewThenEmbedded)).toEqual([
|
||||
{
|
||||
file: previewThenEmbedded._attachedFiles?.[0],
|
||||
imageDataUrl: 'data:image/png;base64,QUFB',
|
||||
},
|
||||
{
|
||||
file: previewThenEmbedded._attachedFiles?.[1],
|
||||
imageDataUrl: 'data:image/png;base64,QkJC',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects empty base64 payloads and MIME-conflicting data urls', () => {
|
||||
const unsafe: RawMessage = {
|
||||
id: 'msg_unsafe_images',
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'image',
|
||||
mimeType: 'image/png',
|
||||
url: 'data:image/png;base64, ',
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
mimeType: 'image/png',
|
||||
url: 'data:image/jpeg;base64,QUFB',
|
||||
},
|
||||
],
|
||||
_attachedFiles: [
|
||||
{
|
||||
fileName: 'empty.png',
|
||||
mimeType: 'image/png',
|
||||
fileSize: 0,
|
||||
preview: null,
|
||||
},
|
||||
{
|
||||
fileName: 'mismatch.png',
|
||||
mimeType: 'image/png',
|
||||
fileSize: 3,
|
||||
preview: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStoredAttachmentsForRestore(unsafe)).toEqual([
|
||||
{ file: unsafe._attachedFiles?.[0] },
|
||||
{ file: unsafe._attachedFiles?.[1] },
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves duplicate same-name image parts and restores their bytes in order', () => {
|
||||
const [normalized] = normalizeOpencodeSessionMessages([{
|
||||
info: {
|
||||
id: 'msg_duplicate_images',
|
||||
role: 'user',
|
||||
time: { created: 1_700_000_004_000 },
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
id: 'prt_duplicate_invocation',
|
||||
type: 'command-invocation',
|
||||
command: 'Review',
|
||||
arguments: '',
|
||||
contextPartIDs: ['prt_duplicate_image_a', 'prt_duplicate_image_b'],
|
||||
},
|
||||
{
|
||||
id: 'prt_duplicate_image_a',
|
||||
type: 'file',
|
||||
mime: 'image/png',
|
||||
filename: 'screen.png',
|
||||
url: 'data:image/png;base64,QUFB',
|
||||
},
|
||||
{
|
||||
id: 'prt_duplicate_image_b',
|
||||
type: 'file',
|
||||
mime: 'image/png',
|
||||
filename: 'screen.png',
|
||||
url: 'data:image/png;base64,QkJC',
|
||||
},
|
||||
],
|
||||
}]);
|
||||
|
||||
expect(normalized?.content).toEqual([
|
||||
{
|
||||
id: 'prt_duplicate_invocation',
|
||||
type: 'text',
|
||||
text: '/Review',
|
||||
},
|
||||
expect.objectContaining({
|
||||
id: 'prt_duplicate_image_a',
|
||||
type: 'image',
|
||||
url: 'data:image/png;base64,QUFB',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'prt_duplicate_image_b',
|
||||
type: 'image',
|
||||
url: 'data:image/png;base64,QkJC',
|
||||
}),
|
||||
]);
|
||||
expect(normalized?._attachedFiles).toHaveLength(2);
|
||||
expect(getStoredAttachmentsForRestore(normalized!)).toEqual([
|
||||
{
|
||||
file: normalized?._attachedFiles?.[0],
|
||||
imageDataUrl: 'data:image/png;base64,QUFB',
|
||||
},
|
||||
{
|
||||
file: normalized?._attachedFiles?.[1],
|
||||
imageDataUrl: 'data:image/png;base64,QkJC',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('treats image MIME prefixes case-insensitively when restoring bytes', () => {
|
||||
const uppercaseMime: RawMessage = {
|
||||
id: 'msg_uppercase_mime',
|
||||
role: 'user',
|
||||
content: [{
|
||||
id: 'part_uppercase_mime',
|
||||
type: 'image',
|
||||
mimeType: 'IMAGE/PNG',
|
||||
url: 'data:image/png;base64,QUFB',
|
||||
}],
|
||||
_attachedFiles: [{
|
||||
fileName: 'upper.png',
|
||||
mimeType: 'IMAGE/PNG',
|
||||
fileSize: 3,
|
||||
preview: null,
|
||||
}],
|
||||
};
|
||||
|
||||
expect(getStoredAttachmentsForRestore(uppercaseMime)).toEqual([{
|
||||
file: uppercaseMime._attachedFiles?.[0],
|
||||
imageDataUrl: 'data:image/png;base64,QUFB',
|
||||
}]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user