Skip to content

Html testing utils - #79

Merged
bolivier merged 2 commits into
mainfrom
html-testing-utils
Feb 11, 2026
Merged

bolivier merged 2 commits into
mainfrom
html-testing-utils

Conversation

@bolivier

@bolivier bolivier commented Feb 11, 2026 •

Copy link
Copy Markdown
Owner

Add the rest of the get-by-role testing utils

Summary by CodeRabbit

  • New Features

    • Added role-based element querying with optional filters for semantic roles (buttons, links, form controls, headings, etc.).
  • Refactor

    • Consolidated role-matching and HTML parsing logic into dedicated modules and simplified the public API surface.
  • Tests

    • Added new utility tests and expanded role-query test coverage to validate behavior and options.

@coderabbitai

coderabbitai Bot commented Feb 11, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Refactors HTML utilities: extracts hiccup inspection/text and option-matching into rapid-test.html.utils, moves role-matching multimethods into rapid-test.html.role, and reduces rapid-test.html to re-export get-by-role and get-all-by-role.

Changes

Cohort / File(s) Summary
Core HTML Module
src/main/rapid_test/html.clj
Removed local hiccup/role implementations and now re-exports get-by-role and get-all-by-role from rapid-test.html.role via import-vars. Reduced namespace deps.
Utilities
src/main/rapid_test/html/utils.clj
New utilities for hiccup inspection and accessibility: has-props?, get-props, get-attribute, get-base-tag, get-text, get-accessible-name, get-heading-level, inside-sectioning-content?, and matches-options?.
Role Matching
src/main/rapid_test/html/role.clj
New multimethod role-match? with implementations for many ARIA-like roles and helpers get-by-role / get-all-by-role that traverse hiccup zippers and apply option filters.
Tests
test/main/rapid_test/html/utils_test.clj, test/main/rapid_test/html_test.clj
Added unit tests for utils; updated/expanded role-related tests and removed granular inline utils tests from the main html test file.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant RoleModule as rapid-test.html.role
  participant Utils as rapid-test.html.utils
  participant HZip as rapid-test.hiccup-zipper

  Caller->>RoleModule: get-by-role(role, hiccup, opts?)
  RoleModule->>HZip: hiccup-zipper(hiccup)
  HZip-->>RoleModule: zipper stream
  loop each node
    RoleModule->>Utils: get-base-tag / get-attribute / get-accessible-name
    Utils-->>RoleModule: properties, text, name
    RoleModule->>RoleModule: role-match?(role, node, props)
    alt matches and opts pass
      RoleModule-->>Caller: return node (or collect for get-all-by-role)
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Create first html testing utils #78: Prior refactor that originally introduced the HTML utilities and hiccup-zipper code now reorganized into rapid-test.html.utils and rapid-test.html.role.

Poem

🐰 I hopped through code, split roles apart,

Pulled out the utils with a clever art,
Multimethods sing, zippers lead the way,
Two clean modules now brighten the day,
A tiny carrot flag for tests that play 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title "Html testing utils" is vague and generic, using non-descriptive terms that do not convey specific information about the changeset. It does not clearly indicate the main restructuring work (consolidation into role.clj and utils.clj) or API changes. Consider a more specific title such as "Refactor HTML testing utilities into separate modules" or "Extract role-based matching into dedicated html.role namespace"
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch html-testing-utils

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/main/rapid_test/html/role.clj`:
- Around line 135-142: The role-match? method for :combobox currently calls
(long size) which will ClassCastException on string attributes; change the size
coercion to use parse-long like get-heading-level does: retrieve size via
util/get-attribute, call parse-long (or a safe parse wrapper used elsewhere)
only when size is present, bind the parsed value (e.g. size-n) and then use (or
(nil? size) (<= size-n 1)) in the existing predicate; update the role-match?
implementation accordingly so strings like "2" are parsed safely.

In `@src/main/rapid_test/html/utils.clj`:
- Around line 21-44: The transient `props` mutations discard assoc! return
values, violating the transient contract; fix by removing transients and using
immutable assoc calls (replace (transient base-props) with base-props and
replace assoc! calls at the :class and :id sites with assoc so the final result
reflects those changes), or alternatively capture the assoc! return values into
the `props` binding (e.g. rebind props from assoc! results before calling
persistent!); update the code around props, assoc!/assoc, persistent!, and the
bindings for class-sb and id to ensure the mutated map is the one returned.
🧹 Nitpick comments (4)
src/main/rapid_test/html/utils.clj (1)

78-90: get-text on nil input relies on hiccup-zipper accepting nil — worth a guard.

The test suite calls (get-text nil) and expects "". This works only if hiccup-zipper doesn't throw on nil. A nil-check at the entry point would make the contract explicit and prevent a subtle breakage if hiccup-zipper ever tightens its preconditions.

Proposed guard
 (defn get-text
   "Get the (nested) text of a hiccup node."
   ([hiccup]
-   (.toString (get-text (StringBuilder.) (hiccup-zipper hiccup))))
+   (if (nil? hiccup)
+     ""
+     (.toString (get-text (StringBuilder.) (hiccup-zipper hiccup)))))
src/main/rapid_test/html/role.clj (2)

186-200: Remove REPL scratch comment block before merging.

This contains mutable def forms that shadow each other and serve no documentation purpose. Consider removing or converting to a proper docstring/test.


19-28: :listitem doesn't support explicit-role? fallback, unlike other roles.

Every other role method falls back to checking (explicit-role? hiccup :listitem), but this one only checks the tag + parent structure. If this is intentional per ARIA spec, a brief comment would help. Otherwise, add the fallback for consistency.

test/main/rapid_test/html/utils_test.clj (1)

1-50: Good foundational tests. Consider adding coverage for get-accessible-name, get-heading-level, matches-options?, and inside-sectioning-content?.

These utility functions have non-trivial logic (aria-label precedence, aria-level parsing, regex matching, zipper traversal) that would benefit from direct unit tests, even though they're exercised indirectly through html_test.clj.

Comment thread src/main/rapid_test/html/role.clj
Comment thread src/main/rapid_test/html/utils.clj Outdated
@bolivier
bolivier force-pushed the html-testing-utils branch 2 times, most recently from d8cd79a to 1b6f285 Compare February 11, 2026 13:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/main/rapid_test/html_test.clj`:
- Around line 92-98: The test currently asserts that [:input {:type "email"}] is
not a :textbox, but per HTML-AAM and Testing Library it should be treated as a
textbox; update the test expectation and the role-detection logic so
sut/get-by-role (and whatever role mapping function in the sut namespace that
classifies inputs by type) treats input elements with type "email" (and no list
attribute) as matching :textbox; modify the code path that checks input types
(e.g., the function or map that handles "text", "search", "tel", "url", "email",
etc.) to include "email", and change the test line to expect some? instead of
nil? for [:input {:type "email"}].

