1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
package mobile
import (
"encoding/json"
"strings"
"testing"
)
func decodeDay(t *testing.T, s string) map[string]any {
t.Helper()
var m map[string]any
if err := json.Unmarshal([]byte(s), &m); err != nil {
t.Fatalf("Day returned invalid JSON: %v\n%s", err, s)
}
return m
}
func TestDayCarriesLocalisedRank(t *testing.T) {
pl := decodeDay(t, Day("2026-08-01", "of", "vul", "pl"))
if pl["rank"] != "memorial" {
t.Errorf("pl rank = %v, want memorial", pl["rank"])
}
if pl["rank_label"] != "wspomnienie obowiązkowe" {
t.Errorf("pl rank_label = %v", pl["rank_label"])
}
en := decodeDay(t, Day("2026-08-01", "of", "vul", "en"))
if en["rank_label"] != "memorial" {
t.Errorf("en rank_label = %v", en["rank_label"])
}
}
// The app shows the colour as a swatch and never as a word, so the raw key must
// be present and no localised label may be. This test exists so the app cannot
// quietly start depending on a colour word.
func TestDayHasColourKeyButNoColourLabel(t *testing.T) {
m := decodeDay(t, Day("2026-08-01", "of", "vul", "pl"))
if m["colour"] != "white" {
t.Errorf("colour = %v, want white", m["colour"])
}
if _, present := m["colour_label"]; present {
t.Error("colour_label must not be returned: the app draws a swatch")
}
}
func decodeDays(t *testing.T, s string) []map[string]any {
t.Helper()
var a []map[string]any
if err := json.Unmarshal([]byte(s), &a); err != nil {
t.Fatalf("Days returned invalid JSON: %v\n%s", err, s)
}
return a
}
func TestDaysReturnsConsecutiveDates(t *testing.T) {
a := decodeDays(t, Days("2026-07-27", 7, "of", "pl"))
if len(a) != 7 {
t.Fatalf("got %d elements, want 7", len(a))
}
want := []string{"2026-07-27", "2026-07-28", "2026-07-29", "2026-07-30",
"2026-07-31", "2026-08-01", "2026-08-02"}
for i, w := range want {
if a[i]["date"] != w {
t.Errorf("element %d date = %v, want %v", i, a[i]["date"], w)
}
}
}
// Days must agree with Day about the same date -- it is a cheaper projection of
// the same computation, not a second implementation.
func TestDaysAgreesWithDay(t *testing.T) {
one := decodeDay(t, Day("2026-08-01", "of", "vul", "pl"))
week := decodeDays(t, Days("2026-08-01", 1, "of", "pl"))
if len(week) != 1 {
t.Fatalf("got %d elements, want 1", len(week))
}
row := week[0]
for _, k := range []string{"name", "colour", "rank", "rank_label"} {
if row[k] != one[k] {
t.Errorf("%s: Days = %v, Day = %v", k, row[k], one[k])
}
}
parts := row["parts"].([]any)
readings := one["readings"].([]any)
if len(parts) != len(readings) {
t.Fatalf("parts = %d, readings = %d", len(parts), len(readings))
}
for i := range parts {
p := parts[i].(map[string]any)
r := readings[i].(map[string]any)
if p["part"] != r["part"] || p["citation"] != r["citation"] {
t.Errorf("element %d: Days %v/%v vs Day %v/%v",
i, p["part"], p["citation"], r["part"], r["citation"])
}
}
}
// No reading text may cross the boundary: that is the whole point of Days.
func TestDaysCarriesNoReadingText(t *testing.T) {
s := Days("2026-08-01", 7, "of", "pl")
if strings.Contains(s, `"text"`) {
t.Error("Days must not return reading text")
}
if strings.Contains(s, "colour_label") {
t.Error("Days must not return a colour label")
}
}
func TestDaysRejectsBadInput(t *testing.T) {
for _, c := range []struct {
start string
count int
}{
{"not-a-date", 7}, {"2026-08-01", 0}, {"2026-08-01", -1}, {"2026-08-01", 400},
} {
if got := Days(c.start, c.count, "of", "pl"); got != "[]" {
t.Errorf("Days(%q, %d) = %s, want []", c.start, c.count, got)
}
}
}
func BenchmarkDaysWeek(b *testing.B) {
for i := 0; i < b.N; i++ {
Days("2026-07-27", 7, "of", "pl")
}
}
|