An audio player with transport, scrubber and volume; an equalizer and a visualizer; a playlist; a cassette, cartridge and record that turn while they play and hold where they stop; piano keys, a step sequencer and a tracker you can type into; a waveform, a 16:9 video frame, an image compare, a gallery, a filmstrip, a record button and an FM tuner. input exports SlideBase, the undrawn rail its sliders share; the gallery treats exported Bases as building blocks, not components. pixel's micro font gains '#' for notes like C#4. pixel.Pad and the slide rail read input only while their base is enabled, so Inert or a fresh Disabled stops a drag or a key already sent. AGENTS records why a drawn container keeps the DISABLED description: MyGo paints a disabled subtree at half opacity.
75 lines
2.0 KiB
Go
75 lines
2.0 KiB
Go
package gallery
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRegistry(t *testing.T) {
|
|
slugs, ids := map[string]bool{}, map[int]bool{}
|
|
known := map[string]bool{}
|
|
for _, c := range Categories {
|
|
known[c.Key] = true
|
|
}
|
|
for _, e := range Entries() {
|
|
if e.Name == "" || e.Zh == "" || e.Summary == "" || e.Demo == nil {
|
|
t.Fatalf("entry %+v is incomplete", e)
|
|
}
|
|
if !known[e.Category] {
|
|
t.Fatalf("%s: unknown category %q", e.Name, e.Category)
|
|
}
|
|
if (e.ID == 0) != (e.Category == "foundations") || e.ID < 0 || e.ID > 500 {
|
|
t.Fatalf("%s: id %d in %s", e.Name, e.ID, e.Category)
|
|
}
|
|
if slugs[e.Slug()] || e.ID > 0 && ids[e.ID] {
|
|
t.Fatalf("%s: duplicate slug or id", e.Slug())
|
|
}
|
|
slugs[e.Slug()], ids[e.ID] = true, true
|
|
if !strings.HasPrefix(e.Code(), "func "+funcName(e.Demo)+"(") {
|
|
t.Fatalf("%s: code snippet %q", e.Slug(), e.Code()[:min(40, len(e.Code()))])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every exported component has a demo; Bases are not components.
|
|
func TestEveryComponentHasADemo(t *testing.T) {
|
|
var demos strings.Builder
|
|
for _, e := range Entries() {
|
|
demos.WriteString(e.Code())
|
|
}
|
|
for _, cat := range Categories[1:] {
|
|
files, _ := filepath.Glob(filepath.Join("..", cat.Key, "*.go"))
|
|
for _, f := range files {
|
|
if strings.HasSuffix(f, "_test.go") {
|
|
continue
|
|
}
|
|
src, err := os.ReadFile(f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file, err := parser.ParseFile(token.NewFileSet(), f, src, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, d := range file.Decls {
|
|
fn, ok := d.(*ast.FuncDecl)
|
|
if !ok || fn.Recv != nil || !fn.Name.IsExported() || strings.HasSuffix(fn.Name.Name, "Base") || fn.Type.Params.NumFields() == 0 {
|
|
continue
|
|
}
|
|
if p, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr); ok {
|
|
if sel, ok := p.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "Context" {
|
|
if call := cat.Key + "." + fn.Name.Name + "("; !strings.Contains(demos.String(), call) {
|
|
t.Errorf("%s has no demo", call)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|