agentleFS
aboio-labsaboio-labs/aboio-skills/skills/frontend-testing/SKILL.md

frontend-testing

Skill3 starsChanged 9 months agoMITView on GitHub

Lustre frontend testing — model, update, and behavior. No styling tests unless explicitly requested.

18 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use backend-code-review, frontend-query-mutation and 4 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • jpoindexterSkill

    backend-code-review

    Review backend code for quality, security, maintainability, and best practices based on established checklist rules. Use when the user requests a review, analysis, or improvement of backend files (e.g., `.py`) under the `api/` directory. Do NOT use for frontend files (e.g., `.tsx`, `.ts`, `.js`). Supports pending-change review, code snippets review, and file-focused review.

    Also in 5 projects that use this

  • lien0219Skill

    frontend-query-mutation

    Guide for implementing Langflow frontend query and mutation patterns with Axios and TanStack React Query v5. Trigger when creating or updating API hooks in controllers/API/queries, consuming UseRequestProcessor in components, deciding whether to use useQuery or useMutation, handling conditional queries, cache invalidation, mutation error handling, or migrating legacy API calls to the query hook pattern.

    Also in 3 projects that use this

  • Charlie85270Skill

    component-refactoring

    Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component --json` shows complexity > 50 or lineCount > 300, when the user asks for code splitting, hook extraction, or complexity reduction, or when `pnpm analyze-component` warns to refactor before testing; avoid for simple/well-structured components, third-party wrappers, or when the user explicitly wants testing without refactoring.

    Also in 4 projects that use this

  • chendongqiSkill

    frontend-code-review

    Trigger when the user requests a review of frontend files (e.g., `.tsx`, `.ts`, `.js`). Support both pending-change reviews and focused file reviews while applying the checklist rules.

    Also in 7 projects that use this

  • etewiahSkill

    e2e-testing

    Playwright E2E testing and Lighthouse performance auditing. Use when setting up E2E tests, running Playwright tests, performing Lighthouse audits, or debugging E2E test failures.

    Also in 3 projects that use this

  • CloudflareSkill

    receiving-code-review

    Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation

    Also in 4 projects that use this

The file

We split this skill into the rules it gives an agent

1 rule. 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 18 edited versions we read keep these.

  • 0

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Level 2b — Attribute assertions: Use simulate.view() + query.has()/query.find() to verify DOM attributes (ARIA, data-state, disabled) …

    line 43

“Common” is measured: kept by at least 6 in 10 of the 18 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Frontend Testing

Lustre frontend testing — model, update, and behavior. No styling tests unless explicitly requested.

Iron Law

Frontend tests verify model, update, and behavior — never CSS classes, Tailwind utilities, or visual styling. Styling tests are only written when the user explicitly requests them.

What to test (always)

  1. Model state — initial state, state after messages, derived state
  2. Update logic — message → model transformation, effect triggers
  3. User interactions — clicks, keyboard events, form input produce correct messages
  4. Conditional rendering — elements appear/disappear based on model state
  5. ARIA and data attributes — accessibility semantics (aria-checked, data-state)
  6. Text content — labels, error messages, computed display values

What to never test (unless explicitly asked)

  1. CSS class names (query.class("flex"), query.class("bg-brand-100"))
  2. Tailwind utilities or design tokens in class lists
  3. Layout structure (grid columns, flex direction)
  4. Color values, spacing, typography classes
  5. Component wrapper styling (card backgrounds, border radius)

Why: CSS is a moving target. Tailwind classes change during design iterations. Tests coupled to class names break on every visual refresh without catching real bugs. Behavioral tests survive redesigns intact.

Simulate limitations (know before you test)

  1. Effects are discarded — simulate runs no effects. FFI calls, HTTP requests, and timers never fire. Feed effect results back manually with simulate.message().
  2. simulate.click() bypasses browser disabled semantics — a click on a disabled element still dispatches in simulate. Test disabled state by asserting the attribute is present, not by testing click behavior.
  3. CustomEvents from FFI don't fire — if a component uses FFI to dispatch a CustomEvent (e.g., radish-value-change), simulate won't see it. Dispatch it manually with simulate.event().

Test structure (Radish pattern)

  • Level 1 — Unit: Pure functions, no simulate. State constructors, helpers, formatters, pure view helpers.
  • Level 2 — Simulate: Wire up a minimal Lustre app, dispatch events, assert on model via simulate.model().
  • Level 2b — Attribute assertions: Use simulate.view() + query.has()/query.find() to verify DOM attributes (ARIA, data-state, disabled) without a browser.

Level 1 — Unit test examples

// State construction
pub fn initial_order_form_has_empty_lines_test() {
  let model = order_form.init()
  model.lines |> should.equal([])
  model.save |> should.equal(submit.SubmitIdle)
}

// Update logic
pub fn add_line_appends_to_model_test() {
  let model = order_form.init()
  let #(updated, _effect) = order_form.update(model, order_form.AddLine)
  updated.lines |> list.length() |> should.equal(1)
}

// Formatters and derived state
pub fn total_sums_line_quantities_test() {
  let model = Model(lines: [Line(qty: 3), Line(qty: 7)])
  order_form.total(model) |> should.equal(10)
}

// Pure view helper — extract as pub fn, test without full Model
pub fn draft_order_shows_10_percent_progress_test() {
  let view = orders.order_progress(order.OrderDraft, language.Portuguese)
  query.find(in: view, matching: query.element(query.text("10%")))
  |> should.be_ok
}

Level 2 — Simulate test examples

// Click produces correct state change
pub fn click_submit_sets_submitting_test() {
  let model =
    test_app()
    |> simulate.click(on: query.element(query.id("submit-btn")))
    |> simulate.model()
  model.save |> should.equal(submit.SubmitSaving)
}

// Keyboard navigation
pub fn escape_closes_dropdown_test() {
  let model =
    open_app()
    |> simulate.event(
      on: query.element(query.id("dropdown")),
      name: "keydown",
      data: [#("key", json.string("Escape"))],
    )
    |> simulate.model()
  model.is_open |> should.equal(False)
}

// Conditional rendering via text/attribute, NOT class
pub fn error_message_shows_on_invalid_test() {
  let view =
    test_app()
    |> simulate.click(on: query.element(query.id("submit-btn")))
    |> simulate.view()
  query.has(in: view, matching: query.text("Email is required"))
  |> should.equal(True)
}

// ARIA semantics
pub fn disabled_button_has_aria_disabled_test() {
  let view =
    test_app()
    |> simulate.view()
  query.has(
    in: view,
    matching: query.id("submit-btn")
      |> query.and(query.aria("disabled", "true")),
  )
  |> should.equal(True)
}

// Disabled state — assert attribute, NOT click behavior
// (simulate.click bypasses browser disabled semantics)
pub fn disabled_checkbox_has_disabled_attribute_test() {
  let view =
    app_with_state(State(..defaults, disabled: True))
    |> simulate.view()
  query.has(
    in: view,
    matching: query.id("cb")
      |> query.and(query.attribute("disabled", "")),
  )
  |> should.equal(True)
}

Anti-patterns (explicit violations)

// BAD — testing CSS class
pub fn header_has_flex_layout_test() {
  let view = test_app() |> simulate.view()
  query.has(in: view, matching: query.class("flex"))  // VIOLATION
}

// BAD — testing Tailwind token
pub fn badge_has_brand_color_test() {
  let view = test_app() |> simulate.view()
  query.has(in: view, matching: query.class("bg-brand-100"))  // VIOLATION
}

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.