Implement typed module contracts and conformance bindings
This commit is contained in:
+31
-11
@@ -39,8 +39,17 @@ const interfaces = new Map([
|
||||
["Data", data],
|
||||
["Other", other],
|
||||
]);
|
||||
const componentSource = (body = 'component Card id "component:card" subject Data runtime react;') =>
|
||||
`import interface Data; package UI id "package:ui" revision "package:ui@1" { ${body} }`;
|
||||
const role = compileCapabilityResourceSource(
|
||||
`import interface Data;
|
||||
interface View id "interface:view" revision "interface:view@1" {
|
||||
module render id "member:view:render" contract "org.quixos.react.subject-only/1" subject interface Data;
|
||||
}`,
|
||||
{ source, environment: { interfaces, interfaceClosure: [...interfaces.values()] } },
|
||||
);
|
||||
assert.ok(role.ok && role.resource.kind === "interface", JSON.stringify(role.diagnostics));
|
||||
interfaces.set("View", role.resource.revision);
|
||||
const componentSource = (body = 'module Card id "component:card" implements View.render;') =>
|
||||
`import interface Data; import interface View; package UI id "package:ui" revision "package:ui@1" { ${body} }`;
|
||||
const compilePackage = (body?: string) =>
|
||||
compileCapabilityResourceSource(componentSource(body), {
|
||||
source,
|
||||
@@ -51,7 +60,7 @@ const workspace = (presentation = 'presentation Card id "presentation:card" for
|
||||
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||
return compileCapabilitySource(
|
||||
`workspace Example id "workspace:example" revision "workspace:example@1" commit "${source.commit}" {
|
||||
import interface Data; import interface Other; import package UI;
|
||||
import interface Data; import interface Other; import interface View; import package UI;
|
||||
atom One id "atom:one"; atom Two id "atom:two";
|
||||
conform One as Data id "conformance:one:data" { }
|
||||
conform Two as Data id "conformance:two:data" { }
|
||||
@@ -88,21 +97,21 @@ test("component declarations survive formatting and syntax inspection", () => {
|
||||
const text = formatQx(componentSource());
|
||||
const parsed = parseQx(text);
|
||||
assert.deepEqual(parsed.diagnostics, []);
|
||||
assert.ok([...walkSyntax(parsed.root)].some((node) => node.kind === "componentDecl"));
|
||||
assert.ok([...walkSyntax(parsed.root)].some((node) => node.kind === "moduleExport"));
|
||||
assert.equal(formatQx(text), text);
|
||||
});
|
||||
|
||||
test("component and presentation contracts reject wrong subjects, duplicates and unavailable runtimes", () => {
|
||||
for (const [input, message] of [
|
||||
['component Card id "component:card" subject Missing runtime react;', /Missing/],
|
||||
['component Card id "component:card" subject Data runtime arduino;', /Unsupported component runtime/],
|
||||
['module Card id "component:card" implements Missing.render;', /Missing/],
|
||||
['module Card id "component:card" implements Data.missing;', /module member/],
|
||||
[
|
||||
'component Card id "component:card" subject Data runtime react; component Other id "component:card" subject Data runtime react;',
|
||||
/Duplicate component/,
|
||||
'module Card id "component:card" implements View.render; module Other id "component:card" implements View.render;',
|
||||
/Duplicate or empty module/,
|
||||
],
|
||||
[
|
||||
'component Card id "component:card" subject Data runtime react; component Card id "component:other" subject Data runtime react;',
|
||||
/Duplicate component/,
|
||||
'module Card id "component:card" implements View.render; module Card id "component:other" implements View.render;',
|
||||
/Duplicate or empty module/,
|
||||
],
|
||||
] as const) {
|
||||
const result = compilePackage(input);
|
||||
@@ -131,6 +140,17 @@ test("model validation rechecks presentation metadata instead of trusting the so
|
||||
assert.throws(() => compileExecutionPlan(result.workspace), /no installed package component/);
|
||||
});
|
||||
|
||||
test("standalone presentation rejects a general component contract that may require extra props", () => {
|
||||
const result = workspace();
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
const candidate = structuredClone(result.workspace);
|
||||
const member = candidate.interfaceImports.find((iface) => iface.revisionId === "interface:view@1")!.members[0]!;
|
||||
assert.equal(member.kind, "module");
|
||||
if (member.kind !== "module") throw Error("Expected module");
|
||||
member.contract = "org.quixos.react.component/1";
|
||||
assert.ok(validateWorkspaceRevision(candidate).some((issue) => /subject-only/.test(issue.message)));
|
||||
});
|
||||
|
||||
test("checked component-only artifacts install without a service and reject missing or substituted entries", async () => {
|
||||
const result = workspace();
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
@@ -150,7 +170,7 @@ test("checked component-only artifacts install without a service and reject miss
|
||||
const body: Omit<ComponentArtifact, "contentDigest"> = {
|
||||
id: "component:card",
|
||||
subject: "interface:Data@1",
|
||||
compatibility: "quixos-react-component-v1",
|
||||
compatibility: "org.quixos.react.esm/1",
|
||||
path: paths[0]!,
|
||||
exportName: "Card",
|
||||
files,
|
||||
|
||||
Reference in New Issue
Block a user