LearnTesting and tooling
Testing and tooling
The chapters so far were about building an app. This one is about the tools around it: the tests that tell you it works, the plugins that extend it, the habits and measurements that keep it fast, and the compiler underneath all of it. Each tool is built for two kinds of user at once: you, and the coding agent working in your app. They give both of you the same answer to "is this done?"
Testing
@weft-js/core/test renders a component under vitest and drives it by what a person sees: roles, labels and text. Every action waits for the page to update, so a test never sleeps.
// src/overview/Like.test.tsximport { state } from '@weft-js/core'import { render, screen, user } from '@weft-js/core/test'import { expect, test } from 'vitest'function Like() { const likes = state(0) return <button type="button" onClick={() => likes.value++}>Likes: {likes.value}</button>}test('a click adds a like', async () => { render(Like) await user.click(screen.getByRole('button', { name: 'Likes: 0' })) expect(screen.getByRole('button').textContent).toBe('Likes: 1')})The test clicks the button by its name and checks the text a person would read. It never looks at likes itself.
weft check reports tests that would pass whatever the code did, such as a test with no assertion (T001) or one that sleeps (T004), and weft test --gate proves each new test fails on the code from before your change.
Read Testing to learn how to test state, time, server functions and live data, and how to put a number on how much work a click does.
Plugins
warp, payments, fonts and the database layers all reach your app as plugins in weft.config.ts. A plugin can add slot providers, design tokens, diagnostics with their own check, and a section of your agent's AGENTS.md. weft add installs and wires one; the plugin lock asks you to approve what it may touch.
weft add pay --dry-runweft pluginsRead Plugins to learn how plugins and their permissions work, how to write one with its own diagnostic, and how project tasks keep your scripts in one list.
Performance and bundle size
A weft component runs once, and each reactive read is wired to the DOM node that shows it, so a change does work in proportion to what it changes. Lists are where that's won or lost: keep items you didn't change as the same objects, and read one entry per row.
- Apple: 0 in cart, worked out 1 times
- Banana: 0 in cart, worked out 1 times
- Cherry: 0 in cart, worked out 1 times
- Damson: 0 in cart, worked out 1 times
- Fig: 0 in cart, worked out 1 times
import { state } from '@weft-js/core'import { Button, Card } from '@weft-js/warp'const FRUIT = ['Apple', 'Banana', 'Cherry', 'Damson', 'Fig']// How many times each row's quantity has been worked out. A plain object, so counting isn't itself a state write.const runs: Record<string, number> = {}const counted = (id: string, qty: number) => { const n = (runs[id] ?? 0) + 1 runs[id] = n return `${qty} in cart, worked out ${n} times`}export default function Rows() { const cart = state<Record<string, number>>({}) const add = (id: string) => cart.set(d => { d[id] = (d[id] ?? 0) + 1 }) return ( <Card title="One entry per row"> <ul> {FRUIT.map(id => ( <li key={id}> {id}: {counted(id, cart.get(id) ?? 0)}{' '} <Button size="sm" emphasis="ghost" onClick={() => add(id)}>Add</Button> </li> ))} </ul> </Card> )}Try this: Add a few of one fruit, then another. Only the row you pressed works its quantity out again, because each row reads cart.get(id).
Read Performance and bundle size to learn the list habits the compiler checks, how islands and server-only code keep JavaScript off the page, how to measure, and how weft compares with other stacks.
Rust, in depth
You never need Rust to use weft. The compiler is written in it and reaches you prebuilt, as a native addon or as WebAssembly with the same output, and weft doctor says which you have. When a server function does heavy CPU work, you can give it a Rust body without changing anything the client sees:
// src/overview/rust.tsimport { native } from '@weft-js/start/rust'export const media = native('media')Read Rust, in depth to learn what the compiler does to your components, and how native() and remote() run Rust server functions with the Zod schema as the one contract.
Recap
@weft-js/core/testtests components by role, label and text, with a fake clock and network.weft checkand the gate catch tests that can't fail.- Plugins add slots, tokens, diagnostics and agent context, under a lock you approve.
- Speed comes from compiled bindings; keep it with stable list items and per-row reads, and measure it.
- Rust is optional: the compiler ships prebuilt, and Rust server functions are there when CPU work calls for them.
What's next
Start with Testing: a test that fails when the code is wrong is the best tool in this chapter.