waygraph 0.7.14 → 0.7.16
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/auto-explore.js +1 -1
- package/dist/engine.d.ts +29 -1
- package/dist/engine.js +29 -1
- package/dist/graph.js +12 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/examples/saucedemo/docs/HELPERS.md +42 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/actions/submit-login.action.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/actions/finish-order.action.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +3 -3
- package/package.json +1 -1
- package/templates/quickstart/docs/HELPERS.md +42 -0
- package/templates/quickstart/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/actions/submit-login.action.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-cart.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/actions/finish-order.action.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +3 -3
package/dist/auto-explore.js
CHANGED
|
@@ -249,7 +249,7 @@ export async function buildExploreMenu(page, graph, library, here) {
|
|
|
249
249
|
else {
|
|
250
250
|
if (forward.length > 0) {
|
|
251
251
|
sections.push({
|
|
252
|
-
title: `
|
|
252
|
+
title: `Actions from ${here}`,
|
|
253
253
|
edges: forward.sort((a, b) => a.block.localeCompare(b.block)),
|
|
254
254
|
});
|
|
255
255
|
}
|
package/dist/engine.d.ts
CHANGED
|
@@ -536,9 +536,37 @@ export type NavBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
536
536
|
* hand.
|
|
537
537
|
* @example defineNavBlock({ name: "nav-web-login", checkpoint: "LoginForm", url: "/login" })
|
|
538
538
|
* @example defineNavBlock({ name: "nav-web-request-detail", checkpoint: "RequestDetail", url: (mem) => `/dashboard/requests/${mem.get(RequestId.key)}` })
|
|
539
|
-
* @example
|
|
539
|
+
* @example defineNavClickBlock({ name: "nav-web-documents", checkpoint: "DocumentsList", click: "nav >> text=Documents" })
|
|
540
|
+
* For click-nav in app code prefer {@link defineNavClickBlock} (url stays here).
|
|
540
541
|
*/
|
|
541
542
|
export declare function defineNavBlock<Out extends Checkpoint<string>>(options: NavBlockOptions<Out>): NavBlock<Out>;
|
|
543
|
+
/**
|
|
544
|
+
* Click-only {@link NavBlock}. Same runtime as {@link defineNavBlock} with
|
|
545
|
+
* `click` - use this in app code so you never wonder whether a Nav is url or
|
|
546
|
+
* click (no `__waygraphNavClick` sniffing at author time). Prefer
|
|
547
|
+
* {@link defineNavBlock} only for `url` / `goto` deep links.
|
|
548
|
+
*/
|
|
549
|
+
export type NavClickBlockOptions<Out extends Checkpoint<string>> = {
|
|
550
|
+
name: string;
|
|
551
|
+
description?: string;
|
|
552
|
+
checkpoint: Out["__state"];
|
|
553
|
+
click: string | ((mem: MemPage) => string);
|
|
554
|
+
requires?: readonly MemKey<any>[];
|
|
555
|
+
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
556
|
+
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
557
|
+
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
558
|
+
};
|
|
559
|
+
export type NavClickBlock<Out extends Checkpoint<string>> = NavBlock<Out>;
|
|
560
|
+
/** @see {@link NavClickBlockOptions} */
|
|
561
|
+
export declare function defineNavClickBlock<Out extends Checkpoint<string>>(options: NavClickBlockOptions<Out>): NavClickBlock<Out>;
|
|
562
|
+
/**
|
|
563
|
+
* TypeScript salt over {@link defineBlock} for one-shot non-nav app steps
|
|
564
|
+
* (submit, finish, logout). Same runtime Block; marks `__waygraphSalt = "action"`
|
|
565
|
+
* so auto/docs can tell Action from bare {@link defineBlock} (tests / unsure).
|
|
566
|
+
* Does not force `instanceOptions` - use {@link defineEffectBlock} for that.
|
|
567
|
+
*/
|
|
568
|
+
export type ActionBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> = DefinedBlock<In, Out>;
|
|
569
|
+
export declare function defineActionBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(base: Parameters<typeof defineBlock<In, Out>>[0]): ActionBlock<In, Out>;
|
|
542
570
|
/**
|
|
543
571
|
* TypeScript salt over {@link defineBlock}: same runtime Block, but the type
|
|
544
572
|
* forces `requires` + `instanceOptions` so `waygraph auto` can list one menu
|
package/dist/engine.js
CHANGED
|
@@ -634,7 +634,8 @@ export function defineBlock(base) {
|
|
|
634
634
|
* hand.
|
|
635
635
|
* @example defineNavBlock({ name: "nav-web-login", checkpoint: "LoginForm", url: "/login" })
|
|
636
636
|
* @example defineNavBlock({ name: "nav-web-request-detail", checkpoint: "RequestDetail", url: (mem) => `/dashboard/requests/${mem.get(RequestId.key)}` })
|
|
637
|
-
* @example
|
|
637
|
+
* @example defineNavClickBlock({ name: "nav-web-documents", checkpoint: "DocumentsList", click: "nav >> text=Documents" })
|
|
638
|
+
* For click-nav in app code prefer {@link defineNavClickBlock} (url stays here).
|
|
638
639
|
*/
|
|
639
640
|
export function defineNavBlock(options) {
|
|
640
641
|
const built = defineBlock({
|
|
@@ -682,6 +683,28 @@ export function defineNavBlock(options) {
|
|
|
682
683
|
}
|
|
683
684
|
return built;
|
|
684
685
|
}
|
|
686
|
+
/** @see {@link NavClickBlockOptions} */
|
|
687
|
+
export function defineNavClickBlock(options) {
|
|
688
|
+
return defineNavBlock({
|
|
689
|
+
name: options.name,
|
|
690
|
+
checkpoint: options.checkpoint,
|
|
691
|
+
click: options.click,
|
|
692
|
+
...(options.description ? { description: options.description } : {}),
|
|
693
|
+
...(options.requires ? { requires: options.requires } : {}),
|
|
694
|
+
...(options.verify ? { verify: options.verify } : {}),
|
|
695
|
+
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
696
|
+
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
export function defineActionBlock(base) {
|
|
700
|
+
const built = defineBlock(base);
|
|
701
|
+
Object.defineProperty(built, "__waygraphSalt", {
|
|
702
|
+
value: "action",
|
|
703
|
+
enumerable: false,
|
|
704
|
+
configurable: false,
|
|
705
|
+
});
|
|
706
|
+
return built;
|
|
707
|
+
}
|
|
685
708
|
/**
|
|
686
709
|
* {@link defineBlock} constrained for effect-style instance menus: `requires` +
|
|
687
710
|
* `instanceOptions` are mandatory. Prefer this when the Block adds / removes /
|
|
@@ -695,6 +718,11 @@ export function defineEffectBlock(base) {
|
|
|
695
718
|
enumerable: false,
|
|
696
719
|
configurable: false,
|
|
697
720
|
});
|
|
721
|
+
Object.defineProperty(built, "__waygraphSalt", {
|
|
722
|
+
value: "effect",
|
|
723
|
+
enumerable: false,
|
|
724
|
+
configurable: false,
|
|
725
|
+
});
|
|
698
726
|
return built;
|
|
699
727
|
}
|
|
700
728
|
/** @deprecated Prefer {@link defineEffectBlock} - identical helper. */
|
package/dist/graph.js
CHANGED
|
@@ -98,7 +98,7 @@ export async function findOrphanBlocks(projectDir) {
|
|
|
98
98
|
const exportName = m[1];
|
|
99
99
|
if (referenced.has(exportName))
|
|
100
100
|
continue;
|
|
101
|
-
if (!/defineBlock|defineNavBlock/.test(src))
|
|
101
|
+
if (!/defineBlock|defineActionBlock|defineEffectBlock|defineMemEffectBlock|defineNavBlock|defineNavClickBlock|defineMemNavBlock/.test(src))
|
|
102
102
|
continue;
|
|
103
103
|
let block = exportNameToBlockName(exportName);
|
|
104
104
|
try {
|
|
@@ -234,14 +234,17 @@ function resolveGenericArg(blockFilePath, arg) {
|
|
|
234
234
|
return [inline[1]];
|
|
235
235
|
return resolveTypeToTags(blockFilePath, arg);
|
|
236
236
|
}
|
|
237
|
-
/** Every `defineBlock
|
|
237
|
+
/** Every `defineBlock` / `defineEffectBlock` / `defineMemEffectBlock` `<In, Out>` call site. */
|
|
238
238
|
function extractDefineBlockGenerics(src) {
|
|
239
239
|
// Each argument is either a plain identifier or an inline `Checkpoint<...>`
|
|
240
|
-
// (its own angle brackets, so the outer
|
|
240
|
+
// (its own angle brackets, so the outer defineX<...> pair alone won't
|
|
241
241
|
// balance correctly against a naive greedy match) - captured as a single
|
|
242
242
|
// alternation so both shapes come out of the same two capture groups.
|
|
243
|
+
// Effect helpers are TypeScript salt over defineBlock - same In/Out edges.
|
|
243
244
|
const arg = String.raw `(Checkpoint<[^>]*>|[A-Za-z_$][\w]*)`;
|
|
244
|
-
const re = new RegExp(String.raw `
|
|
245
|
+
const re = new RegExp(String.raw `define(?:(?:Mem)?Effect|Action)?Block<\s*${arg}\s*,\s*${arg}\s*>`, "g");
|
|
246
|
+
// Matches: defineBlock | defineActionBlock | defineEffectBlock | defineMemEffectBlock
|
|
247
|
+
// (Nav / NavClick use runtime markers, not this regex.)
|
|
245
248
|
const calls = [];
|
|
246
249
|
let m;
|
|
247
250
|
while ((m = re.exec(src)) !== null) {
|
|
@@ -301,7 +304,11 @@ export async function discoverGraph(projectDir) {
|
|
|
301
304
|
}
|
|
302
305
|
const call = calls[callIndex++];
|
|
303
306
|
if (!call) {
|
|
304
|
-
skipped.push({
|
|
307
|
+
skipped.push({
|
|
308
|
+
block: blockName,
|
|
309
|
+
file: relFile,
|
|
310
|
+
reason: "no defineBlock/defineEffectBlock<In, Out> generic call found",
|
|
311
|
+
});
|
|
305
312
|
continue;
|
|
306
313
|
}
|
|
307
314
|
const fromTags = resolveGenericArg(file, call.in);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export type { Checkpoint, Instruction, Block, DefinedBlock, Start, WaygraphInsta
|
|
|
2
2
|
export { connect, checkpoint } from "./types.js";
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
4
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
5
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
6
|
-
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
5
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineActionBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
6
|
+
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, ActionBlock, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
7
7
|
export type { ActionPage } from "./types.js";
|
|
8
8
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
9
9
|
export type { WaygraphGraph, WaygraphNode, WaygraphEdge, SkippedBlock, OrphanBlock } from "./graph.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { connect, checkpoint } from "./types.js";
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
3
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
4
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
4
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineActionBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
5
5
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Block helpers (convention)
|
|
2
|
+
|
|
3
|
+
Runtime is always a `Block`. Helpers are TypeScript salt + clear intent.
|
|
4
|
+
|
|
5
|
+
| Helper | Use when | File |
|
|
6
|
+
|--------|----------|------|
|
|
7
|
+
| `defineNavBlock` | URL / `goto` only | `nav-*.block.ts` |
|
|
8
|
+
| `defineNavClickBlock` | Click-to-navigate only | `nav-*.block.ts` |
|
|
9
|
+
| `defineMemNavBlock` | Nav + per-row `instanceOptions` | `nav-*.block.ts` |
|
|
10
|
+
| `defineActionBlock` | One-shot non-nav app step | `*.action.block.ts` |
|
|
11
|
+
| `defineEffectBlock` | Instance mutate + auto menu rows | `*.effect.block.ts` |
|
|
12
|
+
| `defineBlock` | Escape hatch: tests, unsure, probes | any |
|
|
13
|
+
|
|
14
|
+
`defineNavBlock({ click })` still works (compat) - prefer `defineNavClickBlock` in app code.
|
|
15
|
+
|
|
16
|
+
## Branching (not Mem)
|
|
17
|
+
|
|
18
|
+
Mem / `instanceOptions` = pick *which instance* before run.
|
|
19
|
+
|
|
20
|
+
Conditional outcomes = `observe` + union `Out` (already supported, not a new Block kind):
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
defineActionBlock<LoginPage, LoggedIn | LoginPage>({
|
|
24
|
+
name: "submit-login",
|
|
25
|
+
instruction: {
|
|
26
|
+
async act(page, _in, mem) { /* fill + click */ },
|
|
27
|
+
async observe(page) {
|
|
28
|
+
try {
|
|
29
|
+
await page.waitForURL(/\/inventory\.html/, { timeout: 8000 });
|
|
30
|
+
return "success";
|
|
31
|
+
} catch {
|
|
32
|
+
return "failure";
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
resolve: (o) => checkpoint(o === "success" ? "LoggedIn" : "LoginPage"),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Graph expands the union Out into two edges. Optional flow routing: `branch(block, { LoggedIn: next, LoginPage: null })`.
|
|
41
|
+
|
|
42
|
+
No breaking change for atomics - document observe/resolve (+ optional `branch`) as the Branch pattern.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineActionBlock, checkpoint, Trait } from "waygraph";
|
|
2
2
|
import type { LoginPage, LoginSubmitOutcome } from "../../../states/checkout.states.js";
|
|
3
3
|
import { LoginCreds } from "../../../states/checkout.mem-keys.js";
|
|
4
4
|
|
|
@@ -6,13 +6,13 @@ type LoginObserve = "success" | "failure";
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Kind: Action
|
|
9
|
-
* Helper:
|
|
9
|
+
* Helper: defineActionBlock
|
|
10
10
|
* Route: saucedemo-web/actions/ (page URL /)
|
|
11
11
|
*
|
|
12
12
|
* Fills credentials and submits - never page.goto (nav-login owns that).
|
|
13
13
|
* Branches: good creds -> LoggedIn; bad/locked -> LoginPage with error banner.
|
|
14
14
|
*/
|
|
15
|
-
export const SubmitLoginActionBlock =
|
|
15
|
+
export const SubmitLoginActionBlock = defineActionBlock<LoginPage, LoginSubmitOutcome>({
|
|
16
16
|
name: "submit-login",
|
|
17
17
|
description: "Logs a user in when credentials are valid; stays on LoginPage when auth fails.",
|
|
18
18
|
requires: [LoginCreds.key],
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineActionBlock, checkpoint, Trait } from "waygraph";
|
|
2
2
|
import type { LoggedIn, LoginPage } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Action
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineActionBlock
|
|
7
7
|
* Route: saucedemo-web/actions/ (page URL / after logout)
|
|
8
8
|
*
|
|
9
9
|
* Burger menu Logout - real cycle back to LoginPage (same Checkpoint nav-login lands on).
|
|
10
10
|
*/
|
|
11
|
-
export const SubmitLogoutBlock =
|
|
11
|
+
export const SubmitLogoutBlock = defineActionBlock<LoggedIn, LoginPage>({
|
|
12
12
|
name: "submit-logout",
|
|
13
13
|
description: "Opens the burger menu and logs out.",
|
|
14
14
|
instruction: {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { CartPage } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/cart/ (= /cart.html)
|
|
8
8
|
*
|
|
9
9
|
* click, not url - header cart link from inventory-adjacent pages.
|
|
10
10
|
*/
|
|
11
|
-
export const NavCartBlock =
|
|
11
|
+
export const NavCartBlock = defineNavClickBlock<CartPage>({
|
|
12
12
|
name: "nav-cart",
|
|
13
13
|
description: "Clicks the cart link in the header.",
|
|
14
14
|
checkpoint: "CartPage",
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { CheckoutInfoPage } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/cart/ (click leaves /cart.html -> /checkout-step-one.html)
|
|
8
8
|
*
|
|
9
9
|
* Cart page "Checkout" button - owns the nav into step one.
|
|
10
10
|
*/
|
|
11
|
-
export const NavCheckoutInfoBlock =
|
|
11
|
+
export const NavCheckoutInfoBlock = defineNavClickBlock<CheckoutInfoPage>({
|
|
12
12
|
name: "nav-checkout-info",
|
|
13
13
|
description: "Clicks 'Checkout' on the cart page.",
|
|
14
14
|
checkpoint: "CheckoutInfoPage",
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineActionBlock, checkpoint, Trait } from "waygraph";
|
|
2
2
|
import type { CheckoutInfoPage, CheckoutOverviewPage } from "../../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Action
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineActionBlock
|
|
7
7
|
* Route: saucedemo-web/checkout-step-one/actions/ (= /checkout-step-one.html)
|
|
8
8
|
*
|
|
9
9
|
* Fills shipping form and continues - never navigates itself
|
|
10
10
|
* (nav-checkout-info already got here).
|
|
11
11
|
*/
|
|
12
|
-
export const SubmitCheckoutInfoBlock =
|
|
12
|
+
export const SubmitCheckoutInfoBlock = defineActionBlock<CheckoutInfoPage, CheckoutOverviewPage>({
|
|
13
13
|
name: "submit-checkout-info",
|
|
14
14
|
description: "Fills in the shipping info form and continues to the order overview.",
|
|
15
15
|
instruction: {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineActionBlock, checkpoint, Trait, narrate } from "waygraph";
|
|
2
2
|
import type { CheckoutOverviewPage, OrderComplete } from "../../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Action
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineActionBlock
|
|
7
7
|
* Route: saucedemo-web/checkout-step-two/actions/ (= /checkout-step-two.html)
|
|
8
8
|
*
|
|
9
9
|
* Places the order on the overview page - form/finish click, not a NavBlock.
|
|
10
10
|
*/
|
|
11
|
-
export const FinishOrderBlock =
|
|
11
|
+
export const FinishOrderBlock = defineActionBlock<CheckoutOverviewPage, OrderComplete>({
|
|
12
12
|
name: "finish-order",
|
|
13
13
|
description: "Places the order and confirms the 'Thank you for your order!' completion page.",
|
|
14
14
|
instruction: {
|
package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { LoggedIn } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/inventory-item/ (= /inventory-item.html From)
|
|
8
8
|
*
|
|
9
9
|
* click, not url - detail page "Back to products". Separate Block from
|
|
10
10
|
* nav-back-to-products (different From Checkpoint; atomic convention).
|
|
11
11
|
*/
|
|
12
|
-
export const NavBackToInventoryBlock =
|
|
12
|
+
export const NavBackToInventoryBlock = defineNavClickBlock<LoggedIn>({
|
|
13
13
|
name: "nav-back-to-inventory",
|
|
14
14
|
description: "Clicks 'Back to products' on the item detail page.",
|
|
15
15
|
checkpoint: "LoggedIn",
|
package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { LoggedIn } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/inventory-item/ (lands on /inventory.html)
|
|
8
8
|
*
|
|
9
9
|
* click, not url - order-complete "Back Home" proves the button path
|
|
10
10
|
* (a url NavBlock would teleport without exercising the control).
|
|
11
11
|
*/
|
|
12
|
-
export const NavBackToProductsBlock =
|
|
12
|
+
export const NavBackToProductsBlock = defineNavClickBlock<LoggedIn>({
|
|
13
13
|
name: "nav-back-to-products",
|
|
14
14
|
description: "Clicks 'Back Home' on the order confirmation page.",
|
|
15
15
|
checkpoint: "LoggedIn",
|
package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { LoggedIn } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/inventory-item/ (lands on /inventory.html)
|
|
8
8
|
*
|
|
9
9
|
* click, not url - cart page "Continue Shopping" proves the app's own path.
|
|
10
10
|
*/
|
|
11
|
-
export const NavContinueShoppingBlock =
|
|
11
|
+
export const NavContinueShoppingBlock = defineNavClickBlock<LoggedIn>({
|
|
12
12
|
name: "nav-continue-shopping",
|
|
13
13
|
description: "Clicks 'Continue Shopping' on the cart page.",
|
|
14
14
|
checkpoint: "LoggedIn",
|
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Block helpers (convention)
|
|
2
|
+
|
|
3
|
+
Runtime is always a `Block`. Helpers are TypeScript salt + clear intent.
|
|
4
|
+
|
|
5
|
+
| Helper | Use when | File |
|
|
6
|
+
|--------|----------|------|
|
|
7
|
+
| `defineNavBlock` | URL / `goto` only | `nav-*.block.ts` |
|
|
8
|
+
| `defineNavClickBlock` | Click-to-navigate only | `nav-*.block.ts` |
|
|
9
|
+
| `defineMemNavBlock` | Nav + per-row `instanceOptions` | `nav-*.block.ts` |
|
|
10
|
+
| `defineActionBlock` | One-shot non-nav app step | `*.action.block.ts` |
|
|
11
|
+
| `defineEffectBlock` | Instance mutate + auto menu rows | `*.effect.block.ts` |
|
|
12
|
+
| `defineBlock` | Escape hatch: tests, unsure, probes | any |
|
|
13
|
+
|
|
14
|
+
`defineNavBlock({ click })` still works (compat) - prefer `defineNavClickBlock` in app code.
|
|
15
|
+
|
|
16
|
+
## Branching (not Mem)
|
|
17
|
+
|
|
18
|
+
Mem / `instanceOptions` = pick *which instance* before run.
|
|
19
|
+
|
|
20
|
+
Conditional outcomes = `observe` + union `Out` (already supported, not a new Block kind):
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
defineActionBlock<LoginPage, LoggedIn | LoginPage>({
|
|
24
|
+
name: "submit-login",
|
|
25
|
+
instruction: {
|
|
26
|
+
async act(page, _in, mem) { /* fill + click */ },
|
|
27
|
+
async observe(page) {
|
|
28
|
+
try {
|
|
29
|
+
await page.waitForURL(/\/inventory\.html/, { timeout: 8000 });
|
|
30
|
+
return "success";
|
|
31
|
+
} catch {
|
|
32
|
+
return "failure";
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
resolve: (o) => checkpoint(o === "success" ? "LoggedIn" : "LoginPage"),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Graph expands the union Out into two edges. Optional flow routing: `branch(block, { LoggedIn: next, LoginPage: null })`.
|
|
41
|
+
|
|
42
|
+
No breaking change for atomics - document observe/resolve (+ optional `branch`) as the Branch pattern.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineActionBlock, checkpoint, Trait } from "waygraph";
|
|
2
2
|
import type { LoginPage, LoginSubmitOutcome } from "../../../states/checkout.states.js";
|
|
3
3
|
import { LoginCreds } from "../../../states/checkout.mem-keys.js";
|
|
4
4
|
|
|
@@ -6,13 +6,13 @@ type LoginObserve = "success" | "failure";
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Kind: Action
|
|
9
|
-
* Helper:
|
|
9
|
+
* Helper: defineActionBlock
|
|
10
10
|
* Route: saucedemo-web/actions/ (page URL /)
|
|
11
11
|
*
|
|
12
12
|
* Fills credentials and submits - never page.goto (nav-login owns that).
|
|
13
13
|
* Branches: good creds -> LoggedIn; bad/locked -> LoginPage with error banner.
|
|
14
14
|
*/
|
|
15
|
-
export const SubmitLoginActionBlock =
|
|
15
|
+
export const SubmitLoginActionBlock = defineActionBlock<LoginPage, LoginSubmitOutcome>({
|
|
16
16
|
name: "submit-login",
|
|
17
17
|
description: "Logs a user in when credentials are valid; stays on LoginPage when auth fails.",
|
|
18
18
|
requires: [LoginCreds.key],
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineActionBlock, checkpoint, Trait } from "waygraph";
|
|
2
2
|
import type { LoggedIn, LoginPage } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Action
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineActionBlock
|
|
7
7
|
* Route: saucedemo-web/actions/ (page URL / after logout)
|
|
8
8
|
*
|
|
9
9
|
* Burger menu Logout - real cycle back to LoginPage (same Checkpoint nav-login lands on).
|
|
10
10
|
*/
|
|
11
|
-
export const SubmitLogoutBlock =
|
|
11
|
+
export const SubmitLogoutBlock = defineActionBlock<LoggedIn, LoginPage>({
|
|
12
12
|
name: "submit-logout",
|
|
13
13
|
description: "Opens the burger menu and logs out.",
|
|
14
14
|
instruction: {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { CartPage } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/cart/ (= /cart.html)
|
|
8
8
|
*
|
|
9
9
|
* click, not url - header cart link from inventory-adjacent pages.
|
|
10
10
|
*/
|
|
11
|
-
export const NavCartBlock =
|
|
11
|
+
export const NavCartBlock = defineNavClickBlock<CartPage>({
|
|
12
12
|
name: "nav-cart",
|
|
13
13
|
description: "Clicks the cart link in the header.",
|
|
14
14
|
checkpoint: "CartPage",
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { CheckoutInfoPage } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/cart/ (click leaves /cart.html -> /checkout-step-one.html)
|
|
8
8
|
*
|
|
9
9
|
* Cart page "Checkout" button - owns the nav into step one.
|
|
10
10
|
*/
|
|
11
|
-
export const NavCheckoutInfoBlock =
|
|
11
|
+
export const NavCheckoutInfoBlock = defineNavClickBlock<CheckoutInfoPage>({
|
|
12
12
|
name: "nav-checkout-info",
|
|
13
13
|
description: "Clicks 'Checkout' on the cart page.",
|
|
14
14
|
checkpoint: "CheckoutInfoPage",
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineActionBlock, checkpoint, Trait } from "waygraph";
|
|
2
2
|
import type { CheckoutInfoPage, CheckoutOverviewPage } from "../../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Action
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineActionBlock
|
|
7
7
|
* Route: saucedemo-web/checkout-step-one/actions/ (= /checkout-step-one.html)
|
|
8
8
|
*
|
|
9
9
|
* Fills shipping form and continues - never navigates itself
|
|
10
10
|
* (nav-checkout-info already got here).
|
|
11
11
|
*/
|
|
12
|
-
export const SubmitCheckoutInfoBlock =
|
|
12
|
+
export const SubmitCheckoutInfoBlock = defineActionBlock<CheckoutInfoPage, CheckoutOverviewPage>({
|
|
13
13
|
name: "submit-checkout-info",
|
|
14
14
|
description: "Fills in the shipping info form and continues to the order overview.",
|
|
15
15
|
instruction: {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineActionBlock, checkpoint, Trait, narrate } from "waygraph";
|
|
2
2
|
import type { CheckoutOverviewPage, OrderComplete } from "../../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Action
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineActionBlock
|
|
7
7
|
* Route: saucedemo-web/checkout-step-two/actions/ (= /checkout-step-two.html)
|
|
8
8
|
*
|
|
9
9
|
* Places the order on the overview page - form/finish click, not a NavBlock.
|
|
10
10
|
*/
|
|
11
|
-
export const FinishOrderBlock =
|
|
11
|
+
export const FinishOrderBlock = defineActionBlock<CheckoutOverviewPage, OrderComplete>({
|
|
12
12
|
name: "finish-order",
|
|
13
13
|
description: "Places the order and confirms the 'Thank you for your order!' completion page.",
|
|
14
14
|
instruction: {
|
package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { LoggedIn } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/inventory-item/ (= /inventory-item.html From)
|
|
8
8
|
*
|
|
9
9
|
* click, not url - detail page "Back to products". Separate Block from
|
|
10
10
|
* nav-back-to-products (different From Checkpoint; atomic convention).
|
|
11
11
|
*/
|
|
12
|
-
export const NavBackToInventoryBlock =
|
|
12
|
+
export const NavBackToInventoryBlock = defineNavClickBlock<LoggedIn>({
|
|
13
13
|
name: "nav-back-to-inventory",
|
|
14
14
|
description: "Clicks 'Back to products' on the item detail page.",
|
|
15
15
|
checkpoint: "LoggedIn",
|
package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { LoggedIn } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/inventory-item/ (lands on /inventory.html)
|
|
8
8
|
*
|
|
9
9
|
* click, not url - order-complete "Back Home" proves the button path
|
|
10
10
|
* (a url NavBlock would teleport without exercising the control).
|
|
11
11
|
*/
|
|
12
|
-
export const NavBackToProductsBlock =
|
|
12
|
+
export const NavBackToProductsBlock = defineNavClickBlock<LoggedIn>({
|
|
13
13
|
name: "nav-back-to-products",
|
|
14
14
|
description: "Clicks 'Back Home' on the order confirmation page.",
|
|
15
15
|
checkpoint: "LoggedIn",
|
package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
2
|
import type { LoggedIn } from "../../../states/checkout.states.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Kind: Nav
|
|
6
|
-
* Helper:
|
|
6
|
+
* Helper: defineNavClickBlock
|
|
7
7
|
* Route: saucedemo-web/inventory-item/ (lands on /inventory.html)
|
|
8
8
|
*
|
|
9
9
|
* click, not url - cart page "Continue Shopping" proves the app's own path.
|
|
10
10
|
*/
|
|
11
|
-
export const NavContinueShoppingBlock =
|
|
11
|
+
export const NavContinueShoppingBlock = defineNavClickBlock<LoggedIn>({
|
|
12
12
|
name: "nav-continue-shopping",
|
|
13
13
|
description: "Clicks 'Continue Shopping' on the cart page.",
|
|
14
14
|
checkpoint: "LoggedIn",
|