cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 🍃
charm leaflet readability golang
at main 333 lines 7.7 kB view raw
1package ui 2 3import ( 4 "bytes" 5 "os" 6 "regexp" 7 "strings" 8 "testing" 9 10 "github.com/charmbracelet/lipgloss" 11 "github.com/muesli/termenv" 12) 13 14func stripAnsi(str string) string { 15 return regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`).ReplaceAllString(str, "") 16} 17 18func withColor(t *testing.T, fn func(r *lipgloss.Renderer)) { 19 t.Helper() 20 21 var buf bytes.Buffer 22 r := lipgloss.NewRenderer(&buf) 23 r.SetColorProfile(termenv.TrueColor) 24 lipgloss.SetDefaultRenderer(r) 25 26 fn(r) 27 28 lipgloss.SetDefaultRenderer(lipgloss.NewRenderer(os.Stdout)) 29} 30 31func TestUI(t *testing.T) { 32 t.Run("Hex", func(t *testing.T) { 33 tests := []struct { 34 key Key 35 expected string 36 }{ 37 {Cumin, "#BF976F"}, 38 {Tang, "#FF985A"}, 39 {Yam, "#FFB587"}, 40 {Paprika, "#D36C64"}, 41 {Bengal, "#FF6E63"}, 42 {Uni, "#FF937D"}, 43 {Sriracha, "#EB4268"}, 44 {Coral, "#FF577D"}, 45 {Salmon, "#FF7F90"}, 46 {Chili, "#E23080"}, 47 {Cherry, "#FF388B"}, 48 {Tuna, "#FF6DAA"}, 49 {Macaron, "#E940B0"}, 50 {Pony, "#FF4FBF"}, 51 {Cheeky, "#FF79D0"}, 52 {Flamingo, "#F947E3"}, 53 {Dolly, "#FF60FF"}, 54 {Blush, "#FF84FF"}, 55 {Urchin, "#C337E0"}, 56 {Mochi, "#EB5DFF"}, 57 {Lilac, "#F379FF"}, 58 {Prince, "#9C35E1"}, 59 {Violet, "#C259FF"}, 60 {Mauve, "#D46EFF"}, 61 {Grape, "#7134DD"}, 62 {Plum, "#9953FF"}, 63 {Orchid, "#AD6EFF"}, 64 {Jelly, "#4A30D9"}, 65 {Charple, "#6B50FF"}, 66 {Hazy, "#8B75FF"}, 67 {Ox, "#3331B2"}, 68 {Sapphire, "#4949FF"}, 69 {Guppy, "#7272FF"}, 70 {Oceania, "#2B55B3"}, 71 {Thunder, "#4776FF"}, 72 {Anchovy, "#719AFC"}, 73 {Damson, "#007AB8"}, 74 {Malibu, "#00A4FF"}, 75 {Sardine, "#4FBEFE"}, 76 {Zinc, "#10B1AE"}, 77 {Turtle, "#0ADCD9"}, 78 {Lichen, "#5CDFEA"}, 79 {Guac, "#12C78F"}, 80 {Julep, "#00FFB2"}, 81 {Bok, "#68FFD6"}, 82 {Mustard, "#F5EF34"}, 83 {Citron, "#E8FF27"}, 84 {Zest, "#E8FE96"}, 85 {Pepper, "#201F26"}, 86 {BBQ, "#2d2c35"}, 87 {Charcoal, "#3A3943"}, 88 {Iron, "#4D4C57"}, 89 {Oyster, "#605F6B"}, 90 {Squid, "#858392"}, 91 {Smoke, "#BFBCC8"}, 92 {Ash, "#DFDBDD"}, 93 {Salt, "#F1EFEF"}, 94 {Butter, "#FFFAF1"}, 95 // Diffs: additions 96 {Pickle, "#00A475"}, 97 {Gator, "#18463D"}, 98 {Spinach, "#1C3634"}, 99 {Pom, "#AB2454"}, 100 {Steak, "#582238"}, 101 {Toast, "#412130"}, 102 {NeueGuac, "#00b875"}, 103 {NeueZinc, "#0e9996"}, 104 {Key(-1), ""}, 105 } 106 107 for _, tt := range tests { 108 t.Run(tt.key.String(), func(t *testing.T) { 109 if hex := tt.key.Hex(); hex != tt.expected { 110 t.Errorf("expected hex %q, got %q", tt.expected, hex) 111 } 112 }) 113 } 114 }) 115 116 t.Run("String", func(t *testing.T) { 117 tests := []struct { 118 key Key 119 expected string 120 }{ 121 {Cumin, "Cumin"}, 122 {Tang, "Tang"}, 123 {Yam, "Yam"}, 124 {Paprika, "Paprika"}, 125 {Bengal, "Bengal"}, 126 {Uni, "Uni"}, 127 {Sriracha, "Sriracha"}, 128 {Coral, "Coral"}, 129 {Salmon, "Salmon"}, 130 {Chili, "Chili"}, 131 {Cherry, "Cherry"}, 132 {Tuna, "Tuna"}, 133 {Macaron, "Macaron"}, 134 {Pony, "Pony"}, 135 {Cheeky, "Cheeky"}, 136 {Flamingo, "Flamingo"}, 137 {Dolly, "Dolly"}, 138 {Blush, "Blush"}, 139 {Urchin, "Urchin"}, 140 {Mochi, "Mochi"}, 141 {Lilac, "Lilac"}, 142 {Prince, "Prince"}, 143 {Violet, "Violet"}, 144 {Mauve, "Mauve"}, 145 {Grape, "Grape"}, 146 {Plum, "Plum"}, 147 {Orchid, "Orchid"}, 148 {Jelly, "Jelly"}, 149 {Charple, "Charple"}, 150 {Hazy, "Hazy"}, 151 {Ox, "Ox"}, 152 {Sapphire, "Sapphire"}, 153 {Guppy, "Guppy"}, 154 {Oceania, "Oceania"}, 155 {Thunder, "Thunder"}, 156 {Anchovy, "Anchovy"}, 157 {Damson, "Damson"}, 158 {Malibu, "Malibu"}, 159 {Sardine, "Sardine"}, 160 {Zinc, "Zinc"}, 161 {Turtle, "Turtle"}, 162 {Lichen, "Lichen"}, 163 {Guac, "Guac"}, 164 {Julep, "Julep"}, 165 {Bok, "Bok"}, 166 {Mustard, "Mustard"}, 167 {Citron, "Citron"}, 168 {Zest, "Zest"}, 169 {Pepper, "Pepper"}, 170 {BBQ, "BBQ"}, 171 {Charcoal, "Charcoal"}, 172 {Iron, "Iron"}, 173 {Oyster, "Oyster"}, 174 {Squid, "Squid"}, 175 {Smoke, "Smoke"}, 176 {Ash, "Ash"}, 177 {Salt, "Salt"}, 178 {Butter, "Butter"}, 179 {Pickle, "Pickle"}, 180 {Gator, "Gator"}, 181 {Spinach, "Spinach"}, 182 {Pom, "Pom"}, 183 {Steak, "Steak"}, 184 {Toast, "Toast"}, 185 {NeueGuac, "NeueGuac"}, 186 {NeueZinc, "NeueZinc"}, 187 {Key(-1), "Key(-1)"}, 188 } 189 190 for _, tt := range tests { 191 t.Run(tt.expected, func(t *testing.T) { 192 if str := tt.key.String(); str != tt.expected { 193 t.Errorf("expected string %q, got %q", tt.expected, str) 194 } 195 }) 196 } 197 }) 198 199 t.Run("RGBA", func(t *testing.T) { 200 t.Run("valid key", func(t *testing.T) { 201 r, g, b, a := Cumin.RGBA() 202 if a == 0 { 203 t.Error("Alpha should not be zero for a valid color") 204 } 205 if !(r > 0 && g > 0 && b > 0) { 206 t.Error("RGB components should be greater than zero for Cumin") 207 } 208 }) 209 210 t.Run("invalid key", func(t *testing.T) { 211 defer func() { 212 if r := recover(); r == nil { 213 t.Errorf("The code did not panic") 214 } 215 }() 216 Key(-1).RGBA() 217 }) 218 }) 219 220 t.Run("Color Categories", func(t *testing.T) { 221 if !Guac.IsPrimary() { 222 t.Error("Guac should be a primary color") 223 } 224 if Malibu.IsPrimary() { 225 t.Error("Malibu should not be a primary color") 226 } 227 if !Malibu.IsSecondary() { 228 t.Error("Malibu should be a secondary color") 229 } 230 if Guac.IsSecondary() { 231 t.Error("Guac should not be a secondary color") 232 } 233 if !Violet.IsTertiary() { 234 t.Error("Violet should be a tertiary color") 235 } 236 if Guac.IsTertiary() { 237 t.Error("Guac should not be a tertiary color") 238 } 239 }) 240 241 t.Run("New Palette", func(t *testing.T) { 242 t.Run("dark mode", func(t *testing.T) { 243 p := NewPalette(true) 244 if p == nil { 245 t.Fatal("NewPalette(true) returned nil") 246 } 247 r, g, b, a := p.scheme.Base.RGBA() 248 pr, pg, pb, pa := Pepper.RGBA() 249 if r != pr || g != pg || b != pb || a != pa { 250 t.Errorf("dark mode base color mismatch: expected Pepper, got %v", p.scheme.Base) 251 } 252 }) 253 254 t.Run("light mode", func(t *testing.T) { 255 p := NewPalette(false) 256 if p == nil { 257 t.Fatal("NewPalette(false) returned nil") 258 } 259 r, g, b, a := p.scheme.Base.RGBA() 260 sr, sg, sb, sa := Salt.RGBA() 261 if r != sr || g != sg || b != sb || a != sa { 262 t.Errorf("light mode base color mismatch: expected Salt, got %v", p.scheme.Base) 263 } 264 }) 265 }) 266 267 t.Run("Logo", func(t *testing.T) { 268 logos := []struct { 269 logo Logo 270 name string 271 contains string 272 }{ 273 {Colossal, "Collosal", "888b 888"}, 274 {Georgia, "Georgia", "`7MN. `7MF'"}, 275 {Alligator, "Alligator", ":::: ::: ::::::::"}, 276 {ANSI, "ANSI", "███ ██"}, 277 {ANSIShadow, "ANSIShadow", "███╗ ██╗"}, 278 } 279 for _, l := range logos { 280 t.Run(l.name, func(t *testing.T) { 281 s := l.logo.String() 282 if s == "" { 283 t.Error("logo string should not be empty") 284 } 285 if !strings.Contains(s, l.contains) { 286 t.Errorf("logo string does not contain expected content: %q", l.contains) 287 } 288 }) 289 } 290 291 t.Run("Colored", func(t *testing.T) { 292 withColor(t, func(r *lipgloss.Renderer) { 293 logo := Georgia 294 plain := logo.String() 295 colored := logo.Colored() 296 297 if colored == "" { 298 t.Fatal("colored logo is empty") 299 } 300 if plain == colored { 301 t.Error("Colored logo should be different from plain") 302 } 303 if !strings.Contains(colored, "\u001b[") { 304 t.Error("Colored logo should contain ANSI escape codes") 305 } 306 }) 307 }) 308 309 t.Run("Colored in Viewport", func(t *testing.T) { 310 withColor(t, func(r *lipgloss.Renderer) { 311 logo := Colossal 312 viewport := logo.ColoredInViewport(r) 313 314 if viewport == "" { 315 t.Fatal("viewport is empty") 316 } 317 318 cleanedViewport := stripAnsi(viewport) 319 if !strings.Contains(cleanedViewport, "888") { 320 t.Error("Viewport should contain parts of the logo") 321 } 322 323 borderChars := []string{"╭", "╮", "╯", "╰"} 324 for _, char := range borderChars { 325 if !strings.Contains(viewport, char) { 326 t.Errorf("Viewport should contain rounded border character %s", char) 327 } 328 } 329 }) 330 }) 331 }) 332 333}