2026-05-20 21:39:12 +08:00

90 lines
2.9 KiB
TypeScript

import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { editTool } from '../src/modules/netaclaw/tools/builtin/edit.js';
describe('editTool', () => {
const tempDirs: string[] = [];
afterEach(() => {
tempDirs.splice(0).forEach(dir => fs.rmSync(dir, { recursive: true, force: true }));
});
const createFile = (content: string) => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'neta-edit-tool-'));
tempDirs.push(dir);
const filePath = path.join(dir, 'target.txt');
fs.writeFileSync(filePath, content, 'utf8');
return filePath;
};
it('applies multiple non-overlapping edits against the original file', async () => {
const filePath = createFile('alpha\nbeta\ngamma\n');
const result = await editTool.execute('tool-1', {
path: filePath,
edits: [
{ oldText: 'alpha', newText: 'ALPHA' },
{ oldText: 'gamma', newText: 'GAMMA' },
],
});
expect(fs.readFileSync(filePath, 'utf8')).toBe('ALPHA\nbeta\nGAMMA\n');
expect(result).toEqual(expect.objectContaining({
type: 'text',
text: expect.stringContaining('已编辑 2 处'),
}));
expect((result as any).text).toContain('Diff:');
});
it('rejects duplicate oldText matches instead of editing ambiguously', async () => {
const filePath = createFile('same\nsame\n');
const result = await editTool.execute('tool-1', {
path: filePath,
edits: [{ oldText: 'same', newText: 'changed' }],
});
expect(fs.readFileSync(filePath, 'utf8')).toBe('same\nsame\n');
expect((result as any).text).toContain('Found 2 occurrences');
});
it('keeps legacy oldText/newText input compatible', async () => {
const filePath = createFile('before\n');
await editTool.execute('tool-1', {
path: filePath,
oldText: 'before',
newText: 'after',
} as any);
expect(fs.readFileSync(filePath, 'utf8')).toBe('after\n');
});
it('resolves relative paths from runtime session cwd', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'neta-edit-tool-'));
tempDirs.push(root);
fs.writeFileSync(path.join(root, 'target.txt'), 'before\n', 'utf8');
await editTool.execute('tool-1', {
path: 'target.txt',
edits: [{ oldText: 'before', newText: 'after' }],
_netaRuntime: { sessionCwd: root },
} as any);
expect(fs.readFileSync(path.join(root, 'target.txt'), 'utf8')).toBe('after\n');
});
it('uses Pi-style fuzzy matching for trailing whitespace and smart punctuation', async () => {
const filePath = createFile('const title = “Hello” \n');
const result = await editTool.execute('tool-1', {
path: filePath,
edits: [{ oldText: 'const title = "Hello"', newText: 'const title = "Hi"' }],
});
expect(fs.readFileSync(filePath, 'utf8')).toBe('const title = "Hi"\n');
expect((result as any).text).toContain('首个变更行: 1');
});
});