Comment on lines +92 to +98
(testing "textbox"
(is (some? (sut/get-by-role :textbox [:div [:textarea]])))
(is (some? (sut/get-by-role :textbox
[:div [:input {:type "text"}]])))
(is (some? (sut/get-by-role :textbox [:div [:input]])))
(is (nil? (sut/get-by-role :textbox
[:div [:input {:type "email"}]]))))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

HTML-AAM specification input type email textbox role mapping

💡 Result:

Per the HTML Accessibility API Mappings (HTML-AAM) spec, an <input type="email"> is mapped to the ARIA textbox role (i.e., it is exposed to accessibility APIs as a text input). It may also carry an object attribute indicating its specific text-input type (e.g., text-input-type:email in IA2/MSAA mappings, and UIA localized control type “email”). [1], [2]

If the email input uses a suggestions source (i.e., it has a list attribute pointing to a <datalist>), then the computed ARIA role is combobox (with the relationship to the suggestion list exposed via aria-controls/list). [1]

Sources
[1] HTML-AAM Working Draft (2024-02-26) – mappings for email state and for inputs with suggestions source element. (w3.org)
[2] HTML-AAM Working Draft (2021-08-10) – email state mapping to textbox and text-input-type:email. (w3.org)

Citations:


🌐 Web query:

Testing Library getByRole textbox input type email

💡 Result:

<input type="email"> has the implicit ARIA role textbox (when it has no list attribute), so you query it like any other textbox and (ideally) filter by its accessible name (usually the <label> text). [1][2]

const email = screen.getByRole('textbox', { name: /email/i })

If you don’t have a proper label, add one (or use aria-label / aria-labelledby) so the name option works as expected. [1][3]


input[type="email"] should match :textbox per HTML-AAM spec and Testing Library.

Lines 97-98 assert that [:input {:type "email"}] does not match :textbox, but per the HTML-AAM spec, <input type="email"> (without a list attribute) has the implicit ARIA role textbox. Testing Library's getByRole('textbox') also matches email inputs. Update this test and the underlying implementation to include email inputs in the :textbox role.

🤖 Prompt for AI Agents
In `@test/main/rapid_test/html_test.clj` around lines 92 - 98, The test currently
asserts that [:input {:type "email"}] is not a :textbox, but per HTML-AAM and
Testing Library it should be treated as a textbox; update the test expectation
and the role-detection logic so sut/get-by-role (and whatever role mapping
function in the sut namespace that classifies inputs by type) treats input
elements with type "email" (and no list attribute) as matching :textbox; modify
the code path that checks input types (e.g., the function or map that handles
"text", "search", "tel", "url", "email", etc.) to include "email", and change
the test line to expect some? instead of nil? for [:input {:type "email"}].

@bolivier
bolivier merged commit 20a0c92 into main Feb 11, 2026
2 checks passed
@bolivier
bolivier deleted the html-testing-utils branch February 11, 2026 13:48
This was referenced Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant