| 1 | package docs |
| 2 | |
| 3 | import ( |
| 4 | "io/fs" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | "unicode" |
| 8 | ) |
| 9 | |
| 10 | // TestOurTemplatesUseTheAttributeGrammar holds the markup constraint |
| 11 | // at the source, not the rendered page: every HTML file this app |
| 12 | // authors under views/ (embedded as viewsFS) must write the bare |
| 13 | // rst-* attribute grammar (<div rst-box>), never class="rst-*". |
| 14 | // |
| 15 | // This is deliberately a package-internal test, not a docs_test one, |
| 16 | // because it needs viewsFS itself — walking our own template sources |
| 17 | // rather than a rendered response is the whole point: rastrillo's own |
| 18 | // partials and shell emit class="rst-*", and that is their output, |
| 19 | // not this app's markup, so it must never appear in this walk in the |
| 20 | // first place, rather than being screened out after the fact. |
| 21 | // |
| 22 | // This replaces what used to be a rendered-page assertion |
| 23 | // ("no class=\"rst-\" anywhere in the response body"), which could |
| 24 | // not tell the two apart: rastrillo's column shell hard-codes |
| 25 | // class="rst-skip" and class="rst-page" outside any block this app |
| 26 | // can override, so that blanket check failed on every page this app |
| 27 | // serves regardless of what its own templates wrote — see git history |
| 28 | // on this file for the reverted fix (rewriting rastrillo's shell |
| 29 | // bytes at parse time) and why it was wrong: a string substitution |
| 30 | // over a dependency's markup fails silently the day rastrillo changes |
| 31 | // those two spots, which it has already said it will before 1.0. |
| 32 | // Checking our own sources instead needs no such rewrite, is correct |
| 33 | // (rastrillo's vendored output is not ours to hold to our own |
| 34 | // convention), and stays stable across a rastrillo upgrade that |
| 35 | // changes its shell's own spelling, because it does not depend on |
| 36 | // rastrillo's internal template text at all. |
| 37 | func TestOurTemplatesUseTheAttributeGrammar(t *testing.T) { |
| 38 | err := fs.WalkDir(viewsFS, ".", func(path string, d fs.DirEntry, err error) error { |
| 39 | if err != nil { |
| 40 | return err |
| 41 | } |
| 42 | if d.IsDir() { |
| 43 | return nil |
| 44 | } |
| 45 | b, err := fs.ReadFile(viewsFS, path) |
| 46 | if err != nil { |
| 47 | return err |
| 48 | } |
| 49 | if strings.Contains(string(b), `class="rst-`) { |
| 50 | t.Errorf("%s: class vocabulary found — this app writes the attribute grammar (Global Constraints)", path) |
| 51 | } |
| 52 | return nil |
| 53 | }) |
| 54 | if err != nil { |
| 55 | t.Fatalf("walk viewsFS: %v", err) |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | // The catalog is written in US English, per Paul's ruling on 2026-09-03. |
| 60 | // It reached this rule by shipping the counterexample: the alignment |
| 61 | // buttons read "Align centre" while the command, the CSS class and the |
| 62 | // keyword `text-align` itself all say `center`, so the label disagreed |
| 63 | // with every other name for the same thing — and with the person who |
| 64 | // asked for the feature. |
| 65 | // |
| 66 | // The real answer is a locale on the profile offering both spellings |
| 67 | // (discussion 31), which this test does not pre-empt: when that lands, |
| 68 | // en-GB is a second table and this guard moves to the en-US one. Until |
| 69 | // then there is exactly one table, and nothing else was checking which |
| 70 | // English it was written in. |
| 71 | // |
| 72 | // It is deliberately a word list rather than a dictionary. A general |
| 73 | // speller is a dependency and a source of false positives on product |
| 74 | // nouns; the pairs below are the ones a UK-based writer actually |
| 75 | // reaches for, and a miss costs one wrong word that the next addition |
| 76 | // to the list catches. Each entry is a British form whose US form |
| 77 | // differs — never a word that is merely uncommon. |
| 78 | func TestTheCatalogIsWrittenInUSEnglish(t *testing.T) { |
| 79 | // British form -> the US form it should have been. The value is |
| 80 | // never used to rewrite anything; it is there so a failure tells |
| 81 | // the reader what to write instead of only that something is |
| 82 | // wrong. Derived forms are listed rather than stemmed, because a |
| 83 | // suffix rule that turns "premise" into a finding is worse than |
| 84 | // a list that misses "colourless" until someone writes it. |
| 85 | british := map[string]string{ |
| 86 | "centre": "center", |
| 87 | "centred": "centered", |
| 88 | "centres": "centers", |
| 89 | "colour": "color", |
| 90 | "colours": "colors", |
| 91 | "coloured": "colored", |
| 92 | "behaviour": "behavior", |
| 93 | "behaviours": "behaviors", |
| 94 | "licence": "license", |
| 95 | "defence": "defense", |
| 96 | "grey": "gray", |
| 97 | "catalogue": "catalog", |
| 98 | "dialogue": "dialog", |
| 99 | "favourite": "favorite", |
| 100 | "favourites": "favorites", |
| 101 | "cancelled": "canceled", |
| 102 | "cancelling": "canceling", |
| 103 | "customise": "customize", |
| 104 | "customised": "customized", |
| 105 | "organise": "organize", |
| 106 | "organised": "organized", |
| 107 | "organising": "organizing", |
| 108 | "recognise": "recognize", |
| 109 | "recognised": "recognized", |
| 110 | "initialise": "initialize", |
| 111 | "analyse": "analyze", |
| 112 | "analysed": "analyzed", |
| 113 | "apologise": "apologize", |
| 114 | "programme": "program", |
| 115 | "whilst": "while", |
| 116 | "towards": "toward", |
| 117 | } |
| 118 | |
| 119 | // Words, lowercased, with anything that is not a letter treated as |
| 120 | // a separator: the catalog's values carry apostrophes, ellipses and |
| 121 | // ordinary punctuation, and "couldn't" has to split into "couldn" |
| 122 | // and "t" rather than leaving a word glued to a mark and invisible |
| 123 | // to an exact-match lookup. |
| 124 | words := func(s string) []string { |
| 125 | return strings.FieldsFunc(strings.ToLower(s), func(r rune) bool { |
| 126 | return !unicode.IsLetter(r) |
| 127 | }) |
| 128 | } |
| 129 | |
| 130 | for key, value := range Catalog { |
| 131 | for _, w := range words(value) { |
| 132 | if us, ok := british[w]; ok { |
| 133 | t.Errorf("%s: %q is British English — write %q (this catalog is en-US; see discussion 31)", key, w, us) |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | |