diff --git a/javascript/packages/core/src/ast-utils.ts b/javascript/packages/core/src/ast-utils.ts index f158cd956..1d49ea899 100644 --- a/javascript/packages/core/src/ast-utils.ts +++ b/javascript/packages/core/src/ast-utils.ts @@ -29,12 +29,14 @@ import { isHTMLCommentNode, isHTMLElementNode, isHTMLOpenTagNode, + isERBOpenTagNode, isHTMLTextNode, isWhitespaceNode, isHTMLAttributeNameNode, isHTMLAttributeValueNode, areAllOfType, filterLiteralNodes, + filterHTMLTextNodes, filterHTMLAttributeNodes } from "./node-type-guards.js" @@ -152,6 +154,20 @@ export function getValidatableStaticContent(nodes: Node[]): string | null { return filterLiteralNodes(nodes).map(node => node.content).join("") } +/** + * Gets static text content from element body nodes (includes both LiteralNode and HTMLTextNode) + * Returns concatenated text content for validation, or null if contains output ERB + */ +export function getStaticBodyText(nodes: Node[]): string | null { + if (hasERBOutput(nodes)) { + return null + } + + const textNodes = [...filterLiteralNodes(nodes), ...filterHTMLTextNodes(nodes)] + + return textNodes.map(node => node.content).join("") +} + /** * Extracts a combined string from nodes, including ERB content * For ERB nodes, includes the full tag syntax (e.g., "<%= foo %>") @@ -263,13 +279,27 @@ export function getOpenTag(node: HTMLElementNode | HTMLOpenTagNode | null | unde return null } +/** + * Gets the children array from an element's open tag, regardless of whether + * it's an HTMLOpenTagNode or ERBOpenTagNode (used by action view helpers). + */ +function getOpenTagChildren(node: HTMLElementNode | HTMLOpenTagNode | null | undefined): Node[] { + if (!node) return [] + if (isHTMLOpenTagNode(node)) return node.children + + if (isHTMLElementNode(node) && node.open_tag) { + if (isHTMLOpenTagNode(node.open_tag)) return node.open_tag.children + if (isERBOpenTagNode(node.open_tag)) return node.open_tag.children + } + + return [] +} + /** * Gets attributes from an HTMLElementNode or HTMLOpenTagNode */ export function getAttributes(node: HTMLElementNode | HTMLOpenTagNode | null | undefined): HTMLAttributeNode[] { - const openTag = getOpenTag(node) - - return openTag ? filterHTMLAttributeNodes(openTag.children) : [] + return filterHTMLAttributeNodes(getOpenTagChildren(node)) } /** diff --git a/javascript/packages/linter/docs/rules/README.md b/javascript/packages/linter/docs/rules/README.md index 355a4e2cc..51261ae61 100644 --- a/javascript/packages/linter/docs/rules/README.md +++ b/javascript/packages/linter/docs/rules/README.md @@ -6,6 +6,7 @@ This page contains documentation for all Herb Linter rules. #### Accessibility +- [`a11y-avoid-generic-link-text`](./a11y-avoid-generic-link-text.md) - Avoid generic link text like "Click here" or "Read more" - [`a11y-disabled-attribute`](./a11y-disabled-attribute.md) - Prevent usage of the `disabled` attribute on unsupported elements - [`a11y-no-accesskey-attribute`](./a11y-no-accesskey-attribute.md) - Prevent usage of the `accesskey` attribute - [`a11y-no-aria-unsupported-elements`](./a11y-no-aria-unsupported-elements.md) - Prevent usage of ARIA on unsupported elements diff --git a/javascript/packages/linter/docs/rules/a11y-avoid-generic-link-text.md b/javascript/packages/linter/docs/rules/a11y-avoid-generic-link-text.md new file mode 100644 index 000000000..900bece2c --- /dev/null +++ b/javascript/packages/linter/docs/rules/a11y-avoid-generic-link-text.md @@ -0,0 +1,71 @@ +# Linter Rule: Avoid generic link text + +**Rule:** `a11y-avoid-generic-link-text` + +## Description + +Avoid setting generic link text like "Click here", "Read more", and "Learn more" which do not make sense when read out of context. + +## Rationale + +Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context. Links should clearly describe their destination. + +## Banned generic text + +- `Read more` +- `Learn more` +- `Click here` +- `More` +- `Link` +- `Here` + +## Examples + +### ✅ Good + +```erb +Learn more about GitHub +``` + +```erb +Create a new repository +``` + +```erb +Learn more +``` + +```erb +Learn more +``` + +### 🚫 Bad + +```erb +Learn more +``` + +```erb +Read more +``` + +```erb +Click here +``` + +```erb +More +``` + +```erb +Link +``` + +```erb +Here +``` + +## References + +- [erblint-github: AvoidGenericLinkText](https://github.com/github/erblint-github/blob/main/lib/erblint-github/linters/github/accessibility/avoid_generic_link_text.rb) +- [erblint-github docs](https://github.com/github/erblint-github/blob/main/docs/rules/accessibility/avoid-generic-link-text.md) diff --git a/javascript/packages/linter/src/rules.ts b/javascript/packages/linter/src/rules.ts index c5686fe83..bfffe7d4e 100644 --- a/javascript/packages/linter/src/rules.ts +++ b/javascript/packages/linter/src/rules.ts @@ -1,5 +1,6 @@ import type { RuleClass } from "./types.js" +import { A11yAvoidGenericLinkTextRule } from "./rules/a11y-avoid-generic-link-text.js" import { A11yDisabledAttributeRule } from "./rules/a11y-disabled-attribute.js" import { A11yNoAccesskeyAttributeRule } from "./rules/a11y-no-accesskey-attribute.js" import { A11yNoAriaUnsupportedElementsRule } from "./rules/a11y-no-aria-unsupported-elements.js" @@ -105,6 +106,7 @@ import { SVGTagNameCapitalizationRule } from "./rules/svg-tag-name-capitalizatio import { TurboPermanentRequireIdRule } from "./rules/turbo-permanent-require-id.js" export const rules: RuleClass[] = [ + A11yAvoidGenericLinkTextRule, A11yDisabledAttributeRule, A11yNoAccesskeyAttributeRule, A11yNoAriaUnsupportedElementsRule, diff --git a/javascript/packages/linter/src/rules/a11y-avoid-generic-link-text.ts b/javascript/packages/linter/src/rules/a11y-avoid-generic-link-text.ts new file mode 100644 index 000000000..91079f53a --- /dev/null +++ b/javascript/packages/linter/src/rules/a11y-avoid-generic-link-text.ts @@ -0,0 +1,83 @@ +import { BaseRuleVisitor } from "./rule-utils.js" +import { getTagLocalName, hasAttribute, getStaticBodyText } from "@herb-tools/core" + +import { ParserRule } from "../types.js" +import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js" +import type { HTMLElementNode, ParseResult, ParserOptions } from "@herb-tools/core" + +const BANNED_GENERIC_TEXT = [ + "read more", + "learn more", + "click here", + "more", + "link", + "here", +] + +function stripText(text: string): string { + return text.toLowerCase().replace(/\W+/g, " ").trim() +} + +function isBannedText(text: string): boolean { + return BANNED_GENERIC_TEXT.includes(stripText(text)) +} + +class AvoidGenericLinkTextVisitor extends BaseRuleVisitor { + visitHTMLElementNode(node: HTMLElementNode): void { + this.checkLinkElement(node) + super.visitHTMLElementNode(node) + } + + private checkLinkElement(node: HTMLElementNode): void { + const tagName = getTagLocalName(node) + + if (tagName !== "a") { + return + } + + if (hasAttribute(node, "aria-labelledby")) { + return + } + + if (hasAttribute(node, "aria-label")) { + return + } + + const textContent = node.body ? getStaticBodyText(node.body) : "" + + if (textContent === null) { + return + } + + if (isBannedText(textContent)) { + this.addOffense( + `Avoid using generic link text such as "${textContent.trim()}". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.`, + node.location, + ) + } + } +} + +export class A11yAvoidGenericLinkTextRule extends ParserRule { + static ruleName = "a11y-avoid-generic-link-text" + static introducedIn = this.version("unreleased") + + get defaultConfig(): FullRuleConfig { + return { + enabled: false, + severity: "warning" + } + } + + get parserOptions(): Partial { + return { + action_view_helpers: true, + } + } + + check(result: ParseResult, context?: Partial): UnboundLintOffense[] { + const visitor = new AvoidGenericLinkTextVisitor(this.ruleName, context) + visitor.visit(result.value) + return visitor.offenses + } +} diff --git a/javascript/packages/linter/src/rules/index.ts b/javascript/packages/linter/src/rules/index.ts index 4d0f1479d..c9dba8b6d 100644 --- a/javascript/packages/linter/src/rules/index.ts +++ b/javascript/packages/linter/src/rules/index.ts @@ -1,3 +1,4 @@ +export * from "./a11y-avoid-generic-link-text.js" export * from "./a11y-disabled-attribute.js" export * from "./a11y-no-accesskey-attribute.js" export * from "./a11y-no-aria-unsupported-elements.js" diff --git a/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap b/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap index 1fc729ba8..49638e9e8 100644 --- a/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap +++ b/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap @@ -111,7 +111,7 @@ test/fixtures/ignored.html.erb:8:8 Offenses 5 errors | 2 warnings (7 offenses across 1 file) Note 3 additional offenses reported (would have been ignored) Fixable 7 offenses | 4 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > Excluded Files > skips excluded file in subdirectory with README.md project indicator 1`] = ` @@ -177,7 +177,7 @@ test/fixtures/test-file-with-errors.html.erb:2:22 Checked 1 file Offenses 2 errors | 1 warning (3 offenses across 1 file) Fixable 3 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > GitHub Actions format includes rule codes 1`] = ` @@ -202,7 +202,7 @@ test/fixtures/no-trailing-newline.html.erb:1:29 Checked 1 file Offenses 1 error | 0 warnings (1 offense across 1 file) Fixable 1 offense | 1 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > GitHub Actions format includes rule codes 2`] = ` @@ -266,7 +266,7 @@ test/fixtures/erb-no-extra-whitespace-inside-tags.html.erb:1:4 Checked 1 file Offenses 4 errors | 0 warnings (4 offenses across 1 file) Fixable 4 offenses | 3 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > Ignores disabled rules 1`] = ` @@ -307,7 +307,7 @@ test/fixtures/ignored.html.erb:6:14 Checked 1 file Offenses 2 errors | 0 warnings | 3 ignored (2 offenses across 1 file) Fixable 2 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > allows tag.attributes in attribute position 1`] = ` @@ -346,7 +346,7 @@ test/fixtures/tag-attributes.html.erb:5:0 Checked 1 file Offenses 2 warnings (2 offenses across 1 file) Fixable 2 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > diplays only parsers errors if one is present 1`] = ` @@ -372,7 +372,7 @@ test/fixtures/parser-errors.html.erb:2:16 Checked 1 file Offenses 1 error | 0 warnings (1 offense across 1 file) Fixable 0 offenses - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > displays most violated rules with multiple offenses 1`] = ` @@ -587,7 +587,7 @@ test/fixtures/multiple-rule-offenses.html.erb:4:2 Checked 1 file Offenses 8 errors | 6 warnings (14 offenses across 1 file) Fixable 14 offenses | 4 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > displays rule offenses when showing all rules 1`] = ` @@ -684,7 +684,7 @@ test/fixtures/few-rule-offenses.html.erb:6:0 Checked 1 file Offenses 4 errors | 2 warnings (6 offenses across 1 file) Fixable 6 offenses | 3 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > formats GitHub Actions output correctly for bad file 1`] = ` @@ -723,7 +723,7 @@ test/fixtures/bad-file.html.erb:1:16 Checked 1 file Offenses 2 errors | 0 warnings (2 offenses across 1 file) Fixable 2 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > formats GitHub Actions output correctly for clean file 1`] = ` @@ -735,7 +735,7 @@ exports[`CLI Output Formatting > formats GitHub Actions output correctly for cle Checked 1 file Offenses 0 offenses Fixable 0 offenses - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > formats GitHub Actions output correctly for file with errors 1`] = ` @@ -795,7 +795,7 @@ test/fixtures/test-file-with-errors.html.erb:2:22 Checked 1 file Offenses 2 errors | 1 warning (3 offenses across 1 file) Fixable 3 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > formats GitHub Actions output with --format=github option 1`] = ` @@ -834,7 +834,7 @@ test/fixtures/test-file-simple.html.erb:2:22 Checked 1 file Offenses 2 errors | 0 warnings (2 offenses across 1 file) Fixable 2 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > formats JSON output correctly for bad file 1`] = ` @@ -1038,7 +1038,7 @@ test/fixtures/test-file-with-errors.html.erb:2:22 Checked 1 file Offenses 2 errors | 1 warning (3 offenses across 1 file) Fixable 3 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > formats simple output correctly 1`] = ` @@ -1057,7 +1057,7 @@ test/fixtures/test-file-simple.html.erb: Checked 1 file Offenses 2 errors | 0 warnings (2 offenses across 1 file) Fixable 2 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > formats simple output for bad-file correctly 1`] = ` @@ -1076,7 +1076,7 @@ test/fixtures/bad-file.html.erb: Checked 1 file Offenses 2 errors | 0 warnings (2 offenses across 1 file) Fixable 2 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > formats success output correctly 1`] = ` @@ -1088,7 +1088,7 @@ exports[`CLI Output Formatting > formats success output correctly 1`] = ` Checked 1 file Offenses 0 offenses Fixable 0 offenses - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > handles boolean attributes 1`] = ` @@ -1100,7 +1100,7 @@ exports[`CLI Output Formatting > handles boolean attributes 1`] = ` Checked 1 file Offenses 0 offenses Fixable 0 offenses - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > handles multiple errors correctly 1`] = ` @@ -1135,7 +1135,7 @@ test/fixtures/bad-file.html.erb:1:16 Checked 1 file Offenses 2 errors | 0 warnings (2 offenses across 1 file) Fixable 2 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > herb:disable rules 1`] = ` @@ -1267,7 +1267,7 @@ test/fixtures/disabled-1.html.erb:14:19 Checked 1 file Offenses 2 errors | 6 warnings | 8 ignored (8 offenses across 1 file) Fixable 8 offenses | 1 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > herb:disable rules 2`] = ` @@ -1470,7 +1470,7 @@ test/fixtures/disabled-2.html.erb:2:44 Checked 1 file Offenses 6 errors | 7 warnings | 5 ignored (13 offenses across 1 file) Fixable 13 offenses | 6 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; exports[`CLI Output Formatting > uses GitHub Actions format by default when GITHUB_ACTIONS is true 1`] = ` @@ -1530,5 +1530,5 @@ test/fixtures/test-file-with-errors.html.erb:2:22 Checked 1 file Offenses 2 errors | 1 warning (3 offenses across 1 file) Fixable 3 offenses | 2 autocorrectable using \`--fix\` - Rules 83 enabled | 12 not enabled" + Rules 83 enabled | 13 not enabled" `; diff --git a/javascript/packages/linter/test/rules/a11y-avoid-generic-link-text.test.ts b/javascript/packages/linter/test/rules/a11y-avoid-generic-link-text.test.ts new file mode 100644 index 000000000..fa11196e7 --- /dev/null +++ b/javascript/packages/linter/test/rules/a11y-avoid-generic-link-text.test.ts @@ -0,0 +1,121 @@ +import { describe, test } from "vitest" +import { A11yAvoidGenericLinkTextRule } from "../../src/rules/a11y-avoid-generic-link-text.js" +import { createLinterTest } from "../helpers/linter-test-helper.js" + +const { expectNoOffenses, expectWarning, assertOffenses } = createLinterTest(A11yAvoidGenericLinkTextRule) + +describe("a11y-avoid-generic-link-text", () => { + test("passes for link with descriptive text", () => { + expectNoOffenses('Learn more about GitHub') + }) + + test("passes for link with aria-label", () => { + expectNoOffenses('Learn more') + }) + + test("passes for link with dynamic aria-label", () => { + expectNoOffenses('Learn more') + }) + + test("passes for link with aria-labelledby", () => { + expectNoOffenses('Learn more') + }) + + test("passes for link with ERB content", () => { + expectNoOffenses('<%= link_text %>') + }) + + test("passes for link with no text", () => { + expectNoOffenses('') + }) + + test("passes for non-link element with banned text", () => { + expectNoOffenses('Click here') + }) + + test("fails for link with 'Learn more'", () => { + expectWarning('Avoid using generic link text such as "Learn more". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('Learn more') + }) + + test("fails for link with 'Read more'", () => { + expectWarning('Avoid using generic link text such as "Read more". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('Read more') + }) + + test("fails for link with 'Click here'", () => { + expectWarning('Avoid using generic link text such as "Click here". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('Click here') + }) + + test("fails for link with 'More'", () => { + expectWarning('Avoid using generic link text such as "More". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('More') + }) + + test("fails for link with 'Link'", () => { + expectWarning('Avoid using generic link text such as "Link". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('Link') + }) + + test("fails for link with 'Here'", () => { + expectWarning('Avoid using generic link text such as "Here". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('Here') + }) + + test("fails for link with case-insensitive banned text", () => { + expectWarning('Avoid using generic link text such as "CLICK HERE". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('CLICK HERE') + }) + + test("fails for link with extra whitespace in banned text", () => { + expectWarning('Avoid using generic link text such as "Learn more". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses(' Learn more ') + }) + + test("fails for link_to with generic text", () => { + expectWarning('Avoid using generic link text such as "More". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('<%= link_to "More", root_path %>') + }) + + test("fails for link_to with 'Click here'", () => { + expectWarning('Avoid using generic link text such as "Click here". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('<%= link_to "Click here", "#" %>') + }) + + test("passes for link_to with descriptive text", () => { + expectNoOffenses('<%= link_to "Learn more about GitHub", root_path %>') + }) + + test("passes for link_to with aria-label string key", () => { + expectNoOffenses('<%= link_to "More", root_path, "aria-label": "Learn more about GitHub" %>') + }) + + test("passes for link_to with aria hash label", () => { + expectNoOffenses('<%= link_to "More", root_path, aria: { label: "Learn more about GitHub" } %>') + }) + + test("passes for link_to with dynamic aria-label", () => { + expectNoOffenses('<%= link_to "More", root_path, aria: { label: label_text } %>') + }) + + test("fails for link_to block form with generic text", () => { + expectWarning('Avoid using generic link text such as "Read more". Screen reader users often navigate by links, and generic text like "Read more", "Learn more", "Click here", "More", "Link", or "Here" is not meaningful out of context.') + + assertOffenses('<%= link_to root_path do %>Read more<% end %>') + }) + + test("passes for link_to block form with descriptive text", () => { + expectNoOffenses('<%= link_to root_path do %>Read more about GitHub<% end %>') + }) +}) diff --git a/test/engine/action_view/link_to_test.rb b/test/engine/action_view/link_to_test.rb index c346f0b72..cb651ad00 100644 --- a/test/engine/action_view/link_to_test.rb +++ b/test/engine/action_view/link_to_test.rb @@ -68,6 +68,16 @@ def model_name = "Profile" <% end %> ERB end + + test "link_to with aria-label string key" do + assert_optimized_snapshot('<%= link_to "More", "#", "aria-label": "Learn more about GitHub" %>') + assert_parsed_snapshot('<%= link_to "More", "#", "aria-label": "Learn more about GitHub" %>', action_view_helpers: true) + end + + test "link_to with aria hash label" do + assert_optimized_snapshot('<%= link_to "More", "#", aria: { label: "Learn more about GitHub" } %>') + assert_parsed_snapshot('<%= link_to "More", "#", aria: { label: "Learn more about GitHub" } %>', action_view_helpers: true) + end end end end diff --git a/test/snapshots/engine/action_view/link_to_test/test_0012_link_to_with_aria-label_string_key_4cb1c219b60c5075805803030cc07700.txt b/test/snapshots/engine/action_view/link_to_test/test_0012_link_to_with_aria-label_string_key_4cb1c219b60c5075805803030cc07700.txt new file mode 100644 index 000000000..d83c326a8 --- /dev/null +++ b/test/snapshots/engine/action_view/link_to_test/test_0012_link_to_with_aria-label_string_key_4cb1c219b60c5075805803030cc07700.txt @@ -0,0 +1,6 @@ +--- +source: "Engine::ActionView::LinkToTest#test_0012_link_to with aria-label string key" +input: "{source: \"<%= link_to \\\"More\\\", \\\"#\\\", \\\"aria-label\\\": \\\"Learn more about GitHub\\\" %>\", options: {optimize: true}}" +--- +_buf = ::String.new; _buf << 'More'.freeze; +_buf.to_s diff --git a/test/snapshots/engine/action_view/link_to_test/test_0012_link_to_with_aria-label_string_key_5daec1fb841a45823700bc029cc4c3bc.txt b/test/snapshots/engine/action_view/link_to_test/test_0012_link_to_with_aria-label_string_key_5daec1fb841a45823700bc029cc4c3bc.txt new file mode 100644 index 000000000..7b18c3474 --- /dev/null +++ b/test/snapshots/engine/action_view/link_to_test/test_0012_link_to_with_aria-label_string_key_5daec1fb841a45823700bc029cc4c3bc.txt @@ -0,0 +1,5 @@ +--- +source: "Engine::ActionView::LinkToTest#test_0012_link_to with aria-label string key" +input: "{source: \"<%= link_to \\\"More\\\", \\\"#\\\", \\\"aria-label\\\": \\\"Learn more about GitHub\\\" %>\", locals: {}, options: {action_view_helpers: true}}" +--- +More \ No newline at end of file diff --git a/test/snapshots/engine/action_view/link_to_test/test_0012_link_to_with_aria-label_string_key_b16128baa22222ede038ab6d582a83f0-ef4af315cb33925c38d24ea3c2e8a1cd.txt b/test/snapshots/engine/action_view/link_to_test/test_0012_link_to_with_aria-label_string_key_b16128baa22222ede038ab6d582a83f0-ef4af315cb33925c38d24ea3c2e8a1cd.txt new file mode 100644 index 000000000..03a3fce41 --- /dev/null +++ b/test/snapshots/engine/action_view/link_to_test/test_0012_link_to_with_aria-label_string_key_b16128baa22222ede038ab6d582a83f0-ef4af315cb33925c38d24ea3c2e8a1cd.txt @@ -0,0 +1,67 @@ +--- +source: "Engine::ActionView::LinkToTest#test_0012_link_to with aria-label string key" +input: "<%= link_to \"More\", \"#\", \"aria-label\": \"Learn more about GitHub\" %>" +options: {action_view_helpers: true} +--- +@ DocumentNode (location: (1:0)-(1:67)) +└── children: (1 item) + └── @ HTMLElementNode (location: (1:0)-(1:67)) + ├── open_tag: + │ └── @ ERBOpenTagNode (location: (1:0)-(1:67)) + │ ├── tag_opening: "<%=" (location: (1:0)-(1:3)) + │ ├── content: " link_to "More", "#", "aria-label": "Learn more about GitHub" " (location: (1:3)-(1:65)) + │ ├── tag_closing: "%>" (location: (1:65)-(1:67)) + │ ├── tag_name: "a" (location: (1:4)-(1:11)) + │ └── children: (2 items) + │ ├── @ HTMLAttributeNode (location: (1:26)-(1:64)) + │ │ ├── name: + │ │ │ └── @ HTMLAttributeNameNode (location: (1:26)-(1:36)) + │ │ │ └── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:26)-(1:36)) + │ │ │ └── content: "aria-label" + │ │ │ + │ │ │ + │ │ ├── equals: ": " (location: (1:37)-(1:39)) + │ │ └── value: + │ │ └── @ HTMLAttributeValueNode (location: (1:39)-(1:64)) + │ │ ├── open_quote: """ (location: (1:39)-(1:40)) + │ │ ├── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:40)-(1:63)) + │ │ │ └── content: "Learn more about GitHub" + │ │ │ + │ │ ├── close_quote: """ (location: (1:63)-(1:64)) + │ │ └── quoted: true + │ │ + │ │ + │ └── @ HTMLAttributeNode (location: (1:20)-(1:23)) + │ ├── name: + │ │ └── @ HTMLAttributeNameNode (location: (1:20)-(1:23)) + │ │ └── children: (1 item) + │ │ └── @ LiteralNode (location: (1:20)-(1:23)) + │ │ └── content: "href" + │ │ + │ │ + │ ├── equals: "=" (location: (1:20)-(1:23)) + │ └── value: + │ └── @ HTMLAttributeValueNode (location: (1:20)-(1:23)) + │ ├── open_quote: """ (location: (1:20)-(1:20)) + │ ├── children: (1 item) + │ │ └── @ LiteralNode (location: (1:20)-(1:23)) + │ │ └── content: "#" + │ │ + │ ├── close_quote: """ (location: (1:23)-(1:23)) + │ └── quoted: true + │ + │ + │ + ├── tag_name: "a" (location: (1:4)-(1:11)) + ├── body: (1 item) + │ └── @ HTMLTextNode (location: (1:0)-(1:67)) + │ └── content: "More" + │ + ├── close_tag: + │ └── @ HTMLVirtualCloseTagNode (location: (1:67)-(1:67)) + │ └── tag_name: "a" (location: (1:4)-(1:11)) + │ + ├── is_void: false + └── element_source: "ActionView::Helpers::UrlHelper#link_to" \ No newline at end of file diff --git a/test/snapshots/engine/action_view/link_to_test/test_0013_link_to_with_aria_hash_label_4d39d9fc22da3917c3a3f13bef420650.txt b/test/snapshots/engine/action_view/link_to_test/test_0013_link_to_with_aria_hash_label_4d39d9fc22da3917c3a3f13bef420650.txt new file mode 100644 index 000000000..1ab0f5e54 --- /dev/null +++ b/test/snapshots/engine/action_view/link_to_test/test_0013_link_to_with_aria_hash_label_4d39d9fc22da3917c3a3f13bef420650.txt @@ -0,0 +1,5 @@ +--- +source: "Engine::ActionView::LinkToTest#test_0013_link_to with aria hash label" +input: "{source: \"<%= link_to \\\"More\\\", \\\"#\\\", aria: { label: \\\"Learn more about GitHub\\\" } %>\", locals: {}, options: {action_view_helpers: true}}" +--- +More \ No newline at end of file diff --git a/test/snapshots/engine/action_view/link_to_test/test_0013_link_to_with_aria_hash_label_9f886823e619435b71a40e3a263ef62a-ef4af315cb33925c38d24ea3c2e8a1cd.txt b/test/snapshots/engine/action_view/link_to_test/test_0013_link_to_with_aria_hash_label_9f886823e619435b71a40e3a263ef62a-ef4af315cb33925c38d24ea3c2e8a1cd.txt new file mode 100644 index 000000000..4df819bd4 --- /dev/null +++ b/test/snapshots/engine/action_view/link_to_test/test_0013_link_to_with_aria_hash_label_9f886823e619435b71a40e3a263ef62a-ef4af315cb33925c38d24ea3c2e8a1cd.txt @@ -0,0 +1,67 @@ +--- +source: "Engine::ActionView::LinkToTest#test_0013_link_to with aria hash label" +input: "<%= link_to \"More\", \"#\", aria: { label: \"Learn more about GitHub\" } %>" +options: {action_view_helpers: true} +--- +@ DocumentNode (location: (1:0)-(1:70)) +└── children: (1 item) + └── @ HTMLElementNode (location: (1:0)-(1:70)) + ├── open_tag: + │ └── @ ERBOpenTagNode (location: (1:0)-(1:70)) + │ ├── tag_opening: "<%=" (location: (1:0)-(1:3)) + │ ├── content: " link_to "More", "#", aria: { label: "Learn more about GitHub" } " (location: (1:3)-(1:68)) + │ ├── tag_closing: "%>" (location: (1:68)-(1:70)) + │ ├── tag_name: "a" (location: (1:4)-(1:11)) + │ └── children: (2 items) + │ ├── @ HTMLAttributeNode (location: (1:33)-(1:65)) + │ │ ├── name: + │ │ │ └── @ HTMLAttributeNameNode (location: (1:33)-(1:38)) + │ │ │ └── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:33)-(1:38)) + │ │ │ └── content: "aria-label" + │ │ │ + │ │ │ + │ │ ├── equals: ": " (location: (1:38)-(1:40)) + │ │ └── value: + │ │ └── @ HTMLAttributeValueNode (location: (1:40)-(1:65)) + │ │ ├── open_quote: """ (location: (1:40)-(1:41)) + │ │ ├── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:41)-(1:64)) + │ │ │ └── content: "Learn more about GitHub" + │ │ │ + │ │ ├── close_quote: """ (location: (1:64)-(1:65)) + │ │ └── quoted: true + │ │ + │ │ + │ └── @ HTMLAttributeNode (location: (1:20)-(1:23)) + │ ├── name: + │ │ └── @ HTMLAttributeNameNode (location: (1:20)-(1:23)) + │ │ └── children: (1 item) + │ │ └── @ LiteralNode (location: (1:20)-(1:23)) + │ │ └── content: "href" + │ │ + │ │ + │ ├── equals: "=" (location: (1:20)-(1:23)) + │ └── value: + │ └── @ HTMLAttributeValueNode (location: (1:20)-(1:23)) + │ ├── open_quote: """ (location: (1:20)-(1:20)) + │ ├── children: (1 item) + │ │ └── @ LiteralNode (location: (1:20)-(1:23)) + │ │ └── content: "#" + │ │ + │ ├── close_quote: """ (location: (1:23)-(1:23)) + │ └── quoted: true + │ + │ + │ + ├── tag_name: "a" (location: (1:4)-(1:11)) + ├── body: (1 item) + │ └── @ HTMLTextNode (location: (1:0)-(1:70)) + │ └── content: "More" + │ + ├── close_tag: + │ └── @ HTMLVirtualCloseTagNode (location: (1:70)-(1:70)) + │ └── tag_name: "a" (location: (1:4)-(1:11)) + │ + ├── is_void: false + └── element_source: "ActionView::Helpers::UrlHelper#link_to" \ No newline at end of file diff --git a/test/snapshots/engine/action_view/link_to_test/test_0013_link_to_with_aria_hash_label_b7a0928a858e765acc5b414a15832442.txt b/test/snapshots/engine/action_view/link_to_test/test_0013_link_to_with_aria_hash_label_b7a0928a858e765acc5b414a15832442.txt new file mode 100644 index 000000000..7a2182164 --- /dev/null +++ b/test/snapshots/engine/action_view/link_to_test/test_0013_link_to_with_aria_hash_label_b7a0928a858e765acc5b414a15832442.txt @@ -0,0 +1,6 @@ +--- +source: "Engine::ActionView::LinkToTest#test_0013_link_to with aria hash label" +input: "{source: \"<%= link_to \\\"More\\\", \\\"#\\\", aria: { label: \\\"Learn more about GitHub\\\" } %>\", options: {optimize: true}}" +--- +_buf = ::String.new; _buf << 'More'.freeze; +_buf.to_s