oficina / docs Public

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

Plain git — no account needed to clone.

Download

Download this file

1package docs
2
3import (
4 "net/http/httptest"
5 "os"
6 "regexp"
7 "strconv"
8 "strings"
9 "testing"
10
11 "github.com/carlosframework/docs/internal/export"
12)
13
14// The point ladder is stated in four places that cannot import each
15// other: fixtures/sizes.mjs (what the editor writes),
16// core/src/fragment.rs's MARKS (what the read view renders),
17// views/show.html (what either of them looks like) and
18// internal/export/pdf.go (what the PDF measures). Go and Node and Rust
19// have no way to share a constant, so tests are the only thing that can
20// hold them equal.
21//
22// The failure these prevent is quiet in every direction: a size the
23// editor can write and the renderer cannot render is text that loses
24// its size for every reader without JavaScript, and a size the
25// stylesheet does not style is text that renders at the wrong size for
26// everybody.
27
28// sizeLadderFromBundle reads fixtures/sizes.mjs's SIZES line.
29//
30// Parsed out of the source rather than executed, for the reason
31// TestToolbarOptionValuesMatchTheBundle already relies on: Go cannot
32// run the module, and a second copy of the ladder in this file would be
33// a second thing to keep in step — which is the exact problem being
34// tested for.
35func sizeLadderFromBundle(t *testing.T) []int {
36 t.Helper()
37 src, err := os.ReadFile("../../fixtures/sizes.mjs")
38 if err != nil {
39 t.Fatal(err)
40 }
41 m := regexp.MustCompile(`export const SIZES = \[([^\]]+)\]`).FindSubmatch(src)
42 if m == nil {
43 t.Fatal("fixtures/sizes.mjs has no SIZES array this test can find — it was restructured and this test can no longer see what it is checking")
44 }
45 var out []int
46 for _, field := range strings.Split(string(m[1]), ",") {
47 n, err := strconv.Atoi(strings.TrimSpace(field))
48 if err != nil {
49 t.Fatalf("fixtures/sizes.mjs's SIZES has a non-numeric entry %q", strings.TrimSpace(field))
50 }
51 out = append(out, n)
52 }
53 if len(out) < 2 {
54 t.Fatalf("read only %d sizes from fixtures/sizes.mjs — this test would verify nothing", len(out))
55 }
56 return out
57}
58
59// The control offers exactly the ladder the bundle knows, in the same
60// order — including the default being first, which is what a <select>
61// shows before any script runs and permanently without one.
62func TestSizeLadderMatchesTheBundle(t *testing.T) {
63 want := sizeLadderFromBundle(t)
64
65 got := newToolbarView(httptest.NewRequest("GET", "/d/abc123", nil)).Sizes
66 if len(got) != len(want) {
67 t.Fatalf("the control offers %d sizes, fixtures/sizes.mjs has %d", len(got), len(want))
68 }
69 for i, opt := range got {
70 if opt.Value != strconv.Itoa(want[i]) {
71 t.Errorf("size %d: the control offers %q, fixtures/sizes.mjs has %d", i, opt.Value, want[i])
72 }
73 // The label is the value: a point size is a quantity, not
74 // copy. See sizeOptions' own comment for why this is not a
75 // catalog key.
76 if opt.Label != opt.Value {
77 t.Errorf("size %q has label %q — a point size is its own label", opt.Value, opt.Label)
78 }
79 }
80}
81
82// Every size the editor can write, the Rust renderer renders and the
83// exporter measures.
84//
85// This is the one that catches real data loss. A mark in the schema
86// with no MARKS row renders as nothing for a reader with no JavaScript
87// — core/src/fragment.rs's own "silence here is data loss" — and a mark
88// the exporter does not know silently comes out at body size in the
89// PDF.
90func TestEverySizeMarkIsRenderedAndMeasured(t *testing.T) {
91 ladder := sizeLadderFromBundle(t)
92
93 rust, err := os.ReadFile("../../core/src/fragment.rs")
94 if err != nil {
95 t.Fatal(err)
96 }
97
98 checked := 0
99 for _, pt := range ladder {
100 if pt == 12 {
101 // The default carries no mark anywhere, on purpose.
102 continue
103 }
104 mark := "size_" + strconv.Itoa(pt)
105 checked++
106
107 if !strings.Contains(string(rust), `("`+mark+`"`) {
108 t.Errorf("the editor can write %q and core/src/fragment.rs has no MARKS row for it — the size vanishes for a reader with no JavaScript", mark)
109 }
110 got, ok := export.SizePt(mark)
111 if !ok {
112 t.Errorf("the editor can write %q and internal/export does not know its size — the PDF lays it out at body size", mark)
113 continue
114 }
115 if got != float64(pt) {
116 t.Errorf("%s: internal/export measures it at %vpt", mark, got)
117 }
118 }
119 if checked == 0 {
120 t.Fatal("no size marks were checked — the ladder parsed as nothing but the default, and this test verified nothing")
121 }
122
123 // And the three legacy marks, which are not on the ladder and must
124 // go on being rendered and measured forever: documents in the wild
125 // carry them.
126 for _, mark := range []string{"size_sm", "size_lg", "size_xl"} {
127 if !strings.Contains(string(rust), `("`+mark+`"`) {
128 t.Errorf("core/src/fragment.rs no longer renders %q — every document carrying it silently loses its size", mark)
129 }
130 if pt, ok := export.SizePt(mark); !ok || pt == 0 {
131 t.Errorf("internal/export no longer measures %q", mark)
132 }
133 }
134}
135