klenod.

Guides

Testing

Write tests beside application modules and rerun only the tests related to a change.

Colocated Tests

Test files live anywhere under src and end in .test.rb. They are test entrypoints, not application dependencies. A test can import the component or module beside it:

Button = import("./Button.haml")

def test_renders_button_children
  screen = render(Button, "Save", type: "submit")
  button = screen.get_by_role(:button, name: "Save")

  assert_equal("submit", button["type"])
end

The example adapter turns each test module into a Minitest test case. Top-level test_*, setup, teardown, and helper methods become instance methods on that case. Assertions come from Minitest, so test files do not need a class or a Minitest require.

Run And Watch

Run the tests and keep watching:

bundle exec klenod test

Use bundle exec klenod test --run for one run. CI also selects one-run mode automatically. The examples bin/test command is a shortcut for the same CLI. Each batch runs in a fresh worker process, so test state cannot leak into later batches or the development server.

The framework-independent klenod-test runner owns test selection, watching, and worker isolation. The nearest klenod.test.rb supplies the application context and callbacks that execute tests and format errors:

context { Example::WebConfig.build_config(mode: :development).context }
execute { |context, paths| Example::Testing::TestRunner.execute(context, paths) }
format_error { |error, context| Example::Testing::TestRunner.format_error(error, context) }

This example connects those callbacks to its Minitest adapter. Another application can use a different test library.

Klenod collects every tests eager and lazy dependency closure. Editing a test reruns that test. Editing an imported module or any transitive dependency reruns every related test. Added tests run immediately, removed tests leave the suite, and unrelated changes do not trigger a test run.

The development server ignores .test.rb changes. The separate test watcher handles them, so editing a test reruns it without producing an unrelated server update.

Coverage

Run the complete test suite once with coverage:

bin/coverage

Configure the report or a required overall percentage in klenod.test.rb:

coverage report: :brief, minimum: 90

Reports may be brief, partial, full, markdown, or quiet.

Coverage includes evaluated application Ruby and modules with Klenod source maps. Generated Ruby execution is mapped back to the original Haml or Markdown lines. Test files, gem and virtual modules, and generated data wrappers are excluded. Files that no test evaluates do not appear in the report.

Coverage wraps the same framework callback used by klenod test, so it is not tied to Minitest. A test framework that starts independent subprocesses needs separate subprocess coverage support.

Framework Helpers

Klenod does not choose a test library or rendering API. The example framework renders the component to HTML, parses that HTML with Nokolexbor, and returns a queryable fragment:

screen = render(Button, "Read the docs", href: "/docs/assets")
link = screen.get_by_role(:link, name: "Read the docs")

assert_equal("/docs/assets", link["href"])

screen.html returns the rendered string and screen.fragment returns the Nokolexbor document fragment. within(node) creates a query scope for that nodes descendants.

Component test methods can use Request and Response directly. The example Minitest adapter installs Example::Framework::Request and Example::Framework::Response on each test module without replacing constants declared by the test.

Role, text, and CSS queries provide get_by_*, query_by_*, get_all_by_*, and query_all_by_* variants. A singular query raises when it finds multiple nodes. get queries also raise when nothing matches, while query variants return nil or an empty array. Errors show the query, candidates, and rendered HTML. has_role?, has_text?, and has_css? provide small matcher-style predicates.

Prefer roles and accessible names. The role implementation models useful static HTML semantics, including labels and ARIA naming, but it is not a complete browser accessibility tree. It cannot test layout, computed styles, focus, events, or JavaScript behavior. The helpers intentionally provide no fake click or focus API; use browser tests for those behaviors.

Text queries normalize whitespace and return the most specific matching elements rather than every matching ancestor. CSS queries are available as an escape hatch. Test rendering also keeps authored class names and omits generated classes for scoped tag selectors, but class selectors should not be the default way to describe user-visible behavior.