test(ui): pin CRLF source slices to the index line numbering (CG-42)

A CRLF file must come back with the graph's own line numbers and without a
trailing carriage return on every line — the case a Windows checkout with
core.autocrlf produces. It is decided by bytes rather than by the OS, so it
is covered here rather than only on the VM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-08-26 23:34:44 -05:00
co-authored by Claude Opus 5
parent 951ba3678a
commit e7288ffa36
+30
View File
@@ -192,6 +192,15 @@ ${callers}
` `
); );
// CRLF on purpose: tree-sitter numbers rows by `\n`, so a CRLF file must come
// back with the same line numbers the graph recorded — and without the stray
// `\r` rendering at the end of every line. This is what a Windows checkout
// with core.autocrlf looks like, and it is decided by bytes, not by the OS.
fs.writeFileSync(
path.join(srcDir, 'crlf.ts'),
['export function windowsStyle(n: number): number {', ' return n + 1;', '}', ''].join('\r\n')
);
fs.writeFileSync( fs.writeFileSync(
path.join(testsDir, 'service.test.ts'), path.join(testsDir, 'service.test.ts'),
`import { Service } from '../src/service'; `import { Service } from '../src/service';
@@ -613,6 +622,27 @@ describe('GET /api/source', () => {
expect(body.lines).toHaveLength(2); expect(body.lines).toHaveLength(2);
}); });
it('keeps a CRLF file on the index line numbering, without the stray carriage returns', async () => {
const node = await getJson(`/api/node/${await idOf('windowsStyle', 'function')}`);
expect(node.node.file).toBe('src/crlf.ts');
const body = await getJson('/api/source?file=src/crlf.ts');
expect(body.drift).toBe(false);
expect(body.totalLines).toBe(3);
expect(body.lines).toEqual([
'export function windowsStyle(n: number): number {',
' return n + 1;',
'}',
]);
expect(body.lines.some((l: string) => l.includes('\r'))).toBe(false);
// The symbol's indexed range still names its own body.
const slice = await getJson(
`/api/source?file=src/crlf.ts&from=${node.node.line}&to=${node.node.endLine}`
);
expect(slice.lines[0]).toContain('windowsStyle');
});
it('refuses a path that escapes the project', async () => { it('refuses a path that escapes the project', async () => {
const traversal = await getStatusAndJson( const traversal = await getStatusAndJson(
'/api/source?file=' + encodeURIComponent('../../../etc/passwd') '/api/source?file=' + encodeURIComponent('../../../etc/passwd')