| 1 | //go:build browser |
| 2 | |
| 3 | package check |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "sort" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | "time" |
| 11 | |
| 12 | "github.com/chromedp/chromedp" |
| 13 | |
| 14 | "amadan.net/rastrillo/fiesta" |
| 15 | ) |
| 16 | |
| 17 | // appOverrides is an app stylesheet that rebrands by setting the |
| 18 | // semantic tokens on :root, the way the design system's guide says to: |
| 19 | // the accent and its strong and soft forms, the two surfaces, the two |
| 20 | // lines and the negative tone. Every value is far from what plain and |
| 21 | // signal declare in either scheme, so a token that kept a stock colour |
| 22 | // instead of following its semantic token shows as a pixel difference. |
| 23 | const appOverrides = `<style>:root { |
| 24 | --rst-accent: light-dark(#b4005a, #ff7ab8); |
| 25 | --rst-accent-strong: light-dark(#7d003e, #ffb3d6); |
| 26 | --rst-accent-soft: light-dark(#ffe1ee, #3b1026); |
| 27 | --rst-surface: light-dark(#fff7e3, #1d1a11); |
| 28 | --rst-surface-2: light-dark(#f2e7cb, #2a251a); |
| 29 | --rst-line: light-dark(#d6c184, #514830); |
| 30 | --rst-line-strong: light-dark(#86753a, #ab9c5c); |
| 31 | --rst-tone-negative-fg: light-dark(#6a1b9a, #d7a4ff); |
| 32 | }</style>` |
| 33 | |
| 34 | // TestFlatThemesFollowAnAppsOverrides is the promise to an app that |
| 35 | // customised: under the v0.28 pair, an app's override of --rst-accent |
| 36 | // reached a plain button's hovered text and border, --rst-accent-soft |
| 37 | // reached the hover tints and the current nav item, --rst-surface the |
| 38 | // control faces and the switch's knob, --rst-surface-2 the linked rows, |
| 39 | // --rst-line the card's hairline, --rst-line-strong the control borders, |
| 40 | // the off track and the focused field's edge, and --rst-tone-negative-fg |
| 41 | // the danger button, because tokens.css read those tokens at those |
| 42 | // sites. The new tokens read a new token at each site, and plain and |
| 43 | // signal declared each one as a copy of the stock colour, so the |
| 44 | // override stopped reaching them while the primary button still |
| 45 | // followed it. This is the matrix again, with appOverrides on both |
| 46 | // pages: every crop under the new pair must match the old pair's, |
| 47 | // for plain and signal, in both schemes. |
| 48 | func TestFlatThemesFollowAnAppsOverrides(t *testing.T) { |
| 49 | oldTokens := legacyFile(t, "tokens-v028.css") |
| 50 | for _, theme := range []string{"plain", "signal"} { |
| 51 | t.Run(theme, func(t *testing.T) { |
| 52 | oldTheme := legacyFile(t, theme+"-v028.css") |
| 53 | newTheme, ok := fiesta.ThemeCSS(theme) |
| 54 | if !ok { |
| 55 | t.Fatalf("no theme %q", theme) |
| 56 | } |
| 57 | rig := legacyRig(t, oldTokens, oldTheme, fiesta.TokensCSS(), newTheme, false, appOverrides) |
| 58 | ctx, cancel := context.WithTimeout(rig.Context(), 300*time.Second) |
| 59 | defer cancel() |
| 60 | for _, scheme := range []string{"light", "dark"} { |
| 61 | old := matrixShots(t, ctx, rig.Origin, "/old/", scheme) |
| 62 | now := matrixShots(t, ctx, rig.Origin, "/new/", scheme) |
| 63 | // The override reached the page: the primary button is |
| 64 | // the app's accent, or the comparison is between two |
| 65 | // stock renders and proves nothing. |
| 66 | want := "rgb(180, 0, 90)" |
| 67 | if scheme == "dark" { |
| 68 | want = "rgb(255, 122, 184)" |
| 69 | } |
| 70 | if got := computed(t, ctx, "#b-primary", "background-color"); got != want { |
| 71 | t.Fatalf("%s: the primary button is %s, want the app's accent %s; the override did not reach the page", scheme, got, want) |
| 72 | } |
| 73 | keys := make([]string, 0, len(old)) |
| 74 | for key := range old { |
| 75 | keys = append(keys, key) |
| 76 | } |
| 77 | sort.Strings(keys) |
| 78 | // 40 per channel, not the matrix's 24: the only pixels |
| 79 | // left differing after the aliases are the rounded |
| 80 | // corners the matrix comment describes, 44 to 88 |
| 81 | // per card, measured at up to 32 with this hairline |
| 82 | // (#d6c184 on #fff7e3) where the stock pair's measured |
| 83 | // 9 to 16. A token that kept a stock colour is a whole |
| 84 | // face or border off, which the 1% count rule catches: |
| 85 | // before the aliases, 70 of 95 crops failed in light. |
| 86 | var differ []string |
| 87 | for _, key := range keys { |
| 88 | if ok, why := samePixelsWithin(t, old[key], now[key], 40); !ok { |
| 89 | differ = append(differ, key+": "+why) |
| 90 | } |
| 91 | } |
| 92 | if len(differ) > 0 { |
| 93 | t.Errorf("%s: with the app's overrides, %d of %d crops render differently under the new pair than under the old:\n %s", scheme, len(differ), len(keys), strings.Join(differ, "\n ")) |
| 94 | } |
| 95 | } |
| 96 | // Keep the page open for the fatal above: a rig closes with |
| 97 | // the test, and a navigation after it is the next scheme's. |
| 98 | mustRun(t, ctx, chromedp.Evaluate(`true`, nil)) |
| 99 | }) |
| 100 | } |
| 101 | } |
| 102 | |