caramel
Skip to content
Browse documentation
Guides / Corretto

Test the HTML your app returns.

Describe rendered elements with Blueprint-shaped expectations while Corretto drives real requests and isolated database state.

Start with a real response

Generated spec helpers require caramel/corretto and call Corretto.configure(App). Inside an example, use a session to request the page and assert its observable output.

spec/requests/books_spec.cr · after creating a Book titled <Dune>
shown = client.get(path)
shown.should have_status(200)
shown.should render_page("Book")
shown.should have_html {
  section(class: "form-page") {
    h1 { "Book" }
    dl { dd { "<Dune>" } }
    a(href: "#{path}/edit") { "Edit book" }
  }
}

The block records requirements independently of the view renderer. Expected strings are decoded literal text: "<Dune>" checks escaped user text. Build expectations from values and elements, rather than rendering an application view as the expected result.

Select the requirements that matter

Each block has one root. By default, extra wrappers, markup and attributes are allowed. Nested requirements belong to the same matched ancestor; sibling requirements consume distinct nodes. Text matches exactly after HTML whitespace normalization, while pre and textarea preserve whitespace. Classes match a token subset, and Boolean attributes check presence or absence. Use strings for literal ARIA/data values such as aria_expanded: "false".

Corretto · scopes and counts
shown.should have_html(within: "#books", count: 2) { li(class: "book") }
shown.should have_html(count: 0) { script(src: "/unexpected.js") }
shown.should_not have_html { p { "Access denied" } }

Numeric, Boolean, and character leaf values check their literal text. Convert other values with .to_s. Use plain for direct text beside child elements. Loops and captured local values work; use element("book-card") for custom tags. A misspelled standard element fails compilation.

Check structure and partial responses

strict: true compares complete attributes and ordered direct children, ignoring comments and insignificant formatting whitespace. Use it when the complete subtree is the contract.

Corretto · a complete subtree
shown.should have_html(strict: true) {
  ul(class: "books") {
    li { "Dune" }
    li { "Solaris" }
  }
}

The block overload of render_partial checks target, swap, and content in the same hx-partial envelope. HTML5 table and select contexts are supported, including rows or cells inside partial envelopes.

Corretto · fragment response
created.should render_partial("#note-list", swap: "beforeend") {
  li { "<b>Milk</b> ×2" }
}

render_page("Books") requires an actual source doctype and a parsed title containing Books. Both plain response matchers and block expectations are available. Invalid selectors, missing scopes, and invalid patterns raise even for negative assertions. Keep exact-byte tests for serialization and browser tests for JavaScript and htmx morphing.

Install the test dependencies

Generated applications declare Lexbor 3.6.4 as a development dependency. Keep this pin and the matching lockfile entry when configuring a project; frappe setup uses a frozen install. Development installs build the native parser with cc and ar, and need network access for its verified source download. Production installs omit the parser.

shard.yml · development dependencies
development_dependencies:
  lexbor:
    github: kostya/lexbor
    version: 3.6.4

Use frappe lint for the scoped paragraph and nested-block rules in expectations. Upstream Ameba editor integrations alone do not load Caramel’s custom rules.

Terminal
frappe check
frappe corretto spec/requests/books_spec.cr
frappe lint