saterminal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.envrc +1 -0
- package/AGENTS.md +23 -0
- package/README.md +4 -0
- package/bun.lock +100 -0
- package/flake.lock +59 -0
- package/flake.nix +21 -0
- package/package.json +26 -0
- package/src/api.ts +78 -0
- package/src/focus.ts +231 -0
- package/src/main.ts +4 -0
- package/src/state.ts +203 -0
- package/src/text.ts +256 -0
- package/src/tui/app.ts +77 -0
- package/src/tui/focus-grid.ts +113 -0
- package/src/tui/history.ts +6 -0
- package/src/tui/input.ts +411 -0
- package/src/tui/kit.ts +29 -0
- package/src/tui/layout.ts +25 -0
- package/src/tui/pane-content.ts +150 -0
- package/src/tui/question.ts +104 -0
- package/src/tui/render.ts +618 -0
- package/src/tui/terminal.ts +110 -0
- package/src/tui/timer.ts +43 -0
- package/src/tui/types.ts +48 -0
- package/src/tui/viewport.ts +60 -0
- package/src/tui.ts +1 -0
- package/src/types/terminal-kit.d.ts +118 -0
- package/src/types.ts +52 -0
- package/test/api.test.ts +80 -0
- package/test/focus.test.ts +55 -0
- package/test/state.test.ts +120 -0
- package/test/text.test.ts +72 -0
- package/test/viewport.test.ts +39 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { hasHtmlTable, htmlToText, parseHtmlSegments, wrapSegments, wrapText } from "../src/text.ts";
|
|
3
|
+
|
|
4
|
+
describe("text", () => {
|
|
5
|
+
test("converts the API html subset into terminal text", () => {
|
|
6
|
+
expect(htmlToText("<p><strong>Text 1</strong></p><p>Alice’s claim—briefly.</p>")).toBe(
|
|
7
|
+
"Text 1\nAlice's claim-briefly.",
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("uses media labels instead of leaking svg internals", () => {
|
|
12
|
+
expect(
|
|
13
|
+
htmlToText(
|
|
14
|
+
'<figure><svg aria-label="Line graph titled Exports"><text>90807060</text></svg></figure><p>Read the graph.</p>',
|
|
15
|
+
),
|
|
16
|
+
).toBe("[Graph: Line graph titled Exports]\nRead the graph.");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("keeps image alt text visible", () => {
|
|
20
|
+
expect(htmlToText('<p><img alt="Triangle ABC" src="/triangle.png"></p>')).toBe("[Image: Triangle ABC]");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("detects html tables", () => {
|
|
24
|
+
expect(hasHtmlTable("<p>Text</p>", "<table><tr><td>1</td></tr></table>")).toBe(true);
|
|
25
|
+
expect(hasHtmlTable("<p>Text</p>")).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("normalizes SAT blank markers", () => {
|
|
29
|
+
expect(htmlToText("<p>The answer is ______blank because of the data.</p>")).toBe(
|
|
30
|
+
"The answer is _______ because of the data.",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("wraps text without dropping words", () => {
|
|
35
|
+
expect(wrapText("one two three four", 8)).toEqual(["one two", "three", "four"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("preserves underline segments from u tags", () => {
|
|
39
|
+
const segments = parseHtmlSegments("<p>Before <u>underlined claim</u> after.</p>");
|
|
40
|
+
expect(segments.some((segment) => segment.style.underline && segment.text.includes("underlined claim"))).toBe(true);
|
|
41
|
+
expect(htmlToText("<p>Before <u>underlined claim</u> after.</p>")).toBe("Before underlined claim after.");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("normalizes API blank spans in parsed segments", () => {
|
|
45
|
+
const html =
|
|
46
|
+
'<p>The answer is <span aria-hidden="true">______</span><span class="sr-only">blank</span> because of the data.</p>';
|
|
47
|
+
const text = parseHtmlSegments(html)
|
|
48
|
+
.map((segment) => segment.text)
|
|
49
|
+
.join("")
|
|
50
|
+
.trim();
|
|
51
|
+
expect(text).toBe("The answer is _______ because of the data.");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("wraps styled segments without losing underline spans", () => {
|
|
55
|
+
const segments = parseHtmlSegments("<u>This insightful depiction of a preteen girl</u>");
|
|
56
|
+
const lines = wrapSegments(segments, 20);
|
|
57
|
+
expect(lines.flat().some((segment) => segment.style.underline)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("collapses spacer paragraphs and decodes accented entities in dual-text stimuli", () => {
|
|
61
|
+
const html = `<p><strong><span role="heading">Text 1</span></strong></p>
|
|
62
|
+
<p>the object that struck the Yucatán Peninsula</p>
|
|
63
|
+
<p> </p>
|
|
64
|
+
<p><strong><span role="heading">Text 2</span></strong></p>
|
|
65
|
+
<p>Artemieva argues that an asteroid is plausible.</p>`;
|
|
66
|
+
const lines = wrapSegments(parseHtmlSegments(html), 40).map((line) => line.map((segment) => segment.text).join(""));
|
|
67
|
+
const blankLines = lines.filter((line) => !line.trim()).length;
|
|
68
|
+
expect(blankLines).toBeLessThanOrEqual(2);
|
|
69
|
+
expect(lines.join("\n")).toContain("Yucatán");
|
|
70
|
+
expect(lines.join("\n")).not.toContain("á");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
clampScroll,
|
|
4
|
+
ensureRangeVisible,
|
|
5
|
+
ensureRowVisible,
|
|
6
|
+
maxScroll,
|
|
7
|
+
scrollBy,
|
|
8
|
+
scrollPage,
|
|
9
|
+
scrollToEdge,
|
|
10
|
+
} from "../src/tui/viewport.ts";
|
|
11
|
+
|
|
12
|
+
describe("viewport", () => {
|
|
13
|
+
test("clamps scroll to the available content", () => {
|
|
14
|
+
expect(maxScroll({ scroll: 0, height: 5, contentRows: 12 })).toBe(7);
|
|
15
|
+
expect(clampScroll({ scroll: -3, height: 5, contentRows: 12 })).toBe(0);
|
|
16
|
+
expect(clampScroll({ scroll: 20, height: 5, contentRows: 12 })).toBe(7);
|
|
17
|
+
expect(clampScroll({ scroll: 20, height: 5, contentRows: 3 })).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("scrolls by lines and pages", () => {
|
|
21
|
+
const viewport = { scroll: 4, height: 5, contentRows: 20 };
|
|
22
|
+
|
|
23
|
+
expect(scrollBy(viewport, 2)).toBe(6);
|
|
24
|
+
expect(scrollBy(viewport, -10)).toBe(0);
|
|
25
|
+
expect(scrollPage(viewport, 1)).toBe(8);
|
|
26
|
+
expect(scrollPage(viewport, -1)).toBe(0);
|
|
27
|
+
expect(scrollToEdge(viewport, "bottom")).toBe(15);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("keeps a selected row or wrapped range visible", () => {
|
|
31
|
+
const viewport = { scroll: 4, height: 5, contentRows: 20 };
|
|
32
|
+
|
|
33
|
+
expect(ensureRowVisible(viewport, 3)).toBe(3);
|
|
34
|
+
expect(ensureRowVisible(viewport, 8)).toBe(4);
|
|
35
|
+
expect(ensureRowVisible(viewport, 12)).toBe(8);
|
|
36
|
+
expect(ensureRangeVisible(viewport, 11, 13)).toBe(9);
|
|
37
|
+
expect(ensureRangeVisible(viewport, 5, 7)).toBe(4);
|
|
38
|
+
});
|
|
39
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"types": ["bun"],
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"noEmit": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*.ts", "src/**/*.d.ts", "test/**/*.ts"]
|
|
13
|
+
}
|