| 1 | package www_test |
| 2 | |
| 3 | import ( |
| 4 | "io/fs" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "slices" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | "testing/fstest" |
| 11 | |
| 12 | "amadan.net/rastrillo/fiesta/internal/www" |
| 13 | ) |
| 14 | |
| 15 | // realSite is the repository's site/ directory, the site source the |
| 16 | // landing page is built from. |
| 17 | func realSite() fs.FS { |
| 18 | return os.DirFS(filepath.Join("..", "..", "site")) |
| 19 | } |
| 20 | |
| 21 | // catalogs is a site source holding one landing catalog per language, |
| 22 | // each written as Strings reads it. |
| 23 | func catalogs(t *testing.T, langs map[string]map[string]string) fstest.MapFS { |
| 24 | t.Helper() |
| 25 | m := fstest.MapFS{} |
| 26 | for lang, strs := range langs { |
| 27 | var b strings.Builder |
| 28 | b.WriteString("{\n") |
| 29 | keys := make([]string, 0, len(strs)) |
| 30 | for k := range strs { |
| 31 | keys = append(keys, k) |
| 32 | } |
| 33 | slices.Sort(keys) |
| 34 | for i, k := range keys { |
| 35 | if i > 0 { |
| 36 | b.WriteString(",\n") |
| 37 | } |
| 38 | b.WriteString(" " + quote(k) + ": " + quote(strs[k])) |
| 39 | } |
| 40 | b.WriteString("\n}\n") |
| 41 | m["landing/strings/"+lang+".json"] = &fstest.MapFile{Data: []byte(b.String())} |
| 42 | } |
| 43 | return m |
| 44 | } |
| 45 | |
| 46 | // quote is a JSON string literal, enough for the fixtures here. |
| 47 | func quote(s string) string { |
| 48 | return `"` + strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\n", `\n`).Replace(s) + `"` |
| 49 | } |
| 50 | |
| 51 | // complete is a catalog with every required key, each valued by text. |
| 52 | func complete(text func(key string) string) map[string]string { |
| 53 | out := make(map[string]string, len(www.RequiredKeys)) |
| 54 | for _, k := range www.RequiredKeys { |
| 55 | out[k] = text(k) |
| 56 | } |
| 57 | return out |
| 58 | } |
| 59 | |
| 60 | // keyItself values a key by its own name. |
| 61 | func keyItself(key string) string { return key } |
| 62 | |
| 63 | // keysOf is m's keys, sorted. |
| 64 | func keysOf(m map[string]string) []string { |
| 65 | keys := make([]string, 0, len(m)) |
| 66 | for k := range m { |
| 67 | keys = append(keys, k) |
| 68 | } |
| 69 | slices.Sort(keys) |
| 70 | return keys |
| 71 | } |
| 72 | |
| 73 | // TestEveryLanguageHasEveryKey: every site language has a catalog in |
| 74 | // the site source, and each holds exactly RequiredKeys with nothing |
| 75 | // empty. A catalog that lacks a key, adds one, leaves one empty or is |
| 76 | // not there at all is refused, and the error names what is wrong. |
| 77 | func TestEveryLanguageHasEveryKey(t *testing.T) { |
| 78 | if !slices.IsSorted(www.RequiredKeys) || len(slices.Compact(slices.Clone(www.RequiredKeys))) != len(www.RequiredKeys) { |
| 79 | t.Fatal("RequiredKeys is not sorted and unique") |
| 80 | } |
| 81 | if len(www.Languages) != 11 || www.Languages[0] != "en" { |
| 82 | t.Fatalf("Languages is %v", www.Languages) |
| 83 | } |
| 84 | for _, lang := range www.Languages { |
| 85 | got, err := www.Strings(realSite(), lang) |
| 86 | if err != nil { |
| 87 | t.Errorf("%s: %v", lang, err) |
| 88 | continue |
| 89 | } |
| 90 | if keys := keysOf(got); !slices.Equal(keys, www.RequiredKeys) { |
| 91 | t.Errorf("%s has keys %v, want RequiredKeys", lang, keys) |
| 92 | } |
| 93 | for k, v := range got { |
| 94 | if strings.TrimSpace(v) == "" { |
| 95 | t.Errorf("%s: %s is empty", lang, k) |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | short := complete(keyItself) |
| 101 | delete(short, "sample.saved") |
| 102 | _, err := www.Strings(catalogs(t, map[string]map[string]string{"en": short}), "en") |
| 103 | if err == nil || !strings.Contains(err.Error(), "sample.saved") { |
| 104 | t.Errorf("a catalog lacking sample.saved was accepted, or the error does not name it: %v", err) |
| 105 | } |
| 106 | |
| 107 | extra := complete(keyItself) |
| 108 | extra["sample.extra"] = "x" |
| 109 | _, err = www.Strings(catalogs(t, map[string]map[string]string{"en": extra}), "en") |
| 110 | if err == nil || !strings.Contains(err.Error(), "sample.extra") { |
| 111 | t.Errorf("a catalog with a key the page never reads was accepted, or the error does not name it: %v", err) |
| 112 | } |
| 113 | |
| 114 | blank := complete(keyItself) |
| 115 | blank["copied"] = " " |
| 116 | _, err = www.Strings(catalogs(t, map[string]map[string]string{"en": blank}), "en") |
| 117 | if err == nil || !strings.Contains(err.Error(), "copied") { |
| 118 | t.Errorf("a catalog with an empty copied was accepted, or the error does not name it: %v", err) |
| 119 | } |
| 120 | |
| 121 | if _, err := www.Strings(catalogs(t, nil), "en"); err == nil { |
| 122 | t.Error("a site source with no catalog at all was accepted") |
| 123 | } |
| 124 | |
| 125 | if _, err := www.Strings(catalogs(t, map[string]map[string]string{"en": complete(keyItself)}), "en"); err != nil { |
| 126 | t.Errorf("the complete control catalog was refused: %v", err) |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | // TestPaulsWordsAreVerbatim: the headline, the lede and the install |
| 131 | // line are Paul's words, byte for byte, in the English catalog. |
| 132 | func TestPaulsWordsAreVerbatim(t *testing.T) { |
| 133 | en, err := www.Strings(realSite(), "en") |
| 134 | if err != nil { |
| 135 | t.Fatal(err) |
| 136 | } |
| 137 | for key, want := range map[string]string{ |
| 138 | "headline": "Celebrate your new design system", |
| 139 | "lede": "Polished, accessible, multi-lingual, localised components for everyone.", |
| 140 | "install": "One stylesheet and done. JS optional. <25KiB gzipped.", |
| 141 | } { |
| 142 | if got := en[key]; got != want { |
| 143 | t.Errorf("%s is %q, want %q", key, got, want) |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | // TestThePlaceholdersSurviveTranslation: a {name} placeholder in an |
| 149 | // English string is in its translation too. A Spanish catalog that |
| 150 | // drops the English {n} is refused by key and placeholder; one that |
| 151 | // keeps it passes. No real string holds a placeholder today, so the |
| 152 | // fixture carries this test and the real catalogs merely pass. |
| 153 | func TestThePlaceholdersSurviveTranslation(t *testing.T) { |
| 154 | en := complete(keyItself) |
| 155 | en["sample.next.body"] = "Reminders go out on Friday. {n} guests still need a reply." |
| 156 | |
| 157 | dropped := complete(keyItself) |
| 158 | dropped["sample.next.body"] = "Los recordatorios salen el viernes. Tres invitados aún no han respondido." |
| 159 | _, err := www.Strings(catalogs(t, map[string]map[string]string{"en": en, "es": dropped}), "es") |
| 160 | if err == nil || !strings.Contains(err.Error(), "{n}") || !strings.Contains(err.Error(), "sample.next.body") { |
| 161 | t.Errorf("a Spanish catalog that drops {n} was accepted, or the error does not name the key and the placeholder: %v", err) |
| 162 | } |
| 163 | |
| 164 | kept := complete(keyItself) |
| 165 | kept["sample.next.body"] = "Los recordatorios salen el viernes. {n} invitados aún no han respondido." |
| 166 | if _, err := www.Strings(catalogs(t, map[string]map[string]string{"en": en, "es": kept}), "es"); err != nil { |
| 167 | t.Errorf("a Spanish catalog that keeps {n} was refused: %v", err) |
| 168 | } |
| 169 | |
| 170 | for _, lang := range www.Languages { |
| 171 | if _, err := www.Strings(realSite(), lang); err != nil { |
| 172 | t.Errorf("%s: %v", lang, err) |
| 173 | } |
| 174 | } |
| 175 | } |
| 176 | |