34 lines
6.7 KiB
JavaScript
34 lines
6.7 KiB
JavaScript
import {test} from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {createRendererCatalog,builtinRendererDefinitions} from '../../src/lib/canvas-bindings/renderers.ts';
|
|
import {createReactRenderers} from '../../src/lib/canvas-bindings/react-renderers.ts';
|
|
import {createDocument,createRegistry,builtinComponents,defineComponent,movable,createEditor} from '../../src/lib/canvas-core/index.ts';
|
|
import {projectCanvas} from '../../src/lib/canvas-bindings/projection.ts';
|
|
const descriptor={componentId:'plugin:meter',dataVersion:1,structure:'node',rendererId:'meter'};
|
|
const definition=defineComponent({id:'plugin:meter',dataVersion:1,structure:'node',validate:data=>typeof data.metric==='number',features:[movable()]}),registry=createRegistry([...builtinComponents(),definition]);
|
|
const node={id:'meter',incarnation:1,ownerCanvasId:'root',structure:'node',componentId:'plugin:meter',dataVersion:1,data:{metric:7,title:{opaque:true},__headlessRecord:{untrusted:true}},geometry:{x:0,y:0,width:200,height:100,rotation:0}};
|
|
test('renderer catalog captures exact version/structure mappings without React values or mutable descriptors',()=>{
|
|
const input={...descriptor,render(){}};const catalog=createRendererCatalog([input]);input.rendererId='changed';assert.equal(catalog.resolve(node).rendererId,'meter');assert.equal(catalog.resolve({...node,dataVersion:2}),undefined);assert.equal(catalog.resolve({...node,structure:'connection'}),undefined);assert.equal(catalog.definitions[0].render,undefined);assert.ok(Object.isFrozen(catalog.definitions[0]));assert.throws(()=>createRendererCatalog([descriptor,descriptor]),/duplicate_renderer_definition/);assert.throws(()=>createRendererCatalog([{...descriptor,rendererId:'unsupported'}]),/invalid_renderer_definition/);
|
|
});
|
|
test('React assembly preserves aliases only for the same component and rejects invalid renderers or model contracts',()=>{
|
|
const render=()=>null,assembly=createReactRenderers([{...descriptor,render}],registry);assert.equal(assembly.nodeTypes.meter.type,render);assert.ok(Object.isFrozen(assembly.nodeTypes));assert.equal(assembly.edgeTypes.meter,undefined);
|
|
assert.throws(()=>createReactRenderers([{...descriptor,render:null}]),/invalid_react_renderer/);assert.throws(()=>createReactRenderers([{...descriptor,render},{...descriptor,componentId:'other',render:()=>null}]),/renderer_id_conflict/);assert.throws(()=>createReactRenderers([{...descriptor,dataVersion:2,render}],registry),/renderer_model_mismatch/);
|
|
let reads=0;const getter=createReactRenderers([{...descriptor,get render(){reads++;return render;}}]);assert.equal(reads,1);assert.equal(getter.definitions[0].render,render);assert.equal(getter.nodeTypes.meter,assembly.nodeTypes.meter);
|
|
const alias=createReactRenderers([{...descriptor,render},{...descriptor,componentId:'other',render}]);assert.equal(alias.nodeTypes.meter,assembly.nodeTypes.meter);const memo={$$typeof:Symbol.for('react.memo'),type:render};assert.equal(createReactRenderers([{...descriptor,render:memo}]).nodeTypes.meter,memo);
|
|
});
|
|
test('installed node and edge renderers receive original records and transforms while absent schemas still fall back',()=>{
|
|
const customEdge=defineComponent({id:'plugin:edge',dataVersion:1,structure:'connection',validate:()=>true,features:[]}),r=createRegistry([...builtinComponents(),definition,customEdge]),other={...node,id:'second',geometry:{...node.geometry,x:300}},edge={id:'edge',incarnation:1,structure:'connection',componentId:'plugin:edge',dataVersion:1,ownerCanvasId:'root',source:{elementId:'meter',portId:'custom-out'},target:{elementId:'second',portId:'custom-in'},path:{type:'custom',retained:true},data:{path:{own:true},metric:99}},source=createDocument('project',[{id:'root',parent:null}],[node,other,edge]),catalog=createRendererCatalog([...builtinRendererDefinitions,descriptor,{...descriptor,componentId:'plugin:edge',structure:'connection',rendererId:'meter-edge'}]),p=projectCanvas(source,'root',r,new Set(),()=>undefined,null,catalog);
|
|
assert.equal(p.nodes[0].type,'meter');assert.equal(p.nodes[0].data.__headlessFallback,undefined);assert.equal(p.nodes[0].data.__headlessRecord,source.elements.get('meter'));assert.deepEqual(p.nodes[0].data.__headlessRecord.data.title,{opaque:true});assert.equal(p.nodes[0].data.__headlessTransform.scale,1);assert.equal(p.edges[0].type,'meter-edge');assert.equal(p.edges[0].data.__headlessRecord,source.elements.get('edge'));assert.deepEqual(p.edges[0].data.__headlessRecord.path,{type:'custom',retained:true});
|
|
const missing=projectCanvas(source,'root',r,new Set(),()=>undefined);assert.equal(missing.nodes[0].type,'unsupported');assert.equal(missing.edges[0].type,'unsupported');const future=createDocument('project',[{id:'root',parent:null}],[{...node,dataVersion:2}]);assert.equal(projectCanvas(future,'root',r,new Set(),()=>undefined,null,catalog).nodes[0].type,'unsupported');
|
|
});
|
|
test('pre-engine preview renderers are explicit and captured independently from engine-bound renderers',()=>{
|
|
const render=()=>null,preview=()=>null,assembly=createReactRenderers([{...descriptor,render,preview}]);assert.equal(assembly.nodeTypes.meter.type,render);assert.equal(assembly.previewNodeTypes.meter.type,preview);assert.equal(assembly.definitions[0].preview,preview);assert.ok(Object.isFrozen(assembly.previewNodeTypes));assert.equal(createReactRenderers([{...descriptor,render}]).previewNodeTypes.meter,undefined);assert.throws(()=>createReactRenderers([{...descriptor,render,preview:0}]),/invalid_react_preview/);assert.throws(()=>createReactRenderers([{...descriptor,render,preview},{...descriptor,componentId:'other',render}]),/renderer_id_conflict/);
|
|
});
|
|
|
|
test('independent host renderers for one component share model data and capability behavior',()=>{
|
|
const editor=createEditor({registry,document:createDocument('project',[{id:'root',parent:null}],[node])}),one=createReactRenderers([{...descriptor,rendererId:'host-one',render:()=>null}],registry),two=createReactRenderers([{...descriptor,rendererId:'host-two',render:()=>null}],registry);
|
|
assert.equal(editor.dispatch({type:'element.geometry',target:{id:node.id,incarnation:node.incarnation},patch:{x:42}}).status,'applied');
|
|
const document=editor.getSnapshot().document,a=projectCanvas(document,'root',registry,new Set(),()=>undefined,null,one.catalog),b=projectCanvas(document,'root',registry,new Set(),()=>undefined,null,two.catalog);
|
|
assert.equal(a.nodes[0].type,'host-one');assert.equal(b.nodes[0].type,'host-two');assert.strictEqual(a.nodes[0].data.__headlessRecord,document.elements.get(node.id));assert.strictEqual(b.nodes[0].data.__headlessRecord,a.nodes[0].data.__headlessRecord);assert.equal(a.nodes[0].position.x,42);assert.deepEqual(b.nodes[0].position,a.nodes[0].position);assert.equal(editor.getSnapshot().history.undo.length,1);
|
|
});
|