Testing

[ AxiomCore · Documentation ]

Frontend tests

Test authored Acore UI behavior with axiom ui test, typed JSON suites, scripted operations, virtual time and explicit evidence levels.

axiom ui test checks an Acore UI module. Without a suite it runs a compiler session smoke test. With --suite it runs scripted clicks, typing, navigation and assertions against the application's semantics, with scripted backend operations and virtual time.

Smoke test or application suite

CommandWhat runsAssertions
axiom ui test main.acoreCompiler session smoke: initial load, a compatible edit, and keeping the last good build after an invalid edit3 session checks, 0 interaction assertions
axiom ui test main.acore --suite FILE --layer referenceThe resolved owner, data, component and routing semanticsYour suite's assertions
axiom ui test main.acore --suite FILE --layer adapterThe maintained web input and button bindings, or the emitted native owner hook and runtime facade, on controlled targetsYour suite's assertions

--layer needs --suite and defaults to reference. --target defaults to ios, so pass --target web for the web adapter. --report FILE writes a machine-readable report. A suite needs Node.js 24 or newer and a compiler and runtime that support acore-application-test/v1 and acore-pure/v2.

Mounted interactions, OS keyboard behavior, accessibility trees and paint need separate target tests.

An executable counter

Save this as main.acore:

main.acore
module examples.testing.counter.ui
page Home {
  state count: Int = 0
  action increment() {count = count + 1}
  view {
    Text(to_string(count))
    Button(label: "Increment",on_press: increment)
  }
}

Save this as application.test.json:

application.test.json
{
  "format": "acore-application-test/v1",
  "name": "Counter",
  "cases": [{
    "name": "increment and cleanup",
    "steps": [
      {"do":"click","query":{"role":"button","name":"Increment"}},
      {"do":"expect","query":{"role":"text","text":"1"},"count":1},
      {"do":"dispose"},
      {"do":"expect_clean"}
    ]
  }]
}

Run both layers:

Terminal: directory with main.acore
axiom ui test main.acore --suite application.test.json --target web --layer reference --report reference.json
axiom ui test main.acore --suite application.test.json --target web --layer adapter --report adapter.json

The report names the suite kind, target, layer, assertion count, skipped and unsupported cases, failures, the visible semantic tree, requests and owner traces. Compilation alone does not satisfy the counter or cleanup assertions.

Suite format

A suite has format, name and 1 to 256 cases; the file is at most 4 MiB. A case has a name, optional targets, a start path (default /), an optional session, mocks and up to 4,096 steps.

Queries

Queries select nodes by role, name, label, text, id and within. id accepts a compiler source semantic ID or a complete runtime instance ID; within selects an ancestor instance. Repeated nodes need an unambiguous name or instance, and a control step needs exactly one match. This controlled semantic projection is not a native OS accessibility tree.

Steps

Step (do)FieldsEffect
click, blur, submitqueryInteract with one control. Clicks on disabled controls do not invoke actions.
typequery, valueEnter text
navigatepathGo to a route
backNoneGo back one route
advancemsRun due callbacks in chronological order on the virtual clock
respondrequest, result or errorAnswer a manual mock by zero-based request index
publishrequest, valueSend a typed stream value
expectquery, optional count, text, value, disabled, invalidAssert on the semantic tree
expect_stateowner, path, valueAssert owned state
expect_routepathAssert the current route
expect_requestsoperation, count, optional args, cancelledAssert operation calls
expect_tracekind, countAssert trace events
dispose, expect_cleanNoneDispose the app, then assert no active owner, request or timer remains

Unsupported adapter controls or capabilities fail explicitly; they never count as coverage.

Scripted operations

Each case may declare up to 4,096 mocks. A mock names a qualified locked operation, optional exact args, and exactly one typed result or error.

  • A legacy error has a declared code and a String message.
  • For an operation with selected failure cases, use "error": {"code": "titleTaken", "payload": {"message": "Choose another title"}}. The runner validates the payload against that public case and rejects unknown cases, fields or malformed values.
  • delayMs schedules the response in virtual time. manual: true waits for a respond step.
  • Unexpected calls, undeclared errors, wrong arguments or results, and unused mocks fail the case.
  • Scripts never grant transport or extension authority to the application.

Time and failure rules

Real sleep never advances the controlled clock. A failed setup, a failed assertion, an unsupported case, a suite with no executed cases or a case with no assertions cannot pass. Disposal must leave no active owner, request or timer. The worker has a 60-second limit, including synchronously blocked adapters.

Logical-submission suites use a secure host key hook in the reference and generated-adapter runners. Business randomness and credentials are not replaced by seeded test values, and the transport stays scripted, so this does not show server admission durability.

Evidence in the report

Application reports include per-case cleanup counts and an executionEvidence envelope in axiom-development-execution-evidence/v1. It binds the canonical frontend IR, the suite, runner, assertion API, adapter and facade hashes, and records nonzero assertions and cleanup. Native scenario reports use the same envelope with their contract, plan, schema and scenario identities.

LevelShowsDoes not show
Compiler smokeThe module loads, accepts a compatible edit and keeps the last good buildAny interaction
Application suite, reference or adapterScripted behavior and cleanup at compiler-host levelMounted browser, emulator or simulator behavior
Scenario (compiler-host)Compiled native managed queries with strict synthetic read replies and logical policy timeFrontend interactions (it rejects interaction and state controls)
Mounted target, durable provider or actual guestReal executionMust keep evidence from its own executing adapter

Owned-operation tests use controlled adapter futures to prove overlap and drain, and a virtual clock to prove timer ownership, foreground pause, session invalidation and suppression of stale completion. Durable job recovery needs separate Redis and worker evidence. See Managed logic and concurrency.

Limits and compatibility

  • Existing CI can keep the smoke command. To claim behavior, add a suite and keep its JSON report. Do not label adapter results as mounted native qualification.
  • The web binding adapter covers buttons and text inputs; forms can submit through an authored submit button. Element methods, extensions and other OS surfaces need an explicit capability or a mounted target adapter.
  • Application tests use frontend IR axiom-ui-ir/v10 and acore-ui/v6, and need acore-pure/v2 alongside the reactive, component, data and application helper formats. Use a CLI, UI hosts and playground from the same release.
  • Pure value validation is bounded to 1,000,000 JSON nodes or keys, depth 128 and 10,000 Unicode scalars per String. Expression fuel stays 10,000. Larger transport values do not increase computation fuel or grant custom callbacks.
  • List.slice(start, end) uses an exclusive end, clamps indices, accepts negative indices from the end and returns an empty list when the end precedes the start.

Next steps