agent-ecosystem/test/renderer/utils/chipUtils.test.ts
iliya e1585b4f71 feat: add binary file preview functionality and improve editor performance
- Introduced a new IPC handler for reading binary files as base64 for inline previews, enhancing the editor's capability to display images directly.
- Implemented a placeholder component for non-previewable binary files, providing users with file information and an option to open in the system viewer.
- Enhanced the EditorFileWatcher to ignore permission errors and adjusted debounce timing for better performance during file system events.
- Updated the ProjectFileService to include a method for reading binary previews with security checks and MIME type validation.
- Improved the EditorImagePreview component to handle loading states and display images with a checkerboard background for transparency.
- Refactored the EditorBinaryState component to utilize the new binary preview functionality, streamlining the display of binary files.
2026-03-01 23:51:51 +02:00

224 lines
7.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { chipToken } from '@renderer/types/inlineChip';
import {
createChipFromSelection,
findChipBoundary,
isInsideChip,
reconcileChips,
removeChipTokenFromText,
snapCursorToChipBoundary,
} from '@renderer/utils/chipUtils';
import type { InlineChip } from '@renderer/types/inlineChip';
import type { EditorSelectionAction } from '@shared/types/editor';
function makeChip(overrides: Partial<InlineChip> = {}): InlineChip {
return {
id: 'chip-test-1',
filePath: '/src/auth.ts',
fileName: 'auth.ts',
fromLine: 10,
toLine: 15,
codeText: 'const x = 1;',
language: 'typescript',
...overrides,
};
}
function makeAction(overrides: Partial<EditorSelectionAction> = {}): EditorSelectionAction {
return {
type: 'sendMessage',
filePath: '/src/auth.ts',
fromLine: 10,
toLine: 15,
selectedText: 'const x = 1;',
formattedContext: '**auth.ts** (lines 10-15):\n```typescript\nconst x = 1;\n```',
...overrides,
};
}
describe('createChipFromSelection', () => {
it('creates a chip from an EditorSelectionAction', () => {
const action = makeAction();
const chip = createChipFromSelection(action, []);
expect(chip).not.toBeNull();
expect(chip!.filePath).toBe('/src/auth.ts');
expect(chip!.fileName).toBe('auth.ts');
expect(chip!.fromLine).toBe(10);
expect(chip!.toLine).toBe(15);
expect(chip!.codeText).toBe('const x = 1;');
expect(chip!.language).toBe('typescript');
expect(chip!.id).toMatch(/^chip-/);
});
it('returns null for duplicate (same filePath + lines)', () => {
const existing = makeChip();
const action = makeAction();
expect(createChipFromSelection(action, [existing])).toBeNull();
});
it('allows different line ranges for the same file', () => {
const existing = makeChip({ fromLine: 1, toLine: 5 });
const action = makeAction({ fromLine: 10, toLine: 15 });
expect(createChipFromSelection(action, [existing])).not.toBeNull();
});
});
describe('findChipBoundary', () => {
it('returns boundary when cursor is at chip end', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `Hello ${token} world`;
const cursorPos = 6 + token.length; // at end of token
const boundary = findChipBoundary(text, [chip], cursorPos);
expect(boundary).not.toBeNull();
expect(boundary!.start).toBe(6);
expect(boundary!.end).toBe(6 + token.length);
expect(boundary!.chip).toBe(chip);
});
it('returns boundary when cursor is at chip start', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `Hello ${token} world`;
const boundary = findChipBoundary(text, [chip], 6);
expect(boundary).not.toBeNull();
expect(boundary!.start).toBe(6);
});
it('returns boundary when cursor is inside chip', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `Hello ${token} world`;
const boundary = findChipBoundary(text, [chip], 8);
expect(boundary).not.toBeNull();
});
it('returns null when cursor is not near any chip', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `Hello ${token} world`;
// cursor at beginning
expect(findChipBoundary(text, [chip], 0)).toBeNull();
});
it('handles multiple chips', () => {
const chip1 = makeChip({ id: 'c1', fileName: 'a.ts', fromLine: 1, toLine: 1 });
const chip2 = makeChip({ id: 'c2', fileName: 'b.ts', fromLine: 2, toLine: 2 });
const t1 = chipToken(chip1);
const t2 = chipToken(chip2);
const text = `${t1} ${t2}`;
const boundary = findChipBoundary(text, [chip1, chip2], t1.length + 1);
expect(boundary).not.toBeNull();
expect(boundary!.chip.id).toBe('c2');
});
});
describe('isInsideChip', () => {
it('returns true when cursor is strictly inside', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `X${token}Y`;
expect(isInsideChip(text, [chip], 3)).toBe(true);
});
it('returns false at chip start boundary', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `X${token}Y`;
expect(isInsideChip(text, [chip], 1)).toBe(false);
});
it('returns false at chip end boundary', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `X${token}Y`;
expect(isInsideChip(text, [chip], 1 + token.length)).toBe(false);
});
it('returns false outside chip', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `X${token}Y`;
expect(isInsideChip(text, [chip], 0)).toBe(false);
});
});
describe('snapCursorToChipBoundary', () => {
it('snaps to nearest start when cursor is closer to start', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `X${token}Y`;
// position 2 is inside, closer to start (1)
const snapped = snapCursorToChipBoundary(text, [chip], 2);
expect(snapped).toBe(1);
});
it('snaps to nearest end when cursor is closer to end', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `X${token}Y`;
// position close to end
const nearEnd = 1 + token.length - 1;
const snapped = snapCursorToChipBoundary(text, [chip], nearEnd);
expect(snapped).toBe(1 + token.length);
});
it('returns original position if not inside chip', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `X${token}Y`;
expect(snapCursorToChipBoundary(text, [chip], 0)).toBe(0);
});
});
describe('reconcileChips', () => {
it('keeps chips whose tokens are present', () => {
const chip = makeChip();
const text = `Hello ${chipToken(chip)} world`;
expect(reconcileChips([chip], text)).toEqual([chip]);
});
it('removes chips whose tokens are missing', () => {
const chip = makeChip();
expect(reconcileChips([chip], 'Hello world')).toEqual([]);
});
it('handles partial removal (only some chips gone)', () => {
const chip1 = makeChip({ id: 'c1', fileName: 'a.ts', fromLine: 1, toLine: 1 });
const chip2 = makeChip({ id: 'c2', fileName: 'b.ts', fromLine: 2, toLine: 2 });
const text = chipToken(chip1); // only chip1 token present
const result = reconcileChips([chip1, chip2], text);
expect(result).toEqual([chip1]);
});
});
describe('removeChipTokenFromText', () => {
it('removes the token from text', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `Hello ${token} world`;
expect(removeChipTokenFromText(text, chip)).toBe('Hello world');
});
it('removes trailing newline', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `${token}\nHello`;
expect(removeChipTokenFromText(text, chip)).toBe('Hello');
});
it('does not alter text when token not found', () => {
const chip = makeChip();
const text = 'Hello world';
expect(removeChipTokenFromText(text, chip)).toBe('Hello world');
});
it('removes token from middle of text', () => {
const chip = makeChip();
const token = chipToken(chip);
const text = `A\n${token}\nB`;
expect(removeChipTokenFromText(text, chip)).toBe('A\nB');
});
});