Skip to main content

max / quasi

3.3 KB · 87 lines History Blame Raw
1 // `outline.js`: folding a branch of an outline.
2 //
3 // The fixture is the shape `Row::depth` describes and `node.rs` emits: a flat
4 // list of `[data-row]` siblings, each carrying `data-depth`, each branch
5 // holding a `[data-disclose]` chevron. Nothing here builds a nested list,
6 // because the renderer does not emit one -- the hierarchy is the numbers.
7 //
8 // r0 depth 0
9 // r1 depth 1 <- a branch
10 // r2 depth 2
11 // r3 depth 2
12 // r4 depth 1 <- r1's sibling, not its descendant
13
14 /** The outline above, in the fixture. */
15 const outline = () =>
16 fixture(
17 `<ul>${[
18 [0, "r0"],
19 [1, "r1"],
20 [2, "r2"],
21 [2, "r3"],
22 [1, "r4"],
23 ]
24 .map(
25 ([depth, id]) =>
26 `<li data-row data-depth="${depth}" id="${id}">` +
27 `<button data-disclose aria-expanded="true"></button>${id}</li>`,
28 )
29 .join("")}</ul>`,
30 );
31
32 /** A row's chevron. */
33 const chevron = (id) => document.getElementById(id).querySelector("[data-disclose]");
34
35 /** Whether a row is out of the outline the reader is looking at. */
36 const gone = (id) => document.getElementById(id).hidden;
37
38 describe("outline.js, as shipped", () => {
39 beforeEach(outline);
40
41 it("hides a branch's descendants and leaves its siblings alone", () => {
42 chevron("r1").click();
43
44 expect(gone("r2"), "r2 is under r1").to.equal(true);
45 expect(gone("r3"), "r3 is under r1").to.equal(true);
46 expect(gone("r4"), "r4 is a sibling, not a descendant").to.equal(false);
47 });
48
49 it("keeps an inner shut branch shut when the outer one reopens", () => {
50 // The property `settle` is a walk rather than a row-by-row toggle for:
51 // a reader who left an inner branch shut expects to come back to it
52 // shut. `quasi_router::folded` makes the same reading on the server, so
53 // a reload has to agree with this.
54 chevron("r1").click();
55 chevron("r0").click();
56 expect(gone("r1"), "r1 is under r0").to.equal(true);
57
58 chevron("r0").click();
59 expect(gone("r1"), "r1 comes back").to.equal(false);
60 expect(gone("r2"), "r2 stays shut: r1 is still collapsed").to.equal(true);
61 expect(gone("r3"), "r3 stays shut").to.equal(true);
62 });
63
64 it("says which way the chevron points, in the two places a reader reads", () => {
65 const shut = chevron("r1");
66 shut.click();
67 expect(shut.getAttribute("aria-expanded")).to.equal("false");
68 expect(shut.getAttribute("aria-label")).to.equal("Expand");
69
70 shut.click();
71 expect(shut.getAttribute("aria-expanded")).to.equal("true");
72 expect(shut.getAttribute("aria-label")).to.equal("Collapse");
73 });
74
75 it("does not let the fold reach the row's own act", () => {
76 // A chevron is a separate hit target from the label precisely so that
77 // pressing it is not pressing the row. A row that activates on click
78 // would otherwise navigate out from under the fold.
79 let reached = 0;
80 const row = document.getElementById("r1");
81 row.addEventListener("click", () => { reached += 1; });
82
83 chevron("r1").click();
84 expect(reached, "the click stopped at the chevron").to.equal(0);
85 });
86 });
87