rails_accessibility_testing 1.1.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/ARCHITECTURE.md +307 -0
  3. data/CHANGELOG.md +81 -0
  4. data/CODE_OF_CONDUCT.md +125 -0
  5. data/CONTRIBUTING.md +225 -0
  6. data/GUIDES/continuous_integration.md +326 -0
  7. data/GUIDES/getting_started.md +205 -0
  8. data/GUIDES/working_with_designers_and_content_authors.md +398 -0
  9. data/GUIDES/writing_accessible_views_in_rails.md +412 -0
  10. data/LICENSE +22 -0
  11. data/README.md +350 -0
  12. data/docs_site/404.html +11 -0
  13. data/docs_site/Gemfile +11 -0
  14. data/docs_site/Makefile +14 -0
  15. data/docs_site/_config.yml +41 -0
  16. data/docs_site/_includes/header.html +13 -0
  17. data/docs_site/_layouts/default.html +130 -0
  18. data/docs_site/assets/main.scss +4 -0
  19. data/docs_site/ci_integration.md +76 -0
  20. data/docs_site/configuration.md +114 -0
  21. data/docs_site/contributing.md +69 -0
  22. data/docs_site/getting_started.md +57 -0
  23. data/docs_site/index.md +57 -0
  24. data/exe/rails_a11y +12 -0
  25. data/exe/rails_server_safe +41 -0
  26. data/lib/generators/rails_a11y/install/generator.rb +51 -0
  27. data/lib/rails_accessibility_testing/accessibility_helper.rb +701 -0
  28. data/lib/rails_accessibility_testing/change_detector.rb +114 -0
  29. data/lib/rails_accessibility_testing/checks/aria_landmarks_check.rb +33 -0
  30. data/lib/rails_accessibility_testing/checks/base_check.rb +156 -0
  31. data/lib/rails_accessibility_testing/checks/color_contrast_check.rb +56 -0
  32. data/lib/rails_accessibility_testing/checks/duplicate_ids_check.rb +49 -0
  33. data/lib/rails_accessibility_testing/checks/form_errors_check.rb +40 -0
  34. data/lib/rails_accessibility_testing/checks/form_labels_check.rb +62 -0
  35. data/lib/rails_accessibility_testing/checks/heading_hierarchy_check.rb +53 -0
  36. data/lib/rails_accessibility_testing/checks/image_alt_text_check.rb +52 -0
  37. data/lib/rails_accessibility_testing/checks/interactive_elements_check.rb +66 -0
  38. data/lib/rails_accessibility_testing/checks/keyboard_accessibility_check.rb +36 -0
  39. data/lib/rails_accessibility_testing/checks/skip_links_check.rb +24 -0
  40. data/lib/rails_accessibility_testing/checks/table_structure_check.rb +36 -0
  41. data/lib/rails_accessibility_testing/cli/command.rb +259 -0
  42. data/lib/rails_accessibility_testing/config/yaml_loader.rb +131 -0
  43. data/lib/rails_accessibility_testing/configuration.rb +30 -0
  44. data/lib/rails_accessibility_testing/engine/rule_engine.rb +97 -0
  45. data/lib/rails_accessibility_testing/engine/violation.rb +58 -0
  46. data/lib/rails_accessibility_testing/engine/violation_collector.rb +59 -0
  47. data/lib/rails_accessibility_testing/error_message_builder.rb +354 -0
  48. data/lib/rails_accessibility_testing/integration/minitest_integration.rb +74 -0
  49. data/lib/rails_accessibility_testing/rspec_integration.rb +58 -0
  50. data/lib/rails_accessibility_testing/shared_examples.rb +93 -0
  51. data/lib/rails_accessibility_testing/version.rb +4 -0
  52. data/lib/rails_accessibility_testing.rb +83 -0
  53. data/lib/tasks/accessibility.rake +28 -0
  54. metadata +218 -0
