| 1 | package render_test |
| 2 | |
| 3 | import ( |
| 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 | |
| 19 | var 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. |
| 23 | func 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 | |
| 35 | func 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 | |
| 70 | func 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 | |
| 99 | func 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 | |
| 130 | func 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 | |