package gallery import ( "bytes" "encoding/json" "fmt" "io/fs" "regexp" "slices" "sort" "strings" "testing" "testing/fstest" "amadan.net/rastrillo/fiesta" "amadan.net/rastrillo/fiesta/example" ) // The shape of the whole tree, and what every page in it carries: the // files the four declared-expectation seams name and no other, the // twelve locales, the sequence of pages, the Overview's routes, the // hashed assets, the titles and ids, and the password screen's // warning. These gates read their expectations through pageKinds(), // frameFiles(), extraDocumentNames() and the asset list, so a later // task that extends the tree through its own seam keeps them exact // without an edit here. // newTestSite is a site after its setup, with nothing rendered: the // seams a test reads expectations from live on it. func newTestSite(t *testing.T, src fs.FS, mount string) *site { t.Helper() s, err := newSite(src, mount) if err != nil { t.Fatalf("newSite: %v", err) } return s } // dirs lists every theme and locale directory, with its trailing // slash, in render order. func dirs() []string { var out []string for _, theme := range fiesta.ThemeNames() { for _, locale := range Locales { out = append(out, theme+"/"+locale+"/") } } return out } // isFrame says whether a tree path is a frame document. func isFrame(name string) bool { return strings.Contains(name, "/frames/") } // isSampleDocument says whether a file of the tree is a sample's own // document rather than a gallery page: a frame, or one of the extra // documents (the demo application's four and the modal route), whose // chrome is a sample's and not the gallery's. A gate on what every // gallery page carries reads the pages alone. func isSampleDocument(name string) bool { return isFrame(name) || reserved(name[strings.LastIndex(name, "/")+1:]) } // titleOf is the document's
its body; no other state // does. func TestThePasswordScreenCarriesItsWarning(t *testing.T) { src := withWarning(t, fiesta.FS()) files, err := Render(src, Options{}) if err != nil { t.Fatalf("Render: %v", err) } p, err := loadProse(src) if err != nil { t.Fatal(err) } warning, ok := p.Examples["signin"].Warnings["email-and-password"] if !ok || warning.Title == "" || warning.Body == "" { t.Fatalf("the prose has no whole warning for signin/email-and-password: %+v", warning) } e, ok := manifest(t).Entry("signin") if !ok { t.Fatal("no signin example") } page := files[fiesta.ThemeNames()[0]+"/en/signin.html"] if len(page) == 0 { t.Fatal("no day/en/signin.html") } for _, st := range e.States { sec := stateSection(t, page, "signin", st.ID) callouts := sec.find(func(n node) bool { _, callout := n.attr("rst-callout") tone, _ := n.attr("rst-tone") return callout && tone == "warning" }) if st.ID != "email-and-password" { if len(callouts) != 0 { t.Errorf("signin/%s carries %d warning callouts", st.ID, len(callouts)) } continue } if len(callouts) != 1 { t.Fatalf("signin/%s carries %d warning callouts, want 1", st.ID, len(callouts)) } order := sec.find(func(n node) bool { _, callout := n.attr("rst-callout") return callout || n.hasClass("ds-note") }) if len(order) != 2 || !order[0].hasClass("ds-note") { t.Errorf("signin/%s: the callout does not follow the note", st.ID) } strong, ok := callouts[0].first(func(n node) bool { return n.n.Data == "strong" }) if !ok || strong.text() != squash(warning.Title) { t.Errorf("signin/%s: the callout's title reads %q, want %q", st.ID, strong.text(), warning.Title) } body, ok := callouts[0].first(func(n node) bool { return n.n.Data == "p" }) if !ok || body.text() != squash(warning.Body) { t.Errorf("signin/%s: the callout's body reads %q, want %q", st.ID, body.text(), warning.Body) } } } // TestAnExampleDocumentFramesLikeASeededOne: a document exampleDocument // builds goes through frame like a seeded example does: frame.js first // in the head, the stylesheets and scripts hashed, the theme's own, // the locale on , the style kept and the body's links deadened. // Written against the finished seam; its control is the document // built with no stylesheet, which frame refuses by name. func TestAnExampleDocumentFramesLikeASeededOne(t *testing.T) { s := newTestSite(t, fiesta.FS(), "/x") e := mustEntry(t, "button") doc := exampleDocument("A test", []string{"light.js"}, "body { padding: 0 }", `
`) framed, err := s.frame(doc, "plain", "ar", e, e.Default) if err != nil { t.Fatalf("frame: %v", err) } html, ok := parse(t, []byte(framed)).first(func(n node) bool { return n.n.Data == "html" }) if !ok { t.Fatal("the framed document has no ") } if lang, _ := html.attr("lang"); lang != "ar" { t.Errorf("the framed document says lang=%q, want ar", lang) } if dir, _ := html.attr("dir"); dir != "rtl" { t.Errorf("the framed document says dir=%q, want rtl", dir) } for _, want := range []string{ `{{say "gallery.brand"}}
{{end}}`) fresh := newTestSite(t, fiesta.FS(), "") tmpl, err := fresh.templates("ga") if err != nil { t.Fatal(err) } if tmpl.Lookup("ds-body-scratch") == nil { t.Error("a template registered after the others is not parsed") } var b strings.Builder if err := tmpl.ExecuteTemplate(&b, "ds-body-scratch", nil); err != nil || b.String() != "Fiesta
" { t.Errorf("the registered template rendered %q, %v", b.String(), err) } } // TestContentHeadingsSkipTheShellTitle: the heading selector the two // heading tests share counts a content