diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-02 23:43:08 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-03 09:54:39 -0300 |
| commit | 5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e (patch) | |
| tree | f12f4b700fff4ff6f7e818d2a542672ba0b8d76e /vrsite/themes_test.go | |
| parent | a9263daee9413c5eff3c1f1bebcd224442fd8685 (diff) | |
| download | notevi-5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e.tar.gz notevi-5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e.zip | |
rebrand to notevi: one CLI over the jj sidecar
vr and vrsite become a single binary. vrsite/ folds into a web package in
one module (0x4200.cafe/notevi); "notevi web" serves and exports exactly
what vrsite did, and "notevi read/grep/note/query" is unchanged.
The sidecar is renamed with it: notevi_log, notevi-log.jsonl, and the
description "private: notevi log". The pre-rebrand names are still
recognized, so an old repository opens and reads; it is renamed in place
on the first write, or up front with "notevi migrate DIR...".
That rename cannot be a single mv inside jj run. jj only auto-tracks a
*new* file in the run working copy below a size limit it does not take
from the command line, so writing a whole log under a name the change has
never held is silently dropped while jj reports success. ensureLogFile
creates the file empty first and lets every later byte be a modification
of a tracked file, which snapshots at any size; that also fixes the same
latent bug when importing a large legacy vr-log.jsonl.
Adds a bem-te-vi mark (favicon and nav brand) and a README.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'vrsite/themes_test.go')
| -rw-r--r-- | vrsite/themes_test.go | 294 |
1 files changed, 0 insertions, 294 deletions
diff --git a/vrsite/themes_test.go b/vrsite/themes_test.go deleted file mode 100644 index 4ef1cca..0000000 --- a/vrsite/themes_test.go +++ /dev/null @@ -1,294 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -// everything a helix theme reader has to survive: quoted scope keys, a -// palette, one of the terminal color names, modifiers, and an inline table -// broken over lines the way a formatter leaves it -const helixSrc = ` -# a comment -"ui.background" = { bg = "base" } -"ui.text" = { fg = "text" } -"ui.linenr" = { fg = "muted" } -"ui.statusline" = { fg = "text", bg = "surface" } -"ui.cursorline.primary" = { bg = "highlight" } -"ui.menu.selected" = { fg = "base", bg = "rose" } -"ui.window" = { fg = "muted" } -comment = { fg = "muted", modifiers = ["italic"] } -keyword = { fg = "pine", modifiers = [ - "bold", -] } -string = "gold" -function = { fg = "rose" } -"constant.numeric" = { fg = "iris" } -"variable.other.member" = { fg = "foam" } -"punctuation" = { fg = "muted" } -warning = { fg = "gold" } -type = "white" - -[palette] -base = "#191724" -text = "#e0def4" -muted = "#6e6a86" -surface = "#1f1d2e" -highlight = "#21202e" -pine = "#31748f" -gold = "#f6c177" -rose = "#ebbcba" -iris = "#c4a7e7" -foam = "#9ccfd8" -` - -func writeTheme(t *testing.T, dir, name, body string) string { - t.Helper() - if err := os.MkdirAll(dir, 0o755); err != nil { - t.Fatal(err) - } - path := filepath.Join(dir, name) - if err := os.WriteFile(path, []byte(body), 0o644); err != nil { - t.Fatal(err) - } - return path -} - -func TestHelixTheme(t *testing.T) { - dir := t.TempDir() - path := writeTheme(t, dir, "rose_pine.toml", helixSrc) - th := loadHelixTheme(path, []string{dir}) - if th == nil { - t.Fatal("theme did not load") - } - for _, tc := range []struct{ what, got, want string }{ - {"id", th.ID, "helix/rose-pine"}, - {"source", th.Source, "helix"}, - {"name", th.Name, "rose_pine"}, - {"appearance", th.Appearance, "dark"}, - {"bg", th.Bg, "#191724"}, - {"fg", th.Fg, "#e0def4"}, - {"panel", th.Panel, "#1f1d2e"}, - {"line numbers", th.LineNo, "#6e6a86"}, - {"border", th.Border, "#6e6a86"}, - {"accent", th.Accent, "#ebbcba"}, // function - {"note", th.Note, "#f6c177"}, // warning - {"hover", th.Hover, "#21202e"}, // cursorline, not the loud menu row - {"muted", th.Muted, "#6e6a86"}, // comment - } { - if tc.got != tc.want { - t.Errorf("%s = %q, want %q", tc.what, tc.got, tc.want) - } - } - // syntax keys the site paints with, spelled the way helix spells them - for _, tc := range []struct{ key, want string }{ - {"comment", "#6e6a86"}, - {"keyword", "#31748f"}, - {"string", "#f6c177"}, - {"type", "#e5e5e5"}, // a terminal color name, not a palette one - {"number", "#c4a7e7"}, // zed's name for constant.numeric - {"property", "#9ccfd8"}, // zed's name for variable.other.member - {"punctuation.bracket", "#6e6a86"}, // falls back along its dots - } { - if got := th.syn(tc.key); got != tc.want { - t.Errorf("syntax %s = %q, want %q", tc.key, got, tc.want) - } - } - if s := th.Syntax["comment"]; s.FontStyle == nil || *s.FontStyle != "italic" { - t.Errorf("comment is not italic: %+v", s) - } - if s := th.Syntax["keyword"]; s.FontWeight == nil || *s.FontWeight != 700 { - t.Errorf("keyword is not bold: %+v", s) - } - // agent colors: from the code's own colors, and none of them invisible - if len(th.Players) < 3 { - t.Fatalf("agent colors = %v, want at least 3", th.Players) - } - for _, p := range th.Players { - if colorDist(p, hex6(th.Bg)) <= 60 || colorDist(p, hex6(th.Fg)) <= 60 { - t.Errorf("agent color %s vanishes against the theme", p) - } - } -} - -// A child theme restating only the palette is how helix ships most of its -// families: the parent's scopes have to resolve against the child's colors. -func TestHelixInherits(t *testing.T) { - dir := t.TempDir() - writeTheme(t, dir, "parent.toml", helixSrc) - path := writeTheme(t, dir, "child.toml", ` -inherits = "parent" -comment = { fg = "muted" } - -[palette] -base = "#eff1f5" -text = "#4c4f69" -muted = "#7c7f93" -surface = "#e6e9ef" -highlight = "#dce0e8" -pine = "#1e66f5" -gold = "#df8e1d" -rose = "#ea76cb" -iris = "#8839ef" -foam = "#04a5e5" -`) - th := loadHelixTheme(path, []string{dir}) - if th == nil { - t.Fatal("child theme did not load") - } - if th.Bg != "#eff1f5" || th.Fg != "#4c4f69" { - t.Errorf("inherited scopes kept the parent's palette: bg=%s fg=%s", th.Bg, th.Fg) - } - if th.Appearance != "light" { - t.Errorf("appearance = %q, want light for a pale background", th.Appearance) - } - if got := th.syn("keyword"); got != "#1e66f5" { - t.Errorf("inherited keyword = %q, want the child's pine", got) - } - // the child's own entry wins over the parent's, modifiers and all - if s := th.Syntax["comment"]; s.FontStyle != nil { - t.Errorf("child comment kept the parent's italics: %+v", s) - } -} - -// A theme that names almost nothing still has to dress a whole page. -func TestFillClosesTheGaps(t *testing.T) { - th := &Theme{Bg: "#101010", Fg: "#f0f0f0", Syntax: map[string]SyntaxStyle{ - "function": {Color: "#88c0d0"}, - }} - th.fill() - for _, tc := range []struct{ what, got string }{ - {"panel", th.Panel}, {"border", th.Border}, {"line numbers", th.LineNo}, - {"muted", th.Muted}, {"accent", th.Accent}, {"note", th.Note}, {"hover", th.Hover}, - } { - if okColor(tc.got) == "" { - t.Errorf("%s = %q, want a color", tc.what, tc.got) - } - } - if th.Accent != "#88c0d0" { - t.Errorf("accent = %q, want the theme's own function color", th.Accent) - } - if th.Panel == th.Bg || th.Hover == th.Bg { - t.Errorf("panel %q and hover %q must lift off the background %q", th.Panel, th.Hover, th.Bg) - } - // a panel the text would be lost in is not kept - lost := &Theme{Bg: "#f7f3ee", Fg: "#605a52", Panel: "#605a52"} - lost.fill() - if lost.Panel == "#605a52" { - t.Error("kept a panel the foreground disappears into") - } -} - -func TestZedFamily(t *testing.T) { - fam, err := parseZedFamily([]byte(`{"name":"Fake","themes":[ - {"name":"Fake Dark","appearance":"dark","style":{ - "editor.background":"#101010","editor.foreground":"#e0e0e0", - "text.accent":"0f4b6e","warning":"#e0af68", - "players":[{"cursor":"#7aa2f7"},{"cursor":"#9ece6a"}], - "syntax":{"keyword":{"color":"#bb9af7","font_style":"italic"}, - "string":{"color":"9ece6a"}}}}, - {"name":"Fake Light","appearance":"light","style":{ - "editor.background":"#fafafa","editor.foreground":"#383a42"}}]}`)) - if err != nil { - t.Fatal(err) - } - if len(fam) != 2 { - t.Fatalf("themes = %d, want 2", len(fam)) - } - dark, light := fam[0], fam[1] - if dark.ID != "zed/fake-dark" || light.ID != "zed/fake-light" { - t.Errorf("ids = %q, %q", dark.ID, light.ID) - } - // a theme in the wild does drop the odd "#": what is not a color is - // treated as unsaid, and filled in rather than emitted as nonsense - if dark.Accent == "0f4b6e" { - t.Error("kept a color with no #") - } - if _, ok := dark.Syntax["string"]; ok { - t.Error("kept a syntax color with no #") - } - if dark.syn("keyword") != "#bb9af7" { - t.Errorf("keyword = %q", dark.syn("keyword")) - } - if len(dark.Players) < 2 || dark.Players[0] != "#7aa2f7" { - t.Errorf("agent colors = %v, want the player palette first", dark.Players) - } - // nothing to work from but bg and fg, and still a whole theme - if okColor(light.Muted) == "" || okColor(light.Hover) == "" { - t.Errorf("light theme left gaps: %+v", light) - } -} - -// The picker's two files: a block per theme, and a menu that names them. -func TestThemeFiles(t *testing.T) { - dark, light := loadTheme("", "", "dark"), loadTheme("", "", "light") - helix := loadHelixTheme(writeTheme(t, t.TempDir(), "rose_pine.toml", helixSrc), nil) - ts := themesFrom([]*Theme{dark, light, helix}, dark, light) - - css := string(ts.CSS) - for _, th := range ts.List { - if !strings.Contains(css, `:root[data-theme="`+th.ID+`"]`) { - t.Errorf("no css block for %s", th.ID) - } - if !strings.Contains(string(ts.JS), th.ID) { - t.Errorf("%s is not in the menu", th.ID) - } - } - for _, want := range []string{"--bg:", "--fg:", "--ag0:", "color-scheme: dark", "color-scheme: light"} { - if !strings.Contains(css, want) { - t.Errorf("themes.css never says %q", want) - } - } - // every declaration has to be worth something to the browser - for _, line := range strings.Split(css, "\n") { - if l := strings.TrimSpace(line); strings.HasPrefix(l, "--") && strings.HasSuffix(l, ": ;") { - t.Errorf("empty declaration: %s", l) - } - } - if n := strings.Count(string(ts.JS), `"id":`); n != len(ts.List) { - t.Errorf("menu has %d themes, want %d", n, len(ts.List)) - } -} - -// The two editors' theme directories, as they are on a machine that has them. -func TestDiscoverFindsBothEditors(t *testing.T) { - home := t.TempDir() - t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "config")) - t.Setenv("XDG_DATA_HOME", filepath.Join(home, "data")) - writeTheme(t, filepath.Join(home, "config", "zed", "themes"), "mine.json", - `{"name":"Mine","themes":[{"name":"Mine Dark","appearance":"dark","style":{ - "editor.background":"#101010","editor.foreground":"#e0e0e0"}}]}`) - writeTheme(t, filepath.Join(home, "data", "zed", "extensions", "installed", "ext", "themes"), - "ext.json", `{"name":"Ext","themes":[{"name":"Ext Light","appearance":"light","style":{ - "editor.background":"#fafafa","editor.foreground":"#383a42"}}]}`) - writeTheme(t, filepath.Join(home, "config", "helix", "themes"), "mine_helix.toml", helixSrc) - - found := map[string]*Theme{} - for _, th := range discoverThemes() { - found[th.ID] = th - } - for _, want := range []string{"zed/mine-dark", "zed/ext-light", "helix/mine-helix"} { - if found[want] == nil { - t.Errorf("did not find %s", want) - } - } -} - -// Coverage colors are mixed in the browser so every theme can supply them. -func TestMaskMix(t *testing.T) { - s := &Site{Agents: []*Agent{{Bit: 1}, {Bit: 2}, {Bit: 4}}} - for _, tc := range []struct { - mask uint8 - want string - }{ - {1, "var(--ag0)"}, - {5, "color-mix(in srgb, var(--ag0) 50.000%, var(--ag2))"}, - {7, "color-mix(in srgb, var(--ag0) 33.333%, color-mix(in srgb, var(--ag1) 50.000%, var(--ag2)))"}, - } { - if got := s.maskMix(tc.mask); got != tc.want { - t.Errorf("mask %d = %q, want %q", tc.mask, got, tc.want) - } - } -} |
