diff --git a/internal/check/check_a11y_test.go b/internal/check/check_a11y_test.go new file mode 100644 index 0000000..f8aca28 --- /dev/null +++ b/internal/check/check_a11y_test.go @@ -0,0 +1,441 @@ +package check + +import ( + "context" + "strings" + "testing" + + "github.com/GrayCodeAI/inspect/internal/crawler" +) + +// This file was split out of check_test.go for readability (mechanical move; no behavior change). + +// --- Advanced A11y Tests --- + +func TestCheckARIA_InvalidRole(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + `
Content
`) + + findings := checkARIA(page) + + found := false + for _, f := range findings { + if strings.Contains(f.Message, "Invalid ARIA role") && strings.Contains(f.Message, "banana") { + found = true + if f.Severity != SeverityMedium { + t.Errorf("expected medium severity for invalid role, got %v", f.Severity) + } + } + } + if !found { + t.Error("expected finding for invalid ARIA role 'banana'") + } +} + +func TestCheckARIA_ValidRole(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + `
Nav content
`) + + findings := checkARIA(page) + + for _, f := range findings { + if strings.Contains(f.Message, "Invalid ARIA role") { + t.Errorf("should not flag valid ARIA role: %s", f.Message) + } + } +} + +func TestCheckARIA_PositiveTabindex(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + `
Content
`) + + findings := checkARIA(page) + + found := false + for _, f := range findings { + if strings.Contains(f.Message, "Positive tabindex") { + found = true + if f.Severity != SeverityMedium { + t.Errorf("expected medium severity, got %v", f.Severity) + } + } + } + if !found { + t.Error("expected finding for positive tabindex") + } +} + +func TestCheckARIA_ZeroTabindex(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + `
Content
`) + + findings := checkARIA(page) + + for _, f := range findings { + if strings.Contains(f.Message, "Positive tabindex") { + t.Error("tabindex=0 should not be flagged") + } + } +} + +func TestCheckARIA_NegativeTabindex(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + `
Content
`) + + findings := checkARIA(page) + + for _, f := range findings { + if strings.Contains(f.Message, "Positive tabindex") { + t.Error("tabindex=-1 should not be flagged as positive tabindex") + } + } +} + +func TestCheckARIA_AriaHiddenOnFocusable(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + ``) + + findings := checkARIA(page) + + found := false + for _, f := range findings { + if strings.Contains(f.Message, "Focusable element is aria-hidden") { + found = true + if f.Severity != SeverityHigh { + t.Errorf("expected high severity, got %v", f.Severity) + } + } + } + if !found { + t.Error("expected finding for aria-hidden on focusable element") + } +} + +func TestCheckARIA_AriaHiddenOnNonFocusable(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + ``) + + findings := checkARIA(page) + + for _, f := range findings { + if strings.Contains(f.Message, "Focusable element is aria-hidden") { + t.Error("should not flag aria-hidden on non-focusable element") + } + } +} + +func TestCheckARIA_InteractiveElementRemovedFromTabOrder(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + ``) + + findings := checkARIA(page) + + found := false + for _, f := range findings { + if strings.Contains(f.Message, "Interactive element removed from tab order") { + found = true + } + } + if !found { + t.Error("expected finding for interactive element with tabindex=-1") + } +} + +func TestCheckARIA_RoleRequiringNameWithoutName(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + `
`) + + findings := checkARIA(page) + + found := false + for _, f := range findings { + if strings.Contains(f.Message, "has no accessible name") { + found = true + if f.Severity != SeverityHigh { + t.Errorf("expected high severity, got %v", f.Severity) + } + } + } + if !found { + t.Error("expected finding for role=button without accessible name") + } +} + +func TestCheckARIA_RoleRequiringNameWithAriaLabel(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + `
`) + + findings := checkARIA(page) + + for _, f := range findings { + if strings.Contains(f.Message, "has no accessible name") { + t.Error("should not flag element with aria-label") + } + } +} + +func TestCheckARIA_EmptyBody(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, "") + findings := checkARIA(page) + if len(findings) != 0 { + t.Error("should not produce findings for empty body") + } +} + +func TestCheckLandmarks_AllPresent(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + ` +
Header
+ +
Content
+ + + `) + + findings := checkLandmarks(page) + + for _, f := range findings { + if strings.Contains(f.Message, "missing") { + t.Errorf("should not flag missing landmark when all present: %s", f.Message) + } + } +} + +func TestCheckLandmarks_MissingNav(t *testing.T) { + page := makePage("https://example.com", 200, map[string]string{"Content-Type": "text/html"}, + `
H
Content
`) + + findings := checkLandmarks(page) + + found := false + for _, f := range findings { + if strings.Contains(f.Message, "missing