FIX: Add aria-label attribute to cooked hashtags (#22182)

This commit adds an aria-label attribute to cooked hashtags using
the post/chat message decorateCooked functionality. I have just used
the inner content of the hashtag (the tag/category/channel name) for
the label -- we can reexamine at some point if we want something
different like "Link to dev category" or something, but from what I
can tell things like Twitter don't even have aria-labels for hashtags
so the text would be read out directly.

This commit also refactors any ruby specs checking the HTML of hashtags
to use rspec-html-matchers which is far clearer than having to maintain
the HTML structure in a HEREDOC for comparison, and gives better spec
failures.

c.f. https://meta.discourse.org/t/hashtags-are-getting-a-makeover/248866/23?u=martin
This commit is contained in:
Martin Brennan
2023-06-20 15:47:17 +10:00
committed by GitHub
parent 6781e31195
commit fc199d42fa
10 changed files with 290 additions and 85 deletions

View File

@@ -1,5 +1,5 @@
import { decorateGithubOneboxBody } from "discourse/instance-initializers/onebox-decorators";
import { replaceHashtagIconPlaceholder } from "discourse/lib/hashtag-autocomplete";
import { decorateHashtags } from "discourse/lib/hashtag-autocomplete";
import { withPluginApi } from "discourse/lib/plugin-api";
import highlightSyntax from "discourse/lib/highlight-syntax";
import I18n from "I18n";
@@ -73,10 +73,9 @@ export default {
}
);
api.decorateChatMessage(
(element) => replaceHashtagIconPlaceholder(element, site),
{ id: "hashtagIcons" }
);
api.decorateChatMessage((element) => decorateHashtags(element, site), {
id: "hashtagIcons",
});
},
_getScrollParent(node, maxParentSelector) {

View File

@@ -194,9 +194,20 @@ describe Chat::ChannelArchiveService do
subject.new(@channel_archive).execute
expect(@channel_archive.reload.complete?).to eq(true)
pm_topic = Topic.private_messages.last
expect(pm_topic.first_post.cooked).to include(
"<a class=\"hashtag-cooked\" href=\"#{channel.relative_url}\" data-type=\"channel\" data-slug=\"#{channel.slug}\" data-id=\"#{channel.id}\" data-ref=\"#{channel.slug}::channel\"><span class=\"hashtag-icon-placeholder\"></span><span>#{channel.title(user)}</span></a>",
)
expect(pm_topic.first_post.cooked).to have_tag(
"a",
with: {
class: "hashtag-cooked",
href: channel.relative_url,
"data-type": "channel",
"data-slug": channel.slug,
"data-id": channel.id,
"data-ref": "#{channel.slug}::channel",
},
) do
with_tag("span", with: { class: "hashtag-icon-placeholder" })
with_tag("span", text: channel.title(user))
end
end
end

View File

@@ -241,7 +241,7 @@ describe Chat::Message do
)
end
it "supports hashtag-autocomplete plugin" do
it "supports hashtag autocomplete" do
SiteSetting.chat_enabled = true
SiteSetting.enable_experimental_hashtag_autocomplete = true
@@ -250,9 +250,18 @@ describe Chat::Message do
cooked = described_class.cook("##{category.slug}", user_id: user.id)
expect(cooked).to eq(
"<p><a class=\"hashtag-cooked\" href=\"#{category.url}\" data-type=\"category\" data-slug=\"#{category.slug}\" data-id=\"#{category.id}\"><span class=\"hashtag-icon-placeholder\"></span><span>#{category.name}</span></a></p>",
)
expect(cooked).to have_tag(
"a",
with: {
class: "hashtag-cooked",
href: category.url,
"data-type": "category",
"data-slug": category.slug,
"data-id": category.id,
},
) do
with_tag("span", with: { class: "hashtag-icon-placeholder" })
end
end
it "supports censored plugin" do

View File

@@ -68,15 +68,63 @@ describe "Using #hashtag autocompletion to search for and lookup channels", type
cooked_hashtags = page.all(".hashtag-cooked", count: 3)
expect(cooked_hashtags[0]["outerHTML"]).to eq(<<~HTML.chomp)
<a class=\"hashtag-cooked\" href=\"#{channel2.relative_url}\" data-type=\"channel\" data-slug=\"random\" data-id=\"#{channel2.id}\"><svg class=\"fa d-icon d-icon-comment svg-icon hashtag-color--channel-#{channel2.id} svg-string\" xmlns=\"http://www.w3.org/2000/svg\"><use href=\"#comment\"></use></svg><span>Random</span></a>
HTML
expect(cooked_hashtags[1]["outerHTML"]).to eq(<<~HTML.chomp)
<a class=\"hashtag-cooked\" href=\"#{category.url}\" data-type=\"category\" data-slug=\"raspberry-beret\" data-id="#{category.id}"><span class=\"hashtag-category-badge hashtag-color--category-#{category.id}\"></span><span>Raspberry</span></a>
HTML
expect(cooked_hashtags[2]["outerHTML"]).to eq(<<~HTML.chomp)
<a class=\"hashtag-cooked\" href=\"#{tag.url}\" data-type=\"tag\" data-slug=\"razed\" data-id="#{tag.id}"><svg class=\"fa d-icon d-icon-tag svg-icon hashtag-color--tag-#{tag.id} svg-string\" xmlns=\"http://www.w3.org/2000/svg\"><use href=\"#tag\"></use></svg><span>razed</span></a>
HTML
expect(cooked_hashtags[0]["outerHTML"]).to have_tag(
"a",
with: {
class: "hashtag-cooked",
href: channel2.relative_url,
"data-type": "channel",
"data-slug": "random",
"data-id": channel2.id,
"aria-label": "Random",
},
) do
with_tag(
"svg",
with: {
class:
"fa d-icon d-icon-comment svg-icon hashtag-color--channel-#{channel2.id} svg-string",
},
) { with_tag("use", with: { href: "#comment" }) }
end
expect(cooked_hashtags[1]["outerHTML"]).to have_tag(
"a",
with: {
class: "hashtag-cooked",
href: category.url,
"data-type": "category",
"data-slug": "raspberry-beret",
"data-id": category.id,
"aria-label": "Raspberry",
},
) do
with_tag(
"span",
with: {
class: "hashtag-category-badge hashtag-color--category-#{category.id}",
},
)
end
expect(cooked_hashtags[2]["outerHTML"]).to have_tag(
"a",
with: {
class: "hashtag-cooked",
href: tag.url,
"data-type": "tag",
"data-slug": "razed",
"data-id": tag.id,
"aria-label": "razed",
},
) do
with_tag(
"svg",
with: {
class: "fa d-icon d-icon-tag svg-icon hashtag-color--tag-#{tag.id} svg-string",
},
) { with_tag("use", with: { href: "#tag" }) }
end
end
context "when a user cannot access the category for a cooked channel hashtag" do