| @@ -120,3 +120,63 @@ func TestMainWidthClass(t *testing.T) { |
| 120 | 120 | t.Error("layout.html: no default width") |
| 121 | 121 | } |
| 122 | 122 | } |
| 123 | |
| 124 | // The rail carries no visible text, so every one of its controls has to |
| 125 | // name itself twice over: an aria-label for the accessibility tree and a |
| 126 | // visually hidden span for anything reading the DOM text, with the glyph |
| 127 | // itself hidden from both so it is never announced as a graphic. This |
| 128 | // parses layout.html, so an icon link that forgets one fails here rather |
| 129 | // than on the page. |
| 130 | func TestRailIconsAreLabelled(t *testing.T) { |
| 131 | src, err := TemplateSource("layout.html") |
| 132 | if err != nil { |
| 133 | t.Fatal(err) |
| 134 | } |
| 135 | // A control names its glyph either through the icon partial or with an |
| 136 | // svg of its own; the aria-hidden loop below covers both. |
| 137 | glyph := func(s string) bool { |
| 138 | return strings.Contains(s, `{{template "icon" `) || strings.Contains(s, "<svg") |
| 139 | } |
| 140 | tagRe := regexp.MustCompile(`(?s)<a class="railicon".*?</a>|<button [^>]*class="railicon".*?</button>`) |
| 141 | controls := tagRe.FindAllString(src, -1) |
| 142 | if len(controls) < 7 { |
| 143 | t.Fatalf("found %d railicon controls in layout.html, want the rail's full set", len(controls)) |
| 144 | } |
| 145 | for _, c := range controls { |
| 146 | for _, want := range []string{`aria-label="`, `<span class="vh">`} { |
| 147 | if !strings.Contains(c, want) { |
| 148 | t.Errorf("railicon control missing %s: %.80s", want, c) |
| 149 | } |
| 150 | } |
| 151 | if !glyph(c) { |
| 152 | t.Errorf("railicon control draws no glyph: %.80s", c) |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | // Every button in the rail's foot is an icon button under the same |
| 157 | // rule: the Log out form's submit is the only one today. |
| 158 | foot := src[strings.Index(src, `<div class="railfoot">`):] |
| 159 | foot = foot[:strings.Index(foot, "</nav>")] |
| 160 | buttons := regexp.MustCompile(`(?s)<button.*?</button>`).FindAllString(foot, -1) |
| 161 | if len(buttons) == 0 { |
| 162 | t.Fatal("no button in the rail foot") |
| 163 | } |
| 164 | for _, b := range buttons { |
| 165 | for _, want := range []string{`aria-label="`, `<span class="vh">`} { |
| 166 | if !strings.Contains(b, want) { |
| 167 | t.Errorf("rail foot button missing %s: %.80s", want, b) |
| 168 | } |
| 169 | } |
| 170 | if !glyph(b) { |
| 171 | t.Errorf("rail foot button draws no glyph: %.80s", b) |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | // No decorative graphic anywhere in the layout is exposed, the brand |
| 176 | // mark included: the link around it carries the name. |
| 177 | for _, svg := range regexp.MustCompile(`<svg[^>]*>`).FindAllString(src, -1) { |
| 178 | if !strings.Contains(svg, `aria-hidden="true"`) { |
| 179 | t.Errorf("svg without aria-hidden: %s", svg) |
| 180 | } |
| 181 | } |
| 182 | } |