rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1package check
2
3import (
4 "slices"
5 "strings"
6 "testing"
7
8 "amadan.net/rastrillo/fiesta"
9)
10
11// The two media blocks that undo the depth language where it would do
12// harm. Each flattens the axis at html:root, so no
13// state rule can keep a shadow, a gradient, a tint or a drop; and each
14// names the components that need a border or a system colour the
15// tokens cannot give them. A token or a component left out keeps a
16// shadow forced colours would strip half of, or a tint a printer would
17// drop. The summary's edge is in the flat list too: it is a ring drawn
18// in the shadow by rules at (0,4,1) and up, so a box-shadow: none at
19// the summary's own selector never reaches it, and the token is the
20// one thing that wins in every state.
21func TestForcedColoursAndPrintFlattenTheAxisAndCoverEveryComponent(t *testing.T) {
22 css := string(fiesta.TokensCSS())
23 flat := []string{"--rst-depth-hl: transparent", "--rst-depth-contact: transparent", "--rst-depth-ambient: transparent", "--rst-depth-face: 0", "--rst-press-drop: 0px",
24 "--rst-sheen-fill: transparent", "--rst-sheen-plain: transparent", "--rst-light-wash: transparent", "--rst-light-rim: var(--rst-line)",
25 "--rst-press-wash: 0", "--rst-press-spot: 0", "--rst-press-wash-plain: 0", "--rst-press-spot-plain: 0",
26 "--rst-tint-hover: var(--rst-surface)", "--rst-tint-press: var(--rst-surface)", "--rst-tint-tap: transparent", "--rst-row-tint-hover: var(--rst-surface)", "--rst-row-tint-press: var(--rst-surface)", "--rst-focus-halo: transparent",
27 "--rst-summary-edge: transparent", "--rst-summary-edge-hover: transparent"}
28 forced := append(flat, "html:root {", "[rst-btn]", "[rst-box]", "[rst-card]", "[rst-stats]", "[rst-list]", "[rst-input]", "[rst-textarea]", "[rst-search]", "[rst-dropdown]", "[rst-switch-track]", "[rst-switch-track]::after",
29 "[rst-shell-sidebar] [rst-shell-nav] a[aria-current]", "[rst-shell-console] [rst-shell-nav] a[aria-current]", "[rst-shell-topbar] [rst-shell-tail] > [rst-shell-nav] a[aria-current]", "[rst-dropdown-menu] a[aria-current]", "[rst-pagination] [aria-current=\"page\"]", "[rst-seg-tabs] a[aria-current]", `[rst-seg-tabs] > button[aria-pressed="true"]`, "[rst-side-nav] a[aria-current]",
30 `[rst-shell-sidebar][rst-shell-sidebar~="index"] [rst-shell-nav] > a[aria-current]:hover`, `[rst-shell-console][rst-shell-console~="index"] [rst-shell-nav] > a[aria-current]:active`,
31 "ButtonBorder", "CanvasText", "ButtonText", "Highlight", "HighlightText", "box-shadow: none")
32 print := append(flat, "html:root {", "--rst-accent-fill: transparent", "--rst-accent-fill-hover: transparent", "--rst-on-accent-fill: var(--rst-text)", "--rst-danger-fill: transparent", "--rst-on-danger-fill-hover: var(--rst-text)", "--rst-danger-hover-filter: none", "--rst-control-face-hover: transparent", "--rst-control-text-hover: var(--rst-text)", "--rst-knob: var(--rst-text)",
33 "[rst-btn]", "[rst-box]", "[rst-card]", "[rst-stats]", "[rst-list]", "[rst-input]", "[rst-textarea]", "[rst-search]", "[rst-dropdown]", "[rst-switch-track]", "[rst-switch-track]::after", "[rst-switch]:has(input:checked) [rst-switch-track]", "[rst-tblock] > [rst-tblock-head]:has(> input:checked) > [rst-switch-track]", "[rst-seg-tabs] a[aria-current]", `[rst-seg-tabs] > button[aria-pressed="true"]`, "[rst-side-nav] a[aria-current]", "[rst-pagination] [aria-current=\"page\"]", "[rst-dropdown-menu]", "[rst-row-menu-panel]",
34 "[rst-shell-sidebar] [rst-shell-nav] a[aria-current]", "[rst-shell-console] [rst-shell-nav] a[aria-current]", "var(--rst-line-strong)", "background-image: none", "box-shadow: none", "outline: 1px solid var(--rst-line-strong)")
35 // The blocks are read as rules, the last top-level block of each
36 // query being the one at the end of the file. A want is a rule's
37 // selector when it ends in " {", a declaration when it holds ": ",
38 // a selector a rule names when it starts with [ or ., a var() a value
39 // reads, and otherwise a word a value holds. Read as text, any of them was satisfied by a
40 // comment, and the block was cut by counting braces.
41 rules := leafRules(t, css)
42 printTop := -1
43 for _, block := range []struct {
44 context string
45 want []string
46 }{
47 {"@media (forced-colors: active)", forced},
48 {"@media print", print},
49 } {
50 last := -1
51 for _, r := range rules {
52 if r.context == block.context {
53 last = max(last, r.top)
54 }
55 }
56 if last < 0 {
57 t.Fatalf("tokens.css has no %s block at the end of the file", block.context)
58 }
59 if block.context == "@media print" {
60 printTop = last
61 }
62 var in []leafRule
63 for _, r := range rules {
64 if r.top == last {
65 in = append(in, r)
66 }
67 }
68 for _, w := range block.want {
69 if !blockCarries(in, w) {
70 t.Errorf("the %s block does not carry %q", block.context, w)
71 }
72 }
73 for _, r := range in {
74 for _, d := range r.decls {
75 if d[2] != "" {
76 t.Errorf("the %s block uses !important (C11)", block.context)
77 }
78 }
79 }
80 }
81 // Nothing that paints a button, a card or a nav item may follow the
82 // print block: its rules win by order.
83 for _, sel := range []string{`[rst-btn~="primary"]`, `[rst-shell-nav] a[aria-current]`, `[rst-box]`} {
84 for _, r := range rules {
85 if r.top > printTop && slices.ContainsFunc(splitSelectorList(r.selector), func(s string) bool { return strings.Contains(s, sel) }) {
86 t.Errorf("%q is styled after the print block, by %s; the print rules rely on source order", sel, r.selector)
87 }
88 }
89 }
90}
91
92// blockCarries reports whether the rules of one block carry want, read
93// as TestForcedColoursAndPrintFlattenTheAxisAndCoverEveryComponent
94// says.
95func blockCarries(rules []leafRule, want string) bool {
96 return slices.ContainsFunc(rules, func(r leafRule) bool {
97 sels := splitSelectorList(r.selector)
98 switch {
99 case strings.HasSuffix(want, " {"):
100 return slices.Contains(sels, strings.TrimSuffix(want, " {"))
101 case strings.Contains(want, ": "):
102 prop, value, _ := strings.Cut(want, ": ")
103 return slices.Contains(r.decls, [3]string{prop, value, ""})
104 case strings.HasPrefix(want, "[") || strings.HasPrefix(want, "."):
105 return slices.ContainsFunc(sels, func(s string) bool { return strings.Contains(s, want) })
106 case strings.HasPrefix(want, "var("):
107 name := strings.TrimSuffix(strings.TrimPrefix(want, "var("), ")")
108 return slices.ContainsFunc(r.decls, func(d [3]string) bool {
109 return slices.ContainsFunc(varRefs(d[1]), func(ref varRef) bool { return ref.name == name })
110 })
111 }
112 return slices.ContainsFunc(r.decls, func(d [3]string) bool {
113 return slices.Contains(strings.FieldsFunc(d[1], func(c rune) bool { return strings.ContainsRune(" ,()/", c) }), want)
114 })
115 })
116}
117