Basic element added: anchor and port
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const canvasHtml = fs.readFileSync(path.join(root, 'frontend', 'canvas.html'), 'utf8');
|
||||
|
||||
assert(
|
||||
canvasHtml.includes('buildInstancesYaml'),
|
||||
'canvas.html should use buildInstancesYaml for layout instance export'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('buildCanvasPortsYaml(activePage.nodes)'),
|
||||
'canvas.html should export ports from active canvas port nodes'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('buildPageComponentPorts(page.port, page.nodes)'),
|
||||
'canvas library entries should expose ports from their page-port data'
|
||||
);
|
||||
assert(
|
||||
!canvasHtml.includes("activePage.nodes.filter(n => n.type === 'rotatableNode' && n.data?.type === 'composite')"),
|
||||
'project layout export should not filter out regular PDK instances'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('Elements: {'),
|
||||
'library tree should add an Elements folder'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes("__name__: 'Port'") && canvasHtml.includes("__name__: 'Anchor'"),
|
||||
'Elements folder should expose Port and Anchor as separate virtual components'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('const isElementComponentGrid = isComponentGrid && entries.every(([, childData]) => childData.__element__ === true);'),
|
||||
'Elements folder should bypass category-card grouping and render separate virtual component leaves'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('element-card-icon port-icon') && canvasHtml.includes('element-card-icon anchor-icon'),
|
||||
'virtual element cards should render distinct generated icons for Port and Anchor'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('const selectedIsVirtualElement = selectedNode?.data?.elementType ==='),
|
||||
'right inspector should classify virtual elements separately from PDK/forge components'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('const canChooseComponent = !selectedIsVirtualElement && availableComponentsFromNode.length > 0;'),
|
||||
'virtual elements should not show PDK or generate_with_forge component selection'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('buildElementsYaml(activePage.nodes)'),
|
||||
'canvas layout export should include an elements section'
|
||||
);
|
||||
assert(
|
||||
!canvasHtml.includes("activePage.nodes.filter(n => n.selected && n.id !== 'page-port')"),
|
||||
'copy/delete should not exclude port nodes'
|
||||
);
|
||||
@@ -0,0 +1,147 @@
|
||||
const assert = require('assert');
|
||||
const helpers = require('../frontend/canvas-helpers.js');
|
||||
|
||||
const handles = helpers.buildPortHandles({
|
||||
a0: { x: 0, y: 0, a: 180 },
|
||||
b0: { x: 0, y: 0, a: 0 },
|
||||
a1: { x: -91.7, y: 4.475, a: 180 },
|
||||
a2: { x: -91.7, y: -4.475, a: 180 },
|
||||
b1: { x: 91.7, y: 4.475, a: 0 },
|
||||
b2: { x: 91.7, y: -4.475, a: 0 },
|
||||
ep2a: { x: -37.8, y: -20, a: 270 },
|
||||
ep2b: { x: -37.8, y: 20, a: 90 },
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(handles.map(handle => handle.name), ['a1', 'a2', 'b1', 'b2', 'ep2b', 'ep2a']);
|
||||
assert.deepStrictEqual(handles.filter(handle => handle.position === 'left').map(handle => handle.name), ['a1', 'a2']);
|
||||
assert.deepStrictEqual(handles.filter(handle => handle.position === 'right').map(handle => handle.name), ['b1', 'b2']);
|
||||
assert.deepStrictEqual(handles.find(handle => handle.name === 'ep2b').position, 'top');
|
||||
assert.deepStrictEqual(handles.find(handle => handle.name === 'ep2a').position, 'bottom');
|
||||
assert.strictEqual(handles.find(handle => handle.name === 'a1').style.top, '15%');
|
||||
assert.strictEqual(handles.find(handle => handle.name === 'a2').style.top, '85%');
|
||||
assert.strictEqual(handles.find(handle => handle.name === 'ep2b').style.left, '50%');
|
||||
|
||||
const args = helpers.createForgeArguments();
|
||||
assert(Object.keys(args).length >= 10);
|
||||
assert.strictEqual(helpers.isForgeComponent('generate with mxpic_forge'), true);
|
||||
|
||||
const yaml = helpers.buildInstanceYaml({
|
||||
instanceName: 'component_1',
|
||||
componentName: 'generate with mxpic_forge',
|
||||
componentPath: 'ignored/path',
|
||||
position: { x: 12.34, y: -5 },
|
||||
rotation: 90,
|
||||
forgeArguments: { function_name: 'mmi1x2', length: 25.5, include_heater: true }
|
||||
});
|
||||
|
||||
assert(yaml.includes('component: generate_with_forge'));
|
||||
assert(yaml.includes('function_name: "mmi1x2"'));
|
||||
assert(yaml.includes('length: 25.5'));
|
||||
assert(yaml.includes('include_heater: true'));
|
||||
|
||||
const projectInstancesYaml = helpers.buildInstancesYaml({
|
||||
nodes: [
|
||||
{
|
||||
id: 'node-1',
|
||||
type: 'rotatableNode',
|
||||
position: { x: 10, y: 20 },
|
||||
data: {
|
||||
componentDisplayName: 'component_1',
|
||||
componentName: 'PDK_A',
|
||||
rotation: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'node-2',
|
||||
type: 'rotatableNode',
|
||||
position: { x: 30, y: 40 },
|
||||
data: {
|
||||
componentDisplayName: 'cell_1',
|
||||
componentName: 'canvas_1',
|
||||
type: 'composite',
|
||||
rotation: 90
|
||||
}
|
||||
}
|
||||
],
|
||||
resolveComponentPath: name => name === 'PDK_A' ? 'foundry/path/PDK_A' : name
|
||||
});
|
||||
|
||||
assert(projectInstancesYaml.includes('component_1:'));
|
||||
assert(projectInstancesYaml.includes('component: foundry/path/PDK_A'));
|
||||
assert(projectInstancesYaml.includes('cell_1:'));
|
||||
assert(projectInstancesYaml.includes('component: canvas_1'));
|
||||
|
||||
const pagePortsYaml = helpers.buildPortsYaml({ x: 50, y: 150, a: 90 });
|
||||
assert(pagePortsYaml.includes('- name: port'));
|
||||
assert(pagePortsYaml.includes('x: 50.0'));
|
||||
assert(pagePortsYaml.includes('y: 150.0'));
|
||||
assert(pagePortsYaml.includes('angle: 90.0'));
|
||||
|
||||
const componentPorts = helpers.buildPageComponentPorts({ x: 12, y: -6, a: 180 });
|
||||
assert.deepStrictEqual(componentPorts, {
|
||||
port: { x: 12, y: -6, a: 180, width: 0.5 }
|
||||
});
|
||||
|
||||
const elementNodes = [
|
||||
{
|
||||
id: 'port-1',
|
||||
type: 'portNode',
|
||||
position: { x: 10, y: 20 },
|
||||
data: {
|
||||
componentDisplayName: 'in0',
|
||||
elementType: 'port',
|
||||
angle: 180,
|
||||
width: 0.7,
|
||||
layer: 'WG_CORE',
|
||||
description: 'input port'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'anchor-1',
|
||||
type: 'rotatableNode',
|
||||
position: { x: 30, y: 40 },
|
||||
data: {
|
||||
componentDisplayName: 'anchor_1',
|
||||
componentName: 'Anchor',
|
||||
elementType: 'anchor',
|
||||
rotation: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'mmi-1',
|
||||
type: 'rotatableNode',
|
||||
position: { x: 50, y: 60 },
|
||||
data: {
|
||||
componentDisplayName: 'component_1',
|
||||
componentName: 'MMI',
|
||||
rotation: 0
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
assert.deepStrictEqual(helpers.buildElementPorts('port', { angle: 90, width: 0.8 }), {
|
||||
port: { x: 0, y: 0, a: 90, width: 0.8 }
|
||||
});
|
||||
assert.deepStrictEqual(Object.keys(helpers.buildElementPorts('anchor')), ['left', 'right']);
|
||||
|
||||
const canvasPortsYaml = helpers.buildCanvasPortsYaml(elementNodes);
|
||||
assert(canvasPortsYaml.includes('name: in0'));
|
||||
assert(canvasPortsYaml.includes('description: "input port"'));
|
||||
assert(canvasPortsYaml.includes('width: 0.7'));
|
||||
|
||||
const elementsYaml = helpers.buildElementsYaml(elementNodes);
|
||||
assert(elementsYaml.includes('in0:'));
|
||||
assert(elementsYaml.includes('type: port'));
|
||||
assert(elementsYaml.includes('anchor_1:'));
|
||||
assert(elementsYaml.includes('type: anchor'));
|
||||
|
||||
const instancesWithoutElements = helpers.buildInstancesYaml({
|
||||
nodes: elementNodes,
|
||||
resolveComponentPath: name => name
|
||||
});
|
||||
assert(!instancesWithoutElements.includes('anchor_1:'));
|
||||
assert(!instancesWithoutElements.includes('in0:'));
|
||||
assert(instancesWithoutElements.includes('component_1:'));
|
||||
|
||||
const multiPortComponentPorts = helpers.buildPageComponentPorts(null, elementNodes);
|
||||
assert.deepStrictEqual(multiPortComponentPorts.in0, { x: 10, y: 20, a: 180, width: 0.7 });
|
||||
@@ -0,0 +1,29 @@
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const canvasHtml = fs.readFileSync(path.join(root, 'frontend', 'canvas.html'), 'utf8');
|
||||
const serverPy = fs.readFileSync(path.join(root, 'backend', 'server.py'), 'utf8');
|
||||
|
||||
assert(
|
||||
canvasHtml.includes('src="/canvas-helpers.js"'),
|
||||
'canvas.html should request the canvas helper script from /canvas-helpers.js'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes("@app.route('/canvas-helpers.js')"),
|
||||
'backend/server.py should serve /canvas-helpers.js'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes("send_from_directory(FRONTEND_DIR, 'canvas-helpers.js')") ||
|
||||
serverPy.includes('send_from_directory(FRONTEND_DIR, "canvas-helpers.js")'),
|
||||
'the /canvas-helpers.js route should serve frontend/canvas-helpers.js'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes('def no_cache_response(response):'),
|
||||
'canvas assets should use an explicit no-cache response helper'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes('Cache-Control') && serverPy.includes('no-store'),
|
||||
'canvas routes should prevent stale browser caches while the editor is changing'
|
||||
);
|
||||
Reference in New Issue
Block a user