Set accessible names on table column headers

capybara_accessible_selectors 0.16 resolves role selectors such as
`:columnheader` by the computed accessible name. In a browser that name
folds in the CSS `text-transform: uppercase` styling and the column
action-menu trigger text, so `have_columnheader("Subject")` no longer
matches a header whose accessible name resolves to "SUBJECT Open menu".

Sets an explicit `aria-label` equal to the plain caption on each header
cell, in both the server-rendered tables and the work package table, so
the accessible name is the column title alone. This also improves screen
reader output, which previously announced the uppercased text and the
menu label.
This commit is contained in:
Alexander Brandon Coles
2026-05-29 18:50:27 +02:00
parent 953c96d605
commit 0b4bcb5d3c
5 changed files with 16 additions and 9 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ See COPYRIGHT and LICENSE files for more details.
<% if sortable_column?(name) %>
<%= helpers.sort_header_tag(name, **options) %>
<% else %>
<th>
<th aria-label="<%= options[:caption] %>">
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
+7 -1
View File
@@ -338,7 +338,7 @@ module SortHelper
# Extracts the given `options` and provides them to a block.
# See #sort_header_tag and #sort_header_with_action_menu for usage examples.
def with_sort_header_options(column, allowed_params: nil, with_title: false, **options)
def with_sort_header_options(column, allowed_params: nil, with_title: false, **options) # rubocop:disable Metrics/AbcSize
caption = get_caption(column, options)
default_order = options.delete(:default_order) || "asc"
@@ -349,6 +349,12 @@ module SortHelper
options[:title] = sort_header_title(column, caption, options) if with_title
options[:icon_only_header] = column == :favorited
# Give the header cell an explicit accessible name equal to the plain caption.
# Without it, the column header's accessible name is derived from its contents,
# which can include the action menu trigger text and is uppercased by the
# `text-transform` styling, neither of which should be part of the name.
options[:"aria-label"] = caption if caption.present?
within_sort_header_tag_hierarchy(options, sort_class(column)) do
yield(column, caption, default_order, allowed_params:, param:, lang:, title: options[:title],
sortable: options.fetch(:sortable, false), data:)
+1 -1
View File
@@ -45,7 +45,7 @@ See COPYRIGHT and LICENSE files for more details.
<tr>
<th><div class="generic-table--empty-header"></div></th>
<% @roles.each do |role| %>
<th>
<th aria-label="<%= role.name %>">
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<%= content_tag(role.builtin? ? "em" : "span", h(role.name)) %>
@@ -49,6 +49,7 @@
}
@for (column of columns; track column.href) {
<th
[attr.aria-label]="column.name"
[ngClass]="{'-max': column.id === 'subject', '-min-200': ['estimatedTime', 'remainingTime'].includes(column.id) }"
class="wp-table--table-header">
<sortHeader [headerColumn]="column"
+6 -6
View File
@@ -145,7 +145,7 @@ RSpec.describe SortHelper do
it "renders a th with a sort link" do
expect(output).to be_html_eql(<<-HTML)
<th title="Sort by &quot;Id&quot;">
<th title="Sort by &quot;Id&quot;" aria-label="Id">
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
@@ -165,7 +165,7 @@ RSpec.describe SortHelper do
it "adds the sort class" do
expect(output).to be_html_eql(<<-HTML)
<th title="Ascending sorted by &quot;Id&quot;">
<th title="Ascending sorted by &quot;Id&quot;" aria-label="Id">
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span class="sort asc">
@@ -187,7 +187,7 @@ RSpec.describe SortHelper do
it "adds the sort class" do
expect(output).to be_html_eql(<<-HTML)
<th title="Descending sorted by &quot;Id&quot;">
<th title="Descending sorted by &quot;Id&quot;" aria-label="Id">
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span class="sort desc">
@@ -219,7 +219,7 @@ RSpec.describe SortHelper do
context "when not given allowed parameters" do
it "copies default ones to the link" do
expect(output).to be_html_eql(<<-HTML)
<th title="Sort by &quot;Id&quot;">
<th title="Sort by &quot;Id&quot;" aria-label="Id">
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
@@ -240,7 +240,7 @@ RSpec.describe SortHelper do
it "copies them to the link" do
expect(output).to be_html_eql(<<-HTML)
<th title="Sort by &quot;Id&quot;">
<th title="Sort by &quot;Id&quot;" aria-label="Id">
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
@@ -262,7 +262,7 @@ RSpec.describe SortHelper do
it "includes the passed data param in the link" do
expect(output).to be_html_eql(<<~HTML)
<th title="Sort by &quot;Id&quot;">
<th title="Sort by &quot;Id&quot;" aria-label="Id">
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>