Files
quixos-protocol/test/variants.test.ts

83 lines
3.4 KiB
TypeScript

import { readFileSync } from "node:fs";
import assert from "node:assert/strict";
import { test } from "node:test";
import { compileCapabilityResourceSource } from "../src/capability-language/index.js";
import { compileQuery } from "../src/query/compile.js";
import { defaultQueryBudgets } from "../src/query/types.js";
import { portableValueSchema, tsType } from "../src/bindings/portable.js";
import { isStorableType, TypeSubstitution } from "../src/capability-model/index.js";
const source = { repository: "https://example.test/variants.git", commit: "a".repeat(40) };
const declaration = readFileSync("test/fixtures/canvas-content.qx", "utf8");
const result = compileCapabilityResourceSource(declaration, { source });
assert.ok(result.ok, JSON.stringify(result.diagnostics));
if (result.resource.kind !== "interface") throw Error("Expected interface");
const iface = result.resource.revision;
const entries = iface.members[0];
if (entries.kind !== "value") throw Error("Expected value");
test("native variant aliases lower into nominal cases with typed payloads", () => {
assert.equal(entries.valueType.kind, "list");
assert.ok(isStorableType(entries.valueType));
const schema = portableValueSchema(entries.valueType) as any;
assert.equal(schema.value.id, "canvas:entry@1");
assert.equal(schema.value.cases.stroke.id, "canvas:stroke");
assert.match(tsType(entries.valueType), /kind: "stroke"/);
assert.match(tsType(entries.valueType), /ReadonlyArray<.*"x": number/);
const substitution = new TypeSubstitution({
arguments: new Map([["T", { kind: "value", type: { kind: "scalar", name: "string" } }]]),
applyInterface: () => {
throw Error("unused");
},
implementsInterface: () => false,
});
assert.deepEqual(
substitution.value({
kind: "variant",
id: "box",
cases: { some: { id: "box:some", value: { kind: "parameter", parameterId: "T" } } },
}),
{ kind: "variant", id: "box", cases: { some: { id: "box:some", value: { kind: "scalar", name: "string" } } } },
);
});
test("variant query scalars keep their checked payload type and reject branch selection", async () => {
const query = {
id: "canvas-query",
displayName: "Canvas",
root: { kind: "object" as const, interface: iface.revisionId },
document: "canvas.graphql",
operation: "Canvas",
fragments: [],
views: [],
allowances: [],
budgets: { ...defaultQueryBudgets },
watch: true,
};
const checked = await compileQuery(query, [iface], async () => `query Canvas { root { entries } }`);
assert.equal(checked.output.kind, "record");
if (checked.output.kind !== "record") return;
const root = checked.output.fields.root;
assert.equal(root.kind, "record");
if (root.kind !== "record") return;
assert.deepEqual(root.fields.entries, entries.valueType);
await assert.rejects(compileQuery(query, [iface], async () => `query Canvas { root { entries { kind value } } }`));
});
test("unknown and duplicate cases fail; reference payloads remain nonstorable", () => {
const duplicate = compileCapabilityResourceSource(declaration.replace('"canvas:image"', '"canvas:stroke"'), {
source,
});
assert.equal(duplicate.ok, false);
assert.ok(
!isStorableType({
kind: "variant",
id: "transport",
cases: {
ref: {
id: "ref",
value: { kind: "object-ref", expectation: { kind: "interface", interfaceRevisionId: iface.revisionId } },
},
},
}),
);
});