90 lines
2.9 KiB
TypeScript
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');
|
|
});
|
|
});
|