rails_accessibility_testing 1.4.3 → 1.5.0
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/ARCHITECTURE.md +212 -53
- data/CHANGELOG.md +118 -0
- data/GUIDES/getting_started.md +105 -77
- data/GUIDES/system_specs_for_accessibility.md +13 -12
- data/README.md +136 -36
- data/docs_site/getting_started.md +59 -69
- data/exe/a11y_live_scanner +361 -0
- data/exe/rails_server_safe +18 -1
- data/lib/generators/rails_a11y/install/install_generator.rb +137 -0
- data/lib/rails_accessibility_testing/accessibility_helper.rb +547 -24
- data/lib/rails_accessibility_testing/change_detector.rb +17 -104
- data/lib/rails_accessibility_testing/checks/base_check.rb +56 -7
- data/lib/rails_accessibility_testing/checks/heading_check.rb +138 -0
- data/lib/rails_accessibility_testing/checks/image_alt_text_check.rb +7 -7
- data/lib/rails_accessibility_testing/checks/interactive_elements_check.rb +11 -1
- data/lib/rails_accessibility_testing/cli/command.rb +3 -1
- data/lib/rails_accessibility_testing/config/yaml_loader.rb +1 -1
- data/lib/rails_accessibility_testing/engine/rule_engine.rb +49 -5
- data/lib/rails_accessibility_testing/error_message_builder.rb +63 -7
- data/lib/rails_accessibility_testing/middleware/page_visit_logger.rb +81 -0
- data/lib/rails_accessibility_testing/railtie.rb +22 -0
- data/lib/rails_accessibility_testing/rspec_integration.rb +176 -10
- data/lib/rails_accessibility_testing/version.rb +1 -1
- data/lib/rails_accessibility_testing.rb +8 -3
- metadata +7 -3
- data/lib/generators/rails_a11y/install/generator.rb +0 -51
- data/lib/rails_accessibility_testing/checks/heading_hierarchy_check.rb +0 -53
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module RailsAccessibilityTesting
|
|
7
|
+
module Middleware
|
|
8
|
+
# Middleware to log page visits for live accessibility scanning
|
|
9
|
+
# Only active in development environment
|
|
10
|
+
class PageVisitLogger
|
|
11
|
+
def initialize(app)
|
|
12
|
+
@app = app
|
|
13
|
+
@log_file = Rails.root.join('tmp', 'a11y_page_visits.log')
|
|
14
|
+
FileUtils.mkdir_p(File.dirname(@log_file))
|
|
15
|
+
@pending_logs = []
|
|
16
|
+
@last_flush = Time.now
|
|
17
|
+
@mutex = Mutex.new
|
|
18
|
+
|
|
19
|
+
# Start background thread to flush logs periodically
|
|
20
|
+
@flush_thread = Thread.new do
|
|
21
|
+
loop do
|
|
22
|
+
sleep 2 # Flush every 2 seconds
|
|
23
|
+
flush_logs
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call(env)
|
|
29
|
+
request = ActionDispatch::Request.new(env)
|
|
30
|
+
|
|
31
|
+
# Only log GET requests for HTML pages in development
|
|
32
|
+
if Rails.env.development? &&
|
|
33
|
+
request.get? &&
|
|
34
|
+
request.format.html? &&
|
|
35
|
+
!request.path.start_with?('/assets', '/packs', '/rails', '/letter_opener')
|
|
36
|
+
|
|
37
|
+
# Add to pending logs (thread-safe)
|
|
38
|
+
@mutex.synchronize do
|
|
39
|
+
@pending_logs << {
|
|
40
|
+
path: request.path,
|
|
41
|
+
url: request.url,
|
|
42
|
+
timestamp: Time.now.to_f
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Flush immediately if we have 3+ pending logs
|
|
46
|
+
flush_logs if @pending_logs.length >= 3
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@app.call(env)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def flush_logs
|
|
56
|
+
logs_to_write = []
|
|
57
|
+
|
|
58
|
+
@mutex.synchronize do
|
|
59
|
+
return if @pending_logs.empty?
|
|
60
|
+
logs_to_write = @pending_logs.dup
|
|
61
|
+
@pending_logs.clear
|
|
62
|
+
@last_flush = Time.now
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
return if logs_to_write.empty?
|
|
66
|
+
|
|
67
|
+
# Write all logs at once
|
|
68
|
+
File.open(@log_file, 'a') do |f|
|
|
69
|
+
logs_to_write.each do |log_entry|
|
|
70
|
+
f.puts(log_entry.to_json)
|
|
71
|
+
end
|
|
72
|
+
f.flush
|
|
73
|
+
end
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
# Silently fail - don't break the app if logging fails
|
|
76
|
+
Rails.logger.debug("Failed to flush page visit logs: #{e.message}") if defined?(Rails.logger)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Only define Railtie if Rails is available
|
|
4
|
+
if defined?(Rails)
|
|
5
|
+
module RailsAccessibilityTesting
|
|
6
|
+
# Railtie for Rails integration
|
|
7
|
+
# Makes generators available to Rails
|
|
8
|
+
class Railtie < Rails::Railtie
|
|
9
|
+
# Generators are automatically discovered by Rails
|
|
10
|
+
# when they're in lib/generators/ directory
|
|
11
|
+
|
|
12
|
+
# Add middleware for live scanning in development
|
|
13
|
+
initializer 'rails_accessibility_testing.middleware' do |app|
|
|
14
|
+
if Rails.env.development?
|
|
15
|
+
require 'rails_accessibility_testing/middleware/page_visit_logger'
|
|
16
|
+
app.middleware.use RailsAccessibilityTesting::Middleware::PageVisitLogger
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -27,11 +27,21 @@ module RailsAccessibilityTesting
|
|
|
27
27
|
|
|
28
28
|
# Include accessibility helpers for system specs
|
|
29
29
|
def include_helpers(config)
|
|
30
|
-
config.include AccessibilityHelper, type: :system
|
|
30
|
+
config.include RailsAccessibilityTesting::AccessibilityHelper, type: :system
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Setup automatic accessibility checks
|
|
34
34
|
def setup_automatic_checks(config)
|
|
35
|
+
# Use class variable to track results across all examples
|
|
36
|
+
@@accessibility_results = {
|
|
37
|
+
pages_tested: [],
|
|
38
|
+
total_errors: 0,
|
|
39
|
+
total_warnings: 0,
|
|
40
|
+
pages_passed: 0,
|
|
41
|
+
pages_failed: 0,
|
|
42
|
+
pages_with_warnings: 0
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
config.after(:each, type: :system) do |example|
|
|
36
46
|
# Skip if test failed or explicitly skipped
|
|
37
47
|
next if example.exception
|
|
@@ -45,19 +55,175 @@ module RailsAccessibilityTesting
|
|
|
45
55
|
next
|
|
46
56
|
end
|
|
47
57
|
|
|
58
|
+
# Track this page test
|
|
59
|
+
page_result = {
|
|
60
|
+
path: current_path,
|
|
61
|
+
errors: 0,
|
|
62
|
+
warnings: 0,
|
|
63
|
+
status: :pending
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
# Run comprehensive accessibility checks
|
|
67
|
+
# Note: check_comprehensive_accessibility will:
|
|
68
|
+
# - Raise if there are errors (test fails)
|
|
69
|
+
# - Print warnings if there are warnings (test passes but shows warnings)
|
|
70
|
+
# - Print success message if everything passes (no errors, no warnings)
|
|
71
|
+
# - Return hash with :errors and :warnings counts
|
|
49
72
|
instance = example.example_group_instance
|
|
50
|
-
instance.check_comprehensive_accessibility
|
|
51
73
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
begin
|
|
75
|
+
result = instance.check_comprehensive_accessibility
|
|
76
|
+
|
|
77
|
+
# Get results from return value
|
|
78
|
+
page_result[:errors] = result[:errors] || 0
|
|
79
|
+
page_result[:warnings] = result[:warnings] || 0
|
|
80
|
+
# Capture view_file if available from result
|
|
81
|
+
page_result[:view_file] = result[:page_context][:view_file] if result[:page_context] && result[:page_context][:view_file]
|
|
82
|
+
|
|
83
|
+
if page_result[:errors] > 0
|
|
84
|
+
page_result[:status] = :failed
|
|
85
|
+
@@accessibility_results[:pages_failed] += 1
|
|
86
|
+
@@accessibility_results[:total_errors] += page_result[:errors]
|
|
87
|
+
elsif page_result[:warnings] > 0
|
|
88
|
+
page_result[:status] = :warning
|
|
89
|
+
@@accessibility_results[:pages_with_warnings] += 1
|
|
90
|
+
@@accessibility_results[:total_warnings] += page_result[:warnings]
|
|
91
|
+
else
|
|
92
|
+
page_result[:status] = :passed
|
|
93
|
+
@@accessibility_results[:pages_passed] += 1
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
@@accessibility_results[:pages_tested] << page_result
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
# Accessibility check failed - extract error count from exception message or instance
|
|
99
|
+
errors_count = 0
|
|
100
|
+
warnings_count = 0
|
|
101
|
+
|
|
102
|
+
# Try to extract counts from exception message first
|
|
103
|
+
if e.message =~ /ACCESSIBILITY ERRORS FOUND: (\d+) error\(s\), (\d+) warning\(s\)/
|
|
104
|
+
errors_count = $1.to_i
|
|
105
|
+
warnings_count = $2.to_i
|
|
106
|
+
elsif e.message =~ /(\d+) issue\(s\)/
|
|
107
|
+
errors_count = $1.to_i
|
|
108
|
+
else
|
|
109
|
+
# Fallback: get from instance variables
|
|
110
|
+
errors_count = instance.instance_variable_get(:@accessibility_errors)&.length || 1
|
|
111
|
+
warnings_count = instance.instance_variable_get(:@accessibility_warnings)&.length || 0
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Ensure we have at least 1 error if exception was raised
|
|
115
|
+
errors_count = 1 if errors_count == 0 && e.message.include?('ACCESSIBILITY ERRORS')
|
|
116
|
+
|
|
117
|
+
page_result[:status] = :failed
|
|
118
|
+
page_result[:errors] = errors_count
|
|
119
|
+
page_result[:warnings] = warnings_count
|
|
120
|
+
@@accessibility_results[:pages_failed] += 1
|
|
121
|
+
@@accessibility_results[:total_errors] += errors_count
|
|
122
|
+
@@accessibility_results[:total_warnings] += warnings_count
|
|
123
|
+
@@accessibility_results[:pages_tested] << page_result
|
|
124
|
+
|
|
125
|
+
# Flush stdout BEFORE setting exception to ensure errors are visible
|
|
126
|
+
$stdout.flush
|
|
127
|
+
$stderr.flush
|
|
128
|
+
|
|
129
|
+
# Store error info in example metadata so it's available even if output is cleared
|
|
130
|
+
example.metadata[:a11y_errors] = errors_count
|
|
131
|
+
example.metadata[:a11y_warnings] = warnings_count
|
|
132
|
+
example.metadata[:a11y_failed] = true
|
|
133
|
+
|
|
134
|
+
# Set exception but don't clear output - keep errors visible
|
|
135
|
+
example.set_exception(e)
|
|
136
|
+
|
|
137
|
+
# Flush again after setting exception
|
|
138
|
+
$stdout.flush
|
|
139
|
+
$stderr.flush
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Show overall summary after all tests complete
|
|
144
|
+
config.after(:suite) do
|
|
145
|
+
# Show summary if we tested any pages
|
|
146
|
+
if @@accessibility_results && @@accessibility_results[:pages_tested].any?
|
|
147
|
+
show_overall_summary(@@accessibility_results)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Show overall summary of all pages tested
|
|
153
|
+
def show_overall_summary(results)
|
|
154
|
+
return unless results && results[:pages_tested].any?
|
|
155
|
+
|
|
156
|
+
puts "\n" + "="*80
|
|
157
|
+
puts "📊 COMPREHENSIVE ACCESSIBILITY TEST REPORT"
|
|
158
|
+
puts "="*80
|
|
159
|
+
puts ""
|
|
160
|
+
puts "📈 Test Statistics:"
|
|
161
|
+
puts " Total pages tested: #{results[:pages_tested].length}"
|
|
162
|
+
puts " ✅ Passed (no issues): #{results[:pages_passed]} page#{'s' if results[:pages_passed] != 1}"
|
|
163
|
+
puts " ❌ Failed (errors): #{results[:pages_failed]} page#{'s' if results[:pages_failed] != 1}"
|
|
164
|
+
puts " ⚠️ Warnings only: #{results[:pages_with_warnings]} page#{'s' if results[:pages_with_warnings] != 1}"
|
|
165
|
+
puts ""
|
|
166
|
+
puts "📋 Total Issues Across All Pages:"
|
|
167
|
+
puts " ❌ Total errors: #{results[:total_errors]}"
|
|
168
|
+
puts " ⚠️ Total warnings: #{results[:total_warnings]}"
|
|
169
|
+
puts ""
|
|
170
|
+
|
|
171
|
+
# Show pages with errors (highest priority)
|
|
172
|
+
pages_with_errors = results[:pages_tested].select { |p| p[:status] == :failed }
|
|
173
|
+
if pages_with_errors.any?
|
|
174
|
+
puts "❌ Pages with Errors (#{pages_with_errors.length}):"
|
|
175
|
+
pages_with_errors.each do |page|
|
|
176
|
+
view_file = page[:view_file] || page[:path]
|
|
177
|
+
puts " • #{view_file}"
|
|
178
|
+
puts " Errors: #{page[:errors]}#{", Warnings: #{page[:warnings]}" if page[:warnings] > 0}"
|
|
179
|
+
puts " Path: #{page[:path]}" if page[:view_file] && page[:path] != view_file
|
|
180
|
+
end
|
|
181
|
+
puts ""
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Show pages with warnings only
|
|
185
|
+
pages_with_warnings_only = results[:pages_tested].select { |p| p[:status] == :warning }
|
|
186
|
+
if pages_with_warnings_only.any?
|
|
187
|
+
puts "⚠️ Pages with Warnings Only (#{pages_with_warnings_only.length}):"
|
|
188
|
+
pages_with_warnings_only.each do |page|
|
|
189
|
+
view_file = page[:view_file] || page[:path]
|
|
190
|
+
puts " • #{view_file}"
|
|
191
|
+
puts " Warnings: #{page[:warnings]}"
|
|
192
|
+
puts " Path: #{page[:path]}" if page[:view_file] && page[:path] != view_file
|
|
193
|
+
end
|
|
194
|
+
puts ""
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Show summary of pages that passed
|
|
198
|
+
pages_passed = results[:pages_tested].select { |p| p[:status] == :passed }
|
|
199
|
+
if pages_passed.any?
|
|
200
|
+
if pages_passed.length <= 15
|
|
201
|
+
puts "✅ Pages Passed All Checks (#{pages_passed.length}):"
|
|
202
|
+
pages_passed.each do |page|
|
|
203
|
+
puts " ✓ #{page[:path]}"
|
|
204
|
+
end
|
|
205
|
+
else
|
|
206
|
+
puts "✅ #{pages_passed.length} pages passed all accessibility checks"
|
|
207
|
+
puts " (Showing first 10):"
|
|
208
|
+
pages_passed.first(10).each do |page|
|
|
209
|
+
puts " ✓ #{page[:path]}"
|
|
210
|
+
end
|
|
211
|
+
puts " ... and #{pages_passed.length - 10} more"
|
|
212
|
+
end
|
|
213
|
+
puts ""
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Final summary
|
|
217
|
+
puts "="*80
|
|
218
|
+
if results[:total_errors] > 0
|
|
219
|
+
puts "❌ OVERALL STATUS: FAILED - #{results[:total_errors]} error#{'s' if results[:total_errors] != 1} found across #{results[:pages_failed]} page#{'s' if results[:pages_failed] != 1}"
|
|
220
|
+
elsif results[:total_warnings] > 0
|
|
221
|
+
puts "⚠️ OVERALL STATUS: PASSED WITH WARNINGS - #{results[:total_warnings]} warning#{'s' if results[:total_warnings] != 1} found"
|
|
222
|
+
else
|
|
223
|
+
puts "✅ OVERALL STATUS: PASSED - All #{results[:pages_tested].length} page#{'s' if results[:pages_tested].length != 1} passed accessibility checks!"
|
|
60
224
|
end
|
|
225
|
+
puts "="*80
|
|
226
|
+
puts ""
|
|
61
227
|
end
|
|
62
228
|
end
|
|
63
229
|
end
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# Automatically configures accessibility testing for Rails system specs with
|
|
6
6
|
# comprehensive checks and detailed error messages.
|
|
7
7
|
#
|
|
8
|
-
# @version 1.
|
|
8
|
+
# @version 1.5.0
|
|
9
9
|
# @author Regan Maharjan
|
|
10
10
|
#
|
|
11
11
|
# @example Basic usage
|
|
@@ -38,7 +38,7 @@ begin
|
|
|
38
38
|
require_relative 'rails_accessibility_testing/version'
|
|
39
39
|
rescue LoadError
|
|
40
40
|
module RailsAccessibilityTesting
|
|
41
|
-
VERSION = '1.
|
|
41
|
+
VERSION = '1.5.0'
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -63,7 +63,7 @@ require_relative 'rails_accessibility_testing/checks/base_check'
|
|
|
63
63
|
require_relative 'rails_accessibility_testing/checks/form_labels_check'
|
|
64
64
|
require_relative 'rails_accessibility_testing/checks/image_alt_text_check'
|
|
65
65
|
require_relative 'rails_accessibility_testing/checks/interactive_elements_check'
|
|
66
|
-
require_relative 'rails_accessibility_testing/checks/
|
|
66
|
+
require_relative 'rails_accessibility_testing/checks/heading_check'
|
|
67
67
|
require_relative 'rails_accessibility_testing/checks/keyboard_accessibility_check'
|
|
68
68
|
require_relative 'rails_accessibility_testing/checks/aria_landmarks_check'
|
|
69
69
|
require_relative 'rails_accessibility_testing/checks/form_errors_check'
|
|
@@ -78,6 +78,11 @@ require_relative 'rails_accessibility_testing/config/yaml_loader'
|
|
|
78
78
|
# Load integrations
|
|
79
79
|
require_relative 'rails_accessibility_testing/integration/minitest_integration'
|
|
80
80
|
|
|
81
|
+
# Load railtie (needed for generator discovery) - only if Rails is available
|
|
82
|
+
if defined?(Rails)
|
|
83
|
+
require_relative 'rails_accessibility_testing/railtie'
|
|
84
|
+
end
|
|
85
|
+
|
|
81
86
|
# Auto-configure when RSpec is available
|
|
82
87
|
if defined?(RSpec)
|
|
83
88
|
RSpec.configure do |config|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_accessibility_testing
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Regan Maharjan
|
|
@@ -75,6 +75,7 @@ email:
|
|
|
75
75
|
executables:
|
|
76
76
|
- rails_a11y
|
|
77
77
|
- rails_server_safe
|
|
78
|
+
- a11y_live_scanner
|
|
78
79
|
extensions: []
|
|
79
80
|
extra_rdoc_files: []
|
|
80
81
|
files:
|
|
@@ -101,9 +102,10 @@ files:
|
|
|
101
102
|
- docs_site/contributing.md
|
|
102
103
|
- docs_site/getting_started.md
|
|
103
104
|
- docs_site/index.md
|
|
105
|
+
- exe/a11y_live_scanner
|
|
104
106
|
- exe/rails_a11y
|
|
105
107
|
- exe/rails_server_safe
|
|
106
|
-
- lib/generators/rails_a11y/install/
|
|
108
|
+
- lib/generators/rails_a11y/install/install_generator.rb
|
|
107
109
|
- lib/rails_accessibility_testing.rb
|
|
108
110
|
- lib/rails_accessibility_testing/accessibility_helper.rb
|
|
109
111
|
- lib/rails_accessibility_testing/change_detector.rb
|
|
@@ -113,7 +115,7 @@ files:
|
|
|
113
115
|
- lib/rails_accessibility_testing/checks/duplicate_ids_check.rb
|
|
114
116
|
- lib/rails_accessibility_testing/checks/form_errors_check.rb
|
|
115
117
|
- lib/rails_accessibility_testing/checks/form_labels_check.rb
|
|
116
|
-
- lib/rails_accessibility_testing/checks/
|
|
118
|
+
- lib/rails_accessibility_testing/checks/heading_check.rb
|
|
117
119
|
- lib/rails_accessibility_testing/checks/image_alt_text_check.rb
|
|
118
120
|
- lib/rails_accessibility_testing/checks/interactive_elements_check.rb
|
|
119
121
|
- lib/rails_accessibility_testing/checks/keyboard_accessibility_check.rb
|
|
@@ -127,6 +129,8 @@ files:
|
|
|
127
129
|
- lib/rails_accessibility_testing/engine/violation_collector.rb
|
|
128
130
|
- lib/rails_accessibility_testing/error_message_builder.rb
|
|
129
131
|
- lib/rails_accessibility_testing/integration/minitest_integration.rb
|
|
132
|
+
- lib/rails_accessibility_testing/middleware/page_visit_logger.rb
|
|
133
|
+
- lib/rails_accessibility_testing/railtie.rb
|
|
130
134
|
- lib/rails_accessibility_testing/rspec_integration.rb
|
|
131
135
|
- lib/rails_accessibility_testing/shared_examples.rb
|
|
132
136
|
- lib/rails_accessibility_testing/version.rb
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'rails/generators/base'
|
|
4
|
-
|
|
5
|
-
module RailsAccessibilityTesting
|
|
6
|
-
module Generators
|
|
7
|
-
# Generator to install Rails Accessibility Testing
|
|
8
|
-
#
|
|
9
|
-
# Creates initializer and configuration file.
|
|
10
|
-
#
|
|
11
|
-
# @example
|
|
12
|
-
# rails generate rails_a11y:install
|
|
13
|
-
#
|
|
14
|
-
class InstallGenerator < Rails::Generators::Base
|
|
15
|
-
source_root File.expand_path('templates', __dir__)
|
|
16
|
-
|
|
17
|
-
desc "Install Rails A11y: creates initializer and configuration file"
|
|
18
|
-
|
|
19
|
-
def create_initializer
|
|
20
|
-
template 'initializer.rb.erb', 'config/initializers/rails_a11y.rb'
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def create_config_file
|
|
24
|
-
template 'accessibility.yml.erb', 'config/accessibility.yml'
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def add_to_rails_helper
|
|
28
|
-
rails_helper_path = 'spec/rails_helper.rb'
|
|
29
|
-
|
|
30
|
-
if File.exist?(rails_helper_path)
|
|
31
|
-
inject_into_file rails_helper_path,
|
|
32
|
-
after: "require 'rspec/rails'\n" do
|
|
33
|
-
"require 'rails_accessibility_testing'\n"
|
|
34
|
-
end
|
|
35
|
-
else
|
|
36
|
-
say "⚠️ spec/rails_helper.rb not found. Please add: require 'rails_accessibility_testing'", :yellow
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def show_instructions
|
|
41
|
-
say "\n✅ Rails Accessibility Testing installed successfully!", :green
|
|
42
|
-
say "\nNext steps:", :yellow
|
|
43
|
-
say " 1. Run your system specs: bundle exec rspec spec/system/"
|
|
44
|
-
say " 2. Accessibility checks will run automatically"
|
|
45
|
-
say " 3. Configure checks in config/accessibility.yml"
|
|
46
|
-
say "\nFor more information, see: https://github.com/rayraycodes/rails-accessibility-testing"
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RailsAccessibilityTesting
|
|
4
|
-
module Checks
|
|
5
|
-
# Checks for proper heading hierarchy
|
|
6
|
-
#
|
|
7
|
-
# WCAG 2.1 AA: 1.3.1 Info and Relationships (Level A)
|
|
8
|
-
#
|
|
9
|
-
# @api private
|
|
10
|
-
class HeadingHierarchyCheck < BaseCheck
|
|
11
|
-
def self.rule_name
|
|
12
|
-
:heading_hierarchy
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def check
|
|
16
|
-
violations = []
|
|
17
|
-
headings = page.all('h1, h2, h3, h4, h5, h6', visible: true)
|
|
18
|
-
|
|
19
|
-
if headings.empty?
|
|
20
|
-
return violations # Warning only, not error
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
h1_count = headings.count { |h| h.tag_name == 'h1' }
|
|
24
|
-
if h1_count == 0
|
|
25
|
-
violations << violation(
|
|
26
|
-
message: "Page missing H1 heading",
|
|
27
|
-
element_context: { tag: 'page', text: 'Page has no H1 heading' },
|
|
28
|
-
wcag_reference: "1.3.1",
|
|
29
|
-
remediation: "Add an <h1> heading to your page:\n\n<h1>Main Page Title</h1>"
|
|
30
|
-
)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
previous_level = 0
|
|
34
|
-
headings.each do |heading|
|
|
35
|
-
current_level = heading.tag_name[1].to_i
|
|
36
|
-
if current_level > previous_level + 1
|
|
37
|
-
element_ctx = element_context(heading)
|
|
38
|
-
violations << violation(
|
|
39
|
-
message: "Heading hierarchy skipped (h#{previous_level} to h#{current_level})",
|
|
40
|
-
element_context: element_ctx,
|
|
41
|
-
wcag_reference: "1.3.1",
|
|
42
|
-
remediation: "Fix the heading hierarchy. Don't skip levels."
|
|
43
|
-
)
|
|
44
|
-
end
|
|
45
|
-
previous_level = current_level
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
violations
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|