internal/httpd/inputlabels_test.go
70 lines · 2323 bytes
1package httpd
2
3import (
4 "regexp"
5 "strings"
6 "testing"
7
8 "gitbay.org/gitbay/internal/web"
9)
10
11var (
12 inputTag = regexp.MustCompile(`<input\b[^>]*>`)
13 labelFor = regexp.MustCompile(`<label[^>]*\bfor="([^"]+)"`)
14 labelWraps = regexp.MustCompile(`(?s)<label\b[^>]*>.*?</label>`)
15 attrID = regexp.MustCompile(`\bid="([^"]+)"`)
16 attrType = regexp.MustCompile(`\btype="([^"]+)"`)
17 ariaLabel = regexp.MustCompile(`\baria-label(?:ledby)?="`)
18)
19
20// Every input a person types into needs an accessible name: a <label for>
21// pointing at its id, or an aria-label. A placeholder is not one — it
22// disappears on focus and screen readers are not required to announce it.
23//
24// #133 fixed this class across the templates and missed the topics-remove
25// field, which the first SonarCloud scan then found
26// (Web:InputWithoutLabelCheck, #153). This is the guard that was absent.
27func TestEveryInputHasAnAccessibleName(t *testing.T) {
28 // Types that carry their own name or take no input.
29 exempt := map[string]bool{"hidden": true, "submit": true, "button": true, "reset": true, "image": true}
30 for _, name := range web.Pages() {
31 src, err := web.TemplateSource(name)
32 if err != nil {
33 t.Fatalf("%s: %v", name, err)
34 }
35 labelled := map[string]bool{}
36 for _, m := range labelFor.FindAllStringSubmatch(src, -1) {
37 labelled[m[1]] = true
38 }
39 // `<label>Name <input></label>` associates implicitly and is as
40 // good as `for`/`id`. Six inputs use it, and a check that knew
41 // only the explicit form would report every one of them.
42 wrapped := labelWraps.FindAllStringIndex(src, -1)
43 inWrappedLabel := func(at int) bool {
44 for _, span := range wrapped {
45 if at >= span[0] && at < span[1] {
46 return true
47 }
48 }
49 return false
50 }
51 for _, at := range inputTag.FindAllStringIndex(src, -1) {
52 tag := src[at[0]:at[1]]
53 typ := "text"
54 if m := attrType.FindStringSubmatch(tag); m != nil {
55 typ = m[1]
56 }
57 if exempt[typ] || ariaLabel.MatchString(tag) || inWrappedLabel(at[0]) {
58 continue
59 }
60 m := attrID.FindStringSubmatch(tag)
61 if m == nil {
62 t.Errorf("%s: input has no id and is not inside a <label>: %s", name, strings.TrimSpace(tag))
63 continue
64 }
65 if !labelled[m[1]] {
66 t.Errorf("%s: input id=%q has no <label for>: %s", name, m[1], strings.TrimSpace(tag))
67 }
68 }
69 }
70}