-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathcollapsible-toggle.test.ts
More file actions
89 lines (76 loc) · 3.32 KB
/
Copy pathcollapsible-toggle.test.ts
File metadata and controls
89 lines (76 loc) · 3.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import { describe, it, expect } from "vitest";
import { readFileSync } from "fs";
import { resolve } from "path";
/**
* Regression tests for issue #206: toggle blocks not functioning as toggles.
*
* The root cause was threefold:
* 1. No visual toggle indicator (chevron) — list-none removed the browser
* disclosure triangle with no replacement.
* 2. select-none on <summary> prevented text editing in the title.
* 3. Native <summary> click toggled <details>, conflicting with Lexical
* text editing — clicking to place cursor collapsed the section.
*
* These static tests verify the structural fixes remain in place.
*/
function readSource(relativePath: string): string {
return readFileSync(resolve(__dirname, relativePath), "utf-8");
}
describe("collapsible-node toggle affordance", () => {
const source = readSource("./collapsible-node.tsx");
it("summary does not use select-none (title must be editable)", () => {
// select-none prevents cursor placement and text selection in the
// title, breaking inline editing.
const summaryClass = source.match(/summary\.className\s*=\s*\n?\s*"([^"]*)"/);
expect(summaryClass).not.toBeNull();
expect(summaryClass![1]).not.toContain("select-none");
});
it("summary includes a toggle chevron button", () => {
// A dedicated button with class collapsible-toggle must be created
// as the visual affordance for expand/collapse.
expect(source).toContain("collapsible-toggle");
expect(source).toContain('aria-label", "Toggle section"');
});
it("chevron button is not contentEditable", () => {
// The chevron must be excluded from Lexical's editable content
// so it doesn't interfere with text editing.
expect(source).toContain('contentEditable = "false"');
});
it("summary uses flex layout for chevron + text alignment", () => {
const summaryClass = source.match(/summary\.className\s*=\s*\n?\s*"([^"]*)"/);
expect(summaryClass).not.toBeNull();
expect(summaryClass![1]).toContain("flex");
expect(summaryClass![1]).toContain("items-center");
});
});
describe("collapsible-plugin toggle handling", () => {
const source = readSource("./collapsible-plugin.tsx");
it("prevents native summary click from toggling details", () => {
// The plugin must call preventDefault on summary clicks (except
// on the chevron) to stop the native <details> toggle from
// conflicting with Lexical text editing.
expect(source).toContain("e.preventDefault()");
expect(source).toContain("handleSummaryClick");
});
it("toggles via chevron button click only", () => {
// A dedicated chevron click handler must toggle the node state
// and sync the DOM.
expect(source).toContain("handleChevronClick");
expect(source).toContain("collapsible-toggle");
});
it("syncs DOM open state when toggling via chevron", () => {
// After updating the Lexical node, the DOM must be synced since
// we prevented the native toggle behavior.
expect(source).toContain("dom.open = newOpen");
});
});
describe("collapsible chevron CSS rotation", () => {
const css = readFileSync(
resolve(__dirname, "../../app/globals.css"),
"utf-8"
);
it("rotates chevron when details is open", () => {
expect(css).toContain("details[open] > summary > .collapsible-toggle");
expect(css).toContain("rotate(90deg)");
});
});