nandi/oripublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

EditorPane.test.tsx · 103 lines · 3.1 KBTypeScript Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1import { fireEvent, render, screen, waitFor } from "@testing-library/react";
2import { beforeEach, describe, expect, it, vi } from "vitest";
3import { EditorPane } from "./EditorPane";
4
5const { readFileMock, writeFileMock } = vi.hoisted(() => ({
6 readFileMock: vi.fn(),
7 writeFileMock: vi.fn(),
8}));
9
10vi.mock("../api", () => ({
11 readFile: readFileMock,
12 writeFile: writeFileMock,
13}));
14
15// Monaco is replaced by a plain textarea driving the same onChange contract.
16vi.mock("./MonacoViewer", () => ({
17 default: ({
18 content,
19 onChange,
20 }: { content: string; onChange?: (value: string) => void }) => (
21 <textarea
22 aria-label="editor"
23 value={content}
24 onChange={(event) => onChange?.(event.target.value)}
25 />
26 ),
27}));
28
29async function openEditorOn(path: string, content: string) {
30 readFileMock.mockResolvedValue({ path, content });
31 render(<EditorPane path={path} />);
32 return await screen.findByLabelText("editor");
33}
34
35describe("EditorPane", () => {
36 beforeEach(() => {
37 readFileMock.mockReset();
38 writeFileMock.mockReset();
39 });
40
41 it("asks for a file when none is open", () => {
42 render(<EditorPane path={null} />);
43 expect(screen.getByText(/Double-click a file/)).toBeDefined();
44 });
45
46 it("loads the file into the editor with Save disabled", async () => {
47 const editor = await openEditorOn("/w/main.go", "package main");
48 expect((editor as HTMLTextAreaElement).value).toBe("package main");
49 expect(
50 (screen.getByRole("button", { name: "Save" }) as HTMLButtonElement)
51 .disabled,
52 ).toBe(true);
53 });
54
55 it("marks the file dirty on edit and saves through the API", async () => {
56 writeFileMock.mockResolvedValue(undefined);
57 const editor = await openEditorOn("/w/main.go", "package main");
58
59 fireEvent.change(editor, { target: { value: "package main // edited" } });
60 expect(screen.getByText(/●/)).toBeDefined();
61
62 fireEvent.click(screen.getByRole("button", { name: "Save" }));
63 await waitFor(() =>
64 expect(writeFileMock).toHaveBeenCalledWith(
65 "/w/main.go",
66 "package main // edited",
67 ),
68 );
69 await waitFor(() => expect(screen.getByText("saved")).toBeDefined());
70 expect(screen.queryByText(/●/)).toBeNull();
71 });
72
73 it("saves with Ctrl+S", async () => {
74 writeFileMock.mockResolvedValue(undefined);
75 const editor = await openEditorOn("/w/notes.txt", "a");
76
77 fireEvent.change(editor, { target: { value: "ab" } });
78 fireEvent.keyDown(editor, { key: "s", ctrlKey: true });
79
80 await waitFor(() =>
81 expect(writeFileMock).toHaveBeenCalledWith("/w/notes.txt", "ab"),
82 );
83 });
84
85 it("keeps the dirty state and shows the error when saving fails", async () => {
86 writeFileMock.mockRejectedValue(new Error("disk full"));
87 const editor = await openEditorOn("/w/notes.txt", "a");
88
89 fireEvent.change(editor, { target: { value: "ab" } });
90 fireEvent.click(screen.getByRole("button", { name: "Save" }));
91
92 await waitFor(() => expect(screen.getByText("disk full")).toBeDefined());
93 expect(screen.getByText(/●/)).toBeDefined();
94 });
95
96 it("surfaces read errors", async () => {
97 readFileMock.mockRejectedValue(new Error("not a text file"));
98 render(<EditorPane path="/w/blob.bin" />);
99 await waitFor(() =>
100 expect(screen.getByText("not a text file")).toBeDefined(),
101 );
102 });
103});