oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1package render_test
2
3import (
4 "regexp"
5 "testing"
6
7 "amadan.net/oficina/slides/internal/slides/deck"
8 "amadan.net/oficina/slides/internal/slides/render"
9)
10
11// Codex 10: the phone layer's `order` used to reach the page only as
12// CSS `order`, so a screen reader and the Tab key walked the desktop
13// sequence while sighted readers saw the phone one. Rendering for a
14// phone now emits the blocks in the effective phone sequence; rendering
15// for a desktop keeps the desktop order. These tests read the sequence
16// off the document itself — the data-id attributes in the order they
17// occur — which is the order assistive technology gets.
18
19var idPattern = regexp.MustCompile(`data-id="([^"]+)"`)
20
21// documentOrder is every block's id in the order it occurs in the
22// HTML, the slide's own data-id skipped.
23func documentOrder(t *testing.T, html string) []string {
24 t.Helper()
25 var out []string
26 for i, m := range idPattern.FindAllStringSubmatch(html, -1) {
27 if i == 0 {
28 continue // the <section>'s own data-id
29 }
30 out = append(out, m[1])
31 }
32 return out
33}
34
35func orderedDeck() *deck.Deck {
36 return &deck.Deck{
37 Meta: deck.Meta{Theme: "paper"},
38 Slides: []deck.Slide{
39 // 0 — flow: the phone layer moves the footnote first and the
40 // heading's neighbour last, across two regions; a negative
41 // order and an order on a columns child too.
42 {
43 ID: "flow", Layout: "flow", Template: "title-body",
44 Blocks: []deck.Block{
45 {ID: "head", Type: "heading", Region: "heading", Text: txt("Heading")},
46 {ID: "intro", Type: "text", Region: "body", Text: txt("Intro"), Phone: deck.PhoneOverride{Order: intp(2)}},
47 {ID: "cols", Type: "columns", Region: "body",
48 Props: map[string]any{"children": []any{"left", "right"}}},
49 {ID: "left", Type: "text", Region: "body", Text: txt("Left"), Phone: deck.PhoneOverride{Order: intp(1)}},
50 {ID: "right", Type: "text", Region: "body", Text: txt("Right")},
51 {ID: "foot", Type: "text", Region: "body", Text: txt("Footnote"), Phone: deck.PhoneOverride{Order: intp(-1)}},
52 {ID: "aside", Type: "text", Region: "aside", Text: txt("Aside")},
53 },
54 },
55 // 1 — canvas, reflowed on a phone: z then top-left, and then
56 // the phone order on top of that.
57 {
58 ID: "canvas", Layout: "canvas", Template: "blank", Phone: "reflow",
59 Blocks: []deck.Block{
60 {ID: "late", Type: "text", Box: box(0.1, 0.8, 0.8, 0.1), Z: 3, Text: txt("Late by z"),
61 Phone: deck.PhoneOverride{Order: intp(-5)}},
62 {ID: "top", Type: "heading", Box: box(0.1, 0.1, 0.8, 0.1), Z: 1, Text: txt("Top")},
63 {ID: "mid", Type: "text", Box: box(0.1, 0.4, 0.8, 0.1), Z: 1, Text: txt("Middle")},
64 },
65 },
66 },
67 }
68}
69
70func TestPhoneRenderingEmitsBlocksInThePhoneSequence(t *testing.T) {
71 d := orderedDeck()
72
73 desktop, err := render.Slide(d, 0, render.Desktop)
74 if err != nil {
75 t.Fatal(err)
76 }
77 // Desktop: regions in region order, array order inside each, the
78 // columns children inside the columns block as props.children says.
79 wantDesktop := []string{"head", "intro", "cols", "left", "right", "foot", "aside"}
80 if got := documentOrder(t, string(desktop)); !equal(got, wantDesktop) {
81 t.Errorf("desktop document order = %v, want %v", got, wantDesktop)
82 }
83
84 phone, err := render.Slide(d, 0, render.Phone)
85 if err != nil {
86 t.Fatal(err)
87 }
88 // Phone: within the body region, order -1 (foot) first, then the
89 // unset ones (cols, right — an unset order is 0, the stylesheet's
90 // own default), then 1, then 2; the heading and aside regions keep
91 // their places around it, and the columns children sort among
92 // themselves.
93 wantPhone := []string{"head", "foot", "cols", "right", "left", "intro", "aside"}
94 if got := documentOrder(t, string(phone)); !equal(got, wantPhone) {
95 t.Errorf("phone document order = %v, want %v", got, wantPhone)
96 }
97}
98
99func TestReflowedCanvasEmitsZThenTopLeftThenPhoneOrder(t *testing.T) {
100 d := orderedDeck()
101 desktop, err := render.Slide(d, 1, render.Desktop)
102 if err != nil {
103 t.Fatal(err)
104 }
105 // A canvas on a desktop is array order: the boxes place things.
106 if got, want := documentOrder(t, string(desktop)), []string{"late", "top", "mid"}; !equal(got, want) {
107 t.Errorf("canvas desktop order = %v, want %v", got, want)
108 }
109 phone, err := render.Slide(d, 1, render.Phone)
110 if err != nil {
111 t.Fatal(err)
112 }
113 // Reflowed: z then top-left gives top, mid, late; the phone order
114 // -5 then pulls late to the front.
115 if got, want := documentOrder(t, string(phone)), []string{"late", "top", "mid"}; !equal(got, want) {
116 t.Errorf("reflowed phone order = %v, want %v", got, want)
117 }
118 // Without the override the reading order is the spec's z then
119 // top-left, in the document and not only in the stylesheet.
120 d.Slides[1].Blocks[0].Phone = deck.PhoneOverride{}
121 phone, err = render.Slide(d, 1, render.Phone)
122 if err != nil {
123 t.Fatal(err)
124 }
125 if got, want := documentOrder(t, string(phone)), []string{"top", "mid", "late"}; !equal(got, want) {
126 t.Errorf("reflowed phone order without an override = %v, want %v", got, want)
127 }
128}
129
130func equal(a, b []string) bool {
131 if len(a) != len(b) {
132 return false
133 }
134 for i := range a {
135 if a[i] != b[i] {
136 return false
137 }
138 }
139 return true
140}
141