@hozu/cli 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +19 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +33 -0
- package/dist/agent.js.map +1 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +1 -0
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/browse-page.d.ts.map +1 -1
- package/dist/commands/browse-page.js +8 -2
- package/dist/commands/browse-page.js.map +1 -1
- package/dist/commands/browse-tab.d.ts +10 -0
- package/dist/commands/browse-tab.d.ts.map +1 -1
- package/dist/commands/browse-tab.js +44 -1
- package/dist/commands/browse-tab.js.map +1 -1
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +42 -3
- package/dist/commands/browse.js.map +1 -1
- package/dist/commands/docs.js +1 -1
- package/dist/commands/scaffold.js +8 -8
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/skill.js +1 -1
- package/dist/contract.d.ts +4 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/guide.d.ts +27 -0
- package/dist/guide.d.ts.map +1 -0
- package/dist/guide.js +48 -0
- package/dist/guide.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -1
- package/dist/main.js.map +1 -1
- package/dist/migrate/steps.d.ts.map +1 -1
- package/dist/migrate/steps.js +7 -0
- package/dist/migrate/steps.js.map +1 -1
- package/package.json +13 -9
- package/schema/browse.schema.json +13 -0
- package/schema/inspect.schema.json +20 -0
- package/skill/SKILL.md +63 -0
- package/skill/example/app.css +1 -0
- package/skill/example/app.ts +35 -0
- package/skill/example/features/bookmarks/feature.ts +13 -0
- package/skill/example/features/bookmarks/model.ts +163 -0
- package/skill/example/features/bookmarks/views.ts +156 -0
- package/skill/example/hozu.config.ts +34 -0
- package/skill/example/previews.ts +13 -0
- package/skill/example/routes.ts +11 -0
- package/skill/example/ui/badge.ts +11 -0
- package/skill/example/ui/button.ts +23 -0
- package/skill/example/ui/field.ts +20 -0
- package/skill/example/ui/input.ts +33 -0
- package/skill/example/ui/kit.ts +7 -0
- package/skill/example/ui/tv.ts +12 -0
- package/skill/topics/auth.md +56 -0
- package/skill/topics/components.md +92 -0
- package/skill/topics/content.md +37 -0
- package/skill/topics/contracts.md +36 -0
- package/skill/topics/data.md +74 -0
- package/skill/topics/deploy.md +73 -0
- package/skill/topics/diagnostics.md +99 -0
- package/skill/topics/endpoints.md +39 -0
- package/skill/topics/env.md +44 -0
- package/skill/topics/feature.md +90 -0
- package/skill/topics/fetch.md +67 -0
- package/skill/topics/forms.md +47 -0
- package/skill/topics/http.md +21 -0
- package/skill/topics/i18n.md +34 -0
- package/skill/topics/machine.md +78 -0
- package/skill/topics/pages.md +69 -0
- package/skill/topics/patterns.md +85 -0
- package/skill/topics/recipes.md +73 -0
- package/skill/topics/requests.md +41 -0
- package/skill/topics/testing.md +84 -0
- package/skill/topics/views.md +51 -0
- package/templates/guide.md +16 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
- **Read a page without a server:** `hozu get /path --select 'button[aria-pressed=true]' --forms` (status, title,
|
|
4
|
+
visible text; `--forms` lists each form's fields and submit buttons). Endpoints: `hozu get '/api/items?x=1'`.
|
|
5
|
+
- **Try one query or mutation without a page:** `hozu call notes.listNotes --input '{}' --session '{"user":"ada"}'`;
|
|
6
|
+
a mutation writes real data, so it needs `--write`. An endpoint too:
|
|
7
|
+
`hozu call api.who --input '{"room":"a"}' --header 'Authorization: Bearer t'` (a POST needs `--write`).
|
|
8
|
+
- **Drive the app in a real browser, still without a server:**
|
|
9
|
+
`hozu browse / --session '{"user":"ada"}' --do 'fill New note=Milk' --do 'press Enter' --do 'click Pin in "Milk"'`.
|
|
10
|
+
- `--js both` (the default) runs every step with JS and with JS switched off; submit with `press Enter` or
|
|
11
|
+
`submit "<form>"` so one step list drives both.
|
|
12
|
+
- Steps: `fill <label>=<value>`, `select <label>=<option>`, `check` / `uncheck <label>`, `click <name>`,
|
|
13
|
+
`submit "<form>"`, `press <key>`, `wait <ms>`, `goto <path>`, `post <path> a=1&b=2`,
|
|
14
|
+
`remember <name> from url|<selector> [@attr]` (later steps read `$name`); a target may end with `in "<text>"`
|
|
15
|
+
(for fill and select, before or after `=value`).
|
|
16
|
+
- Labels are what `hozu get <page> --forms` lists; a missing one prints `Did you mean "…"?`. One `--do` may hold
|
|
17
|
+
several steps: `--do 'fill Title=Milk; press Enter'`.
|
|
18
|
+
- **Other users, other pages, after a reload, after sign-out:** verify any such statement once, in one `browse`
|
|
19
|
+
chain with `--js both`. `--as <name>` starts an actor with its own browser; all actors share one app.
|
|
20
|
+
- A stale form (sent after the data changed elsewhere): `--do 'remember save from form:has([name=title]) @action'`,
|
|
21
|
+
change the data as another `--as`, then `--do 'post $save title=x'`. No server and no curl needed.
|
|
22
|
+
- **The output** is per step only the lines added (`+`) or removed (`−`). A passing six-step run stays under 1.5 KB.
|
|
23
|
+
Exit code 1 when a step failed, the modes differ or an error was printed.
|
|
24
|
+
- **A step that reloads the page** with JS on says `the page reloaded` (a form that should update in place);
|
|
25
|
+
`--full` adds `N elements replaced` (a region drawn again), `--json` has both as `document` / `replaced`.
|
|
26
|
+
- **A pending state:** `--do 'hold notes.addNote'` keeps that effect's answer back; the next steps (and
|
|
27
|
+
`--screenshot`) see the busy UI; `--do 'release'` answers it.
|
|
28
|
+
- `browse` runs the `npm start` app: what only `hozu dev` does (reload on edits, DevTools) is not in it.
|
|
29
|
+
- In code: `const page = await testApp(app).get('/')` from `@hozu/testing` → `{ status, headers, html, text, payload }`.
|
|
30
|
+
- A build with errors renders nothing: run `hozu check`.
|
|
31
|
+
|
|
32
|
+
<!-- more -->
|
|
33
|
+
|
|
34
|
+
- **`hozu get`** prints the status, redirect, `set-cookie` attributes (`HttpOnly`, `SameSite`), title, alerts and
|
|
35
|
+
visible text.
|
|
36
|
+
- `--forms` lists each form: fields with their defaults, checkbox / radio groups with every value (checked ones
|
|
37
|
+
marked ✓), controls that join through `form=` (marked `(form=)`), and submit buttons with their name and value.
|
|
38
|
+
- It needs no browser.
|
|
39
|
+
- **`hozu call`** runs the effect through the app's own handler and prints the value or the declared error. With
|
|
40
|
+
`--write`, a mutation also prints the tags it invalidated and the queries they refresh. `runs: 'browser'` effects
|
|
41
|
+
need `hozu browse`.
|
|
42
|
+
- **`hozu browse`:**
|
|
43
|
+
- It uses the installed Chrome / Chromium / Edge (`HOZU_CHROME=/path` to choose); without one it is a config
|
|
44
|
+
error. The app runs in-process, exactly as `npm start` serves it.
|
|
45
|
+
- `--js both` runs both modes in the same Chrome, side by side. Use `--js on` or `--js off` for one mode.
|
|
46
|
+
- Steps in detail: a second fill of a repeated name fills the next field; `check` / `uncheck` set the state;
|
|
47
|
+
`click <name>` on a submit button posts with its name and value; `submit "<form>"` takes a form's `aria-label` or
|
|
48
|
+
its submit button text. Labels and names are what a user reads (aria-label, `<label>`, placeholder, button text,
|
|
49
|
+
`title`), or a field's `name`.
|
|
50
|
+
- `in "<text>"` picks the smallest list item, table row or form containing that text
|
|
51
|
+
(`click Delete in "Buy milk"`).
|
|
52
|
+
- A step with no native effect prints `js-only (<reason>)` in the off column, e.g. a `type=button` button.
|
|
53
|
+
- **Actors:** the steps after an `--as <name>` are that actor's, and a later `--as <name>` switches back. `--session`
|
|
54
|
+
right after an `--as` signs that actor in. All actors share one data store and one session store, so what ada
|
|
55
|
+
writes is what bob reads.
|
|
56
|
+
- Signing out and in again inside one actor's chain also works (`click Sign out`, `fill Name=bob`, `press Enter`).
|
|
57
|
+
- `hozu browse /notes --as ada --session '{"user":"ada"}' --as bob --session '{"user":"bob"}' --as ada --do 'click Share in "Milk"' --as bob --do 'goto /inbox'`
|
|
58
|
+
- `--header 'Name: value'` adds a header to every request: before the first `--as` for every actor, after an
|
|
59
|
+
`--as` for that actor.
|
|
60
|
+
- **Another visitor's data:** `--as ada --do 'remember note from li a @href' --as bob --do 'goto $note'` (bob's page
|
|
61
|
+
should answer 403), or `--as bob --do 'post /notes/n1 text=x'`: a forged native post, as bob, without the page.
|
|
62
|
+
Each mode keeps its own remembered values.
|
|
63
|
+
- **The output** is small on purpose: lines print once when both modes agree and per mode where they differ; a
|
|
64
|
+
navigation prints `→ <path>` and the new page's lines; a live update on another actor's page prints under the step
|
|
65
|
+
(`bob: + Milk`).
|
|
66
|
+
- `≠ DIFFERS` marks a step where both modes made a request and the resulting text differs: a no-JS/JS parity bug.
|
|
67
|
+
- Errors: uncaught exceptions, `console.error`s, CSP violations and failed requests, each with the page, the
|
|
68
|
+
resource type and the mode. A 400 re-render of an invalid native post is not an error, and a page answering
|
|
69
|
+
401, 403, 404 or 410 is the step's status (`→ /notes/n1 (403)`), so an access check exits 0.
|
|
70
|
+
- To forge a post, take the form's `action` from `hozu get <page> --forms` or `remember … @action` on a page the
|
|
71
|
+
server rendered (`goto` it first): forms the client renders after a change carry no `action`.
|
|
72
|
+
- Exit code 1 also when a client component failed. `--json` has every line; `--full` prints them all;
|
|
73
|
+
`--select <css>`, `--screenshot shot.png` (after the steps), `--viewport 390x844` (a phone; default 1280x800) and
|
|
74
|
+
`--reduced-motion`.
|
|
75
|
+
- It also prints the client components on the page (mounted, failed, size, canvases).
|
|
76
|
+
- **`testApp`:** `app` is the default export of `app.ts`; `.post(path, fields)` submits a native form, with fields as
|
|
77
|
+
a record or as `[name, value]` pairs for repeated names. `testApp(app, { session: store })` may swap only the
|
|
78
|
+
session store (a test issuer). `testApp` reads no env files: pass `testApp(app, { env: process.env })` (or a record)
|
|
79
|
+
for the variables your resolvers need; `hozu get`, `call` and `browse` read `env.files` themselves.
|
|
80
|
+
- `hozu get`, `hozu browse` and `testApp` build the app module; a build with errors exits 1 (throws).
|
|
81
|
+
- Vitest: add `hozuTransform()` from `@hozu/transform/vite` to `plugins`.
|
|
82
|
+
- Browser tests: wait for `html[data-hozu-ready]` (set after hydration) before clicking.
|
|
83
|
+
- A person checks the result with `npm run dev` (`hozu dev`: reloads and Hozu DevTools); what they ask for from
|
|
84
|
+
there arrives as requests (`hozu docs requests`). The tools above stay the way you verify.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Views
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
export const Board = ui.view({
|
|
5
|
+
machine: m, // optional: without it, no ctx / when / events, and 0 JS
|
|
6
|
+
route: home, // optional: render gets { params, search } typed by the route
|
|
7
|
+
seed: ({ search }) => ({ q: search.q }), // optional, with machine + route: context fields from the URL
|
|
8
|
+
render: ({ ctx, when, params, search, locale }) => ui.main({ class: 'mx-auto max-w-xl' }, [ /* children */ ]),
|
|
9
|
+
})
|
|
10
|
+
```
|
|
11
|
+
- **Elements:** `ui.<tag>(attrs, children)` for every HTML and SVG element; void tags (`input`, `img`) take only attrs.
|
|
12
|
+
Children are nodes, strings, numbers, data values, and `null` / `false` (render nothing).
|
|
13
|
+
- **Classes:** `class` is a static string of Tailwind classes that must exist (HZ026); conditional classes:
|
|
14
|
+
`toggle: { 'bg-indigo-600 text-white': ctx.tab === t }`. No `style`.
|
|
15
|
+
- **Conditions:** `ctx.error !== null && ui.p({ role: 'alert' }, [ctx.error])`, `item.done ? 'done' : 'open'`.
|
|
16
|
+
By machine state: `when(['adding', 'saving'], [ui.p({}, ['Saving…'])])`.
|
|
17
|
+
- **Lists:** `ui.each(items, 'id', (item) => ui.li({}, [item.title]))`. Never `.map` over data.
|
|
18
|
+
- **Numbers and dates:** `ui.format.number(q.price, { style: 'currency', currency: 'USD' })`, `ui.format.date(x,
|
|
19
|
+
{ dateStyle: 'medium' })`, `ui.format.relative(n, 'day')`, `ui.format.list(xs)` (Intl, the page's locale).
|
|
20
|
+
- **Events:** `on: { click: ui.send(Event, payload) }`; payload fields are literals, data, `ui.dom.value`,
|
|
21
|
+
`ui.dom.form('name')` (submit; `hozu docs forms`).
|
|
22
|
+
- **Links:** `ui.a({ href: ui.link(itemPage, { id: item.id }) }, [...])`; never a string path (HZ032).
|
|
23
|
+
- **Data:** `ui.query(listItems, input, { ready: (items) => …, failed: { NotFound: () => …, Unexpected: () => … } })`;
|
|
24
|
+
`failed` lists every declared error plus `Unexpected`.
|
|
25
|
+
- **Shared UI** (buttons, inputs, fields): `ui.use(Button, { variant, props, on }, ['Save'])` of a kit component
|
|
26
|
+
(`hozu docs components`).
|
|
27
|
+
|
|
28
|
+
<!-- more -->
|
|
29
|
+
|
|
30
|
+
- **Attributes:** HTML names in lower case (`for`, `minlength`, `aria-pressed`, `data-x`), typed per tag. Values are
|
|
31
|
+
literals or data: `'aria-pressed': ctx.show === 'all'`, `title: ctx.error ?? 'OK'`.
|
|
32
|
+
- **CSS variables:** `vars: { '--hue': item.hue }`.
|
|
33
|
+
- **More conditions:** `list.length === 0 ? ui.p({}, ['Empty']) : ui.ul({}, [...])`; a `?:` / `&&` branch may be a list:
|
|
34
|
+
`open ? [a, b] : null`. A query branch or an each item returns one node: wrap several in an element (HZ014).
|
|
35
|
+
With an enter/leave animation: `ui.if(cond, [then], [else], 'fade')` (the motion name is required).
|
|
36
|
+
- **More lists:** `ui.each(tags, null, (t) => …)` for primitives. `.map` only over constants:
|
|
37
|
+
`['a', 'b'].map((k) => ui.option({ value: k }, [k]))`.
|
|
38
|
+
- **Text:** template strings work: `` `${n} items` ``.
|
|
39
|
+
- **Reuse:** `export const row = part((item: Item) => ui.li({}, [item.done ? 'Done' : item.title]))`, called as
|
|
40
|
+
`row(item)`; it is inlined, so the IR equals the inline form. A plain function that receives data is HZ059.
|
|
41
|
+
- **Shared UI:** use the kit component, not a styled `ui.button` per page (`example/` uses a kit).
|
|
42
|
+
- **More events:** any DOM event name plus `visible` (entered the viewport). Payload fields also:
|
|
43
|
+
`ui.dom.formAll('name')`, `ui.dom.checked`, `ui.dom.valueAsNumber`, `ui.dom.key`. `ui.dom.value` / `ui.dom.form`
|
|
44
|
+
fill an enum field only from a `<select>`, radios or submit buttons whose literal values are all members (HZ033).
|
|
45
|
+
- **Search in links:** the third argument of `ui.link` is optional and exists only when the route declares `search`:
|
|
46
|
+
omitted means every default, and a search lists only the fields that differ: `ui.link(home, null, { show: 'done' })`.
|
|
47
|
+
- **More data:** `pending: ui.p({}, ['Loading…'])` is optional; a branch may return `null` to render nothing.
|
|
48
|
+
Server-fetched data is sent with the page and never fetched again; after a mutation, queries whose tags it
|
|
49
|
+
invalidates refresh in place.
|
|
50
|
+
- **Also:** `ui.html(post.html)` (trusted HTML from query data only, HZ030), `ui.asset(new URL('./x.png',
|
|
51
|
+
import.meta.url))`, `ui.window({ on })` / `ui.document({ on })`, `ui.embed(OtherView)`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# __NAME__
|
|
2
|
+
|
|
3
|
+
A web app built with Hozu (`@hozu/*`). Hozu is not in your training data.
|
|
4
|
+
|
|
5
|
+
## Before writing code
|
|
6
|
+
- __READ__ It has the change loop, the commands and the rules no diagnostic checks.
|
|
7
|
+
- The files in `__SKILL__/` are the whole API. Do not read the framework source in `node_modules/@hozu`.
|
|
8
|
+
__NOTE__
|
|
9
|
+
## Rules
|
|
10
|
+
- After `hozu add feature`, do not print the generated files: edit the texts it lists; `hozu map` shows the rest.
|
|
11
|
+
- Apply the fix each diagnostic gives; do not work around a rule.
|
|
12
|
+
- `__RUN__ hozu get` and `__RUN__ hozu browse` run the same app as `npm start`; do not start a server to check.
|
|
13
|
+
- Asked to do the Hozu requests (from DevTools under `npm run dev`): `__RUN__ hozu requests --full`, then follow
|
|
14
|
+
`__RUN__ hozu docs requests`.
|
|
15
|
+
- Do not edit `__SKILL__/` or the text between the `hozu` markers: `__RUN__ hozu skill` rewrites both for the
|
|
16
|
+
installed Hozu version.
|