create-waygraph 0.0.3 → 0.0.5
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/README.md +23 -21
- package/bin/create-waygraph.js +2 -2
- package/package.json +1 -1
- package/templates/README.md +28 -0
- package/templates/STRUCTURE.md +61 -0
- package/templates/package.json +13 -7
- package/templates/playwright.config.ts +15 -0
- package/templates/scripts/fixture-server.mjs +41 -0
- package/templates/scripts/with-fixture.mjs +69 -0
- package/templates/src/blocks/SITE-MAP.md +12 -0
- package/templates/src/blocks/demo-web/NAV.md +29 -0
- package/templates/src/blocks/demo-web/demo-sel.ts +14 -0
- package/templates/src/blocks/demo-web/home.html +65 -0
- package/templates/src/blocks/demo-web/home.page.block.ts +29 -0
- package/templates/src/blocks/demo-web/methods/add-item.effect.block.ts +68 -0
- package/templates/src/blocks/demo-web/methods/assert-hello.method.block.ts +50 -0
- package/templates/src/blocks/demo-web/methods/clear-cart.method.block.ts +26 -0
- package/templates/src/blocks/demo-web/methods/remove-item.effect.block.ts +63 -0
- package/templates/src/blocks/demo-web/nav-home.block.ts +22 -0
- package/templates/src/flows/example.flow.ts +24 -3
- package/templates/src/flows/shop.flow.ts +42 -0
- package/templates/src/states/demo.mem-keys.ts +7 -0
- package/templates/src/states/demo.states.ts +6 -0
- package/templates/tests/example.spec.ts +11 -2
- package/templates/src/blocks/load-page.block.ts +0 -18
package/README.md
CHANGED
|
@@ -5,48 +5,50 @@ one fixed shape, no prompts, no template picker.
|
|
|
5
5
|
|
|
6
6
|
This is the scaffold command linked from
|
|
7
7
|
[waygraph GitHub Pages](https://deviate-dv8.github.io/waygraph/) (`npx create-waygraph`).
|
|
8
|
-
Same template as `npx waygraph init` (waygraph >= 0.
|
|
8
|
+
Same template as `npx waygraph init` (waygraph >= 0.12.6). For a live saucedemo walkthrough
|
|
9
9
|
without creating a folder in your cwd, use `npx waygraph try demo` instead.
|
|
10
10
|
|
|
11
|
+
**Scaffold tree (authoritative):** https://deviate-dv8.github.io/waygraph/scaffold.html
|
|
12
|
+
|
|
11
13
|
```bash
|
|
12
14
|
npx create-waygraph my-project
|
|
13
15
|
cd my-project
|
|
14
16
|
npm install
|
|
15
17
|
npx playwright install chromium
|
|
16
18
|
npm test
|
|
19
|
+
npm run demo
|
|
17
20
|
```
|
|
18
21
|
|
|
19
|
-
Generates:
|
|
22
|
+
Generates (0.12+):
|
|
20
23
|
|
|
21
|
-
```
|
|
24
|
+
```text
|
|
22
25
|
my-project/
|
|
23
|
-
package.json
|
|
24
|
-
|
|
25
|
-
playwright.config.ts
|
|
26
|
-
.gitignore
|
|
26
|
+
package.json
|
|
27
|
+
STRUCTURE.md
|
|
27
28
|
src/
|
|
28
|
-
blocks/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
blocks/
|
|
30
|
+
SITE-MAP.md
|
|
31
|
+
demo-web/ # synthetic "/" (data: URL)
|
|
32
|
+
NAV.md
|
|
33
|
+
nav-home.block.ts
|
|
34
|
+
methods/
|
|
35
|
+
assert-hello.method.block.ts
|
|
36
|
+
states/demo.states.ts
|
|
37
|
+
flows/example.flow.ts # withTitle + withHighlightFixtures
|
|
38
|
+
tests/example.spec.ts
|
|
32
39
|
```
|
|
33
40
|
|
|
34
|
-
`npm test` goes green
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Ships as its own package rather than a `waygraph init` subcommand, so consuming `waygraph`
|
|
38
|
-
at runtime never pulls in scaffolding code - `npx create-waygraph` resolves and runs without
|
|
39
|
-
adding anything to your own `node_modules`.
|
|
41
|
+
`npm test` goes green offline - nav uses a `data:` URL. Stubs / fixtures / one YAP
|
|
42
|
+
slide are already wired so `npm run demo` shows narration.
|
|
40
43
|
|
|
41
44
|
## Consumer layout (App Router projects)
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
app has real Next.js routes, **block folders mirror `app/` page routes**:
|
|
46
|
+
When the target app has real Next.js routes, **block folders mirror `app/` page routes**:
|
|
45
47
|
|
|
46
48
|
- URL `/` -> nav at namespace root (`zsign-web/` or `pia-web/`), not `landing/` or `root/`
|
|
47
49
|
- Route groups `(auth)` / `(app)` vanish from folder names
|
|
48
50
|
- Folders with no `page.tsx` are grouping only - no NAV.md, no nav block
|
|
49
51
|
- Sidebar / layout chrome -> `shared/chrome/`, not a parallel `shell/nav/*` family
|
|
50
52
|
|
|
51
|
-
Full contract:
|
|
52
|
-
|
|
53
|
+
Full contract: waygraph [Consumer layout](https://deviate-dv8.github.io/waygraph/consumer.html)
|
|
54
|
+
and mesh handout `WAYGRAPH-CONSUMER-CONVENTION.md`.
|
package/bin/create-waygraph.js
CHANGED
|
@@ -39,7 +39,7 @@ console.log(" npm test");
|
|
|
39
39
|
console.log(" npx waygraph list");
|
|
40
40
|
console.log(" npx waygraph check");
|
|
41
41
|
console.log(" npx waygraph auto # headed panel");
|
|
42
|
-
console.log(" npx waygraph
|
|
42
|
+
console.log(" npx waygraph agent-dive --loop claude");
|
|
43
43
|
console.log("");
|
|
44
|
-
console.log("(Same offline scaffold as: npx waygraph init <name> - waygraph >= 0.
|
|
44
|
+
console.log("(Same offline scaffold as: npx waygraph init <name> - waygraph >= 0.12.7.)");
|
|
45
45
|
console.log("(Live saucedemo: npx waygraph try auto | try demo - temp dir, not a scaffold.)");
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
Offline waygraph scaffold - Nav, Page hub, Method, Effect (`instanceOptions` for
|
|
4
|
+
`waygraph auto`), stubs/fixtures/YAP, mem keys.
|
|
5
|
+
|
|
6
|
+
## Layout
|
|
7
|
+
|
|
8
|
+
See [STRUCTURE.md](./STRUCTURE.md). Docs:
|
|
9
|
+
https://deviate-dv8.github.io/waygraph/scaffold.html
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install
|
|
15
|
+
npx playwright install chromium
|
|
16
|
+
npm test
|
|
17
|
+
npm run list && npm run check && npm run graph
|
|
18
|
+
npm run demo # exampleFlow (hello + YAP)
|
|
19
|
+
npm run demo:shop # Effect path (needs --data selectedItem)
|
|
20
|
+
npm run auto # headed explore - pick Add Alpha / …
|
|
21
|
+
npm run auto:cli
|
|
22
|
+
npx waygraph agent-dive --loop claude # coding-agent defs (optional)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Point `package.json` `waygraph.baseUrl` at your real app when you leave the
|
|
26
|
+
fixture (and drop `scripts/with-fixture.mjs` wrappers). Keep route folders =
|
|
27
|
+
app URLs (consumer layout).
|
|
28
|
+
Offline fixture is HTTP `http://127.0.0.1:4177/home.html` (Flatpak-safe).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Offline scaffold layout (`waygraph init` / `create-waygraph`)
|
|
2
|
+
|
|
3
|
+
Shipped under `templates/scaffold/` in the **waygraph** package.
|
|
4
|
+
Docs: [docs/scaffold.html](../../docs/scaffold.html)
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
.
|
|
8
|
+
├── package.json
|
|
9
|
+
├── STRUCTURE.md # this file
|
|
10
|
+
├── README.md
|
|
11
|
+
├── src/
|
|
12
|
+
│ ├── blocks/
|
|
13
|
+
│ │ ├── SITE-MAP.md
|
|
14
|
+
│ │ └── demo-web/ # synthetic "/" (HTTP fixture :4177)
|
|
15
|
+
│ │ ├── NAV.md
|
|
16
|
+
│ │ ├── home.html # offline fixture (catalog + cart)
|
|
17
|
+
│ │ ├── demo-sel.ts
|
|
18
|
+
│ │ ├── nav-home.block.ts # Nav
|
|
19
|
+
│ │ ├── home.page.block.ts # Page hub (methods registry)
|
|
20
|
+
│ │ └── methods/
|
|
21
|
+
│ │ ├── assert-hello.method.block.ts # Method + stubs + YAP
|
|
22
|
+
│ │ ├── add-item.effect.block.ts # Effect + instanceOptions (auto)
|
|
23
|
+
│ │ ├── remove-item.effect.block.ts # Effect + instanceOptions
|
|
24
|
+
│ │ └── clear-cart.method.block.ts # Method
|
|
25
|
+
│ ├── states/
|
|
26
|
+
│ │ ├── demo.states.ts
|
|
27
|
+
│ │ └── demo.mem-keys.ts # SelectedItem for Effect / auto
|
|
28
|
+
│ └── flows/
|
|
29
|
+
│ ├── example.flow.ts # nav + assert (smoke)
|
|
30
|
+
│ └── shop.flow.ts # nav + add-item + clear (Effect path)
|
|
31
|
+
├── scripts/
|
|
32
|
+
│ ├── fixture-server.mjs # serves home.html on :4177
|
|
33
|
+
│ └── with-fixture.mjs # start server + run waygraph auto/demo/run
|
|
34
|
+
└── tests/
|
|
35
|
+
└── example.spec.ts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Features covered
|
|
39
|
+
|
|
40
|
+
| Feature | Where |
|
|
41
|
+
|---------|--------|
|
|
42
|
+
| Nav (`defineNavBlock`) | `nav-home.block.ts` |
|
|
43
|
+
| Page hub (`definePageBlock` + `methods`) | `home.page.block.ts` |
|
|
44
|
+
| Method (`defineMethodBlock`) | `assert-hello`, `clear-cart` |
|
|
45
|
+
| Effect + `instanceOptions` (auto menus) | `add-item`, `remove-item` |
|
|
46
|
+
| Mem (`keyGroup`) | `demo.mem-keys.ts` |
|
|
47
|
+
| `stubBefore` / `stubAfter` / `stubOnError` | all instruction blocks |
|
|
48
|
+
| Flow fixtures + title | `example.flow.ts`, `shop.flow.ts` |
|
|
49
|
+
| YAP slides | `assert-hello` |
|
|
50
|
+
| `withSessionReset` | `shop.flow.ts` |
|
|
51
|
+
| `waygraph auto` / `auto --cli` | `npm run auto` |
|
|
52
|
+
| `demo` / `run` / `list` / `check` / `graph` | `package.json` scripts |
|
|
53
|
+
|
|
54
|
+
## Auto
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm run auto # headed explore - pick Add Alpha / Add Beta from live DOM
|
|
58
|
+
npm run auto:cli # same menus in the terminal
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
After `nav-home`, Effect blocks expose one menu row per visible Add/Remove button.
|
package/templates/package.json
CHANGED
|
@@ -6,19 +6,25 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"typecheck": "tsc --noEmit",
|
|
8
8
|
"test": "playwright test",
|
|
9
|
+
"fixture": "node scripts/fixture-server.mjs",
|
|
9
10
|
"list": "waygraph list",
|
|
10
11
|
"check": "waygraph check",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
12
|
+
"graph": "waygraph graph",
|
|
13
|
+
"graph:mermaid": "waygraph graph --mermaid",
|
|
14
|
+
"auto": "node scripts/with-fixture.mjs waygraph auto",
|
|
15
|
+
"auto:cli": "node scripts/with-fixture.mjs waygraph auto --cli",
|
|
16
|
+
"demo": "node scripts/with-fixture.mjs waygraph demo --blocks exampleFlow",
|
|
17
|
+
"demo:auto-next": "node scripts/with-fixture.mjs waygraph demo --blocks exampleFlow --auto-next --title \"Scaffold hello\"",
|
|
18
|
+
"demo:shop": "node scripts/with-fixture.mjs waygraph demo --blocks shopFlow --data \"{\\\"demo.selectedItem\\\":{\\\"id\\\":\\\"alpha\\\",\\\"name\\\":\\\"Alpha\\\"}}\" --title \"Scaffold shop\"",
|
|
19
|
+
"demo:shop:auto-next": "node scripts/with-fixture.mjs waygraph demo --blocks shopFlow --auto-next --data \"{\\\"demo.selectedItem\\\":{\\\"id\\\":\\\"alpha\\\",\\\"name\\\":\\\"Alpha\\\"}}\"",
|
|
20
|
+
"run": "node scripts/with-fixture.mjs waygraph run --blocks exampleFlow",
|
|
21
|
+
"run:shop": "node scripts/with-fixture.mjs waygraph run --blocks shopFlow --data \"{\\\"demo.selectedItem\\\":{\\\"id\\\":\\\"alpha\\\",\\\"name\\\":\\\"Alpha\\\"}}\""
|
|
16
22
|
},
|
|
17
23
|
"waygraph": {
|
|
18
|
-
"baseUrl": "
|
|
24
|
+
"baseUrl": "http://127.0.0.1:4177"
|
|
19
25
|
},
|
|
20
26
|
"dependencies": {
|
|
21
|
-
"waygraph": "^0.
|
|
27
|
+
"waygraph": "^0.12.7"
|
|
22
28
|
},
|
|
23
29
|
"devDependencies": {
|
|
24
30
|
"@playwright/test": "^1.55.0",
|
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import { defineConfig } from "@playwright/test";
|
|
2
2
|
|
|
3
|
+
const FIXTURE_PORT = process.env.WAYGRAPH_FIXTURE_PORT || "4177";
|
|
4
|
+
const FIXTURE_HOST = process.env.WAYGRAPH_FIXTURE_HOST || "127.0.0.1";
|
|
5
|
+
const FIXTURE_ORIGIN = `http://${FIXTURE_HOST}:${FIXTURE_PORT}`;
|
|
6
|
+
|
|
3
7
|
export default defineConfig({
|
|
4
8
|
testDir: "./tests",
|
|
5
9
|
fullyParallel: true,
|
|
6
10
|
reporter: "list",
|
|
7
11
|
use: {
|
|
12
|
+
baseURL: FIXTURE_ORIGIN,
|
|
8
13
|
launchOptions: {
|
|
9
14
|
executablePath: process.env.CHROME_PATH || process.env.CHROMIUM_PATH,
|
|
10
15
|
},
|
|
11
16
|
},
|
|
17
|
+
webServer: {
|
|
18
|
+
command: "node scripts/fixture-server.mjs",
|
|
19
|
+
url: `${FIXTURE_ORIGIN}/home.html`,
|
|
20
|
+
reuseExistingServer: !process.env.CI,
|
|
21
|
+
env: {
|
|
22
|
+
WAYGRAPH_FIXTURE_PORT: FIXTURE_PORT,
|
|
23
|
+
WAYGRAPH_FIXTURE_HOST: FIXTURE_HOST,
|
|
24
|
+
WAYGRAPH_FIXTURE_QUIET: "1",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
12
27
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Tiny static server for the offline scaffold fixture (home.html).
|
|
4
|
+
* Flatpak Chromium cannot open file:// under /tmp — HTTP localhost works.
|
|
5
|
+
*/
|
|
6
|
+
import { createServer } from "node:http";
|
|
7
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
8
|
+
import { join, dirname, extname, normalize } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "../src/blocks/demo-web");
|
|
12
|
+
const PORT = Number(process.env.WAYGRAPH_FIXTURE_PORT || 4177);
|
|
13
|
+
const HOST = process.env.WAYGRAPH_FIXTURE_HOST || "127.0.0.1";
|
|
14
|
+
|
|
15
|
+
const TYPES = {
|
|
16
|
+
".html": "text/html; charset=utf-8",
|
|
17
|
+
".css": "text/css; charset=utf-8",
|
|
18
|
+
".js": "text/javascript; charset=utf-8",
|
|
19
|
+
".svg": "image/svg+xml",
|
|
20
|
+
".png": "image/png",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const server = createServer((req, res) => {
|
|
24
|
+
const raw = (req.url || "/").split("?")[0] || "/";
|
|
25
|
+
const rel = raw === "/" ? "home.html" : raw.replace(/^\//, "");
|
|
26
|
+
const full = normalize(join(ROOT, rel));
|
|
27
|
+
if (!full.startsWith(ROOT) || !existsSync(full)) {
|
|
28
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
29
|
+
res.end("not found");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const type = TYPES[extname(full)] || "application/octet-stream";
|
|
33
|
+
res.writeHead(200, { "content-type": type });
|
|
34
|
+
res.end(readFileSync(full));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
server.listen(PORT, HOST, () => {
|
|
38
|
+
if (process.env.WAYGRAPH_FIXTURE_QUIET !== "1") {
|
|
39
|
+
console.error(`waygraph fixture: http://${HOST}:${PORT}/home.html`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Start fixture-server, run a child command, tear down on exit.
|
|
4
|
+
* Usage: node scripts/with-fixture.mjs [--] <cmd> [args...]
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const PORT = process.env.WAYGRAPH_FIXTURE_PORT || "4177";
|
|
12
|
+
const HOST = process.env.WAYGRAPH_FIXTURE_HOST || "127.0.0.1";
|
|
13
|
+
|
|
14
|
+
const argv = process.argv.slice(2).filter((a) => a !== "--");
|
|
15
|
+
if (argv.length === 0) {
|
|
16
|
+
console.error("usage: node scripts/with-fixture.mjs <cmd> [args...]");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const env = {
|
|
21
|
+
...process.env,
|
|
22
|
+
WAYGRAPH_FIXTURE_PORT: PORT,
|
|
23
|
+
WAYGRAPH_FIXTURE_HOST: HOST,
|
|
24
|
+
WAYGRAPH_FIXTURE_QUIET: "1",
|
|
25
|
+
WAYGRAPH_BASE_URL: process.env.WAYGRAPH_BASE_URL || `http://${HOST}:${PORT}`,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const server = spawn(process.execPath, [join(HERE, "fixture-server.mjs")], {
|
|
29
|
+
env,
|
|
30
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function killServer() {
|
|
34
|
+
try {
|
|
35
|
+
server.kill("SIGTERM");
|
|
36
|
+
} catch {
|
|
37
|
+
/* ignore */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function waitReady(ms = 5000) {
|
|
42
|
+
const url = `http://${HOST}:${PORT}/home.html`;
|
|
43
|
+
const start = Date.now();
|
|
44
|
+
while (Date.now() - start < ms) {
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetch(url);
|
|
47
|
+
if (res.ok) return;
|
|
48
|
+
} catch {
|
|
49
|
+
/* retry */
|
|
50
|
+
}
|
|
51
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`fixture server not ready at ${url}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let code = 1;
|
|
57
|
+
try {
|
|
58
|
+
await waitReady();
|
|
59
|
+
const child = spawn(argv[0], argv.slice(1), { env, stdio: "inherit", shell: false });
|
|
60
|
+
code = await new Promise((resolve) => {
|
|
61
|
+
child.on("exit", (c, signal) => resolve(signal ? 1 : (c ?? 1)));
|
|
62
|
+
});
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
65
|
+
code = 1;
|
|
66
|
+
} finally {
|
|
67
|
+
killServer();
|
|
68
|
+
}
|
|
69
|
+
process.exit(code);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Site map (scaffold)
|
|
2
|
+
|
|
3
|
+
Offline catalog uses one synthetic namespace (`demo-web/`) and a local
|
|
4
|
+
`home.html` fixture so `npm test` / `waygraph auto` need no network.
|
|
5
|
+
|
|
6
|
+
| Folder | URL | Notes |
|
|
7
|
+
|--------|-----|-------|
|
|
8
|
+
| `demo-web/` | `/` (synthetic) | Nav + Page hub + methods/effects |
|
|
9
|
+
| `demo-web/methods/` | same URL | assert, add/remove Effects, clear |
|
|
10
|
+
|
|
11
|
+
When you wire a real app, rename `demo-web/` to your FE origin namespace and
|
|
12
|
+
add one folder per real `page.tsx` route. See **Consumer layout** docs.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# demo-web / (synthetic home)
|
|
2
|
+
|
|
3
|
+
**URL:** `http://127.0.0.1:4177/home.html` (offline catalog via `scripts/fixture-server.mjs` - replace with your real `/`)
|
|
4
|
+
|
|
5
|
+
## Nav-to
|
|
6
|
+
|
|
7
|
+
| From | Block | How |
|
|
8
|
+
|------|-------|-----|
|
|
9
|
+
| start | `nav-home` | `defineNavBlock` `url` → home.html |
|
|
10
|
+
|
|
11
|
+
## Page hub
|
|
12
|
+
|
|
13
|
+
| Block | Checkpoint | Methods |
|
|
14
|
+
|-------|------------|---------|
|
|
15
|
+
| `page-home` | `Home` | add-item, remove-item, clear-cart, assert-hello |
|
|
16
|
+
|
|
17
|
+
## Methods / Effects
|
|
18
|
+
|
|
19
|
+
| Block | Kind | Auto |
|
|
20
|
+
|-------|------|------|
|
|
21
|
+
| `assert-hello` | Method | single row |
|
|
22
|
+
| `add-item` | Effect | one row per live Add button (`instanceOptions`) |
|
|
23
|
+
| `remove-item` | Effect | one row per live Remove button |
|
|
24
|
+
| `clear-cart` | Method | single row |
|
|
25
|
+
|
|
26
|
+
## States / mem-keys
|
|
27
|
+
|
|
28
|
+
- Checkpoints: `Home`, `HomeVerified`, `ItemInCart`, `CartEmpty`
|
|
29
|
+
- Mem: `demo.selectedItem` (`SelectedItem`) - filled by auto or `--data`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Offline catalog URL — HTTP fixture server (not file://; Flatpak Chromium blocks /tmp file URLs). */
|
|
2
|
+
export const HOME_URL =
|
|
3
|
+
process.env.WAYGRAPH_HOME_URL ??
|
|
4
|
+
`http://${process.env.WAYGRAPH_FIXTURE_HOST || "127.0.0.1"}:${process.env.WAYGRAPH_FIXTURE_PORT || "4177"}/home.html`;
|
|
5
|
+
|
|
6
|
+
export const DemoSel = {
|
|
7
|
+
title: "h1",
|
|
8
|
+
sub: "#sub",
|
|
9
|
+
cartCount: "[data-test=cart-count]",
|
|
10
|
+
catalog: "#catalog",
|
|
11
|
+
clear: "[data-test=clear-cart]",
|
|
12
|
+
addBtn: (id: string) => `[data-test=add-${id}]`,
|
|
13
|
+
removeBtn: (id: string) => `[data-test=remove-${id}]`,
|
|
14
|
+
} as const;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>Waygraph scaffold demo</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 32rem; }
|
|
8
|
+
h1 { margin: 0 0 0.5rem; }
|
|
9
|
+
#cart { margin: 1rem 0; font-weight: 600; }
|
|
10
|
+
.item { display: flex; gap: 0.75rem; align-items: center; margin: 0.5rem 0; }
|
|
11
|
+
button { cursor: pointer; }
|
|
12
|
+
</style>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<h1>Hello Waygraph</h1>
|
|
16
|
+
<p id="sub">Scaffold demo - Effect / auto / Page hub</p>
|
|
17
|
+
<div id="cart">Cart: <span data-test="cart-count">0</span></div>
|
|
18
|
+
<ul id="catalog">
|
|
19
|
+
<li class="item" data-id="alpha">
|
|
20
|
+
<span class="name">Alpha</span>
|
|
21
|
+
<button type="button" data-test="add-alpha" class="add">Add Alpha</button>
|
|
22
|
+
<button type="button" data-test="remove-alpha" class="remove" hidden>Remove Alpha</button>
|
|
23
|
+
</li>
|
|
24
|
+
<li class="item" data-id="beta">
|
|
25
|
+
<span class="name">Beta</span>
|
|
26
|
+
<button type="button" data-test="add-beta" class="add">Add Beta</button>
|
|
27
|
+
<button type="button" data-test="remove-beta" class="remove" hidden>Remove Beta</button>
|
|
28
|
+
</li>
|
|
29
|
+
<li class="item" data-id="gamma">
|
|
30
|
+
<span class="name">Gamma</span>
|
|
31
|
+
<button type="button" data-test="add-gamma" class="add">Add Gamma</button>
|
|
32
|
+
<button type="button" data-test="remove-gamma" class="remove" hidden>Remove Gamma</button>
|
|
33
|
+
</li>
|
|
34
|
+
</ul>
|
|
35
|
+
<button type="button" data-test="clear-cart" id="clear">Clear cart</button>
|
|
36
|
+
<script>
|
|
37
|
+
const countEl = document.querySelector("[data-test=cart-count]");
|
|
38
|
+
const sync = () => {
|
|
39
|
+
const n = document.querySelectorAll(".remove:not([hidden])").length;
|
|
40
|
+
countEl.textContent = String(n);
|
|
41
|
+
};
|
|
42
|
+
document.querySelectorAll(".item").forEach((row) => {
|
|
43
|
+
const add = row.querySelector(".add");
|
|
44
|
+
const remove = row.querySelector(".remove");
|
|
45
|
+
add.addEventListener("click", () => {
|
|
46
|
+
add.hidden = true;
|
|
47
|
+
remove.hidden = false;
|
|
48
|
+
sync();
|
|
49
|
+
});
|
|
50
|
+
remove.addEventListener("click", () => {
|
|
51
|
+
remove.hidden = true;
|
|
52
|
+
add.hidden = false;
|
|
53
|
+
sync();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
document.getElementById("clear").addEventListener("click", () => {
|
|
57
|
+
document.querySelectorAll(".item").forEach((row) => {
|
|
58
|
+
row.querySelector(".add").hidden = false;
|
|
59
|
+
row.querySelector(".remove").hidden = true;
|
|
60
|
+
});
|
|
61
|
+
sync();
|
|
62
|
+
});
|
|
63
|
+
</script>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { definePageBlock, Trait } from "waygraph";
|
|
2
|
+
import type { Home } from "../../states/demo.states.js";
|
|
3
|
+
import { DemoSel } from "./demo-sel.js";
|
|
4
|
+
import { AddItemBlock } from "./methods/add-item.effect.block.js";
|
|
5
|
+
import { RemoveItemBlock } from "./methods/remove-item.effect.block.js";
|
|
6
|
+
import { ClearCartBlock } from "./methods/clear-cart.method.block.js";
|
|
7
|
+
import { AssertHelloBlock } from "./methods/assert-hello.method.block.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Kind: Page
|
|
11
|
+
* Helper: definePageBlock
|
|
12
|
+
* Route: demo-web/
|
|
13
|
+
*
|
|
14
|
+
* Hub for the offline home screen. Methods hang here for docs/auto grouping;
|
|
15
|
+
* edges in flows still reference the method Blocks directly.
|
|
16
|
+
* Arrival-only (nav-home already landed on Home).
|
|
17
|
+
*/
|
|
18
|
+
export const HomePageBlock = definePageBlock<Home>({
|
|
19
|
+
name: "page-home",
|
|
20
|
+
description: "Offline home hub - add/remove effects, clear, assert-hello.",
|
|
21
|
+
checkpoint: "Home",
|
|
22
|
+
verify: [Trait.visible(DemoSel.catalog)],
|
|
23
|
+
methods: {
|
|
24
|
+
addItem: () => AddItemBlock,
|
|
25
|
+
removeItem: () => RemoveItemBlock,
|
|
26
|
+
clearCart: () => ClearCartBlock,
|
|
27
|
+
assertHello: () => AssertHelloBlock,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineEffectBlock,
|
|
3
|
+
checkpoint,
|
|
4
|
+
type Checkpoint,
|
|
5
|
+
type WaygraphInstanceOption,
|
|
6
|
+
} from "waygraph";
|
|
7
|
+
import type { ItemInCart } from "../../../states/demo.states.js";
|
|
8
|
+
import { SelectedItem } from "../../../states/demo.mem-keys.js";
|
|
9
|
+
import { DemoSel } from "../demo-sel.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Kind: Effect
|
|
13
|
+
* Helper: defineEffectBlock
|
|
14
|
+
* Route: demo-web/methods/
|
|
15
|
+
*
|
|
16
|
+
* Mutates one catalog row from mem. `waygraph auto` lists one "Add …" row per
|
|
17
|
+
* live Add button via instanceOptions.
|
|
18
|
+
*/
|
|
19
|
+
export const AddItemBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>({
|
|
20
|
+
name: "add-item",
|
|
21
|
+
description: "Adds the mem-selected catalog item to the cart.",
|
|
22
|
+
requires: [SelectedItem.key],
|
|
23
|
+
instruction: {
|
|
24
|
+
async act(page, _input, mem) {
|
|
25
|
+
const { id } = mem.get(SelectedItem.key);
|
|
26
|
+
await page.locator(DemoSel.addBtn(id)).click();
|
|
27
|
+
},
|
|
28
|
+
resolve: () => checkpoint("ItemInCart"),
|
|
29
|
+
verify: [
|
|
30
|
+
{
|
|
31
|
+
name: "item-added",
|
|
32
|
+
async check(page, mem) {
|
|
33
|
+
const { id } = mem.get(SelectedItem.key);
|
|
34
|
+
return page.locator(DemoSel.removeBtn(id)).isVisible();
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
stubBefore: {
|
|
39
|
+
add: { selector: DemoSel.catalog, label: "Catalog" },
|
|
40
|
+
},
|
|
41
|
+
stubAfter: {
|
|
42
|
+
cart: { selector: DemoSel.cartCount, label: "Cart count", duration: true },
|
|
43
|
+
},
|
|
44
|
+
stubOnError: {
|
|
45
|
+
cart: { selector: DemoSel.cartCount, label: "Cart at failure", tag: "FAIL" },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
|
|
49
|
+
const buttons = page.locator("button.add:not([hidden])");
|
|
50
|
+
const n = await buttons.count();
|
|
51
|
+
const out: WaygraphInstanceOption[] = [];
|
|
52
|
+
for (let i = 0; i < n; i++) {
|
|
53
|
+
const btn = buttons.nth(i);
|
|
54
|
+
const label = ((await btn.textContent()) || "").trim();
|
|
55
|
+
const testId = await btn.getAttribute("data-test");
|
|
56
|
+
const id = (testId || "").replace(/^add-/, "");
|
|
57
|
+
if (!id) continue;
|
|
58
|
+
out.push({
|
|
59
|
+
id,
|
|
60
|
+
label: label || `Add ${id}`,
|
|
61
|
+
key: SelectedItem.key,
|
|
62
|
+
value: { id, name: id },
|
|
63
|
+
highlight: DemoSel.addBtn(id),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineMethodBlock, checkpoint, Trait } from "waygraph";
|
|
2
|
+
import { DemoSel } from "../demo-sel.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Kind: Method
|
|
6
|
+
* Helper: defineMethodBlock
|
|
7
|
+
* Route: demo-web/methods/
|
|
8
|
+
*
|
|
9
|
+
* Same-URL assert after nav - stubs + fixtures + YAP slide.
|
|
10
|
+
*/
|
|
11
|
+
export const AssertHelloBlock = defineMethodBlock({
|
|
12
|
+
name: "assert-hello",
|
|
13
|
+
description: "Confirms the hello heading is still visible after arrival.",
|
|
14
|
+
instruction: {
|
|
15
|
+
async act(_page) {},
|
|
16
|
+
async observe(page) {
|
|
17
|
+
await page.locator(DemoSel.title).first().waitFor({ state: "visible", timeout: 5_000 });
|
|
18
|
+
return "ok" as const;
|
|
19
|
+
},
|
|
20
|
+
resolve: () => checkpoint("HomeVerified"),
|
|
21
|
+
verify: [Trait.text(DemoSel.title, "Hello Waygraph"), Trait.visible(DemoSel.sub)],
|
|
22
|
+
stubBefore: {
|
|
23
|
+
title: { selector: DemoSel.title, label: "Heading about to check" },
|
|
24
|
+
},
|
|
25
|
+
stubAfter: {
|
|
26
|
+
title: {
|
|
27
|
+
selector: DemoSel.title,
|
|
28
|
+
label: "Hello confirmed",
|
|
29
|
+
detail: "Offline scaffold proof point.",
|
|
30
|
+
duration: true,
|
|
31
|
+
},
|
|
32
|
+
sub: { selector: DemoSel.sub, label: "Subcopy", duration: 1500 },
|
|
33
|
+
},
|
|
34
|
+
stubOnError: {
|
|
35
|
+
title: {
|
|
36
|
+
selector: DemoSel.title,
|
|
37
|
+
label: "Heading at failure",
|
|
38
|
+
tag: "FAIL",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
slides: [
|
|
42
|
+
{
|
|
43
|
+
caption: "Scaffold covers auto + narration",
|
|
44
|
+
detail: "Page hub, Effect instanceOptions, stubs, fixtures, YAP.",
|
|
45
|
+
tag: "YAP",
|
|
46
|
+
duration: true,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineMethodBlock, checkpoint, Trait } from "waygraph";
|
|
2
|
+
import { DemoSel } from "../demo-sel.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Kind: Method
|
|
6
|
+
* Helper: defineMethodBlock
|
|
7
|
+
* Route: demo-web/methods/
|
|
8
|
+
*/
|
|
9
|
+
export const ClearCartBlock = defineMethodBlock({
|
|
10
|
+
name: "clear-cart",
|
|
11
|
+
description: "Clears every item via the Clear cart control.",
|
|
12
|
+
instruction: {
|
|
13
|
+
async act(page) {
|
|
14
|
+
await page.locator(DemoSel.clear).click();
|
|
15
|
+
},
|
|
16
|
+
resolve: () => checkpoint("CartEmpty"),
|
|
17
|
+
verify: [Trait.text(DemoSel.cartCount, "0")],
|
|
18
|
+
stubBefore: {
|
|
19
|
+
clear: { selector: DemoSel.clear, label: "Clear cart" },
|
|
20
|
+
},
|
|
21
|
+
stubAfter: {
|
|
22
|
+
cart: { selector: DemoSel.cartCount, label: "Empty cart", duration: true },
|
|
23
|
+
},
|
|
24
|
+
stubOnError: {},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineEffectBlock,
|
|
3
|
+
checkpoint,
|
|
4
|
+
type Checkpoint,
|
|
5
|
+
type WaygraphInstanceOption,
|
|
6
|
+
} from "waygraph";
|
|
7
|
+
import type { CartEmpty } from "../../../states/demo.states.js";
|
|
8
|
+
import { SelectedItem } from "../../../states/demo.mem-keys.js";
|
|
9
|
+
import { DemoSel } from "../demo-sel.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Kind: Effect
|
|
13
|
+
* Helper: defineEffectBlock
|
|
14
|
+
* Route: demo-web/methods/
|
|
15
|
+
*
|
|
16
|
+
* Removes one in-cart row. Auto menus one row per visible Remove button.
|
|
17
|
+
*/
|
|
18
|
+
export const RemoveItemBlock = defineEffectBlock<Checkpoint<string>, CartEmpty>({
|
|
19
|
+
name: "remove-item",
|
|
20
|
+
description: "Removes the mem-selected catalog item from the cart.",
|
|
21
|
+
requires: [SelectedItem.key],
|
|
22
|
+
instruction: {
|
|
23
|
+
async act(page, _input, mem) {
|
|
24
|
+
const { id } = mem.get(SelectedItem.key);
|
|
25
|
+
await page.locator(DemoSel.removeBtn(id)).click();
|
|
26
|
+
},
|
|
27
|
+
resolve: () => checkpoint("CartEmpty"),
|
|
28
|
+
verify: [
|
|
29
|
+
{
|
|
30
|
+
name: "item-removed",
|
|
31
|
+
async check(page, mem) {
|
|
32
|
+
const { id } = mem.get(SelectedItem.key);
|
|
33
|
+
return page.locator(DemoSel.addBtn(id)).isVisible();
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
stubBefore: {},
|
|
38
|
+
stubAfter: {
|
|
39
|
+
cart: { selector: DemoSel.cartCount, label: "Cart count", duration: true },
|
|
40
|
+
},
|
|
41
|
+
stubOnError: {},
|
|
42
|
+
},
|
|
43
|
+
async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
|
|
44
|
+
const buttons = page.locator("button.remove:not([hidden])");
|
|
45
|
+
const n = await buttons.count();
|
|
46
|
+
const out: WaygraphInstanceOption[] = [];
|
|
47
|
+
for (let i = 0; i < n; i++) {
|
|
48
|
+
const btn = buttons.nth(i);
|
|
49
|
+
const label = ((await btn.textContent()) || "").trim();
|
|
50
|
+
const testId = await btn.getAttribute("data-test");
|
|
51
|
+
const id = (testId || "").replace(/^remove-/, "");
|
|
52
|
+
if (!id) continue;
|
|
53
|
+
out.push({
|
|
54
|
+
id,
|
|
55
|
+
label: label || `Remove ${id}`,
|
|
56
|
+
key: SelectedItem.key,
|
|
57
|
+
value: { id, name: id },
|
|
58
|
+
highlight: DemoSel.removeBtn(id),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineNavBlock, Trait } from "waygraph";
|
|
2
|
+
import { DemoSel, HOME_URL } from "./demo-sel.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Kind: Nav
|
|
6
|
+
* Helper: defineNavBlock
|
|
7
|
+
* Route: demo-web/ (synthetic "/")
|
|
8
|
+
*/
|
|
9
|
+
export const NavHomeBlock = defineNavBlock({
|
|
10
|
+
name: "nav-home",
|
|
11
|
+
description: "Opens the offline catalog page (HTTP fixture :4177).",
|
|
12
|
+
checkpoint: "Home",
|
|
13
|
+
url: HOME_URL,
|
|
14
|
+
verify: [Trait.text(DemoSel.title, "Hello Waygraph"), Trait.visible(DemoSel.catalog)],
|
|
15
|
+
stubBefore: {},
|
|
16
|
+
stubAfter: {
|
|
17
|
+
title: { selector: DemoSel.title, label: "Home heading", duration: true },
|
|
18
|
+
},
|
|
19
|
+
stubOnError: {
|
|
20
|
+
title: { selector: DemoSel.title, label: "Home heading at failure" },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -1,6 +1,27 @@
|
|
|
1
|
-
import { Engine, start, end } from "waygraph";
|
|
2
|
-
import {
|
|
1
|
+
import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
|
|
2
|
+
import { NavHomeBlock } from "../blocks/demo-web/nav-home.block.js";
|
|
3
|
+
import { AssertHelloBlock } from "../blocks/demo-web/methods/assert-hello.method.block.js";
|
|
4
|
+
// Page hub registered for auto/docs grouping (arrival-only after nav).
|
|
5
|
+
import "../blocks/demo-web/home.page.block.js";
|
|
3
6
|
|
|
4
7
|
const engine = new Engine();
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
/** Smoke: nav + assert (no mem). Green offline. */
|
|
10
|
+
export const exampleFlow = withHighlightFixtures(
|
|
11
|
+
withTitle(
|
|
12
|
+
engine.defineFlow([start, NavHomeBlock, AssertHelloBlock, end]),
|
|
13
|
+
"Scaffold: Hello Waygraph",
|
|
14
|
+
),
|
|
15
|
+
{
|
|
16
|
+
"assert-hello": {
|
|
17
|
+
stubAfter: {
|
|
18
|
+
title: {
|
|
19
|
+
label: "AC · Hello visible",
|
|
20
|
+
detail: "Replace this fixture copy with your ticket lines.",
|
|
21
|
+
tag: "AC",
|
|
22
|
+
duration: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Engine,
|
|
3
|
+
start,
|
|
4
|
+
end,
|
|
5
|
+
withTitle,
|
|
6
|
+
withHighlightFixtures,
|
|
7
|
+
withSessionReset,
|
|
8
|
+
} from "waygraph";
|
|
9
|
+
import { NavHomeBlock } from "../blocks/demo-web/nav-home.block.js";
|
|
10
|
+
import { AddItemBlock } from "../blocks/demo-web/methods/add-item.effect.block.js";
|
|
11
|
+
import { ClearCartBlock } from "../blocks/demo-web/methods/clear-cart.method.block.js";
|
|
12
|
+
import "../blocks/demo-web/home.page.block.js";
|
|
13
|
+
|
|
14
|
+
const engine = new Engine();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Effect + Method demo (set mem for add-item, or use `waygraph auto` which
|
|
18
|
+
* fills SelectedItem via instanceOptions).
|
|
19
|
+
*
|
|
20
|
+
* waygraph demo --blocks shopFlow --data '{"demo.selectedItem":{"id":"alpha","name":"Alpha"}}'
|
|
21
|
+
* waygraph auto # after landing on home, pick Add Alpha / Add Beta / …
|
|
22
|
+
*/
|
|
23
|
+
export const shopFlow = withHighlightFixtures(
|
|
24
|
+
withTitle(
|
|
25
|
+
withSessionReset(
|
|
26
|
+
engine.defineFlow([start, NavHomeBlock, AddItemBlock, ClearCartBlock, end]),
|
|
27
|
+
),
|
|
28
|
+
"Scaffold: Shop (Effect + clear)",
|
|
29
|
+
),
|
|
30
|
+
{
|
|
31
|
+
"add-item": {
|
|
32
|
+
stubAfter: {
|
|
33
|
+
cart: {
|
|
34
|
+
label: "AC · item in cart",
|
|
35
|
+
detail: "Cart count bumped after Add.",
|
|
36
|
+
tag: "AC",
|
|
37
|
+
duration: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { keyGroup } from "waygraph";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which catalog row `add-item` / `remove-item` act on.
|
|
5
|
+
* `waygraph auto` fills this via Effect `instanceOptions` (one menu row per live button).
|
|
6
|
+
*/
|
|
7
|
+
export const SelectedItem = keyGroup<{ id: string; name: string }>("demo.selectedItem");
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { test, expect } from "@playwright/test";
|
|
2
2
|
import { MemPage, checkpoint } from "waygraph";
|
|
3
3
|
import { exampleFlow } from "../src/flows/example.flow.js";
|
|
4
|
+
import { shopFlow } from "../src/flows/shop.flow.js";
|
|
5
|
+
import { SelectedItem } from "../src/states/demo.mem-keys.js";
|
|
4
6
|
|
|
5
|
-
test("example flow reaches
|
|
7
|
+
test("example flow reaches HomeVerified", async ({ context }) => {
|
|
6
8
|
const result = await exampleFlow.run(context, new MemPage());
|
|
7
|
-
expect(result).toEqual(checkpoint("
|
|
9
|
+
expect(result).toEqual(checkpoint("HomeVerified"));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("shop flow adds then clears (Effect + Method)", async ({ context }) => {
|
|
13
|
+
const mem = new MemPage();
|
|
14
|
+
mem.set(SelectedItem.key, { id: "alpha", name: "Alpha" });
|
|
15
|
+
const result = await shopFlow.run(context, mem);
|
|
16
|
+
expect(result).toEqual(checkpoint("CartEmpty"));
|
|
8
17
|
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { defineBlock, checkpoint, Trait } from "waygraph";
|
|
2
|
-
import type { Checkpoint } from "waygraph";
|
|
3
|
-
|
|
4
|
-
export type Start = Checkpoint<"__start__">;
|
|
5
|
-
export type Loaded = Checkpoint<"Loaded">;
|
|
6
|
-
|
|
7
|
-
const PAGE_URL = "data:text/html,<h1>Hello Waygraph</h1>";
|
|
8
|
-
|
|
9
|
-
export const LoadPageBlock = defineBlock<Start, Loaded>({
|
|
10
|
-
name: "load-page",
|
|
11
|
-
instruction: {
|
|
12
|
-
async act(page) {
|
|
13
|
-
await page.goto(PAGE_URL);
|
|
14
|
-
},
|
|
15
|
-
resolve: () => checkpoint("Loaded"),
|
|
16
|
-
verify: [Trait.text("h1", "Hello Waygraph")],
|
|
17
|
-
},
|
|
18
|
-
});
|