Complete prepared presentation build and runtime integration
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/index.js";
|
||||
import type { InterfaceRevision, PackageRevision } from "../src/capability-model/types.js";
|
||||
import { compileQuery } from "../src/query/compile.js";
|
||||
import { compileQueryComposition } from "../src/query/composition.js";
|
||||
import { defaultQueryBudgets, type CheckedQuery } from "../src/query/types.js";
|
||||
import {
|
||||
presentationDeclarations,
|
||||
preparationArtifact,
|
||||
type ReactPreparationArtifact,
|
||||
} from "../src/react-build/declarations.js";
|
||||
import { completeWorkspace } from "../src/react-build/workspace.js";
|
||||
|
||||
const source = { repository: "https://example.test/prepared", commit: "a".repeat(40) };
|
||||
async function fixture(replacement = "First", change?: (artifact: ReactPreparationArtifact) => void) {
|
||||
const interfaces = new Map<string, InterfaceRevision>();
|
||||
const resource = (text: string) => {
|
||||
const result = compileCapabilityResourceSource(text, {
|
||||
source,
|
||||
environment: { interfaces, interfaceClosure: [...interfaces.values()] },
|
||||
});
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
return result.resource;
|
||||
};
|
||||
for (const text of [
|
||||
'interface Row id "row" revision "row@1" { queryable value title id "title" : string {get id "title:get" execution snapshot targets [browser, server];} queryable value detail id "detail" : string {get id "detail:get" execution snapshot targets [browser, server];} }',
|
||||
'import interface Row; interface Library id "library" revision "library@1" { queryable relation selected id "selected" : optional-one interface Row {resolve id "selected:get" execution snapshot targets [browser, server];} }',
|
||||
'import interface Row; interface RowRole id "row-role" revision "row-role@1" { module render id "row-render" contract "org.quixos.react.subject-only/1" subject interface Row; }',
|
||||
'import interface Library; interface LibraryRole id "library-role" revision "library-role@1" { module render id "library-render" contract "org.quixos.react.subject-only/1" subject interface Library; }',
|
||||
]) {
|
||||
const result = resource(text);
|
||||
assert.equal(result.kind, "interface");
|
||||
if (result.kind === "interface") interfaces.set(result.revision.displayName, result.revision);
|
||||
}
|
||||
const result = resource(
|
||||
'import interface Row; import interface Library; import interface RowRole; import interface LibraryRole; package UI id "ui" revision "ui@1" { module LibraryCard id "library-card" implements LibraryRole.render; module First id "first" implements RowRole.render; module Second id "second" implements RowRole.render; }',
|
||||
);
|
||||
assert.equal(result.kind, "package");
|
||||
if (result.kind !== "package") throw Error("package expected");
|
||||
const pkg: PackageRevision = result.revision;
|
||||
const checked: CheckedQuery[] = [];
|
||||
for (const [name, iface, fields] of [
|
||||
["LibraryData", "library@1", "selected {_camino {ref atom}}"],
|
||||
["Title", "row@1", "title"],
|
||||
["Detail", "row@1", "detail"],
|
||||
]) {
|
||||
for (const local of [false, true]) {
|
||||
const id = name + (local ? "" : "Inputs");
|
||||
checked.push(
|
||||
await compileQuery(
|
||||
{
|
||||
id,
|
||||
displayName: id,
|
||||
root: {
|
||||
kind: "object",
|
||||
interface: [...interfaces.values()].find((entry) => entry.revisionId === iface)!.revisionId,
|
||||
},
|
||||
document: id + ".graphql",
|
||||
operation: id,
|
||||
fragments: [],
|
||||
views: [],
|
||||
allowances: [],
|
||||
budgets: { ...defaultQueryBudgets },
|
||||
watch: local,
|
||||
placement: local ? { kind: "local", sourceQueryId: name + "Inputs" } : { kind: "replica" },
|
||||
},
|
||||
[...interfaces.values()],
|
||||
async () => `query ${id} {root {${fields}}}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
pkg.checkedQueries = checked;
|
||||
pkg.queries = checked.map((query) => query.declaration);
|
||||
const input = (query: string) => ({
|
||||
name: "data",
|
||||
query,
|
||||
root: { from: "input", path: ["subject"] },
|
||||
arguments: { record: {} },
|
||||
});
|
||||
const authored = {
|
||||
modules: {
|
||||
LibraryCard: {
|
||||
inputs: [input("LibraryData")],
|
||||
children: [
|
||||
{
|
||||
name: "selected",
|
||||
subject: "Row",
|
||||
slot: "selected",
|
||||
with: { from: "result", name: "data", path: ["root", "selected"] },
|
||||
root: { from: "item", path: ["_camino", "ref"] },
|
||||
role: { interface: "RowRole", member: "render" },
|
||||
},
|
||||
],
|
||||
},
|
||||
First: { inputs: [input("Title")] },
|
||||
Second: { inputs: [input("Detail")] },
|
||||
},
|
||||
};
|
||||
const declarations = presentationDeclarations(JSON.stringify(authored), pkg, [...interfaces.values()]);
|
||||
change?.(declarations.artifact);
|
||||
pkg.buildArtifacts = { [preparationArtifact]: declarations.artifact };
|
||||
pkg.queryCompositions = declarations.compositions.map((composition) =>
|
||||
compileQueryComposition(composition, new Map(checked.map((query) => [query.declaration.id, query]))),
|
||||
);
|
||||
const workspace = compileCapabilitySource(
|
||||
`workspace Example id "example" revision "example@1" commit "${source.commit}" {
|
||||
import interface Row; import interface Library; import interface RowRole; import interface LibraryRole; import package UI;
|
||||
atom A id "a"; atom B id "b";
|
||||
conform A as Row id "a-row" {private state TitleA id "title-A" on A : string policy register default "title"; private state DetailA id "detail-A" on A : string policy register default "detail"; bind title.get to state TitleA.read; bind detail.get to state DetailA.read;}
|
||||
conform B as Row id "b-row" {private state TitleB id "title-B" on B : string policy register default "title"; private state DetailB id "detail-B" on B : string policy register default "detail"; bind title.get to state TitleB.read; bind detail.get to state DetailB.read;}
|
||||
conform A as RowRole id "a-role" {bind render to UI.${replacement};}
|
||||
conform B as RowRole id "b-role" {bind render to UI.Second;}
|
||||
}`,
|
||||
"workspace.qx",
|
||||
{ interfaces, interfaceClosure: [...interfaces.values()], packages: new Map([["UI", pkg]]) },
|
||||
);
|
||||
assert.ok(workspace.ok, JSON.stringify(workspace.diagnostics));
|
||||
await completeWorkspace(workspace.workspace);
|
||||
return { workspace: workspace.workspace, pkg: workspace.workspace.packageImports[0]!, authored, interfaces };
|
||||
}
|
||||
|
||||
test("workspace expands distinct providers' private inputs without parent field declarations", async () => {
|
||||
const { pkg } = await fixture();
|
||||
const parent = pkg.checkedQueries!.find((query) => query.declaration.id === "react:input:library-card:data")!;
|
||||
assert.match(parent.document, /title/);
|
||||
assert.match(parent.document, /detail/);
|
||||
const plan = pkg.queryCompositions!.find((composition) => composition.id === "react:inputs:library-card")!;
|
||||
assert.equal(plan.steps[0]!.query, parent.declaration.id);
|
||||
assert.equal(Object.keys(parent.fragmentContracts ?? {}).length, 2);
|
||||
const discovery = pkg.queryCompositions!.find(
|
||||
(composition) => composition.id === "react:inputs:library-card:discovery",
|
||||
)!;
|
||||
assert.equal(
|
||||
discovery.steps[0]!.query,
|
||||
"LibraryData",
|
||||
"Cold child data cannot block discovering its selected reference",
|
||||
);
|
||||
});
|
||||
|
||||
test("eager cycles and wrongly typed child bindings fail before runtime", async () => {
|
||||
await assert.rejects(
|
||||
fixture("First", (artifact) => {
|
||||
artifact.modules
|
||||
.find((module) => module.module === "first")!
|
||||
.children.push({
|
||||
name: "again",
|
||||
subject: "row@1",
|
||||
slot: "again",
|
||||
root: { from: "input", path: ["subject"] },
|
||||
role: { interface: "row-role@1", member: "render" },
|
||||
});
|
||||
}),
|
||||
/Eager React preparation cycle/,
|
||||
);
|
||||
await assert.rejects(
|
||||
fixture("First", (artifact) => {
|
||||
artifact.modules.find((module) => module.module === "library-card")!.children[0]!.root = {
|
||||
literal: "not a reference",
|
||||
};
|
||||
}),
|
||||
/child root must be/,
|
||||
);
|
||||
});
|
||||
|
||||
test("workspace replacement changes acquisition requirements without changing the parent", async () => {
|
||||
const { pkg } = await fixture("Second");
|
||||
const parent = pkg.checkedQueries!.find((query) => query.declaration.id === "react:input:library-card:data")!;
|
||||
assert.doesNotMatch(parent.document, /\btitle\b/);
|
||||
assert.match(parent.document, /detail/);
|
||||
assert.equal(Object.keys(parent.fragmentContracts ?? {}).length, 1);
|
||||
});
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
type PackageArtifacts,
|
||||
} from "../src/execution-world/index.js";
|
||||
import { validateWorkspaceRevision } from "../src/capability-model/index.js";
|
||||
import { presentationDeclarations, preparationArtifact } from "../src/react-build/declarations.js";
|
||||
import { compileQueryComposition } from "../src/query/composition.js";
|
||||
|
||||
const source = { repository: "https://example.test/components.git", commit: "a".repeat(40) };
|
||||
const contract = (name: string) => {
|
||||
@@ -58,6 +60,13 @@ const compilePackage = (body?: string) =>
|
||||
const workspace = (presentation = 'presentation Card id "presentation:card" for Data using UI.Card;') => {
|
||||
const pkg = compilePackage();
|
||||
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||
const preparation = presentationDeclarations('{"modules":{"Card":{"inputs":[]}}}', pkg.resource.revision, [
|
||||
...interfaces.values(),
|
||||
]);
|
||||
pkg.resource.revision.buildArtifacts = { [preparationArtifact]: preparation.artifact };
|
||||
pkg.resource.revision.queryCompositions = preparation.compositions.map((entry) =>
|
||||
compileQueryComposition(entry, new Map()),
|
||||
);
|
||||
return compileCapabilitySource(
|
||||
`workspace Example id "workspace:example" revision "workspace:example@1" commit "${source.commit}" {
|
||||
import interface Data; import interface Other; import interface View; import package UI;
|
||||
|
||||
Reference in New Issue
Block a user