- Parse api="name", api="name()", and api="name({ ... })" render bindings
for apis {} (mode "any") blocks, mirroring @click="fn()" syntax.
- Render-bind by calling Task 4's generated server `api` object directly
(api.<name>(args)) rather than re-implementing the fetch/response
transport, spliced into the SSR template via the existing loop/expression
sentinel mechanism so the call runs inside the async render function with
await support.
- A block that is both render-bound and called from code runs twice by
design (no dedup); pinned with a test.
- Fix packages/syntax's attribute-value lexer (readQuoted) to honor
backslash-escaped quotes, needed so an api="..." call expression can
itself contain a quoted string/object literal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
56 lines
1.9 KiB
TypeScript
56 lines
1.9 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { parse } from "@wrnexus/syntax";
|
|
import { generate } from "../src/codegen.ts";
|
|
|
|
const page = (attr: string) => `page Probe {
|
|
apis {
|
|
listTeams GET /api/teams {
|
|
request { parameters { team?: string } }
|
|
response { return data.teams }
|
|
}
|
|
}
|
|
|
|
view { <main><p ${attr}>loading</p></main> }
|
|
}
|
|
`;
|
|
|
|
test("a bare name binds", () => {
|
|
expect(generate(parse(page('api="listTeams"')))).toContain('"/api/teams"');
|
|
});
|
|
|
|
test("an empty call binds identically to a bare name", () => {
|
|
const bare = generate(parse(page('api="listTeams"')));
|
|
const called = generate(parse(page('api="listTeams()"')));
|
|
|
|
expect(called).toContain('"/api/teams"');
|
|
expect(called.length).toBeGreaterThan(0);
|
|
expect(bare).toContain('"/api/teams"');
|
|
});
|
|
|
|
test("an argument expression is carried into the binding", () => {
|
|
const generated = generate(parse(page('api="listTeams({ team: \\"platform\\" })"')));
|
|
|
|
expect(generated).toContain("platform");
|
|
});
|
|
|
|
test("a block that is both bound and called is invoked twice", () => {
|
|
// Deliberate: a render-time fetch and a user-triggered fetch are usually
|
|
// meant to be different requests. Collapsing them silently would be worse
|
|
// than the duplication.
|
|
const generated = generate(
|
|
parse(`page P {
|
|
apis { listTeams GET /api/teams { response { return data.teams } } }
|
|
load server x { return await api.listTeams() }
|
|
view { <main><p api="listTeams">loading</p></main> }
|
|
}
|
|
`),
|
|
);
|
|
|
|
// Both call paths are emitted: the load block's call and the binding's.
|
|
// The route path itself is declared once, inside the shared server `api`
|
|
// object (Task 4) that both call sites invoke -- so the route string alone
|
|
// isn't a reliable proxy for "called twice". The call expression is.
|
|
const occurrences = generated.split("api.listTeams()").length - 1;
|
|
expect(occurrences).toBeGreaterThanOrEqual(2);
|
|
});
|