| 1 | package check |
| 2 | |
| 3 | import ( |
| 4 | "regexp" |
| 5 | "slices" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "amadan.net/rastrillo/fiesta" |
| 10 | ) |
| 11 | |
| 12 | // The rake line is retired (design doc §6-v2.2), and "retired" has to |
| 13 | // mean one place. tokens.css is the single stylesheet every rastrillo |
| 14 | // page links, so a pseudo-element declared there once, as content:none, |
| 15 | // is a pseudo-element that draws nothing anywhere — provided nothing |
| 16 | // else in the shipped CSS declares one too. |
| 17 | // |
| 18 | // That last clause is what this test is for. A theme is colour, type |
| 19 | // and shape; a theme that grew a [rst-page-header]::after would put the |
| 20 | // flourish back on one theme and nowhere else, which is both the |
| 21 | // hardest version of this bug to see and the easiest one to write. |
| 22 | // |
| 23 | // The browser half of the claim — that content:none lays out no box, in |
| 24 | // every theme, in both schemes, in ar as well as en — is |
| 25 | // header_rule_browser_test.go's. This is the cheap half, and it runs in |
| 26 | // the ordinary suite. |
| 27 | func TestTheRakeLineIsDeclaredRetiredExactlyOnce(t *testing.T) { |
| 28 | // The rule as it now stands, spelled in both markup vocabularies |
| 29 | // because tokens.css pairs a class selector with every attribute one |
| 30 | // until stage 3 of the migration. |
| 31 | // |
| 32 | // Read as rules, not text: a copy in a comment counted as the one |
| 33 | // declaration, and a pattern that stopped at a comma missed a |
| 34 | // pseudo-element after a selector list inside :is(). |
| 35 | rules := leafRules(t, string(fiesta.TokensCSS())) |
| 36 | const retired = ".rst-page-header::after, [rst-page-header]::after" |
| 37 | got := 0 |
| 38 | for _, r := range rules { |
| 39 | if r.selector == retired && slices.Equal(r.decls, [][3]string{{"content", "none", ""}}) { |
| 40 | got++ |
| 41 | } |
| 42 | } |
| 43 | if got != 1 { |
| 44 | t.Errorf("tokens.css declares the retired header pseudo-element %d times as\n%s { content: none; }\nwant exactly 1 — the whole point of retiring it in one file is that there is one place to look", got, retired) |
| 45 | } |
| 46 | |
| 47 | // Nothing else may draw one. The reading is deliberately loose (any |
| 48 | // selector naming a page header, any pseudo-element after it, : or |
| 49 | // ::) so a flourish reintroduced under ::before, or on the titles, |
| 50 | // is caught as readily as the exact rule that was removed. |
| 51 | headerPseudo := regexp.MustCompile(`(\.rst-page-header|\[rst-page-header).*::?(after|before)`) |
| 52 | for _, r := range rules { |
| 53 | if r.selector == retired { |
| 54 | continue |
| 55 | } |
| 56 | for _, sel := range splitSelectorList(r.selector) { |
| 57 | if headerPseudo.MatchString(sel) { |
| 58 | t.Errorf("tokens.css draws %q on the page header; the header's one decoration is its rule, and that lives on the theme axis as --rst-header-rule", sel) |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | for _, name := range fiesta.ThemeNames() { |
| 63 | theme, ok := fiesta.ThemeCSS(name) |
| 64 | if !ok { |
| 65 | t.Fatalf("fiesta.ThemeCSS(%q) missing", name) |
| 66 | } |
| 67 | // A theme file has no selectors at all — it is one :root block |
| 68 | // and two toggle rules — so any mention of the header at all is |
| 69 | // the beginning of this bug. |
| 70 | if strings.Contains(string(theme), "page-header") { |
| 71 | t.Errorf("themes/%s.css mentions the page header. A theme sets --rst-header-rule and stops there: structure is tokens.css's, and a theme that styles a component has started a second stylesheet", name) |
| 72 | } |
| 73 | } |
| 74 | } |
| 75 | |