2026-09-10 18:27:41 -07:00
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" ;
2026-09-14 20:35:53 -07:00
import { sealMigrations } from "../src/capability-language/migration-seal.js" ;
2026-09-14 23:38:07 -07:00
import { reactPlatformTypes } from "../src/bindings/react-platform.js" ;
2026-09-10 18:27:41 -07:00
const execFile = promisify ( callback );
2026-09-13 22:07:18 -07:00
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 ));
2026-09-14 20:35:53 -07:00
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/ );
2026-09-14 23:38:07 -07:00
assert . equal ( await fs . readFile ( path . join ( root , "src/gen/web-studio-react-runtime.d.ts" ), "utf8" ), reactPlatformTypes );
2026-09-14 20:35:53 -07:00
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" )));
2026-09-13 22:07:18 -07:00
assert . equal ( JSON . parse ( await fs . readFile ( path . join ( root , "tsconfig.json" ), "utf8" )). compilerOptions . jsx , "react-jsx" );
});
2026-09-14 20:35:53 -07:00
test ( "imperative scaffolds preserve authored wiring; migration sealing is explicit and separate" , async ( context ) => {
2026-09-10 18:27:41 -07:00
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" );
2026-09-13 22:07:18 -07:00
const edited = 'import type {Implementation} from "../gen/qx.js";\nexport const handler: Implementation["play"] = async () => null;\n' ;
2026-09-10 18:27:41 -07:00
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" );
2026-09-14 20:35:53 -07:00
await sealMigrations ( path . join ( root , base . directory ));
2026-09-10 18:27:41 -07:00
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/ );
2026-09-14 15:24:37 -07:00
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' ));
2026-09-14 20:35:53 -07:00
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" )));
2026-09-14 15:24:37 -07:00
assert . equal ( await fs . readFile ( filename , "utf8" ), edited );
2026-09-14 20:35:53 -07:00
await planStructure ( root , { kind : "package" , source , resourceRoot : base.directory , validation : "syntax" , files : [{
2026-09-14 10:26:11 -07:00
file : ` ${ base . directory } /package.qx` , edits : [{ operation : "replace" , target : { kind : "packageResourceDecl" , id : "package:chess" },
source : 'package Other id "package:other" revision "package:other@1" {}' }],
2026-09-14 20:35:53 -07:00
}]});
2026-09-10 18:27:41 -07:00
});