rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1//go:build browser
2
3package check
4
5import (
6 "context"
7 "errors"
8 "net/http"
9 "sort"
10 "testing"
11 "time"
12
13 "github.com/chromedp/chromedp"
14
15 "amadan.net/rastrillo/fiesta/internal/axe"
16 "amadan.net/rastrillo/fiesta/internal/harness"
17)
18
19// axePage is a page with the stylesheets and three planted WCAG
20// failures, each a rule of its own: no lang on <html>
21// (html-has-lang), an image with no alt (image-alt) and a text input
22// with no label (label). Three rather than one so that "sorted by id"
23// is a claim about an order and not about a list of one.
24const axePage = `<!doctype html><html><head><meta charset="utf-8"><title>axe</title>` +
25 `<link rel="stylesheet" href="/tokens.css"><link rel="stylesheet" href="/theme.css"></head>` +
26 `<body><main id="app"><h1>Hello</h1><p>Some text with a <a href="#x">link</a>.</p>` +
27 `<button rst-btn id="b">Save</button><input type="text" id="unlabelled">` +
28 `<img id="bad" src="/images/a.svg" width="10" height="10"></main></body></html>`
29
30// TestAxeScanFindsFloorsAndSorts is the gate's own control, ahead of
31// the drives that scan with it: the three planted failures are found
32// and come back in id order, Run is Scan's violations, and a scan that
33// reaches almost nothing (a selector matching no element, or a lone
34// button under the five-rule floor) is ErrNothingScanned rather than
35// a clean result.
36func TestAxeScanFindsFloorsAndSorts(t *testing.T) {
37 pages := map[string]string{"/": axePage}
38 rig := harness.New(t, func(string) http.Handler { return sizingMux(t, pages) })
39 ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
40 defer cancel()
41 mustRun(t, ctx, chromedp.Navigate(rig.Origin+"/"), chromedp.WaitVisible("#app", chromedp.ByQuery))
42
43 res, err := axe.Scan(ctx, "", nil)
44 if err != nil {
45 t.Fatalf("Scan(whole document): %v", err)
46 }
47 if len(res.Incomplete) > 0 {
48 t.Logf("needs review, axe could not decide: %v", res.Incomplete)
49 }
50 ids := make([]string, 0, len(res.Violations))
51 for _, v := range res.Violations {
52 ids = append(ids, v.ID)
53 if len(v.Nodes) == 0 {
54 t.Errorf("%s names no node; a failure with no selector is not a report", v.ID)
55 }
56 }
57 for _, want := range []string{"html-has-lang", "image-alt", "label"} {
58 found := false
59 for _, id := range ids {
60 found = found || id == want
61 }
62 if !found {
63 t.Errorf("the planted %s violation was not reported; got %v", want, ids)
64 }
65 }
66 if len(ids) < 2 {
67 t.Fatalf("%d violation(s) reported; an order of fewer than two proves nothing", len(ids))
68 }
69 if !sort.StringsAreSorted(ids) {
70 t.Errorf("violations are not sorted by id: %v", ids)
71 }
72
73 vs, err := axe.Run(ctx, "", nil)
74 if err != nil {
75 t.Fatalf("Run(whole document): %v", err)
76 }
77 if len(vs) != len(res.Violations) {
78 t.Errorf("Run reports %d violations, Scan %d", len(vs), len(res.Violations))
79 }
80 for i := range vs {
81 if i < len(res.Violations) && vs[i].ID != res.Violations[i].ID {
82 t.Errorf("Run's violation %d is %s, Scan's is %s", i, vs[i].ID, res.Violations[i].ID)
83 }
84 }
85
86 // A scan of nothing is ErrNothingScanned, by either route to it.
87 if _, err := axe.Scan(ctx, "#no-such-element", nil); !errors.Is(err, axe.ErrNothingScanned) {
88 t.Errorf("a selector matching nothing gave %v, want ErrNothingScanned", err)
89 }
90 if _, err := axe.Scan(ctx, "#b", nil); !errors.Is(err, axe.ErrNothingScanned) {
91 t.Errorf("a lone button gave %v, want ErrNothingScanned (under the five-rule floor)", err)
92 }
93
94 // The caller's copy of the default ruleset is its own.
95 tags := axe.DefaultTags()
96 tags[0] = "best-practice"
97 if got := axe.DefaultTags(); got[0] != "wcag2a" {
98 t.Errorf("DefaultTags() hands out its own backing array; a caller changed it to %v", got)
99 }
100 rig.Screen("#app", "the axe page")
101}
102