| 1 | package rowrule |
| 2 | |
| 3 | import ( |
| 4 | "slices" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | |
| 8 | xhtml "golang.org/x/net/html" |
| 9 | |
| 10 | "amadan.net/rastrillo/fiesta/internal/markup" |
| 11 | ) |
| 12 | |
| 13 | // read is markup as Breaches wants it: respelled, then parsed. |
| 14 | func read(t *testing.T, src string) *xhtml.Node { |
| 15 | t.Helper() |
| 16 | respelled, _ := markup.Respell([]byte(src)) |
| 17 | doc, err := xhtml.Parse(strings.NewReader(string(respelled))) |
| 18 | if err != nil { |
| 19 | t.Fatalf("parsing %q: %v", src, err) |
| 20 | } |
| 21 | return doc |
| 22 | } |
| 23 | |
| 24 | // TestTheRowCheckNamesEachBreach: each way a dropdown can break the |
| 25 | // rule is named by its own message, in both spellings, and each shape |
| 26 | // the rule allows passes. A checker that only counted breaches would |
| 27 | // pass a page that broke the rule one way and was fixed another. |
| 28 | func TestTheRowCheckNamesEachBreach(t *testing.T) { |
| 29 | const header = `<header rst-page-header><h1>T</h1></header>` |
| 30 | cases := []struct { |
| 31 | name, markup string |
| 32 | want []string |
| 33 | }{ |
| 34 | {"alone at the top of the page, above its title", |
| 35 | `<main rst-page><details rst-dropdown></details>` + header + `</main>`, |
| 36 | []string{"a dropdown sits in main[rst-page], not in a row", "a dropdown comes before the page header"}}, |
| 37 | {"in a bare div", |
| 38 | `<div><details rst-dropdown></details></div>`, |
| 39 | []string{"a dropdown sits in div, not in a row"}}, |
| 40 | {"in a toolbar above the page's title", |
| 41 | `<main rst-page><div rst-lbar><details rst-dropdown></details></div>` + header + `</main>`, |
| 42 | []string{"a dropdown comes before the page header"}}, |
| 43 | {"a page header with no title", |
| 44 | `<header rst-page-header><details rst-dropdown></details></header>`, |
| 45 | []string{"a page header holds a dropdown and no h1"}}, |
| 46 | {"a section head with no heading", |
| 47 | `<div rst-box-head><details rst-dropdown></details></div>`, |
| 48 | []string{"a section head holds a dropdown and no heading"}}, |
| 49 | {"class-spelled, alone in the body", |
| 50 | `<body><details class="rst-dropdown"></details></body>`, |
| 51 | []string{"a dropdown sits in body, not in a row"}}, |
| 52 | {"in a form that lays itself out", |
| 53 | `<div rst-lbar><form rst-search><details rst-dropdown></details></form></div>`, |
| 54 | []string{"a dropdown sits in form[rst-search], not in a row"}}, |
| 55 | {"a heading inside the menu is not the section's", |
| 56 | `<div rst-box-head><details rst-dropdown><summary>S</summary><div rst-dropdown-menu><h2>P</h2></div></details></div>`, |
| 57 | []string{"a section head holds a dropdown and no heading"}}, |
| 58 | |
| 59 | {"in a toolbar inside a page header with no title", |
| 60 | `<header rst-page-header><div rst-lbar><details rst-dropdown><summary>Menu</summary></details></div></header>`, |
| 61 | []string{"a page header holds a dropdown and no h1"}}, |
| 62 | {"class-spelled, in a toolbar inside a page header with no title", |
| 63 | `<header class="rst-page-header"><div class="rst-lbar"><details class="rst-dropdown"><summary>Menu</summary></details></div></header>`, |
| 64 | []string{"a page header holds a dropdown and no h1"}}, |
| 65 | {"in a toolbar inside a section head with no heading", |
| 66 | `<div rst-box-head><div rst-lbar><details rst-dropdown><summary>Menu</summary></details></div></div>`, |
| 67 | []string{"a section head holds a dropdown and no heading"}}, |
| 68 | {"class-spelled, in a toolbar inside a section head with no heading", |
| 69 | `<div class="rst-box-head"><div class="rst-lbar"><details class="rst-dropdown"><summary>Menu</summary></details></div></div>`, |
| 70 | []string{"a section head holds a dropdown and no heading"}}, |
| 71 | {"a class list the respeller cannot read", |
| 72 | `<main rst-page><details class=rst-dropdown{{.Open}}></details></main>`, |
| 73 | []string{`a class list could not be respelled, so the check cannot read it: <details class="rst-dropdown{{.Open}}">`}}, |
| 74 | |
| 75 | {"escaped markup in a code sample", |
| 76 | `<p><code>class="rst-box"</code></p>`, nil}, |
| 77 | {"in a toolbar inside a page header with its title", |
| 78 | `<header rst-page-header><h1>T</h1><div rst-lbar><details rst-dropdown><summary>Menu</summary></details></div></header>`, nil}, |
| 79 | {"class-spelled, in a toolbar inside a section head with its heading", |
| 80 | `<div class="rst-box-head"><h2>P</h2><div class="rst-lbar"><details class="rst-dropdown"><summary>Menu</summary></details></div></div>`, nil}, |
| 81 | {"class-spelled, in a section head with its heading", |
| 82 | `<div class="rst-box-head"><h2>P</h2><details rst-dropdown></details></div>`, nil}, |
| 83 | {"in a toolbar, inside a form, after the toolbar's search", |
| 84 | `<div rst-lbar><search><form rst-search method="get" action="/posts"><input type="search" name="q"></form></search>` + |
| 85 | `<form method="get" action="/posts"><details rst-dropdown><summary>S</summary></details></form></div>`, nil}, |
| 86 | {"in a toolbar, inside a search's form", |
| 87 | `<div rst-lbar><search><form method="get"><details rst-dropdown></details></form></search></div>`, nil}, |
| 88 | {"in the shell's bar, before the page header", |
| 89 | `<div rst-shell-tail><details rst-dropdown></details></div><main rst-page>` + header + `</main>`, nil}, |
| 90 | {"in a menu in the rail's foot, before the page header", |
| 91 | `<div rst-shell-rail-foot><details rst-dropdown><summary>S</summary><div rst-dropdown-menu><details rst-dropdown></details></div></details></div><main rst-page>` + header + `</main>`, nil}, |
| 92 | {"inside another dropdown's menu", |
| 93 | `<div rst-box-head><h2>P</h2><details rst-dropdown><summary>S</summary><div rst-dropdown-menu><details rst-dropdown></details></div></details></div>`, nil}, |
| 94 | {"in the page header beside its title", |
| 95 | `<main rst-page><header rst-page-header><div><h1>T</h1></div><details rst-dropdown></details></header></main>`, nil}, |
| 96 | {"in the footer row, after the page header", |
| 97 | `<main rst-page>` + header + `<footer rst-page-foot><details rst-dropdown></details></footer></main>`, nil}, |
| 98 | } |
| 99 | for _, c := range cases { |
| 100 | if got := Breaches(read(t, c.markup), nil); !slices.Equal(got, c.want) { |
| 101 | t.Errorf("%s:\n got %q\n want %q", c.name, got, c.want) |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | // TestAPageNamesItsOwnRows: a page that lays out its own bars names |
| 107 | // them by class, and a menu in one of them is in a row. A name a menu's |
| 108 | // row does not carry, or one written as an attribute, is no row: the |
| 109 | // page's names extend Fiesta's rows, they do not loosen the rule for |
| 110 | // the rest of the page. |
| 111 | func TestAPageNamesItsOwnRows(t *testing.T) { |
| 112 | bar := map[string]string{"site-bar": "the site's theme bar"} |
| 113 | cases := []struct { |
| 114 | name, markup string |
| 115 | rows map[string]string |
| 116 | want []string |
| 117 | }{ |
| 118 | {"in a row the page names", |
| 119 | `<div class="site-stage site-bar"><span>Theme</span><details rst-dropdown></details></div>`, bar, nil}, |
| 120 | {"in a form inside a row the page names", |
| 121 | `<nav class="site-bar"><form method="post"><details rst-dropdown></details></form></nav>`, bar, nil}, |
| 122 | {"in a div the page does not name", |
| 123 | `<div class="site-stage"><details rst-dropdown></details></div>`, bar, |
| 124 | []string{"a dropdown sits in div, not in a row"}}, |
| 125 | {"the name written as an attribute", |
| 126 | `<div site-bar><details rst-dropdown></details></div>`, bar, |
| 127 | []string{"a dropdown sits in div, not in a row"}}, |
| 128 | {"a row the page names, with no names passed", |
| 129 | `<div class="site-bar"><details rst-dropdown></details></div>`, nil, |
| 130 | []string{"a dropdown sits in div, not in a row"}}, |
| 131 | {"a Fiesta row, beside the page's names", |
| 132 | `<div rst-lbar><details rst-dropdown></details></div>`, bar, nil}, |
| 133 | } |
| 134 | for _, c := range cases { |
| 135 | if got := Breaches(read(t, c.markup), c.rows); !slices.Equal(got, c.want) { |
| 136 | t.Errorf("%s:\n got %q\n want %q", c.name, got, c.want) |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | |