diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a51321..ca808404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [#210: Fix issue with WCAG 2.1 success criterion 1.3.1 (Info and Relationships)](https://github.com/alphagov/tech-docs-gem/pull/210) - [#209: Some search and keyboard navigation updates](https://github.com/alphagov/tech-docs-gem/pull/209) +- [#214: Implement row level table headings to allow accessible tables with row headings](https://github.com/alphagov/tech-docs-gem/pull/214) ### Ruby version bump diff --git a/lib/govuk_tech_docs/tech_docs_html_renderer.rb b/lib/govuk_tech_docs/tech_docs_html_renderer.rb index a5805775..8b191ccc 100644 --- a/lib/govuk_tech_docs/tech_docs_html_renderer.rb +++ b/lib/govuk_tech_docs/tech_docs_html_renderer.rb @@ -30,5 +30,49 @@ def table(header, body) ) end + + def table_row(body) + # Post-processing the table_cell HTML to implement row headings. + # + # Doing this in table_row instead of table_cell is a hack. + # + # Ideally, we'd use the table_cell callback like: + # + # def table_cell(content, alignment, header) + # if header + # "#{content}" + # elsif content.start_with? "# " + # "#{content.sub(/^# /, "")}" + # else + # "#{content}" + # end + # end + # + # Sadly, Redcarpet's table_cell callback doesn't allow you to distinguish + # table cells and table headings until https://github.com/vmg/redcarpet/commit/27dfb2a738a23aadd286ac9e7ecd61c4545d29de + # (which is not yet released). This means we can't use the table_cell callback + # without breaking column headers, so we're having to hack it in table_row. + + fragment = Nokogiri::HTML::DocumentFragment.parse(body) + fragment.children.each do |cell| + next unless cell.name == "td" + next if cell.children.empty? + + first_child = cell.children.first + next unless first_child.text? + + leading_text = first_child.content + next unless leading_text.start_with?("#") + + cell.name = "th" + cell["scope"] = "row" + first_child.content = leading_text.sub(/# */, "") + end + + tr = Nokogiri::XML::Node.new "tr", fragment + tr.children = fragment.children + + tr.to_html + end end end diff --git a/spec/govuk_tech_docs/tech_docs_html_renderer_spec.rb b/spec/govuk_tech_docs/tech_docs_html_renderer_spec.rb new file mode 100644 index 00000000..3e856bac --- /dev/null +++ b/spec/govuk_tech_docs/tech_docs_html_renderer_spec.rb @@ -0,0 +1,44 @@ +RSpec.describe GovukTechDocs::TechDocsHTMLRenderer do + let(:app) { double("app") } + let(:context) { double("context") } + let(:processor) { + allow(context).to receive(:app) { app } + allow(app).to receive(:api) + Redcarpet::Markdown.new(described_class.new(context: context), tables: true) + } + + describe "#render a table" do + markdown_table = <<~MARKDOWN + | A | B | + |------|---| + |# C | D | + | E | F | + |# *G* | H | + MARKDOWN + + it "treats cells in the heading row as headings" do + output = processor.render markdown_table + + expect(output).to include("A") + expect(output).to include("B") + end + + it "treats cells starting with # as row headings" do + output = processor.render markdown_table + expect(output).to include('C') + end + + it "treats cells starting with # with more complex markup as row headings" do + output = processor.render markdown_table + expect(output).to match(/G<\/em>\s*<\/th>/) + end + + it "treats other cells as ordinary cells" do + output = processor.render markdown_table + expect(output).to include("D") + expect(output).to include("E") + expect(output).to include("F") + expect(output).to include("H") + end + end +end