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 "testing"
5
6 "github.com/carlosframework/docs/internal/export"
7)
8
9// bars is the only arithmetic in the thumbnail path that is not simply
10// reported from the layout, so it is the part worth pinning.
11
12func TestBarsOfNothingIsNothing(t *testing.T) {
13 if got := bars(nil); got != nil {
14 t.Errorf("got %+v, want nil", got)
15 }
16}
17
18func TestABarSitsInsideTheLineItStandsFor(t *testing.T) {
19 line := export.LineBox{Left: 72, Top: 100, Width: 300, Height: 16.8, Size: 12}
20 got := bars([]export.LineBox{line})
21 if len(got) != 1 {
22 t.Fatalf("got %d bars, want 1", len(got))
23 }
24 b := got[0]
25 if b.X != 72 {
26 t.Errorf("X = %d, want the line's own left edge 72", b.X)
27 }
28 if b.W != 300 {
29 t.Errorf("W = %d, want the line's measured width 300", b.W)
30 }
31 // Vertically centred in the line box, and never taller than it —
32 // a bar that overflowed its line would overlap its neighbour and
33 // a page of text would read as one solid block.
34 if float64(b.Y) < line.Top-1 || float64(b.Y+b.H) > line.Top+line.Height+1 {
35 t.Errorf("bar [%d, %d] is not inside the line box [%.1f, %.1f]",
36 b.Y, b.Y+b.H, line.Top, line.Top+line.Height)
37 }
38}
39
40// A heading draws heavier than body text, and it does so because the
41// type really is bigger — this is what carries the distinction without
42// colour (WCAG 2.2, and AGENTS.md's "colour never carries meaning
43// alone").
44func TestAHeadingBarIsTallerThanABodyBar(t *testing.T) {
45 got := bars([]export.LineBox{
46 {Left: 72, Top: 100, Width: 200, Height: 25, Size: 19.2},
47 {Left: 72, Top: 130, Width: 200, Height: 16.8, Size: 12},
48 })
49 if len(got) != 2 {
50 t.Fatalf("got %d bars, want 2", len(got))
51 }
52 if got[0].H <= got[1].H {
53 t.Errorf("heading bar %d is not taller than body bar %d", got[0].H, got[1].H)
54 }
55}
56
57// The floor. A bar of literally correct height at this scale is a
58// fraction of a pixel; without a minimum, small type vanishes.
59func TestATinyLineStillDrawsSomething(t *testing.T) {
60 got := bars([]export.LineBox{{Left: 72, Top: 100, Width: 200, Height: 4, Size: 1}})
61 if len(got) != 1 || got[0].H <= 0 {
62 t.Fatalf("a tiny line drew nothing: %+v", got)
63 }
64}
65
66// The cache's whole freshness story is this test: an edit changes
67// updated_at, and a changed updated_at makes the old entry
68// unreachable without anyone having called an invalidate.
69func TestAChangedUpdatedAtMissesTheCache(t *testing.T) {
70 p := newPreviews()
71 want := []previewBar{{X: 1, Y: 2, W: 3, H: 4}}
72 p.put(7, 1000, want)
73
74 if got, ok := p.get(7, 1000); !ok || len(got) != 1 || got[0] != want[0] {
75 t.Errorf("same version did not hit: got %+v ok=%v", got, ok)
76 }
77 if _, ok := p.get(7, 1001); ok {
78 t.Error("a document edited since caching still hit the cache")
79 }
80 if _, ok := p.get(8, 1000); ok {
81 t.Error("a different document hit another document's entry")
82 }
83}
84
85// A cached failure is a hit, not a miss: the answer is known and it is
86// "draw nothing". Without this a document whose log will not replay is
87// laid out again on every single list render.
88func TestACachedFailureIsAHit(t *testing.T) {
89 p := newPreviews()
90 p.put(7, 1000, nil)
91 got, ok := p.get(7, 1000)
92 if !ok {
93 t.Fatal("a cached failure missed, so it will be retried on every render")
94 }
95 if got != nil {
96 t.Errorf("a cached failure returned bars: %+v", got)
97 }
98}
99
100func TestTheCacheIsBounded(t *testing.T) {
101 p := newPreviews()
102 for i := int64(0); i < maxPreviews+10; i++ {
103 p.put(i, 1, []previewBar{{X: 1}})
104 }
105 p.mu.Lock()
106 n := len(p.by)
107 p.mu.Unlock()
108 if n > maxPreviews {
109 t.Errorf("cache holds %d entries, past its %d bound", n, maxPreviews)
110 }
111}
112
113// Overflowing the bound must not empty it. This is the bug an earlier
114// version had: the map was wiped when full, and combined with
115// thumbnailBudget that left an instance with more documents than the
116// bound permanently cold — everything past the budget drew the empty
117// box on every render, for ever, because each render wiped what the
118// last one warmed.
119func TestOverflowingTheCacheKeepsItWarm(t *testing.T) {
120 p := newPreviews()
121 for i := int64(0); i < maxPreviews*2; i++ {
122 p.put(i, 1, []previewBar{{X: 1}})
123 }
124 p.mu.Lock()
125 n := len(p.by)
126 p.mu.Unlock()
127 if n < maxPreviews {
128 t.Errorf("cache holds only %d entries after overflowing — it was emptied rather than trimmed", n)
129 }
130}
131
132// Re-caching the same document must not evict anything: a list that
133// re-renders the same rows would otherwise churn the map for no reason.
134func TestReplacingAnEntryEvictsNothing(t *testing.T) {
135 p := newPreviews()
136 for i := int64(0); i < maxPreviews; i++ {
137 p.put(i, 1, []previewBar{{X: 1}})
138 }
139 for i := int64(0); i < maxPreviews; i++ {
140 p.put(i, 2, []previewBar{{X: 2}})
141 }
142 p.mu.Lock()
143 n := len(p.by)
144 p.mu.Unlock()
145 if n != maxPreviews {
146 t.Errorf("cache holds %d entries after re-caching the same documents, want %d", n, maxPreviews)
147 }
148}
149