Planning tests before the code exists
Acceptance criteria written beside the page, component, store, or endpoint they are about — and exported as a real test suite where each case is pending until somebody proves it.
Tests written from the code test the code
The usual order is build first, test after. It sounds responsible and it has one quiet flaw: a test written after the fact is written from the implementation. You open the file, see what it does, and assert that it does it. If the thing was built to the wrong requirement, the test agrees with the mistake and turns it green.
The requirement itself usually did exist. It was in a ticket, a thread, or somebody's head during a call — and none of those are attached to the page they were about. By the time the code is written, the sentence that justified it has scrolled out of view.
Nodlume treats acceptance criteria as part of the plan, in the same place as the routes and the data model. You write down what a surface has to do beside the design that claims it, and the export turns those sentences into a test suite.
A test case is four fields
Every writing panel in the Structure inspector ends in Test cases, and so does each endpoint's contract in Backend. A case has four parts:
- Title — the behaviour, as a sentence: "shows an empty state when the traveller has no trips". It reads like the
it(...)title someone would write, which is the point. A case whose title needs its body explained is really two cases. - Proven by — unit, integration, or end-to-end.
- Steps — the setup and the action, one per line, in the application's own vocabulary: "Sign in as a viewer", then "Open the trip".
- Expected — what must be true once the steps have run.
That is the whole shape, and it is deliberately the same for a page, a component, a store, and an API endpoint. "Shows an empty state when the list is empty" and "returns 404 for a trip that is not yours" are the same kind of sentence about different things. Four shapes would mean four editors and four ways to get it slightly wrong, for one fact that never varies: what must be true, and when.
Proven by is the field that does work
Three options is not a shrug at a longer list. The kind decides who runs the test and therefore what it is allowed to assume: a unit case may hold nothing but the module, an end-to-end case may assume the whole application is up and a person is driving it.
New cases start at unit — the cheapest thing that can be true. A default of end-to-end would have the tool quietly recommending the most expensive kind of test for every behaviour anyone writes down, and the bill for that arrives later, in a suite that takes eleven minutes and fails on Tuesdays.
Cases live on the thing they describe
A route's cases sit on that route. A component's sit on the component — not on each of the six pages that render it — because a Header painted onto six routes is one Header, and "it collapses below 640px" is a fact about the component, not about the sixth page that happens to hold it. Same for a store: "clearing the cart empties it" belongs to the store, not to checkout.
An endpoint's cases sit on the endpoint, full width under its request and response tables. Not under one or the other, because "a viewer gets 403" is a statement about the parameters and the responses at once, and filing it under either would file it under half of what it says.
Everything saves with the project and syncs live to anyone you have shared it with.
Generating a first set
The Generate button writes cases from what the board already knows about the thing you have selected — a route's parameters, guards, attachments and generated path; a component's props, slots and render guard; a store's state and actions; an endpoint's parameters and declared responses — plus the description, roles, and features from your Domain tab.
That grounding is the entire difference between a useful case and a filler one. The instruction it runs under is blunt about it: a case that would read the same for any other route in any other application is not worth writing. It is also told to prefer the cheapest kind that can actually prove the behaviour, so it does not reach for a browser when a function call would do.
The merge is add-only, matched on the behaviour rather than on some hidden id. Press it twice and the second press adds nothing. Press it after rewording a case and your wording stands. Press it after adding a parameter and you get the cases that parameter implies, and nothing else. That is what makes a generator safe to press when you are not sure — it can only ever add sentences you did not already have.
Each press covers one subject and costs one credit, so covering a board costs in proportion to how much of it you actually asked about.
What lands in the export
Every subject with at least one case written on it gets a test suite in the generated project, beside the file it is about — a page and its suite, a component and its suite, a route handler and its suite. Several methods on one path share a route.ts, so they share one suite with a block per method.
Each case arrives as a pending test carrying its steps and expectation in the comment above it:
describe("/posts", () => {
/**
* Proven by: End-to-end — A user driving the running application
* Steps:
* Sign in as a viewer
* Open /posts
* Expected: every published post is listed, newest first
*/
it.todo("lists every published post")
})
it.todo rather than an empty it is the load-bearing choice. A runner reports a todo as pending, so a criterion nobody has implemented yet can never sit in your output looking like a passing test. The suite is honest from the first download: it tells you how much is outstanding instead of congratulating you on assertions nobody wrote. The generated project declares its test runner and a test script too, so the suites run before you have added anything.
The rules around the edges are the ones worth knowing. A subject with nothing written on it generates no file at all — an empty suite beside every module would claim tests the project does not have. A row you added and never filled in is not coverage, so it is not counted and not exported. And the Scaffolding header counts cases, not files, because a suite holding nine criteria is not a 1.
What this is not
Being clear about the boundary: these are criteria, not tests. Nothing here executes. Nodlume never runs your suite and never reads a result back — a criterion in the report says what has to be true, never that it is.
They do appear in the project report, under Test coverage: grouped by the surface each was written beside, with its steps and expectation, and a feature traced to a route you wrote criteria on now counts as covered rather than as untested.
What you get is the sentence in the right place, in a file the runner already understands, next to the code it is about. Turning it into an assertion is still your job — it is just no longer your job to first remember what the assertion was supposed to be.
Where to start
Open a project's Structure tab and select a route that does something conditional — anything with a guard, a dynamic segment, or an empty state. Write one case by hand so the shape is yours, then press Generate and see what it proposes about the parts you had not considered. Do the same on one endpoint in Backend, then open the Export tab: the suites are in the tree, and the download runs them.
The test cases reference covers the panel in detail. For the plan those criteria are written against, designing application structure covers the routes, navigation, and guards they tend to be about.
Design your application
Map pages, components, routes, and stores on the Nodlume canvas, then export the project skeleton when you are ready.
Open projects