Files
quixos-protocol/test/scaffold-recipes.test.ts
T
Timothy J. Aveni a174faea5c Add imperative feature bundles, intrinsic component sizing, and authoring timing logs
Generate state-backed atoms, interfaces, relationships and React/CSS packages
in a resumable command that prints the full source diff. Keep scaffold output
ordinary editable code. Shorten installed authoring guides while preserving
contracts and clarify historical design context.

Test generated bundles through immutable Nix verification and cover browser
isolation, nested sizing, interruption recovery, and command help.
2026-09-15 14:20:02 -07:00

68 lines
5.4 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import {execFile as callback} from "node:child_process";
import {promisify} from "node:util";
import {scaffoldRecipe} from "../src/capability-language/scaffold-recipes.js";
import {planStructure, applyStructure} from "../src/capability-language/structural-plan.js";
import {contentDigest} from "../src/capability-model/evolution.js";
import {sealMigrations} from "../src/capability-language/migration-seal.js";
import {reactPlatformTypes} from "../src/bindings/react-platform.js";
const execFile = promisify(callback);
test("React preset applies its browser build script and shared-platform imports", async (context) => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-react-recipe-"));
context.after(() => fs.rm(root, {recursive: true, force: true}));
await execFile("git", ["-C", root, "init"]);
const source = {repository: "https://example.test/react.git", commit: "a".repeat(40)};
const request = await scaffoldRecipe(root, "package", {source, name: "React", id: "package:react", revision: "package:react@1", template: "typescript-react", tools: {quixos: source, protocol: source, helpers: source, sdk: source}});
await applyStructure(await planStructure(root, request));
assert.match(await fs.readFile(path.join(root, "src/impl/sourceGet.ts"), "utf8"), /component.js\?browser-source/);
assert.match(await fs.readFile(path.join(root, "flake.nix"), "utf8"), /browserSources = true/);
assert.equal(await fs.readFile(path.join(root, "src/gen/web-studio-react-runtime.d.ts"), "utf8"), reactPlatformTypes);
assert.match(await fs.readFile(path.join(root, "src/browser-assets.d.ts"), "utf8"), /declare module "\*\.css"/);
assert.equal(JSON.parse(await fs.readFile(path.join(root, "quixos.check.json"), "utf8")).options.messages["org.quixos.web-studio.ReactProps"].export, "opaqueReactPropsBinding");
await assert.rejects(fs.access(path.join(root, "quixos.scaffold.json")));
assert.equal(JSON.parse(await fs.readFile(path.join(root, "tsconfig.json"), "utf8")).compilerOptions.jsx, "react-jsx");
});
test("imperative scaffolds preserve authored wiring; migration sealing is explicit and separate", async (context) => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-recipes-"));
context.after(() => fs.rm(root, {recursive: true, force: true}));
await execFile("git", ["-C", root, "init"]);
await fs.writeFile(path.join(root, ".gitignore"), ".quixos/\n");
const source = {repository: "https://example.test/chess.git", commit: "a".repeat(40)};
const base = {source, directory: "packages/Chess"};
const apply = async (command: Parameters<typeof scaffoldRecipe>[1], input: Parameters<typeof scaffoldRecipe>[2]) => applyStructure(await planStructure(root, await scaffoldRecipe(root, command, input)));
await apply("package", {...base, name: "Chess", id: "package:chess", revision: "package:chess@1", tools: {quixos: source, protocol: source, helpers: source, sdk: source}});
await apply("function", {...base, name: "play", id: "export:play"});
const filename = path.join(root, base.directory, "src/impl/play.ts");
const edited = 'import type {Implementation} from "../gen/qx.js";\nexport const handler: Implementation["play"] = async () => null;\n';
await fs.writeFile(filename, edited);
const old = {version: 1}, next = {version: 2}, from = contentDigest(old), to = contentDigest(next);
await apply("migration", {...base, name: "upgrade", id: "export:upgrade", migration: {id: "upgrade-v2", scopeId: "board", from, to, predecessors: [], ports: [], contracts: {[from]: old, [to]: next}}});
const migrationFile = path.join(root, base.directory, "src/migrations/upgrade.ts");
await fs.appendFile(migrationFile, "\n// authored migration change\n");
await sealMigrations(path.join(root, base.directory));
assert.equal(await fs.readFile(filename, "utf8"), edited);
const catalog = JSON.parse(await fs.readFile(path.join(root, base.directory, "quixos.migrations.json"), "utf8"));
assert.equal(catalog.migrations[0].implementation.digest, contentDigest(await fs.readFile(migrationFile, "utf8")));
assert.match(await fs.readFile(path.join(root, base.directory, "src/migrate.ts"), "utf8"), /export:upgrade/);
await assert.rejects(() => apply("function", {...base, name: "play", id: "export:play"}), /unique/);
const declarations = path.join(root, base.directory, "package.qx");
await fs.writeFile(declarations, (await fs.readFile(declarations, "utf8")).replace(/}\s*$/, ' function authored id "export:authored" : unit -> unit;\n}\n'));
const server = path.join(root, base.directory, "src/server.ts");
await fs.appendFile(server, "\n// authored comment must survive\n");
await apply("function", {...base, name: "another", id: "export:another"});
assert.match(await fs.readFile(server, "utf8"), /authored comment must survive/);
await assert.rejects(() => apply("refresh", base), /removed/);
await assert.rejects(fs.access(path.join(root, base.directory, "src/impl/authored.ts")));
assert.equal(await fs.readFile(filename, "utf8"), edited);
await planStructure(root, {kind: "package", source, resourceRoot: base.directory, validation: "syntax", files: [{
file: `${base.directory}/package.qx`, edits: [{operation: "replace", target: {kind: "packageResourceDecl", id: "package:chess"},
source: 'package Other id "package:other" revision "package:other@1" {}'}],
}]});
});