main bcc9a45

css_test.go RawHistoryFolder

1package css
2
3import (
4	"encoding/json"
5	"os"
6	"reflect"
7	"testing"
8)
9
10func TestFixture(t *testing.T) {
11	src, e := os.ReadFile("testdata/basic.css")
12	if e != nil {
13		t.Fatal(e)
14	}
15	want, e := os.ReadFile("testdata/basic.json")
16	if e != nil {
17		t.Fatal(e)
18	}
19	root := Element{Type: "app"}
20	child := Element{Type: "row"}
21	sheet := Parse(string(src))
22	engine := Compile(UA(), sheet)
23	p := engine.Compute(&root, nil)
24	got := engine.Compute(&child, p).Style
25	v := map[string]any{"display": keywordNames[got.Display], "padding-top": got.Padding.Top, "padding-right": got.Padding.Right, "background-color": got.BackgroundColor, "font-size": got.FontSize}
26	b, _ := json.Marshal(v)
27	var a, c any
28	_ = json.Unmarshal(b, &a)
29	_ = json.Unmarshal(want, &c)
30	if !reflect.DeepEqual(a, c) {
31		t.Fatalf("got %s want %s", b, want)
32	}
33}
34func TestColors(t *testing.T) {
35	tests := []struct {
36		s string
37		c Color
38	}{{"#f00", Color{1, 0, 0, 1}}, {"#f008", Color{1, 0, 0, 136.0 / 255}}, {"#ff0000", Color{1, 0, 0, 1}}, {"#ff000080", Color{1, 0, 0, 128.0 / 255}}, {"rgb(255,0,0)", Color{1, 0, 0, 1}}, {"rgba(255 0 0 / 50%)", Color{1, 0, 0, .5}}, {"hsl(0 100% 50%)", Color{1, 0, 0, 1}}, {"hsla(0,100%,50%,.5)", Color{1, 0, 0, .5}}, {"red", Color{1, 0, 0, 1}}, {"transparent", Color{}}}
39	for _, tt := range tests {
40		t.Run(tt.s, func(t *testing.T) {
41			v, ok := parseColor(tt.s, Color{})
42			if !ok || v != tt.c {
43				t.Fatalf("%v %v", v, ok)
44			}
45		})
46	}
47}
48func TestCascade(t *testing.T) {
49	tests := []struct {
50		css, typ, id string
51		hover        bool
52		want         string
53	}{{"row{display:block}", "row", "", false, "block"}, {"row{display:none} row{display:flex}", "row", "", false, "flex"}, {"#x {display:none} row {display:flex}", "row", "x", false, "none"}, {"row:hover{display:none} row{display:flex}", "row", "", true, "none"}, {"row{display:none!important} #x{display:flex}", "row", "x", false, "none"}}
54	for _, tt := range tests {
55		t.Run(tt.css, func(t *testing.T) {
56			e := Element{Type: tt.typ, ID: tt.id}
57			if tt.hover {
58				e.State = Hover
59			}
60			s := Compile(UA(), Parse(tt.css)).Compute(&e, nil).Style
61			if keywordNames[s.Display] != tt.want {
62				t.Fatalf("got %s want %s", keywordNames[s.Display], tt.want)
63			}
64		})
65	}
66}
67func TestInvalidAndInheritance(t *testing.T) {
68	p := Element{Type: "app"}
69	e := Element{Type: "button"}
70	sheet := Parse("app {color: blue; font-size: 20px; --a: var(--b); --b: var(--a)} button { color: var(--a); padding: 1em 2px; opacity: 1e999; unknown: 1; background: var(--a, red)}")
71	engine := Compile(UA(), sheet)
72	ps := engine.Compute(&p, nil)
73	s := engine.Compute(&e, ps).Style
74	if s.Color != ps.Style.Color || s.Padding.Top.Value != 20 || s.Opacity != 1 || s.BackgroundColor.R != 1 {
75		t.Fatalf("style %#v", s)
76	}
77}
78func TestRecovery(t *testing.T) {
79	s := Parse("button { wat; color:red } bad:hover::foo {color:blue} button { color: green }")
80	if len(s.Rules) != 2 || len(s.Diagnostics) < 2 {
81		t.Fatalf("%+v", s)
82	}
83	e := Element{Type: "button"}
84	v := Compile(UA(), s).Compute(&e, nil).Style
85	if v.Color.G != namedColors["green"].G {
86		t.Fatal(v.Color)
87	}
88}
89func TestSelector(t *testing.T) {
90	p := Element{Type: "app"}
91	e := Element{Type: "button", Classes: []string{"ok"}, State: FocusVisible | Checked}
92	for _, tt := range []struct {
93		s     string
94		match bool
95	}{{"app > button.ok:focus-visible", true}, {"app button", true}, {"row > button", false}, {"app > button:disabled", false}, {"button:checked", true}} {
96		s := Parse(tt.s + " {color:red}")
97		en := Compile(Sheet{}, s)
98		pv := en.Compute(&p, nil)
99		got := en.Compute(&e, pv).Style.Color == namedColors["red"]
100		if len(s.Rules) != 1 || got != tt.match {
101			t.Fatalf("%s %+v", tt.s, s)
102		}
103	}
104}
105func FuzzTokenizerParser(f *testing.F) {
106	for _, s := range []string{"a { color:red }", "\xff{bad:'\n; background: rgb(1,2,3)}", "/*", "a{--x:var(--x); color:var(--x)}"} {
107		f.Add(s)
108	}
109	f.Fuzz(func(t *testing.T, s string) {
110		if len(s) > 1<<16 {
111			t.Skip()
112		}
113		_ = Tokenize(s)
114		sheet := Parse(s)
115		e := Element{Type: "a"}
116		_ = Compile(UA(), sheet).Compute(&e, nil)
117	})
118}
119func TestHugeNumbers(t *testing.T) {
120	for _, v := range []string{"1e999", "NaN", "Inf", "1e308em"} {
121		e := Element{Type: "app"}
122		s := Compile(UA(), Parse("app{width:"+v+";font-size:"+v+"; opacity:"+v+"}")).Compute(&e, nil).Style
123		if s.Width.Unit != "auto" || s.FontSize.Value != 16 || s.Opacity != 1 {
124			t.Fatalf("%s: %+v", v, s)
125		}
126	}
127}
128func TestTokenRecovery(t *testing.T) {
129	ts := Tokenize("a {color:'broken\n; background:url(foo); width:2px}")
130	found := false
131	for _, tok := range ts {
132		if tok.Kind == "bad-string" {
133			found = true
134		}
135	}
136	if !found {
137		t.Fatal(ts)
138	}
139}
140func TestEscapesAndBadURL(t *testing.T) {
141	for _, tt := range []struct{ src, kind, value string }{{`b\75 tton`, "ident", "button"}, {`#\78 `, "hash", "x"}, {`url(foo)`, "url", "foo"}, {`url(foo"bar);a`, "bad-url", "foo\"bar"}} {
142		ts := Tokenize(tt.src)
143		if len(ts) == 0 || ts[0].Kind != tt.kind || ts[0].Value != tt.value {
144			t.Errorf("%q: %+v", tt.src, ts)
145		}
146	}
147	e := Element{Type: "button"}
148	s := Parse(`b\75 tton {color:red}`)
149	if len(s.Rules) != 1 || Compile(Sheet{}, s).Compute(&e, nil).Style.Color != namedColors["red"] {
150		t.Fatal(s)
151	}
152}
153func TestRecoveryNested(t *testing.T) {
154	tests := []struct {
155		src          string
156		rules, decls int
157	}{{`button {broken: fn(a;b); color: red;}`, 1, 2}, {`button {bad { x:y; }; color:red} row{display:flex}`, 2, 1}, {`button:unknown { color:red } button {color:blue}`, 1, 1}, {`button { color:red`, 0, 0}}
158	for _, tt := range tests {
159		s := Parse(tt.src)
160		if len(s.Rules) != tt.rules {
161			t.Errorf("%q rules=%d diagnostics=%v", tt.src, len(s.Rules), s.Diagnostics)
162			continue
163		}
164		if tt.rules > 0 && len(s.Rules[0].Declarations) != tt.decls {
165			t.Errorf("%q decls=%d diagnostics=%v", tt.src, len(s.Rules[0].Declarations), s.Diagnostics)
166		}
167	}
168}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 12

↑↓ moveenter openesc close