rails_accessibility_testing 1.5.7 → 1.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/README.md +9 -1
- data/docs_site/architecture.md +39 -1
- data/docs_site/getting_started.md +3 -3
- data/docs_site/index.md +1 -1
- data/exe/a11y_live_scanner +22 -0
- data/exe/a11y_static_scanner +22 -0
- data/lib/generators/rails_a11y/install/templates/accessibility.yml.erb +6 -0
- data/lib/generators/rails_a11y/install/templates/all_pages_accessibility_spec.rb.erb +1 -1
- data/lib/rails_accessibility_testing/accessibility_helper.rb +68 -3
- data/lib/rails_accessibility_testing/checks/heading_check.rb +24 -11
- data/lib/rails_accessibility_testing/checks/interactive_elements_check.rb +10 -2
- data/lib/rails_accessibility_testing/composed_page_scanner.rb +326 -0
- data/lib/rails_accessibility_testing/config/yaml_loader.rb +3 -0
- data/lib/rails_accessibility_testing/erb_extractor.rb +7 -3
- data/lib/rails_accessibility_testing/rspec_integration.rb +17 -0
- data/lib/rails_accessibility_testing/static_file_scanner.rb +60 -5
- data/lib/rails_accessibility_testing/version.rb +1 -1
- data/lib/rails_accessibility_testing/view_composition_builder.rb +313 -0
- data/lib/rails_accessibility_testing.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f8824e08bc8db71c7a6598998b44c6b39826f367398c8c98a1ef85366a044e2
|
|
4
|
+
data.tar.gz: bd9337c4b6b89deae25b1adfc9dbec92645c934703e930d5f5048c71a59117b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd9fdfced6342b861ae5d1db4ee852b4c566c9572d62dac463fdc0d7566d37ef1201201c36c3af4d45393d24af7f0a504f76f7eaf25322e7025a577eeb3d94dc
|
|
7
|
+
data.tar.gz: a2672850fd85a29fb3a304243f1367215c282a34e63df58f5878d274dd31176465afaad23734699124bd6649032b68ff578f0d6a1ae4b6ec853d2f4ffd7ef23c
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,88 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.10] - 2024-12-01
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Configuration flag renamed**: The `enabled` flag has been renamed to `accessibility_enabled` for better clarity. The old `enabled` key is still supported for backward compatibility.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **Composed Page Scanning**: The static scanner now analyzes complete page compositions (layout + view + partials) for page-level accessibility checks, eliminating false positives for heading hierarchy, ARIA landmarks, duplicate IDs, and heading issues.
|
|
15
|
+
- **View Composition Builder**: New comprehensive system that traces the complete page structure by finding all partials recursively across all directories in `app/views`.
|
|
16
|
+
- **Exhaustive Partial Detection**: Enhanced partial detection that finds partials in any subdirectory, not just specific folders. Works for deeply nested namespaces (e.g., `collections/collection_questions`).
|
|
17
|
+
- **ERB Content Detection**: Improved handling of ERB expressions (`<%= ... %>`) to prevent false positives for empty headings and missing accessible names on buttons.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Heading Hierarchy Checks**: Now performed on the complete composed page instead of individual files, preventing false positives when H1 is in layout or partials.
|
|
21
|
+
- **ARIA Landmarks Checks**: Now checks for `<main>` landmark across the entire composed page.
|
|
22
|
+
- **Duplicate ID Checks**: Now checks for duplicate IDs across the complete page composition.
|
|
23
|
+
- **Empty Heading Checks**: Now checks for empty headings across the complete composed page.
|
|
24
|
+
- **Partial Search**: Enhanced to traverse ALL folders in `app/views` recursively using `Dir.glob`, making it a general solution that works for any folder structure.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Fixed false positive for "Page missing MAIN landmark" when `<main>` is in the layout file.
|
|
28
|
+
- Fixed false positive for "Page has h2 but no h1 heading" when H1 is in a partial rendered via `render @model`.
|
|
29
|
+
- Fixed false positive for "Heading hierarchy skipped (h0 to h2)" when first heading is h2.
|
|
30
|
+
- Fixed false positive for "Duplicate ID 'ERB_CONTENT'" by filtering out ERB placeholder strings.
|
|
31
|
+
- Fixed partial detection for Rails shorthand patterns (`render @model`, `render collection: @models`).
|
|
32
|
+
- Fixed namespaced partial path resolution (e.g., `layouts/_advance_search`).
|
|
33
|
+
- Fixed path normalization to handle both relative and absolute paths consistently.
|
|
34
|
+
|
|
35
|
+
### Performance
|
|
36
|
+
- Optimized exhaustive directory search to return first match found instead of checking all matches.
|
|
37
|
+
|
|
38
|
+
## [Unreleased]
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- **Composed Page Scanning**: The static scanner now analyzes complete page compositions (layout + view + partials) for page-level accessibility checks, eliminating false positives for heading hierarchy, ARIA landmarks, duplicate IDs, and heading issues.
|
|
42
|
+
- **View Composition Builder**: New comprehensive system that traces the complete page structure by finding all partials recursively across all directories in `app/views`.
|
|
43
|
+
- **Exhaustive Partial Detection**: Enhanced partial detection that finds partials in any subdirectory, not just specific folders. Works for deeply nested namespaces (e.g., `collections/collection_questions`).
|
|
44
|
+
- **ERB Content Detection**: Improved handling of ERB expressions (`<%= ... %>`) to prevent false positives for empty headings and missing accessible names on buttons.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- **Heading Hierarchy Checks**: Now performed on the complete composed page instead of individual files, preventing false positives when H1 is in layout or partials.
|
|
48
|
+
- **ARIA Landmarks Checks**: Now checks for `<main>` landmark across the entire composed page.
|
|
49
|
+
- **Duplicate ID Checks**: Now checks for duplicate IDs across the complete page composition.
|
|
50
|
+
- **Empty Heading Checks**: Now checks for empty headings across the complete composed page.
|
|
51
|
+
- **Partial Search**: Enhanced to traverse ALL folders in `app/views` recursively using `Dir.glob`, making it a general solution that works for any folder structure.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
- Fixed false positive for "Page missing MAIN landmark" when `<main>` is in the layout file.
|
|
55
|
+
- Fixed false positive for "Page has h2 but no h1 heading" when H1 is in a partial rendered via `render @model`.
|
|
56
|
+
- Fixed false positive for "Heading hierarchy skipped (h0 to h2)" when first heading is h2.
|
|
57
|
+
- Fixed false positive for "Duplicate ID 'ERB_CONTENT'" by filtering out ERB placeholder strings.
|
|
58
|
+
- Fixed partial detection for Rails shorthand patterns (`render @model`, `render collection: @models`).
|
|
59
|
+
- Fixed namespaced partial path resolution (e.g., `layouts/_advance_search`).
|
|
60
|
+
- Fixed path normalization to handle both relative and absolute paths consistently.
|
|
61
|
+
|
|
62
|
+
### Performance
|
|
63
|
+
- Optimized exhaustive directory search to return first match found instead of checking all matches.
|
|
64
|
+
|
|
65
|
+
## [1.5.9] - 2024-12-01
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
- **Force option for manual checks**: Added `force: true` parameter to `check_comprehensive_accessibility` and `check_basic_accessibility` to bypass `enabled: false` setting
|
|
69
|
+
- **Manual check flexibility**: You can now force manual checks to run even when accessibility checks are globally disabled
|
|
70
|
+
|
|
71
|
+
### Improved
|
|
72
|
+
- **Manual check documentation**: Clarified that manual checks can be run separately in RSpec specs regardless of configuration
|
|
73
|
+
|
|
74
|
+
## [1.5.8] - 2024-12-01
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
- **ERB template detection**: Fixed false positives for empty headings and missing accessible names when ERB output tags (`<%= ... %>`) are present
|
|
78
|
+
- **Static scanning**: ERB tags are now replaced with placeholder text so checks can detect that content will be present at runtime
|
|
79
|
+
- **Scanner process management**: Static and live scanners now stay alive (sleep loop) when `enabled: false` instead of exiting, preventing Foreman from killing other processes
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
- **Global enable/disable flag**: Added `enabled` configuration option in `accessibility.yml` to completely disable all accessibility checks (manual and automatic)
|
|
83
|
+
- **ERB content detection**: `ErbExtractor` now replaces `<%= ... %>` with `"ERB_CONTENT"` placeholder instead of removing it
|
|
84
|
+
- **ERB-aware checks**: `HeadingCheck` and `InteractiveElementsCheck` now detect ERB placeholders and skip empty checks
|
|
85
|
+
|
|
86
|
+
### Improved
|
|
87
|
+
- **Static scanner behavior**: When `enabled: false`, scanner shows message and keeps running instead of exiting
|
|
88
|
+
- **Live scanner behavior**: When `enabled: false`, scanner shows message and keeps running instead of exiting
|
|
89
|
+
|
|
8
90
|
## [1.5.7] - 2024-12-01
|
|
9
91
|
|
|
10
92
|
### Fixed
|
data/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
**The RSpec + RuboCop of accessibility for Rails. Catch WCAG violations before they reach production.**
|
|
9
9
|
|
|
10
|
-
**Current Version:** 1.5.
|
|
10
|
+
**Current Version:** 1.5.9
|
|
11
11
|
|
|
12
12
|
📖 **[📚 Full Documentation](https://rayraycodes.github.io/rails-accessibility-testing/)** | [💻 GitHub](https://github.com/rayraycodes/rails-accessibility-testing) | [💎 RubyGems](https://rubygems.org/gems/rails_accessibility_testing)
|
|
13
13
|
|
|
@@ -51,6 +51,13 @@ Rails Accessibility Testing fills a critical gap in the Rails testing ecosystem.
|
|
|
51
51
|
- **Accurate error counting**: Properly tracks and displays error/warning counts
|
|
52
52
|
- **Persistent output**: Errors stay visible in terminal (no clearing)
|
|
53
53
|
|
|
54
|
+
#### 🔍 Composed Page Scanning (NEW in 1.5.9+)
|
|
55
|
+
- **Complete page analysis**: Analyzes full page composition (layout + view + partials) for page-level checks
|
|
56
|
+
- **Eliminates false positives**: No more false positives when H1 is in layout or partials
|
|
57
|
+
- **Exhaustive partial finding**: Traverses ALL folders recursively to find all partials
|
|
58
|
+
- **Works for any structure**: General solution that works for any Rails folder structure (collections, items, profiles, loan_requests, etc.)
|
|
59
|
+
- **Page-level checks**: Heading hierarchy, ARIA landmarks, duplicate IDs, and empty headings checked across complete page
|
|
60
|
+
|
|
54
61
|
#### 🔍 Smart View File Detection
|
|
55
62
|
- **Intelligent matching**: Automatically finds view files even when action names don't match
|
|
56
63
|
- **Controller directory scanning**: Searches all view files to find the correct template
|
|
@@ -61,6 +68,7 @@ Rails Accessibility Testing fills a critical gap in the Rails testing ecosystem.
|
|
|
61
68
|
- **Optimized DOM queries**: Faster image alt checks without JavaScript evaluation
|
|
62
69
|
- **Removed delays**: Eliminated unnecessary sleep calls in live scanner
|
|
63
70
|
- **Efficient scanning**: ~25-30% faster page scans
|
|
71
|
+
- **Optimized directory search**: Returns first match found instead of checking all matches
|
|
64
72
|
|
|
65
73
|
#### 🎨 Enhanced Developer Experience
|
|
66
74
|
- **Real-time progress**: Step-by-step feedback during accessibility checks
|
data/docs_site/architecture.md
CHANGED
|
@@ -3,7 +3,45 @@ layout: default
|
|
|
3
3
|
title: Architecture
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Architecture
|
|
6
|
+
# Architecture
|
|
7
|
+
|
|
8
|
+
## Composed Page Scanning
|
|
9
|
+
|
|
10
|
+
The gem now uses **composed page scanning** for page-level accessibility checks. This ensures that checks like heading hierarchy, ARIA landmarks, and duplicate IDs are evaluated against the complete rendered page (layout + view + partials), not individual files.
|
|
11
|
+
|
|
12
|
+
### View Composition Builder
|
|
13
|
+
|
|
14
|
+
The `ViewCompositionBuilder` class traces the complete page structure:
|
|
15
|
+
|
|
16
|
+
1. **Finds Layout File**: Identifies the layout file (defaults to `application.html.erb`)
|
|
17
|
+
2. **Finds View File**: The main view file being rendered
|
|
18
|
+
3. **Recursively Finds Partials**: Discovers all partials rendered in the view, including:
|
|
19
|
+
- Partials in the same directory
|
|
20
|
+
- Partials in `layouts/`, `shared/`, `application/`
|
|
21
|
+
- Partials in any subdirectory (exhaustive search)
|
|
22
|
+
- Nested partials (partials within partials)
|
|
23
|
+
|
|
24
|
+
### Partial Detection
|
|
25
|
+
|
|
26
|
+
The gem detects all Rails render patterns:
|
|
27
|
+
- `render 'partial'`
|
|
28
|
+
- `render partial: 'partial'`
|
|
29
|
+
- `render @model` (Rails shorthand)
|
|
30
|
+
- `render collection: @models`
|
|
31
|
+
- `render partial: 'item', collection: @items`
|
|
32
|
+
- `render partial: 'form', locals: {...}`
|
|
33
|
+
|
|
34
|
+
### Exhaustive Folder Traversal
|
|
35
|
+
|
|
36
|
+
The partial search traverses ALL folders in `app/views` recursively using `Dir.glob`, ensuring partials are found regardless of their location:
|
|
37
|
+
- `app/views/collections/`
|
|
38
|
+
- `app/views/collections/collection_questions/`
|
|
39
|
+
- `app/views/items/`
|
|
40
|
+
- `app/views/profiles/`
|
|
41
|
+
- `app/views/loan_requests/`
|
|
42
|
+
- Any other nested structure
|
|
43
|
+
|
|
44
|
+
This makes it a general solution that works for any Rails application structure. Overview
|
|
7
45
|
|
|
8
46
|
This guide explains how Rails Accessibility Testing works under the hood in simple terms.
|
|
9
47
|
|
|
@@ -144,12 +144,12 @@ Rails Accessibility Testing runs **11 comprehensive checks** automatically. Thes
|
|
|
144
144
|
1. **Form Labels** - All form inputs have associated labels
|
|
145
145
|
2. **Image Alt Text** - All images have descriptive alt attributes
|
|
146
146
|
3. **Interactive Elements** - Buttons, links have accessible names
|
|
147
|
-
4. **Heading Hierarchy** - Proper h1-h6 structure without skipping levels
|
|
147
|
+
4. **Heading Hierarchy** - Proper h1-h6 structure without skipping levels (checked across complete page: layout + view + partials)
|
|
148
148
|
5. **Keyboard Accessibility** - All interactive elements are keyboard accessible
|
|
149
|
-
6. **ARIA Landmarks** - Proper use of ARIA landmark roles
|
|
149
|
+
6. **ARIA Landmarks** - Proper use of ARIA landmark roles (checked across complete page: layout + view + partials)
|
|
150
150
|
7. **Form Error Associations** - Form errors are properly linked to form fields
|
|
151
151
|
8. **Table Structure** - Tables have proper headers
|
|
152
|
-
9. **Duplicate IDs** - No duplicate ID attributes
|
|
152
|
+
9. **Duplicate IDs** - No duplicate ID attributes (checked across complete page: layout + view + partials)
|
|
153
153
|
10. **Skip Links** - Skip navigation links present
|
|
154
154
|
11. **Color Contrast** - Text meets WCAG contrast requirements (optional, disabled by default)
|
|
155
155
|
|
data/docs_site/index.md
CHANGED
|
@@ -7,7 +7,7 @@ title: Home
|
|
|
7
7
|
|
|
8
8
|
**The RSpec + RuboCop of accessibility for Rails. Catch WCAG violations before they reach production.**
|
|
9
9
|
|
|
10
|
-
**Version:** 1.5.
|
|
10
|
+
**Version:** 1.5.9
|
|
11
11
|
|
|
12
12
|
Rails Accessibility Testing is a comprehensive, opinionated but configurable gem that makes accessibility testing as natural as unit testing. It integrates seamlessly into your Rails workflow, catching accessibility issues as you code—not after deployment.
|
|
13
13
|
|
data/exe/a11y_live_scanner
CHANGED
|
@@ -29,6 +29,28 @@ begin
|
|
|
29
29
|
unless defined?(RailsAccessibilityTesting::AccessibilityHelper)
|
|
30
30
|
raise LoadError, "RailsAccessibilityTesting::AccessibilityHelper not found after requiring gem"
|
|
31
31
|
end
|
|
32
|
+
|
|
33
|
+
# Check if accessibility checks are globally disabled
|
|
34
|
+
begin
|
|
35
|
+
require 'rails_accessibility_testing/config/yaml_loader'
|
|
36
|
+
profile = defined?(Rails) && Rails.env.test? ? :test : :development
|
|
37
|
+
config = RailsAccessibilityTesting::Config::YamlLoader.load(profile: profile)
|
|
38
|
+
# Support both 'accessibility_enabled' (new) and 'enabled' (legacy) for backward compatibility
|
|
39
|
+
enabled = config.fetch('accessibility_enabled', config.fetch('enabled', true))
|
|
40
|
+
unless enabled
|
|
41
|
+
puts "⏸️ Accessibility checks are disabled (accessibility_enabled: false in config/accessibility.yml)"
|
|
42
|
+
puts " Set accessibility_enabled: true to enable accessibility scanning"
|
|
43
|
+
puts " Scanner process will remain running but will not scan files"
|
|
44
|
+
puts ""
|
|
45
|
+
# Keep process alive so Foreman doesn't kill other processes
|
|
46
|
+
# Sleep indefinitely until interrupted
|
|
47
|
+
loop do
|
|
48
|
+
sleep 60
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
# If config can't be loaded, continue (assume enabled)
|
|
53
|
+
end
|
|
32
54
|
rescue LoadError => e
|
|
33
55
|
$stderr.puts "❌ Error loading rails_accessibility_testing gem: #{e.message}"
|
|
34
56
|
$stderr.puts " Make sure the gem is installed: bundle install"
|
data/exe/a11y_static_scanner
CHANGED
|
@@ -23,6 +23,28 @@ begin
|
|
|
23
23
|
require 'rails_accessibility_testing'
|
|
24
24
|
require 'rails_accessibility_testing/static_file_scanner'
|
|
25
25
|
require 'rails_accessibility_testing/file_change_tracker'
|
|
26
|
+
|
|
27
|
+
# Check if accessibility checks are globally disabled
|
|
28
|
+
begin
|
|
29
|
+
require 'rails_accessibility_testing/config/yaml_loader'
|
|
30
|
+
profile = defined?(Rails) && Rails.env.test? ? :test : :development
|
|
31
|
+
config = RailsAccessibilityTesting::Config::YamlLoader.load(profile: profile)
|
|
32
|
+
# Support both 'accessibility_enabled' (new) and 'enabled' (legacy) for backward compatibility
|
|
33
|
+
enabled = config.fetch('accessibility_enabled', config.fetch('enabled', true))
|
|
34
|
+
unless enabled
|
|
35
|
+
puts "⏸️ Accessibility checks are disabled (accessibility_enabled: false in config/accessibility.yml)"
|
|
36
|
+
puts " Set accessibility_enabled: true to enable accessibility scanning"
|
|
37
|
+
puts " Scanner process will remain running but will not scan files"
|
|
38
|
+
puts ""
|
|
39
|
+
# Keep process alive so Foreman doesn't kill other processes
|
|
40
|
+
# Sleep indefinitely until interrupted
|
|
41
|
+
loop do
|
|
42
|
+
sleep 60
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
# If config can't be loaded, continue (assume enabled)
|
|
47
|
+
end
|
|
26
48
|
rescue LoadError => e
|
|
27
49
|
$stderr.puts "❌ Error loading rails_accessibility_testing gem: #{e.message}"
|
|
28
50
|
$stderr.puts " Make sure the gem is installed: bundle install"
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
# This file configures accessibility checks for your Rails application.
|
|
4
4
|
# See https://github.com/your-org/rails-a11y for full documentation.
|
|
5
5
|
|
|
6
|
+
# Global enable/disable flag for all accessibility checks
|
|
7
|
+
# Set to false to completely disable all accessibility checks (manual and automatic)
|
|
8
|
+
# When false, check_comprehensive_accessibility and automatic checks will be skipped
|
|
9
|
+
# Default: true
|
|
10
|
+
accessibility_enabled: true
|
|
11
|
+
|
|
6
12
|
# WCAG compliance level (A, AA, AAA)
|
|
7
13
|
wcag_level: AA
|
|
8
14
|
|
|
@@ -142,7 +142,7 @@ RSpec.describe 'All Pages Accessibility', type: :system do
|
|
|
142
142
|
puts format_static_errors(errors, warnings)
|
|
143
143
|
|
|
144
144
|
if errors.any?
|
|
145
|
-
|
|
145
|
+
puts "Found #{errors.length} accessibility error#{'s' if errors.length != 1} in #{view_file}"
|
|
146
146
|
end
|
|
147
147
|
else
|
|
148
148
|
puts "✅ #{view_file}: No errors found"
|
|
@@ -46,14 +46,34 @@ module RailsAccessibilityTesting
|
|
|
46
46
|
# render partial: "partial_name"
|
|
47
47
|
# render 'path/to/partial'
|
|
48
48
|
# render partial: 'path/to/partial'
|
|
49
|
+
# render @model (Rails shorthand - renders _model.html.erb)
|
|
50
|
+
# render collection: @models (renders _model.html.erb for each)
|
|
51
|
+
# render partial: 'partial', locals: {...}
|
|
52
|
+
# render partial: 'partial', collection: @items
|
|
49
53
|
# <%= render 'partial_name' %>
|
|
50
54
|
# <%= render partial: 'partial_name' %>
|
|
55
|
+
# <%= render @model %>
|
|
56
|
+
# <%= render collection: @models %>
|
|
51
57
|
|
|
52
58
|
patterns = [
|
|
59
|
+
# Standard partial renders (with or without partial: keyword)
|
|
53
60
|
/render\s+(?:partial:\s*)?['"]([^'"]+)['"]/,
|
|
54
61
|
/render\s+(?:partial:\s*)?:(\w+)/,
|
|
55
62
|
/<%=?\s*render\s+(?:partial:\s*)?['"]([^'"]+)['"]/,
|
|
56
|
-
/<%=?\s*render\s+(?:partial:\s*)?:(\w+)
|
|
63
|
+
/<%=?\s*render\s+(?:partial:\s*)?:(\w+)/,
|
|
64
|
+
# Rails shorthand: render @model (renders _model.html.erb)
|
|
65
|
+
/render\s+@(\w+)/,
|
|
66
|
+
/<%=?\s*render\s+@(\w+)/,
|
|
67
|
+
# Collection renders: render collection: @models (renders _model.html.erb)
|
|
68
|
+
/render\s+collection:\s*@(\w+)/,
|
|
69
|
+
/<%=?\s*render\s+collection:\s*@(\w+)/,
|
|
70
|
+
# Partial with collection: render partial: 'item', collection: @items
|
|
71
|
+
/render\s+partial:\s*['"]([^'"]+)['"]\s*,\s*collection:/,
|
|
72
|
+
/<%=?\s*render\s+partial:\s*['"]([^'"]+)['"]\s*,\s*collection:/,
|
|
73
|
+
# Partial with locals: render partial: 'form', locals: {...}
|
|
74
|
+
# (extract just the partial name, ignore locals)
|
|
75
|
+
/render\s+partial:\s*['"]([^'"]+)['"]\s*,\s*locals:/,
|
|
76
|
+
/<%=?\s*render\s+partial:\s*['"]([^'"]+)['"]\s*,\s*locals:/
|
|
57
77
|
]
|
|
58
78
|
|
|
59
79
|
patterns.each do |pattern|
|
|
@@ -77,6 +97,23 @@ module RailsAccessibilityTesting
|
|
|
77
97
|
end
|
|
78
98
|
end
|
|
79
99
|
|
|
100
|
+
# Also check for Rails shorthand: render @model (renders _model.html.erb)
|
|
101
|
+
# This is separate because it needs special handling
|
|
102
|
+
content.scan(/render\s+@(\w+)/) do |match|
|
|
103
|
+
model_name = match[0]
|
|
104
|
+
partial_name = model_name.underscore
|
|
105
|
+
partial_name = "_#{partial_name}" unless partial_name.start_with?('_')
|
|
106
|
+
partials << partial_name unless partials.include?(partial_name)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Check for collection renders: render collection: @models
|
|
110
|
+
content.scan(/render\s+collection:\s*@(\w+)/) do |match|
|
|
111
|
+
model_name = match[0]
|
|
112
|
+
partial_name = model_name.underscore.singularize # collection uses singular
|
|
113
|
+
partial_name = "_#{partial_name}" unless partial_name.start_with?('_')
|
|
114
|
+
partials << partial_name unless partials.include?(partial_name)
|
|
115
|
+
end
|
|
116
|
+
|
|
80
117
|
partials
|
|
81
118
|
end
|
|
82
119
|
|
|
@@ -210,7 +247,12 @@ module RailsAccessibilityTesting
|
|
|
210
247
|
end
|
|
211
248
|
|
|
212
249
|
# Basic accessibility check - runs 5 basic checks
|
|
213
|
-
|
|
250
|
+
# @param force [Boolean] If true, bypasses the accessibility_enabled: false setting (default: false)
|
|
251
|
+
def check_basic_accessibility(force: false)
|
|
252
|
+
# Check if accessibility checks are globally disabled
|
|
253
|
+
# Manual calls can use force: true to bypass the accessibility_enabled: false setting
|
|
254
|
+
return if !force && accessibility_disabled?
|
|
255
|
+
|
|
214
256
|
@accessibility_errors ||= []
|
|
215
257
|
@accessibility_warnings ||= []
|
|
216
258
|
|
|
@@ -241,8 +283,15 @@ module RailsAccessibilityTesting
|
|
|
241
283
|
|
|
242
284
|
# Full comprehensive check - runs all 11 checks including advanced
|
|
243
285
|
# Uses the RuleEngine and checks from the checks/ folder for consistency
|
|
286
|
+
# @param force [Boolean] If true, bypasses the accessibility_enabled: false setting (default: false)
|
|
244
287
|
# @return [Hash] Hash with :errors and :warnings counts
|
|
245
|
-
def check_comprehensive_accessibility
|
|
288
|
+
def check_comprehensive_accessibility(force: false)
|
|
289
|
+
# Check if accessibility checks are globally disabled - do this FIRST before any output
|
|
290
|
+
# Manual calls can use force: true to bypass the accessibility_enabled: false setting
|
|
291
|
+
if !force && accessibility_disabled?
|
|
292
|
+
return { errors: 0, warnings: 0, page_context: {} }
|
|
293
|
+
end
|
|
294
|
+
|
|
246
295
|
# Note: Page scanning cache is disabled for RSpec tests to ensure accurate error reporting
|
|
247
296
|
# The cache is only used in live scanner to avoid duplicate scans
|
|
248
297
|
|
|
@@ -433,6 +482,22 @@ module RailsAccessibilityTesting
|
|
|
433
482
|
|
|
434
483
|
private
|
|
435
484
|
|
|
485
|
+
# Check if accessibility checks are globally disabled via config
|
|
486
|
+
def accessibility_disabled?
|
|
487
|
+
begin
|
|
488
|
+
require 'rails_accessibility_testing/config/yaml_loader'
|
|
489
|
+
profile = defined?(Rails) && Rails.env.test? ? :test : :development
|
|
490
|
+
config = RailsAccessibilityTesting::Config::YamlLoader.load(profile: profile)
|
|
491
|
+
# Support both 'accessibility_enabled' (new) and 'enabled' (legacy) for backward compatibility
|
|
492
|
+
enabled = config.fetch('accessibility_enabled', config.fetch('enabled', true)) # Default to enabled if not specified
|
|
493
|
+
disabled = !enabled # Return true if disabled
|
|
494
|
+
disabled
|
|
495
|
+
rescue StandardError => e
|
|
496
|
+
# If config can't be loaded, assume enabled
|
|
497
|
+
false
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
436
501
|
# Format timestamp for terminal output (shorter, more readable)
|
|
437
502
|
def format_timestamp_for_terminal
|
|
438
503
|
# Use just time for same-day reports, or full date if different day
|
|
@@ -23,7 +23,7 @@ module RailsAccessibilityTesting
|
|
|
23
23
|
return violations # Warning only, not error
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# Check 1: Missing H1
|
|
26
|
+
# Check 1: Missing H1 (page-level check - checks complete composed page)
|
|
27
27
|
h1_count = headings.count { |h| h.tag_name == 'h1' }
|
|
28
28
|
first_heading = headings.first
|
|
29
29
|
first_heading_level = first_heading ? first_heading.tag_name[1].to_i : nil
|
|
@@ -32,30 +32,35 @@ module RailsAccessibilityTesting
|
|
|
32
32
|
# If the first heading is h2 or higher, provide a more specific message
|
|
33
33
|
if first_heading_level && first_heading_level >= 2
|
|
34
34
|
element_ctx = element_context(first_heading)
|
|
35
|
+
# Mark as page-level check - H1 might be in layout or partial
|
|
36
|
+
element_ctx[:page_level_check] = true
|
|
37
|
+
element_ctx[:check_type] = 'heading_hierarchy'
|
|
35
38
|
violations << violation(
|
|
36
|
-
message: "Page has h#{first_heading_level} but no h1 heading",
|
|
39
|
+
message: "Page has h#{first_heading_level} but no h1 heading (checked complete page: layout + view + partials)",
|
|
37
40
|
element_context: element_ctx,
|
|
38
41
|
wcag_reference: "1.3.1",
|
|
39
|
-
remediation: "Add an <h1> heading
|
|
42
|
+
remediation: "Add an <h1> heading to your page. The H1 can be in the layout, view, or a partial:\n\n<h1>Main Page Title</h1>\n<h#{first_heading_level}>#{first_heading.text}</h#{first_heading_level}>"
|
|
40
43
|
)
|
|
41
44
|
else
|
|
42
45
|
violations << violation(
|
|
43
|
-
message: "Page missing H1 heading",
|
|
44
|
-
element_context: { tag: 'page', text: 'Page has no H1 heading' },
|
|
46
|
+
message: "Page missing H1 heading (checked complete page: layout + view + partials)",
|
|
47
|
+
element_context: { tag: 'page', text: 'Page has no H1 heading', page_level_check: true, check_type: 'heading_hierarchy' },
|
|
45
48
|
wcag_reference: "1.3.1",
|
|
46
|
-
remediation: "Add an <h1> heading to your page:\n\n<h1>Main Page Title</h1>"
|
|
49
|
+
remediation: "Add an <h1> heading to your page. The H1 can be in the layout, view, or a partial:\n\n<h1>Main Page Title</h1>"
|
|
47
50
|
)
|
|
48
51
|
end
|
|
49
52
|
end
|
|
50
53
|
|
|
51
|
-
# Check 2: Multiple H1s (WCAG 1.3.1)
|
|
54
|
+
# Check 2: Multiple H1s (WCAG 1.3.1) - page-level check
|
|
52
55
|
if h1_count > 1
|
|
53
56
|
# Report error for each h1 after the first one
|
|
54
57
|
h1_elements = headings.select { |h| h.tag_name == 'h1' }
|
|
55
58
|
h1_elements[1..-1].each do |h1|
|
|
56
59
|
element_ctx = element_context(h1)
|
|
60
|
+
element_ctx[:page_level_check] = true
|
|
61
|
+
element_ctx[:check_type] = 'heading_hierarchy'
|
|
57
62
|
violations << violation(
|
|
58
|
-
message: "Page has multiple h1 headings (#{h1_count} total) - only one h1 should be used per page",
|
|
63
|
+
message: "Page has multiple h1 headings (#{h1_count} total) - only one h1 should be used per page (checked complete page: layout + view + partials)",
|
|
59
64
|
element_context: element_ctx,
|
|
60
65
|
wcag_reference: "1.3.1",
|
|
61
66
|
remediation: "Use only one <h1> per page. Convert additional h1s to h2 or lower:\n\n<h1>Main Title</h1>\n<h2>Section Title</h2>"
|
|
@@ -63,14 +68,16 @@ module RailsAccessibilityTesting
|
|
|
63
68
|
end
|
|
64
69
|
end
|
|
65
70
|
|
|
66
|
-
# Check 3: Heading hierarchy skipped levels (WCAG 1.3.1)
|
|
71
|
+
# Check 3: Heading hierarchy skipped levels (WCAG 1.3.1) - page-level check
|
|
67
72
|
previous_level = 0
|
|
68
73
|
headings.each do |heading|
|
|
69
74
|
current_level = heading.tag_name[1].to_i
|
|
70
75
|
if current_level > previous_level + 1
|
|
71
76
|
element_ctx = element_context(heading)
|
|
77
|
+
element_ctx[:page_level_check] = true
|
|
78
|
+
element_ctx[:check_type] = 'heading_hierarchy'
|
|
72
79
|
violations << violation(
|
|
73
|
-
message: "Heading hierarchy skipped (h#{previous_level} to h#{current_level})",
|
|
80
|
+
message: "Heading hierarchy skipped (h#{previous_level} to h#{current_level}) - checked complete page: layout + view + partials",
|
|
74
81
|
element_context: element_ctx,
|
|
75
82
|
wcag_reference: "1.3.1",
|
|
76
83
|
remediation: "Fix the heading hierarchy. Don't skip levels. Use h#{previous_level + 1} instead of h#{current_level}."
|
|
@@ -82,8 +89,14 @@ module RailsAccessibilityTesting
|
|
|
82
89
|
# Check 4: Empty headings (WCAG 4.1.2)
|
|
83
90
|
headings.each do |heading|
|
|
84
91
|
heading_text = heading.text.strip
|
|
92
|
+
|
|
93
|
+
# Check if heading contains ERB placeholder (for static scanning)
|
|
94
|
+
# ErbExtractor replaces <%= ... %> with "ERB_CONTENT" so we can detect it
|
|
95
|
+
has_erb_content = heading_text.include?('ERB_CONTENT')
|
|
96
|
+
|
|
85
97
|
# Check if heading is empty or only contains whitespace/formatting
|
|
86
|
-
if
|
|
98
|
+
# Skip if it contains ERB content (will be populated at runtime)
|
|
99
|
+
if (heading_text.empty? || heading_text.match?(/^\s*$/)) && !has_erb_content
|
|
87
100
|
element_ctx = element_context(heading)
|
|
88
101
|
violations << violation(
|
|
89
102
|
message: "Empty heading detected (<#{heading.tag_name}>) - headings must have accessible text",
|
|
@@ -46,16 +46,24 @@ module RailsAccessibilityTesting
|
|
|
46
46
|
aria_labelledby = element[:"aria-labelledby"]
|
|
47
47
|
title = element[:title]
|
|
48
48
|
|
|
49
|
+
# Check if element contains ERB placeholder (for static scanning)
|
|
50
|
+
# ErbExtractor replaces <%= ... %> with "ERB_CONTENT" so we can detect it
|
|
51
|
+
# If text includes "ERB_CONTENT", it means there's ERB code that will produce content at runtime
|
|
52
|
+
has_erb_content = text.include?('ERB_CONTENT')
|
|
53
|
+
|
|
49
54
|
# Check if element contains an image with alt text (common pattern for logo links)
|
|
50
55
|
has_image_with_alt = false
|
|
51
|
-
if text.empty?
|
|
56
|
+
if text.empty? && !has_erb_content
|
|
52
57
|
# For static scanning, we can't easily check images within elements
|
|
53
58
|
# This check works better in dynamic scanning with Capybara
|
|
54
59
|
# For now, skip image checking in static mode
|
|
55
60
|
has_image_with_alt = false
|
|
56
61
|
end
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
# Text is empty only if it's actually empty AND doesn't contain ERB content
|
|
64
|
+
# If it contains ERB_CONTENT, it will have content at runtime, so it's not empty
|
|
65
|
+
text_empty = text.empty? && !has_erb_content
|
|
66
|
+
|
|
59
67
|
aria_label_empty = aria_label.nil? || aria_label.to_s.strip.empty?
|
|
60
68
|
aria_labelledby_empty = aria_labelledby.nil? || aria_labelledby.to_s.strip.empty?
|
|
61
69
|
title_empty = title.nil? || title.to_s.strip.empty?
|