@@ -0,0 +1,412 @@
1
+ # Writing Accessible Views in Rails
2
+
3
+ This guide shows you how to write accessible Rails views that pass Rails A11y checks and meet WCAG 2.1 AA standards.
4
+
5
+ ## Core Principles
6
+
7
+ 1. **Semantic HTML** - Use the right elements for the job
8
+ 2. **Clear Structure** - Logical heading hierarchy and landmarks
9
+ 3. **Accessible Forms** - Proper labels and error associations
10
+ 4. **Descriptive Content** - Alt text, link text, and button labels
11
+ 5. **Keyboard Navigation** - Everything works without a mouse
12
+
13
+ ## Forms
14
+
15
+ ### ✅ Good: Proper Labels
16
+
17
+ ```erb
18
+ <%= form_with model: @user do |f| %>
19
+ <%= f.label :email, "Email Address" %>
20
+ <%= f.email_field :email %>
21
+
22
+ <%= f.label :password, "Password" %>
23
+ <%= f.password_field :password %>
24
+ <% end %>
25
+ ```
26
+
27
+ ### ❌ Bad: Missing Labels
28
+
29
+ ```erb
30
+ <%= form_with model: @user do |f| %>
31
+ <%= f.email_field :email %> <!-- No label! -->
32
+ <%= f.password_field :password %> <!-- No label! -->
33
+ <% end %>
34
+ ```
35
+
36
+ ### Alternative: aria-label
37
+
38
+ For icon-only inputs, use `aria-label`:
39
+
40
+ ```erb
41
+ <%= f.search_field :query, aria: { label: "Search" } %>
42
+ ```
43
+
44
+ ### Form Errors
45
+
46
+ Associate error messages with inputs:
47
+
48
+ ```erb
49
+ <%= form_with model: @user do |f| %>
50
+ <%= f.label :email %>
51
+ <%= f.email_field :email,
52
+ aria: {
53
+ describedby: "email-error",
54
+ invalid: @user.errors[:email].any?
55
+ } %>
56
+ <% if @user.errors[:email].any? %>
57
+ <div id="email-error" class="error">
58
+ <%= @user.errors[:email].first %>
59
+ </div>
60
+ <% end %>
61
+ <% end %>
62
+ ```
63
+
64
+ ## Images
65
+
66
+ ### ✅ Good: Descriptive Alt Text
67
+
68
+ ```erb
69
+ <%= image_tag "logo.png", alt: "Company Logo" %>
70
+
71
+ <!-- For decorative images -->
72
+ <%= image_tag "decoration.png", alt: "" %>
73
+ ```
74
+
75
+ ### ❌ Bad: Missing Alt
76
+
77
+ ```erb
78
+ <%= image_tag "logo.png" %> <!-- Missing alt! -->
79
+ ```
80
+
81
+ ### When to Use Empty Alt
82
+
83
+ Use `alt=""` only for purely decorative images:
84
+
85
+ ```erb
86
+ <!-- Decorative border -->
87
+ <%= image_tag "border.png", alt: "" %>
88
+
89
+ <!-- Spacer image -->
90
+ <%= image_tag "spacer.gif", alt: "" %>
91
+ ```
92
+
93
+ ## Links and Buttons
94
+
95
+ ### ✅ Good: Descriptive Text
96
+
97
+ ```erb
98
+ <%= link_to "Read More", article_path(@article) %>
99
+
100
+ <%= button_to "Submit", submit_path, method: :post %>
101
+ ```
102
+
103
+ ### ❌ Bad: Generic or Missing Text
104
+
105
+ ```erb
106
+ <%= link_to "Click here", article_path(@article) %> <!-- Generic! -->
107
+ <%= link_to article_path(@article) do %>
108
+ <i class="icon"></i> <!-- No accessible name! -->
109
+ <% end %>
110
+ ```
111
+
112
+ ### Icon-Only Links/Buttons
113
+
114
+ For icon-only interactive elements, use `aria-label`:
115
+
116
+ ```erb
117
+ <%= link_to article_path(@article), aria: { label: "Read article" } do %>
118
+ <i class="icon-read"></i>
119
+ <% end %>
120
+
121
+ <%= button_tag type: "submit", aria: { label: "Close dialog" } do %>
122
+ <i class="icon-close"></i>
123
+ <% end %>
124
+ ```
125
+
126
+ ### Visually Hidden Text
127
+
128
+ Alternative approach using CSS:
129
+
130
+ ```erb
131
+ <%= link_to article_path(@article) do %>
132
+ <i class="icon-read"></i>
133
+ <span class="visually-hidden">Read article</span>
134
+ <% end %>
135
+ ```
136
+
137
+ ```css
138
+ .visually-hidden {
139
+ position: absolute;
140
+ width: 1px;
141
+ height: 1px;
142
+ padding: 0;
143
+ margin: -1px;
144
+ overflow: hidden;
145
+ clip: rect(0, 0, 0, 0);
146
+ white-space: nowrap;
147
+ border-width: 0;
148
+ }
149
+ ```
150
+
151
+ ## Headings
152
+
153
+ ### ✅ Good: Proper Hierarchy
154
+
155
+ ```erb
156
+ <h1>Page Title</h1>
157
+ <h2>Section Title</h2>
158
+ <h3>Subsection Title</h3>
159
+ <h2>Another Section</h2>
160
+ ```
161
+
162
+ ### ❌ Bad: Skipped Levels
163
+
164
+ ```erb
165
+ <h1>Page Title</h1>
166
+ <h3>Subsection</h3> <!-- Skipped h2! -->
167
+ ```
168
+
169
+ ### ❌ Bad: Multiple H1s
170
+
171
+ ```erb
172
+ <h1>Main Title</h1>
173
+ <h1>Another Title</h1> <!-- Should be h2! -->
174
+ ```
175
+
176
+ ### Rails Helper
177
+
178
+ Use a helper to manage heading levels:
179
+
180
+ ```ruby
181
+ # app/helpers/application_helper.rb
182
+ def heading(text, level: 2)
183
+ content_tag("h#{level}", text)
184
+ end
185
+ ```
186
+
187
+ ## Landmarks
188
+
189
+ ### ✅ Good: Semantic Structure
190
+
191
+ ```erb
192
+ <body>
193
+ <header>
194
+ <nav>
195
+ <!-- Navigation -->
196
+ </nav>
197
+ </header>
198
+
199
+ <main>
200
+ <%= yield %>
201
+ </main>
202
+
203
+ <footer>
204
+ <!-- Footer content -->
205
+ </footer>
206
+ </body>
207
+ ```
208
+
209
+ ### ARIA Landmarks
210
+
211
+ If you can't use semantic HTML:
212
+
213
+ ```erb
214
+ <div role="main">
215
+ <%= yield %>
216
+ </div>
217
+
218
+ <div role="navigation">
219
+ <!-- Navigation -->
220
+ </div>
221
+ ```
222
+
223
+ ## Tables
224
+
225
+ ### ✅ Good: Proper Headers
226
+
227
+ ```erb
228
+ <table>
229
+ <thead>
230
+ <tr>
231
+ <th>Name</th>
232
+ <th>Email</th>
233
+ <th>Role</th>
234
+ </tr>
235
+ </thead>
236
+ <tbody>
237
+ <% @users.each do |user| %>
238
+ <tr>
239
+ <td><%= user.name %></td>
240
+ <td><%= user.email %></td>
241
+ <td><%= user.role %></td>
242
+ </tr>
243
+ <% end %>
244
+ </tbody>
245
+ </table>
246
+ ```
247
+
248
+ ### With Caption
249
+
250
+ ```erb
251
+ <table>
252
+ <caption>User Directory</caption>
253
+ <thead>
254
+ <!-- ... -->
255
+ </thead>
256
+ </table>
257
+ ```
258
+
259
+ ## Skip Links
260
+
261
+ Add skip navigation links:
262
+
263
+ ```erb
264
+ <a href="#main-content" class="skip-link">Skip to main content</a>
265
+
266
+ <header>
267
+ <!-- Navigation -->
268
+ </header>
269
+
270
+ <main id="main-content">
271
+ <%= yield %>
272
+ </main>
273
+ ```
274
+
275
+ ```css
276
+ .skip-link {
277
+ position: absolute;
278
+ top: -40px;
279
+ left: 0;
280
+ background: #000;
281
+ color: #fff;
282
+ padding: 8px;
283
+ text-decoration: none;
284
+ z-index: 100;
285
+ }
286
+
287
+ .skip-link:focus {
288
+ top: 0;
289
+ }
290
+ ```
291
+
292
+ ## Modals and Dialogs
293
+
294
+ ### ✅ Good: Focusable Elements
295
+
296
+ ```erb
297
+ <div role="dialog" aria-labelledby="modal-title">
298
+ <h2 id="modal-title">Confirm Action</h2>
299
+ <p>Are you sure?</p>
300
+ <button>Cancel</button>
301
+ <button>Confirm</button>
302
+ </div>
303
+ ```
304
+
305
+ ### Focus Management
306
+
307
+ Use JavaScript to trap focus:
308
+
309
+ ```javascript
310
+ // Trap focus in modal
311
+ const modal = document.querySelector('[role="dialog"]');
312
+ const focusableElements = modal.querySelectorAll(
313
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
314
+ );
315
+ const firstElement = focusableElements[0];
316
+ const lastElement = focusableElements[focusableElements.length - 1];
317
+
318
+ firstElement.focus();
319
+ ```
320
+
321
+ ## Color and Contrast
322
+
323
+ ### ✅ Good: Sufficient Contrast
324
+
325
+ ```erb
326
+ <!-- Dark text on light background -->
327
+ <p style="color: #000; background: #fff;">Readable text</p>
328
+
329
+ <!-- Light text on dark background -->
330
+ <p style="color: #fff; background: #000;">Readable text</p>
331
+ ```
332
+
333
+ ### ❌ Bad: Low Contrast
334
+
335
+ ```erb
336
+ <!-- Hard to read -->
337
+ <p style="color: #ccc; background: #fff;">Poor contrast</p>
338
+ ```
339
+
340
+ ### Tools
341
+
342
+ - [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
343
+ - Browser DevTools (Chrome Lighthouse)
344
+
345
+ ## Common Patterns
346
+
347
+ ### Breadcrumbs
348
+
349
+ ```erb
350
+ <nav aria-label="Breadcrumb">
351
+ <ol>
352
+ <li><%= link_to "Home", root_path %></li>
353
+ <li><%= link_to "Products", products_path %></li>
354
+ <li aria-current="page"><%= @product.name %></li>
355
+ </ol>
356
+ </nav>
357
+ ```
358
+
359
+ ### Error Messages
360
+
361
+ ```erb
362
+ <% if @user.errors.any? %>
363
+ <div role="alert" aria-live="polite">
364
+ <h2>Please fix the following errors:</h2>
365
+ <ul>
366
+ <% @user.errors.full_messages.each do |message| %>
367
+ <li><%= message %></li>
368
+ <% end %>
369
+ </ul>
370
+ </div>
371
+ <% end %>
372
+ ```
373
+
374
+ ### Loading States
375
+
376
+ ```erb
377
+ <button aria-busy="true" aria-label="Loading...">
378
+ <span class="spinner"></span>
379
+ <span class="visually-hidden">Loading</span>
380
+ </button>
381
+ ```
382
+
383
+ ## Testing Your Views
384
+
385
+ Run Rails A11y checks:
386
+
387
+ ```bash
388
+ # Run all system tests
389
+ bundle exec rspec spec/system/
390
+
391
+ # Check specific routes
392
+ bundle exec rails_a11y check --routes home_path about_path
393
+ ```
394
+
395
+ ## Resources
396
+
397
+ - [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
398
+ - [WebAIM](https://webaim.org/) - Accessibility resources
399
+ - [A11y Project](https://www.a11yproject.com/) - Community-driven
400
+ - [MDN Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility)
401
+
402
+ ## Next Steps
403
+
404
+ - **Run checks regularly** - Catch issues early
405
+ - **Review with screen readers** - Test with NVDA, JAWS, or VoiceOver
406
+ - **Keyboard testing** - Navigate without a mouse
407
+ - **Color blindness** - Test with color blindness simulators
408
+
409
+ ---
410
+
411
+ **Remember:** Accessibility isn't optional—it's a requirement. These practices make your app usable for everyone.
412
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2025 Regan Maharjan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+