package check import ( "regexp" "strings" "testing" "amadan.net/rastrillo/fiesta" ) // The column's footer row: the column shell ends with it, and it holds // the language and colour-scheme menus that used to stand above the // page's title. A menu there pushed the title down and read as the // page's first action; at the foot of the column it is out of the way // and still one tap from every page. // summaryFace matches the four selectors, one per state, of the rules // that give a dropdown's summary its face, in the attribute spelling: // the :not() list of the dropdowns that stay flat, then the summary // and its state. var summaryFace = regexp.MustCompile(`^\[rst-dropdown\]:not\((.*)\) > summary:not\(\[rst-btn\]\)(|:active|:hover|:hover:active)$`) // summaryFaceClass is summaryFace in the class spelling. var summaryFaceClass = regexp.MustCompile(`^\.rst-dropdown:not\((.*)\) > summary:not\(\.rst-btn\)(|:active|:hover|:hover:active)$`) // TestTheColumnShellEndsWithItsFooterRow: the column example opens its // page with the slot, so the page header is the first thing in it, and // closes it with the footer row holding the language menu and then the // scheme menu; no menu is left above the slot. The stylesheet keeps // the row's menus flat in every state of the summary face, hides a row // that holds only template whitespace, and makes the row, not each // menu, the box a panel hangs from where nothing can anchor it. func TestTheColumnShellEndsWithItsFooterRow(t *testing.T) { doc := defaultMarkup(t, "layout-column") const open, slot, slotEnd = `
`, "", "" i, j, k := strings.Index(doc, open), strings.Index(doc, slot), strings.Index(doc, slotEnd) if i < 0 || j < i || k < j { t.Fatalf("layout-column has no %s followed by its slot; the checks below read nothing", open) } if before := doc[i+len(open) : j]; strings.TrimSpace(before) != "" { t.Errorf("layout-column: between %s and the slot there is %.80q; the page header must be the first thing in the page", open, before) } if strings.Contains(doc[i:j], " sits between
and the slot") } rest := doc[k+len(slotEnd):] for _, want := range []string{ `
`, `
`, `
`, `
`, `
`, `
`, `
`, } { rest = strings.TrimLeft(rest, " \t\n") if strings.HasPrefix(want, "") { // The menu's own markup comes before its closing tag. end := strings.Index(rest, want) if end < 0 || strings.Contains(rest[:end], " and
", rest, want) } rest = rest[len(want):] } decls, _ := parseDeclarations(t, "tokens.css", string(fiesta.TokensCSS())) faces := map[string]bool{} has := map[string]bool{} for _, d := range decls { has[d.Selector+" { "+d.Property+": "+d.Value+" }"] = true for _, spelling := range []struct { re *regexp.Regexp flat string }{{summaryFace, "[rst-page-foot] [rst-dropdown]"}, {summaryFaceClass, ".rst-page__foot .rst-dropdown"}} { m := spelling.re.FindStringSubmatch(d.Selector) if m == nil || d.Property != "box-shadow" { continue } state := m[2] if state == "" { state = "at rest" } key := "rule " + state + " does not list " + spelling.flat if _, seen := faces[key]; !seen { faces[key] = false } for _, arg := range splitSelectorList(m[1]) { if arg == spelling.flat { faces[key] = true } } } } if len(faces) != 8 { t.Fatalf("read %d summary-face rules by spelling and state, want 8 (four states, two spellings): %v", len(faces), faces) } for _, key := range sortedKeys(faces) { if !faces[key] { t.Errorf("the summary face's %s in its :not(); a footer menu would wear a face in that state", key) } } for _, want := range []string{ "[rst-page-foot]:not(:has(> *)) { display: none }", ".rst-page__foot:not(:has(> *)) { display: none }", "[rst-page-foot] [rst-dropdown] { position: static }", ".rst-page__foot .rst-dropdown { position: static }", } { if !has[want] { t.Errorf("tokens.css does not carry %s", want) } } }