Files
Triple-C/app/src/viewer/textFormat.test.ts
T
jknappandClaude Opus 5.5 8305c96e20
Build App / compute-version (push) Successful in 7s
Secret Scan / scan (push) Successful in 8s
Build App / build-macos (push) Successful in 2m53s
Build App / build-linux (push) Successful in 5m12s
Build App / build-windows (push) Successful in 5m15s
Build App / create-tag (push) Successful in 3s
Build App / sync-to-github (push) Successful in 1m5s
Terminal file viewer/editor + per-window app-command lockdown (#60)
Clicking a file path in Claude's terminal output now opens the file in its own window with a CodeMirror 6 editor. The editor highlights the target line, live-reloads while the file changes, and saves explicitly with hash-based conflict detection. The viewer commands are gated by window label.

Every app command is now ACL-gated per window through a Tauri AppManifest. build.rs checks the handler list against the capability files and fails the build on any mismatch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 17:05:50 +00:00

44 lines
2.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { decodeViewerText, encodeViewerText } from "./textFormat";
import type { Editability } from "./editability";
const editable: Editability = { kind: "text", editable: true, reason: null };
const bytes = (s: string) => new TextEncoder().encode(s);
const BOM = [0xef, 0xbb, 0xbf];
/** What CodeMirror hands back: every line break normalised to "\n". */
const asEditorText = (s: string) => s.replace(/\r\n?/g, "\n");
describe("decodeViewerText / encodeViewerText", () => {
it("round-trips an LF file byte for byte", () => {
const d = decodeViewerText(bytes("a\nb\n"), editable);
expect(d.format).toEqual({ bom: false, eol: "\n" });
expect(Array.from(encodeViewerText(asEditorText(d.text), d.format))).toEqual(Array.from(bytes("a\nb\n")));
});
it("keeps CRLF line endings through the editor's LF buffer", () => {
const d = decodeViewerText(bytes("a\r\nb\r\nc"), editable);
expect(d.format.eol).toBe("\r\n");
const edited = asEditorText(d.text).replace("b", "B");
expect(new TextDecoder().decode(encodeViewerText(edited, d.format))).toBe("a\r\nB\r\nc");
});
it("uses the dominant separator for a mixed file", () => {
expect(decodeViewerText(bytes("a\r\nb\r\nc\nd"), editable).format.eol).toBe("\r\n");
expect(decodeViewerText(bytes("a\nb\nc\r\nd"), editable).format.eol).toBe("\n");
expect(decodeViewerText(bytes("a\rb\rc"), editable).format.eol).toBe("\r");
});
it("strips a UTF-8 BOM from the text and puts it back on save", () => {
const d = decodeViewerText(new Uint8Array([...BOM, ...bytes("hi\n")]), editable);
expect(d.text).toBe("hi\n");
expect(d.format.bom).toBe(true);
expect(Array.from(encodeViewerText("hi\n", d.format))).toEqual([...BOM, ...bytes("hi\n")]);
});
it("makes invalid UTF-8 read-only rather than rewriting it", () => {
const d = decodeViewerText(new Uint8Array([0x61, 0xff, 0x62]), editable);
expect(d.editability).toMatchObject({ editable: false, reason: expect.stringMatching(/not valid UTF-8/) });
});
});