293 lines
7.5 KiB
JavaScript
293 lines
7.5 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import {
|
|
buildNodeClipboardPayload,
|
|
buildNodeClipboardPayloadForIds,
|
|
instantiateNodeClipboardPayload,
|
|
NODE_CLIPBOARD_KIND,
|
|
parseNodeClipboardPayload,
|
|
} from '../src/nodeClipboard.js';
|
|
|
|
test('buildNodeClipboardPayload keeps only selected nodes and internal edges', () => {
|
|
const nodes = [
|
|
{
|
|
id: '1',
|
|
selected: true,
|
|
type: 'custom',
|
|
position: { x: 10, y: 20 },
|
|
data: {
|
|
label: 'Image',
|
|
className: 'Image',
|
|
widgetValues: { filename: 'scan.ibw' },
|
|
runtimeValues: { layerIndex: 2 },
|
|
},
|
|
},
|
|
{
|
|
id: '2',
|
|
selected: true,
|
|
position: { x: 100, y: 200 },
|
|
data: {
|
|
label: 'Preview',
|
|
className: 'Preview',
|
|
widgetValues: { mode: 'auto' },
|
|
},
|
|
},
|
|
{
|
|
id: '3',
|
|
selected: false,
|
|
position: { x: 500, y: 600 },
|
|
data: {
|
|
label: 'Save',
|
|
className: 'Save',
|
|
},
|
|
},
|
|
];
|
|
|
|
const edges = [
|
|
{
|
|
id: 'e1-2',
|
|
source: '1',
|
|
sourceHandle: 'output::0::DATA_FIELD',
|
|
target: '2',
|
|
targetHandle: 'input::field::DATA_FIELD',
|
|
style: { stroke: '#fff', strokeWidth: 2 },
|
|
},
|
|
{
|
|
id: 'e2-3',
|
|
source: '2',
|
|
sourceHandle: 'output::0::IMAGE',
|
|
target: '3',
|
|
targetHandle: 'input::value::SAVE_VALUE',
|
|
},
|
|
];
|
|
|
|
const payload = buildNodeClipboardPayload(nodes, edges);
|
|
|
|
assert.equal(payload.kind, NODE_CLIPBOARD_KIND);
|
|
assert.equal(payload.nodes.length, 2);
|
|
assert.deepEqual(payload.nodes.map((node) => node.id), ['1', '2']);
|
|
assert.equal(payload.edges.length, 1);
|
|
assert.deepEqual(payload.edges[0], {
|
|
source: '1',
|
|
sourceHandle: 'output::0::DATA_FIELD',
|
|
target: '2',
|
|
targetHandle: 'input::field::DATA_FIELD',
|
|
style: { stroke: '#fff', strokeWidth: 2 },
|
|
});
|
|
|
|
const reparsed = parseNodeClipboardPayload(JSON.stringify(payload));
|
|
assert.deepEqual(reparsed, payload);
|
|
});
|
|
|
|
test('instantiateNodeClipboardPayload remaps ids, offsets positions, and hydrates node shells', () => {
|
|
const payload = {
|
|
kind: NODE_CLIPBOARD_KIND,
|
|
version: 1,
|
|
nodes: [
|
|
{
|
|
id: '1',
|
|
position: { x: 10, y: 20 },
|
|
data: {
|
|
label: 'Image',
|
|
className: 'Image',
|
|
widgetValues: { filename: 'scan.ibw', colormap: 'viridis' },
|
|
runtimeValues: { layerIndex: 1 },
|
|
},
|
|
},
|
|
{
|
|
id: '2',
|
|
position: { x: 100, y: 200 },
|
|
data: {
|
|
label: 'Preview',
|
|
className: 'Preview',
|
|
widgetValues: { colormap: 'gray' },
|
|
},
|
|
},
|
|
],
|
|
edges: [
|
|
{
|
|
source: '1',
|
|
sourceHandle: 'output::0::DATA_FIELD',
|
|
target: '2',
|
|
targetHandle: 'input::field::DATA_FIELD',
|
|
style: { stroke: '#abc', strokeWidth: 2 },
|
|
},
|
|
],
|
|
};
|
|
|
|
const defs = {
|
|
Image: { output: ['DATA_FIELD'], output_name: ['field'] },
|
|
Preview: { output: ['IMAGE'], output_name: ['preview'] },
|
|
};
|
|
|
|
const instantiated = instantiateNodeClipboardPayload(payload, defs, 12, { x: 32, y: 48 });
|
|
|
|
assert.equal(instantiated.nextNodeId, 14);
|
|
assert.deepEqual(instantiated.nodes.map((node) => node.id), ['12', '13']);
|
|
assert.deepEqual(instantiated.nodes.map((node) => node.position), [
|
|
{ x: 42, y: 68 },
|
|
{ x: 132, y: 248 },
|
|
]);
|
|
assert.equal(instantiated.nodes[0].selected, true);
|
|
assert.deepEqual(instantiated.nodes[0].data.widgetValues, { filename: 'scan.ibw', colormap: 'viridis' });
|
|
assert.deepEqual(instantiated.nodes[0].data.runtimeValues, { layerIndex: 1 });
|
|
assert.equal(instantiated.nodes[0].data.previewImage, null);
|
|
assert.deepEqual(instantiated.nodes[0].data.definition, defs.Image);
|
|
|
|
assert.deepEqual(instantiated.edges, [
|
|
{
|
|
id: 'e12-13-0',
|
|
source: '12',
|
|
sourceHandle: 'output::0::DATA_FIELD',
|
|
target: '13',
|
|
targetHandle: 'input::field::DATA_FIELD',
|
|
selected: false,
|
|
style: { stroke: '#abc', strokeWidth: 2 },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('buildNodeClipboardPayloadForIds can include upstream external edges for duplicated nodes', () => {
|
|
const nodes = [
|
|
{ id: '1', position: { x: 0, y: 0 }, data: { className: 'Image' } },
|
|
{ id: '2', position: { x: 100, y: 0 }, data: { className: 'Preview' } },
|
|
{ id: '3', position: { x: 200, y: 0 }, data: { className: 'Save' } },
|
|
];
|
|
|
|
const edges = [
|
|
{
|
|
source: '1',
|
|
sourceHandle: 'output::0::DATA_FIELD',
|
|
target: '2',
|
|
targetHandle: 'input::field::DATA_FIELD',
|
|
},
|
|
{
|
|
source: '2',
|
|
sourceHandle: 'output::0::IMAGE',
|
|
target: '3',
|
|
targetHandle: 'input::value::SAVE_VALUE',
|
|
},
|
|
];
|
|
|
|
const payload = buildNodeClipboardPayloadForIds(nodes, edges, ['2'], {
|
|
includeIncomingExternalEdges: true,
|
|
});
|
|
|
|
assert.equal(payload.nodes.length, 1);
|
|
assert.deepEqual(payload.edges, [
|
|
{
|
|
source: '1',
|
|
sourceHandle: 'output::0::DATA_FIELD',
|
|
target: '2',
|
|
targetHandle: 'input::field::DATA_FIELD',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('instantiateNodeClipboardPayload can keep external upstream sources when duplicating nodes', () => {
|
|
const payload = {
|
|
kind: NODE_CLIPBOARD_KIND,
|
|
version: 1,
|
|
nodes: [
|
|
{
|
|
id: '2',
|
|
position: { x: 100, y: 0 },
|
|
data: {
|
|
label: 'Preview',
|
|
className: 'Preview',
|
|
widgetValues: { colormap: 'viridis' },
|
|
},
|
|
},
|
|
],
|
|
edges: [
|
|
{
|
|
source: '1',
|
|
sourceHandle: 'output::0::DATA_FIELD',
|
|
target: '2',
|
|
targetHandle: 'input::field::DATA_FIELD',
|
|
},
|
|
],
|
|
};
|
|
|
|
const defs = {
|
|
Preview: { output: ['IMAGE'], output_name: ['preview'] },
|
|
};
|
|
|
|
const instantiated = instantiateNodeClipboardPayload(
|
|
payload,
|
|
defs,
|
|
7,
|
|
{ x: 50, y: 25 },
|
|
{ keepExternalSources: true },
|
|
);
|
|
|
|
assert.deepEqual(instantiated.nodes.map((node) => node.id), ['7']);
|
|
assert.deepEqual(instantiated.edges, [
|
|
{
|
|
id: 'e1-7-0',
|
|
source: '1',
|
|
sourceHandle: 'output::0::DATA_FIELD',
|
|
target: '7',
|
|
targetHandle: 'input::field::DATA_FIELD',
|
|
selected: false,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('clipboard payload deep-copies local widget and runtime fields', () => {
|
|
const nodes = [
|
|
{
|
|
id: '9',
|
|
selected: true,
|
|
position: { x: 0, y: 0 },
|
|
data: {
|
|
label: 'Markup',
|
|
className: 'Markup',
|
|
widgetValues: {
|
|
stroke_width: 3,
|
|
markup_shapes: [
|
|
{ kind: 'line', points: [0.1, 0.2, 0.3, 0.4] },
|
|
],
|
|
},
|
|
runtimeValues: {
|
|
camera: { azimuth: 15, polar: 60 },
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
const payload = buildNodeClipboardPayload(nodes, []);
|
|
|
|
nodes[0].data.widgetValues.markup_shapes[0].points[0] = 0.9;
|
|
nodes[0].data.runtimeValues.camera.azimuth = 90;
|
|
|
|
assert.equal(payload.nodes[0].data.widgetValues.markup_shapes[0].points[0], 0.1);
|
|
assert.equal(payload.nodes[0].data.runtimeValues.camera.azimuth, 15);
|
|
});
|
|
|
|
test('clipboard payload preserves wrapper class names for group shells', () => {
|
|
const payload = buildNodeClipboardPayloadForIds(
|
|
[
|
|
{
|
|
id: '50',
|
|
type: 'custom',
|
|
className: 'group-shell',
|
|
position: { x: 0, y: 0 },
|
|
data: {
|
|
label: 'group',
|
|
className: 'Group',
|
|
widgetValues: {},
|
|
},
|
|
},
|
|
],
|
|
[],
|
|
['50'],
|
|
);
|
|
|
|
const instantiated = instantiateNodeClipboardPayload(payload, {}, 80);
|
|
|
|
assert.equal(payload.nodes[0].className, 'group-shell');
|
|
assert.equal(instantiated.nodes[0].className, 'group-shell');
|
|
});
|