Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require_relative "../../custom_helpers"

module ERBLint
module Linters
module GitHub
module Accessibility
class DetailsHasSummary < Linter
include ERBLint::Linters::CustomHelpers
include LinterRegistry

MESSAGE = "<details> elements need to have explict <summary> elements"
Comment thread
koddsson marked this conversation as resolved.
Outdated

def run(processed_source)
tags(processed_source).each do |tag|
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tags(processed_source).each do |tag|
tags(processed_source).each_with_index do |tag, index|

then you could get rid of line 21!

next if tag.closing?
next unless tag.name == "details"

# Find the details element index in the AST
index = processed_source.ast.to_a.find_index(tag.node)

# Get the next element in the AST
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if there's any reason to support <summary> not being the immediate next element? I don't know it's valid anyways but worth verifying.

<details>
  <p> some text </p>
  <summary>Toggle</summary>
</details>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't an easy way for getting the children of a element with erblint since we only have access to the tag nodes without the actual HTML tree structure.

One approach we've taken is keeping track of the opening/closing tag with variables. Here is an examples where we do this in dotcom:

This approach might make sense if we wanted to allow <summary> that isn't a first child, or if we wanted to give a specific warning message for <summary> that isn't first child.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if there's any reason to support <summary> not being the immediate next element? I don't know it's valid anyways but worth verifying.

<details>
  <p> some text </p>
  <summary>Toggle</summary>
</details>

Hmm, I thought it needed to be the first element but that doesn't seem to be true. I wonder if it's worth supporting or if we should also validate that <summary> is the first element for consistency 🤔

I'll change this to allow <summary> to be anywhere within <details> for now.

next_node = processed_source.ast.to_a[index + 1]
next_tag = BetterHtml::Tree::Tag.from_node(next_node)

# If the next element is a summary, we're good
next if next_tag.name == "summary"

generate_offense(self.class, processed_source, tag)
end

rule_disabled?(processed_source)
end
end
end
end
end
end
23 changes: 23 additions & 0 deletions test/linters/accessibility/details_has_summary_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

require "test_helper"

class DetailsHasSummary < LinterTestCase
def linter_class
ERBLint::Linters::GitHub::Accessibility::DetailsHasSummary
end

def test_warns_if_details_doesnt_have_a_summary
@file = "<details>I don't have a summary element</details>"
@linter.run(processed_source)

assert_equal @linter.offenses.count, 1
end

def test_does_not_warn_if_only_disabled_attribute_is_set
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test description needs to be updated.

@file = "<details><summary>Expand me!</summary><button>Surprise button!</button></details>"
@linter.run(processed_source)

assert_empty @linter.offenses
end
end