Module: Dommy::Rails::Lint
- Defined in:
- lib/dommy/rails/lint.rb
Constant Summary collapse
- NON_LABELABLE_INPUT_TYPES =
Inputs whose accessible name comes from elsewhere (value attribute) or that are not user-visible, so a <label> is not expected.
%w[hidden submit button reset image].freeze
Class Method Summary collapse
- .accessible_link_text(link) ⇒ Object
- .duplicate_ids(document) ⇒ Object
- .empty_links(document) ⇒ Object
- .image_alt_text(link) ⇒ Object
- .interactive_element?(element) ⇒ Boolean
- .interactive_elements(document) ⇒ Object
- .invalid_aria_references(document) ⇒ Object
-
.missing_form_labels(document) ⇒ Object
Lenient policy: aria-label / aria-labelledby / placeholder are all accepted as label substitutes, even though a placeholder is not a sufficient accessible name under WCAG.
- .nested_interactive_elements(document) ⇒ Object
Class Method Details
.accessible_link_text(link) ⇒ Object
95 96 97 98 99 100 101 102 |
# File 'lib/dommy/rails/lint.rb', line 95 def accessible_link_text(link) [ link.text_content, link.get_attribute("aria-label"), link.get_attribute("title"), image_alt_text(link) ].compact.join.strip end |
.duplicate_ids(document) ⇒ Object
15 16 17 18 19 |
# File 'lib/dommy/rails/lint.rb', line 15 def duplicate_ids(document) all_elements = document.query_selector_all("*[id]").to_a ids = all_elements.map { |el| el.get_attribute("id") } ids.select { |id| ids.count(id) > 1 }.uniq end |
.empty_links(document) ⇒ Object
53 54 55 56 57 |
# File 'lib/dommy/rails/lint.rb', line 53 def empty_links(document) document.query_selector_all("a[href]").to_a.select do |link| accessible_link_text(link).empty? end end |
.image_alt_text(link) ⇒ Object
104 105 106 |
# File 'lib/dommy/rails/lint.rb', line 104 def image_alt_text(link) link.query_selector_all("img").to_a.map { |image| image.get_attribute("alt").to_s }.join end |
.interactive_element?(element) ⇒ Boolean
80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/dommy/rails/lint.rb', line 80 def interactive_element?(element) return false unless element.respond_to?(:tag_name) case element.tag_name when "A" element.has_attribute?("href") when "BUTTON", "SELECT", "TEXTAREA", "SUMMARY" true when "INPUT" element.get_attribute("type").to_s.downcase != "hidden" else false end end |
.interactive_elements(document) ⇒ Object
74 75 76 77 78 |
# File 'lib/dommy/rails/lint.rb', line 74 def interactive_elements(document) document.query_selector_all("a[href], button, input, select, textarea, summary").to_a.reject do |element| element.tag_name == "INPUT" && element.get_attribute("type").to_s.downcase == "hidden" end end |
.invalid_aria_references(document) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/dommy/rails/lint.rb', line 21 def invalid_aria_references(document) issues = [] document.query_selector_all("*").each do |el| %w[aria-labelledby aria-describedby].each do |attr| next unless el.has_attribute?(attr) el.get_attribute(attr).to_s.split.each do |id| issues << { element: el, attribute: attr, id: id } unless document.get_element_by_id(id) end end end issues end |
.missing_form_labels(document) ⇒ Object
Lenient policy: aria-label / aria-labelledby / placeholder are all accepted as label substitutes, even though a placeholder is not a sufficient accessible name under WCAG.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/dommy/rails/lint.rb', line 38 def missing_form_labels(document) issues = [] document.query_selector_all("input, textarea, select").each do |field| next if field.tag_name == "INPUT" && NON_LABELABLE_INPUT_TYPES.include?(field.get_attribute("type").to_s.downcase) next if field.has_attribute?("aria-label") next if field.has_attribute?("aria-labelledby") next if field.has_attribute?("placeholder") next if Dommy::Internal::ElementMatching.field_labels(field).any? issues << { element: field, name: field.get_attribute("name") } end issues end |
.nested_interactive_elements(document) ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/dommy/rails/lint.rb', line 59 def nested_interactive_elements(document) issues = [] interactive_elements(document).each do |element| parent = element.parent_node while parent if interactive_element?(parent) issues << { element: element, ancestor: parent } break end parent = parent.respond_to?(:parent_node) ? parent.parent_node : nil end end issues end |