mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-11 23:02:56 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
178
packages/noodl-editor/tests/nodegraph/conflictwarnings.js
Normal file
178
packages/noodl-editor/tests/nodegraph/conflictwarnings.js
Normal file
@@ -0,0 +1,178 @@
|
||||
const { ProjectModel } = require('@noodl-models/projectmodel');
|
||||
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
|
||||
const WarningsModel = require('@noodl-models/warningsmodel').WarningsModel;
|
||||
|
||||
describe('Conflict warnings', function () {
|
||||
var p;
|
||||
|
||||
beforeEach(() => {
|
||||
window.NodeLibraryData = require('../nodegraph/nodelibrary');
|
||||
NodeLibrary.instance.loadLibrary();
|
||||
|
||||
p = ProjectModel.fromJSON(getProject());
|
||||
ProjectModel.instance = p;
|
||||
});
|
||||
|
||||
it('can load project', function () {
|
||||
expect(p).not.toBe(undefined);
|
||||
});
|
||||
|
||||
it('can count total number of conflicts', function () {
|
||||
p.getComponentWithName('comp1').graph.evaluateHealth();
|
||||
p.getComponentWithName('comp2').graph.evaluateHealth();
|
||||
p.getComponentWithName('comp3').graph.evaluateHealth();
|
||||
|
||||
expect(
|
||||
WarningsModel.instance.getTotalNumberOfWarningsMatching(
|
||||
(key, ref, warning) => warning.warning.type === 'conflict'
|
||||
)
|
||||
).toBe(4);
|
||||
});
|
||||
|
||||
it('can count warnings for a components', function () {
|
||||
const c = p.getComponentWithName('comp1');
|
||||
c.graph.evaluateHealth();
|
||||
let warnings = WarningsModel.instance.getNumberOfWarningsForComponent(c);
|
||||
expect(warnings).toBe(1);
|
||||
|
||||
//conflict warnings are global, so test counting all non-global conflicts as well
|
||||
warnings = WarningsModel.instance.getNumberOfWarningsForComponent(c, {
|
||||
excludeGlobal: true
|
||||
});
|
||||
expect(warnings).toBe(0);
|
||||
});
|
||||
|
||||
it('can detect param conflict warnings', function () {
|
||||
var c = p.getComponentWithName('comp1');
|
||||
c.graph.evaluateHealth();
|
||||
|
||||
var w = WarningsModel.instance.warnings['comp1'];
|
||||
expect(w['node/A']['node-param-conflict-A']).not.toBe(undefined);
|
||||
expect(WarningsModel.instance.getNumberOfWarningsForComponent(c)).toBe(1);
|
||||
});
|
||||
|
||||
it('can detect type conflict warnings', function () {
|
||||
var c = p.getComponentWithName('comp2');
|
||||
c.graph.evaluateHealth();
|
||||
|
||||
var w = WarningsModel.instance.warnings['comp2'];
|
||||
expect(w['node/B']['node-type-conflict']).not.toBe(undefined);
|
||||
expect(WarningsModel.instance.getNumberOfWarningsForComponent(c)).toBe(1);
|
||||
});
|
||||
|
||||
it('can clear warnings', function () {
|
||||
// Clear the warning
|
||||
var c = p.getComponentWithName('comp1');
|
||||
WarningsModel.instance.setWarning(
|
||||
{
|
||||
component: c,
|
||||
node: c.graph.roots[0],
|
||||
key: 'node-param-conflict-A'
|
||||
},
|
||||
undefined
|
||||
);
|
||||
expect(WarningsModel.instance.hasComponentWarnings(c)).toBe(false);
|
||||
|
||||
// Clear warnings
|
||||
var c = p.getComponentWithName('comp2');
|
||||
WarningsModel.instance.clearWarningsForRef({
|
||||
component: c,
|
||||
node: c.graph.roots[0]
|
||||
});
|
||||
expect(WarningsModel.instance.hasComponentWarnings(c)).toBe(false);
|
||||
});
|
||||
|
||||
it('can format warnings', function () {
|
||||
var c = p.getComponentWithName('comp3');
|
||||
c.graph.evaluateHealth();
|
||||
|
||||
var w = WarningsModel.instance.warnings['comp3'];
|
||||
expect(w['node/C']['node-param-conflict-alignX']).not.toBe(undefined);
|
||||
expect(w['node/C']['node-param-conflict-alignX'].warning.message).toEqual('Merge conflict at parameter AlignX');
|
||||
expect(w['node/C']['node-param-conflict-x']).not.toBe(undefined);
|
||||
expect(w['node/C']['node-param-conflict-x'].warning.message).toEqual('Merge conflict at parameter x');
|
||||
});
|
||||
|
||||
it('clear warnings when project is unloaded', function () {
|
||||
var c = p.getComponentWithName('comp1');
|
||||
c.graph.evaluateHealth();
|
||||
expect(WarningsModel.instance.hasComponentWarnings(c)).toBe(true);
|
||||
|
||||
ProjectModel.instance = undefined;
|
||||
expect(WarningsModel.instance.warnings).toEqual({});
|
||||
});
|
||||
|
||||
function getProject() {
|
||||
return {
|
||||
name: 'p',
|
||||
components: [
|
||||
{
|
||||
name: 'comp1',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
type: 'group',
|
||||
id: 'A',
|
||||
conflicts: [
|
||||
{
|
||||
type: 'parameter',
|
||||
name: 'A',
|
||||
ours: 0,
|
||||
theirs: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
connections: []
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'comp2',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
type: 'group',
|
||||
id: 'B',
|
||||
conflicts: [
|
||||
{
|
||||
type: 'typename',
|
||||
ours: '/a',
|
||||
theirs: '/b'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'comp3',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
type: 'group',
|
||||
id: 'C',
|
||||
conflicts: [
|
||||
{
|
||||
type: 'parameter',
|
||||
name: 'x',
|
||||
ours: {
|
||||
value: 10,
|
||||
unit: '%'
|
||||
},
|
||||
theirs: 10
|
||||
},
|
||||
{
|
||||
type: 'parameter',
|
||||
name: 'alignX',
|
||||
ours: 'left',
|
||||
theirs: 'right'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
51
packages/noodl-editor/tests/nodegraph/createnewnode.js
Normal file
51
packages/noodl-editor/tests/nodegraph/createnewnode.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const CreateNewNodePanel = require('@noodl-views/createnewnodepanel');
|
||||
const { ProjectModel } = require('@noodl-models/projectmodel');
|
||||
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
|
||||
|
||||
describe('Create new node panel unit tests', function () {
|
||||
var p, cp;
|
||||
|
||||
xit('can setup panel', function () {
|
||||
ProjectModel.instance = new ProjectModel();
|
||||
|
||||
p = ProjectModel.fromJSON(project);
|
||||
|
||||
cp = new CreateNewNodePanel({
|
||||
model: p.getComponentWithName('Root').graph,
|
||||
pos: {
|
||||
x: 100,
|
||||
y: 50
|
||||
},
|
||||
runtimeType: 'browser'
|
||||
});
|
||||
cp.render();
|
||||
expect(cp).not.toBe(undefined);
|
||||
});
|
||||
|
||||
xit('can create nodes', function () {
|
||||
var c = p.getComponentWithName('Root');
|
||||
cp.performCreate(NodeLibrary.instance.getNodeTypeWithName('group'));
|
||||
|
||||
expect(c.graph.roots.length).toBe(2);
|
||||
expect(c.graph.roots[1].type).toBe(NodeLibrary.instance.getNodeTypeWithName('group'));
|
||||
expect(c.graph.roots[1].x).toBe(100);
|
||||
expect(c.graph.roots[1].y).toBe(50);
|
||||
expect(c.graph.roots[1].version).toBe(2);
|
||||
});
|
||||
|
||||
var project = {
|
||||
components: [
|
||||
{
|
||||
name: 'Root',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'A',
|
||||
type: 'group'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
101
packages/noodl-editor/tests/nodegraph/createstatus.js
Normal file
101
packages/noodl-editor/tests/nodegraph/createstatus.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const NodeGraphModel = require('@noodl-models/nodegraphmodel').NodeGraphModel;
|
||||
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
|
||||
const { ComponentModel } = require('@noodl-models/componentmodel');
|
||||
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
|
||||
|
||||
describe('Create status tests', function () {
|
||||
var g1, c1, c2;
|
||||
|
||||
beforeEach(() => {
|
||||
window.NodeLibraryData = require('../nodegraph/nodelibrary');
|
||||
NodeLibrary.instance.loadLibrary();
|
||||
});
|
||||
|
||||
it("can detect nodes that can't have/be children", function () {
|
||||
g1 = new NodeGraphModel();
|
||||
|
||||
c1 = new ComponentModel({
|
||||
graph: g1,
|
||||
name: 'c1'
|
||||
});
|
||||
|
||||
var n1 = NodeGraphNode.fromJSON(
|
||||
{
|
||||
type: 'group',
|
||||
id: 'A'
|
||||
},
|
||||
g1
|
||||
);
|
||||
g1.addRoot(n1);
|
||||
|
||||
var n2 = NodeGraphNode.fromJSON(
|
||||
{
|
||||
type: 'animation',
|
||||
id: 'B'
|
||||
},
|
||||
g1
|
||||
);
|
||||
g1.addRoot(n2);
|
||||
|
||||
var n3 = NodeGraphNode.fromJSON(
|
||||
{
|
||||
type: 'Component Children',
|
||||
id: 'C'
|
||||
},
|
||||
g1
|
||||
);
|
||||
g1.addRoot(n3);
|
||||
|
||||
// Nodes that can have children
|
||||
var status = c1.getCreateStatus({
|
||||
parent: n1,
|
||||
type: NodeLibrary.instance.getNodeTypeWithName('group')
|
||||
});
|
||||
expect(status.creatable).toBe(true);
|
||||
|
||||
// Animation nodes cannot be children
|
||||
var status = c1.getCreateStatus({
|
||||
parent: n1,
|
||||
type: NodeLibrary.instance.getNodeTypeWithName('animation')
|
||||
});
|
||||
expect(status.creatable).toBe(false);
|
||||
|
||||
// Animation nodes cannot have children
|
||||
var status = c1.getCreateStatus({
|
||||
parent: n2,
|
||||
type: NodeLibrary.instance.getNodeTypeWithName('group')
|
||||
});
|
||||
expect(status.creatable).toBe(false);
|
||||
|
||||
// Instance of itself
|
||||
var status = c1.getCreateStatus({
|
||||
type: c1
|
||||
});
|
||||
expect(status.creatable).toBe(false);
|
||||
});
|
||||
|
||||
it('can detect circular references', function () {
|
||||
c1 = ComponentModel.fromJSON({
|
||||
name: 'comp1',
|
||||
graph: {}
|
||||
});
|
||||
c2 = ComponentModel.fromJSON({
|
||||
name: 'comp2',
|
||||
graph: {}
|
||||
});
|
||||
c1.graph.addRoot(
|
||||
NodeGraphNode.fromJSON(
|
||||
{
|
||||
type: c2,
|
||||
id: 'C'
|
||||
},
|
||||
c1.graph
|
||||
)
|
||||
);
|
||||
|
||||
var status = c2.getCreateStatus({
|
||||
type: c1
|
||||
});
|
||||
expect(status.creatable).toBe(false);
|
||||
});
|
||||
});
|
||||
496
packages/noodl-editor/tests/nodegraph/export.js
Normal file
496
packages/noodl-editor/tests/nodegraph/export.js
Normal file
@@ -0,0 +1,496 @@
|
||||
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
|
||||
const Exporter = require('@noodl-utils/exporter');
|
||||
const { ProjectModel } = require('@noodl-models/projectmodel');
|
||||
|
||||
describe('export tests', function () {
|
||||
xit('can export ports on components', function () {
|
||||
ProjectModel.instance = ProjectModel.fromJSON(project1);
|
||||
NodeLibrary.instance.registerModule(ProjectModel.instance);
|
||||
|
||||
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('Image-1'));
|
||||
|
||||
const json = Exporter.exportToJSON(ProjectModel.instance);
|
||||
|
||||
// Should not export types where the type has not been resolved
|
||||
expect(json.components[0].ports.length).toBe(2);
|
||||
expect(json.components[0].ports[0].name).toBe('out1');
|
||||
expect(json.components[0].ports[0].plug).toBe('output');
|
||||
expect(NodeLibrary.nameForPortType(json.components[0].ports[0].type)).toBe('string');
|
||||
expect(json.components[0].ports[1].name).toBe('out2');
|
||||
expect(json.components[0].ports[1].plug).toBe('output');
|
||||
expect(NodeLibrary.nameForPortType(json.components[0].ports[1].type)).toBe('number');
|
||||
|
||||
NodeLibrary.instance.unregisterModule(ProjectModel.instance);
|
||||
});
|
||||
|
||||
it('project settings are exported', function () {
|
||||
ProjectModel.instance = ProjectModel.fromJSON(project2);
|
||||
NodeLibrary.instance.registerModule(ProjectModel.instance);
|
||||
|
||||
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('Group-1'));
|
||||
|
||||
const json = Exporter.exportToJSON(ProjectModel.instance);
|
||||
expect(json.settings.canvasWidth).toBe(303);
|
||||
expect(json.settings.canvasHeight).toBe(404);
|
||||
|
||||
NodeLibrary.instance.unregisterModule(ProjectModel.instance);
|
||||
});
|
||||
|
||||
function matchBundle(bundle, componentIndex) {
|
||||
function arrayHasSameElements(a, b) {
|
||||
//check if equal but ignore order
|
||||
if (a.length !== b.length) return false;
|
||||
return a.every((e) => b.includes(e));
|
||||
}
|
||||
|
||||
return Object.values(componentIndex).some((b) => arrayHasSameElements(bundle, b));
|
||||
}
|
||||
|
||||
it('can export an index that includes pages and for each nodes', function () {
|
||||
ProjectModel.instance = ProjectModel.fromJSON({
|
||||
components: [
|
||||
{
|
||||
name: '/root',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'nav-stack',
|
||||
type: 'Page Stack',
|
||||
parameters: {
|
||||
pages: [
|
||||
{
|
||||
id: 'p1',
|
||||
label: 'Page1'
|
||||
},
|
||||
{
|
||||
id: 'p2',
|
||||
label: 'Page2'
|
||||
}
|
||||
],
|
||||
'pageComp-p1': '/page1',
|
||||
'pageComp-p2': '/page2'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'n0',
|
||||
type: '/shared-comp'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/page1',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'n0',
|
||||
type: '/shared-comp'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/page2',
|
||||
graph: {}
|
||||
},
|
||||
{
|
||||
name: '/shared-comp',
|
||||
graph: {}
|
||||
},
|
||||
{
|
||||
name: '/remaining-comp',
|
||||
graph: {}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('nav-stack'));
|
||||
|
||||
const json = Exporter.exportToJSON(ProjectModel.instance);
|
||||
|
||||
//make sure the root components are present directly in the export, not in a bundle
|
||||
expect(json.components.find((c) => c.name === '/root'));
|
||||
expect(json.components.find((c) => c.name === '/shared-comp'));
|
||||
|
||||
//this test assume the bundles are emitted in a specific order
|
||||
//it makes the test tied to implementation specifics, so not great
|
||||
const bundles = {
|
||||
b2: {
|
||||
components: ['/page1'],
|
||||
dependencies: []
|
||||
},
|
||||
b3: {
|
||||
components: ['/page2'],
|
||||
dependencies: []
|
||||
},
|
||||
b4: {
|
||||
components: ['/remaining-comp'],
|
||||
dependencies: []
|
||||
}
|
||||
};
|
||||
|
||||
expect(json.componentIndex).toEqual(bundles);
|
||||
});
|
||||
|
||||
it('can follow For Each nodes when collecting dependencies', function () {
|
||||
ProjectModel.instance = ProjectModel.fromJSON({
|
||||
components: [
|
||||
{
|
||||
name: '/root',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'node1',
|
||||
type: 'For Each',
|
||||
parameters: {
|
||||
template: '/for-each-comp'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/for-each-comp',
|
||||
graph: {}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const allComponents = ProjectModel.instance.getComponents();
|
||||
const rootComponent = allComponents.find((c) => c.name === '/root');
|
||||
|
||||
const graph = Exporter._collectDependencyGraph(rootComponent, allComponents);
|
||||
const deps = Exporter._flattenDependencyGraph(graph);
|
||||
|
||||
expect(deps.length).toBe(2);
|
||||
expect(deps.find((c) => c.name === '/root'));
|
||||
expect(deps.find((c) => c.name === '/for-each-comp'));
|
||||
});
|
||||
|
||||
it("creates bundles that doesn't have duplicates", function () {
|
||||
ProjectModel.instance = ProjectModel.fromJSON({
|
||||
components: [
|
||||
{
|
||||
name: '/comp1',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'nav-stack',
|
||||
type: 'Page Stack',
|
||||
parameters: {
|
||||
pages: [
|
||||
{
|
||||
id: 'p1',
|
||||
label: 'Page1'
|
||||
},
|
||||
{
|
||||
id: 'p2',
|
||||
label: 'Page2'
|
||||
}
|
||||
],
|
||||
'pageComp-p1': '/page1',
|
||||
'pageComp-p2': '/page2'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'n0',
|
||||
type: '/shared-comp'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/page1',
|
||||
id: 'page1',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'n1',
|
||||
type: '/shared-comp'
|
||||
},
|
||||
{
|
||||
id: 'n2',
|
||||
type: '/comp-used-on-both-pages'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/page2',
|
||||
id: 'page2',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'n3',
|
||||
type: '/comp-used-on-both-pages'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/shared-comp',
|
||||
graph: {}
|
||||
},
|
||||
{
|
||||
name: '/comp-used-on-both-pages',
|
||||
graph: {}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('nav-stack'));
|
||||
|
||||
const json = Exporter.exportToJSON(ProjectModel.instance);
|
||||
|
||||
const componentCount = {};
|
||||
for (const name in json.componentIndex) {
|
||||
for (const comp of json.componentIndex[name].components) {
|
||||
if (componentCount[comp]) componentCount[comp]++;
|
||||
else componentCount[comp] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (const name in componentCount) {
|
||||
expect(componentCount[name]).toBe(1, 'Component ' + name + ' is in multiple bundles');
|
||||
}
|
||||
});
|
||||
|
||||
it('calculated dependencies for bundles', function () {
|
||||
ProjectModel.instance = ProjectModel.fromJSON({
|
||||
components: [
|
||||
{
|
||||
name: '/comp1',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'nav-stack',
|
||||
type: 'Page Stack',
|
||||
parameters: {
|
||||
pages: [
|
||||
{
|
||||
id: 'p1',
|
||||
label: 'Page1'
|
||||
},
|
||||
{
|
||||
id: 'p2',
|
||||
label: 'Page2'
|
||||
}
|
||||
],
|
||||
'pageComp-p1': '/page1',
|
||||
'pageComp-p2': '/page2'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'n0',
|
||||
type: '/shared-comp'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/page1',
|
||||
id: 'page1',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'n1',
|
||||
type: '/shared-comp'
|
||||
},
|
||||
{
|
||||
id: 'n2',
|
||||
type: '/comp-used-on-both-pages'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/page2',
|
||||
id: 'page2',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'n3',
|
||||
type: '/comp-used-on-both-pages'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/shared-comp',
|
||||
graph: {}
|
||||
},
|
||||
{
|
||||
name: '/comp-used-on-both-pages',
|
||||
graph: {}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('nav-stack'));
|
||||
|
||||
const allComponents = ProjectModel.instance.getComponents();
|
||||
const rootComponent = allComponents.find((c) => c.name === '/comp1');
|
||||
|
||||
const componentIndex = Exporter.getComponentIndex(rootComponent, allComponents);
|
||||
|
||||
//this test assume the bundles are emitted in a specific order
|
||||
//it makes the test tied to implementation specifics, so not great
|
||||
const bundles = {
|
||||
b0: {
|
||||
components: ['/comp1'],
|
||||
dependencies: ['b1']
|
||||
},
|
||||
b1: {
|
||||
components: ['/shared-comp'],
|
||||
dependencies: []
|
||||
},
|
||||
b2: {
|
||||
components: ['/page1'],
|
||||
dependencies: ['b1', 'b3']
|
||||
},
|
||||
b3: {
|
||||
components: ['/comp-used-on-both-pages'],
|
||||
dependencies: []
|
||||
},
|
||||
b4: {
|
||||
components: ['/page2'],
|
||||
dependencies: ['b3']
|
||||
}
|
||||
};
|
||||
|
||||
expect(componentIndex).toEqual(bundles);
|
||||
});
|
||||
|
||||
xit('ignores project settings flagged to be excluded', function () {
|
||||
ProjectModel.instance = ProjectModel.fromJSON({
|
||||
components: [
|
||||
{
|
||||
name: '/comp2',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'Group-1',
|
||||
type: 'group'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
settings: {
|
||||
someSetting: 'test',
|
||||
someSetting2: 'test2',
|
||||
settingIgnoredInExport: 'test3'
|
||||
}
|
||||
});
|
||||
|
||||
NodeLibrary.instance.registerModule(ProjectModel.instance);
|
||||
|
||||
ProjectModel.instance.setRootNode(ProjectModel.instance.findNodeWithId('Group-1'));
|
||||
|
||||
const json = Exporter.exportToJSON(ProjectModel.instance);
|
||||
expect(json.settings.someSetting).toBe('test');
|
||||
expect(json.settings.someSetting2).toBe('test2');
|
||||
expect(json.settings.settingIgnoredInExport).toBe(undefined);
|
||||
NodeLibrary.instance.unregisterModule(ProjectModel.instance);
|
||||
});
|
||||
|
||||
var project1 = {
|
||||
components: [
|
||||
{
|
||||
name: '/comp1',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'Image-1',
|
||||
type: 'image',
|
||||
parameters: {
|
||||
image: 'pic1.png',
|
||||
css: '%%%mycss {background:#ff00ff;}'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comp-2',
|
||||
type: '/comp2'
|
||||
},
|
||||
{
|
||||
id: 'CO-1',
|
||||
type: 'Component Outputs',
|
||||
ports: [
|
||||
{
|
||||
name: 'out1',
|
||||
type: '*',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'out2',
|
||||
type: '*',
|
||||
plug: 'input'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
connections: [
|
||||
{
|
||||
fromId: 'Image-1',
|
||||
fromProperty: 'image',
|
||||
toId: 'CO-1',
|
||||
toProperty: 'out1'
|
||||
},
|
||||
{
|
||||
fromId: 'Image-1',
|
||||
fromProperty: 'screenX',
|
||||
toId: 'CO-1',
|
||||
toProperty: 'out2'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Should be excluded
|
||||
{
|
||||
name: '/comp3',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'Image-3',
|
||||
type: 'image'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: '/comp2',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'Image-2',
|
||||
type: 'image'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Second project for cross project reference
|
||||
var project2 = {
|
||||
components: [
|
||||
{
|
||||
name: '/comp2',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'Group-1',
|
||||
type: 'group'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
settings: {
|
||||
canvasWidth: 303,
|
||||
canvasHeight: 404
|
||||
}
|
||||
};
|
||||
});
|
||||
117
packages/noodl-editor/tests/nodegraph/hierarchy.js
Normal file
117
packages/noodl-editor/tests/nodegraph/hierarchy.js
Normal file
@@ -0,0 +1,117 @@
|
||||
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
|
||||
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
|
||||
const { ComponentModel } = require('@noodl-models/componentmodel');
|
||||
|
||||
describe('Component instances', function () {
|
||||
var c;
|
||||
|
||||
beforeEach(() => {
|
||||
window.NodeLibraryData = require('../nodegraph/nodelibrary');
|
||||
NodeLibrary.instance.loadLibrary();
|
||||
|
||||
c = ComponentModel.fromJSON({
|
||||
name: 'test',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
type: 'group',
|
||||
id: 'Group-1'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('can load component', function () {
|
||||
expect(c).not.toBe(undefined);
|
||||
});
|
||||
|
||||
it('can detect children that can be attached', function () {
|
||||
const n1 = NodeGraphNode.fromJSON({
|
||||
type: 'image'
|
||||
});
|
||||
|
||||
const n2 = NodeGraphNode.fromJSON({
|
||||
type: 'animation'
|
||||
});
|
||||
|
||||
const n3 = NodeGraphNode.fromJSON({
|
||||
type: 'scaleModifier'
|
||||
});
|
||||
|
||||
const n4 = NodeGraphNode.fromJSON({
|
||||
type: 'image'
|
||||
});
|
||||
|
||||
// Can accept an image
|
||||
expect(c.graph.findNodeWithId('Group-1').canAcceptChildren([n1])).toBe(true);
|
||||
|
||||
// Not all nodes can be children
|
||||
expect(c.graph.findNodeWithId('Group-1').canAcceptChildren([n1, n2])).toBe(false);
|
||||
|
||||
// Not all nodes have accepted categories
|
||||
expect(c.graph.findNodeWithId('Group-1').canAcceptChildren([n1, n3])).toBe(false);
|
||||
|
||||
// Multiple nodes with same category should be OK
|
||||
expect(c.graph.findNodeWithId('Group-1').canAcceptChildren([n1, n4])).toBe(true);
|
||||
});
|
||||
|
||||
it('can report allow as child for components', function () {
|
||||
var c2 = ComponentModel.fromJSON({
|
||||
name: 'test',
|
||||
graph: {}
|
||||
});
|
||||
|
||||
// No root nodes that can be children
|
||||
expect(c2.allowAsChild).toBe(false);
|
||||
|
||||
c2.graph.addRoot(
|
||||
NodeGraphNode.fromJSON({
|
||||
type: 'group'
|
||||
})
|
||||
);
|
||||
|
||||
// Group can be child
|
||||
expect(c2.allowAsChild).toBe(true);
|
||||
|
||||
// Report correct category
|
||||
expect(c2.category).toBe('visuals');
|
||||
});
|
||||
|
||||
it('can report accepted children for components', function () {
|
||||
expect(c.allowChildrenWithCategory).toBe(undefined);
|
||||
|
||||
c.graph.addRoot(
|
||||
NodeGraphNode.fromJSON({
|
||||
type: 'Component Children'
|
||||
})
|
||||
);
|
||||
|
||||
expect(c.allowChildrenWithCategory).toEqual(['visuals']);
|
||||
|
||||
c.graph.addRoot(
|
||||
NodeGraphNode.fromJSON({
|
||||
type: 'Component Modifier Children'
|
||||
})
|
||||
);
|
||||
|
||||
expect(c.allowChildrenWithCategory).toEqual(['modifiers', 'visuals']);
|
||||
});
|
||||
|
||||
it('can detect allow export root', function () {
|
||||
var c2 = ComponentModel.fromJSON({
|
||||
name: 'test',
|
||||
graph: {}
|
||||
});
|
||||
|
||||
expect(c2.allowAsExportRoot).toBe(false);
|
||||
|
||||
c2.graph.addRoot(
|
||||
NodeGraphNode.fromJSON({
|
||||
type: 'group'
|
||||
})
|
||||
);
|
||||
|
||||
expect(c2.allowAsExportRoot).toBe(true);
|
||||
});
|
||||
});
|
||||
11
packages/noodl-editor/tests/nodegraph/index.ts
Normal file
11
packages/noodl-editor/tests/nodegraph/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export * from './conflictwarnings';
|
||||
export * from './createnewnode';
|
||||
export * from './createstatus';
|
||||
export * from './export';
|
||||
export * from './hierarchy';
|
||||
export * from './nodegrapheditor';
|
||||
export * from './nodegraphmodel';
|
||||
export * from './nodelibrary-spec';
|
||||
export * from './propertyeditor';
|
||||
export * from './typechangepropagation';
|
||||
export * from './warnings-model-spec';
|
||||
574
packages/noodl-editor/tests/nodegraph/nodegrapheditor.js
Normal file
574
packages/noodl-editor/tests/nodegraph/nodegrapheditor.js
Normal file
@@ -0,0 +1,574 @@
|
||||
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
|
||||
const NodeGraphEditor = require('@noodl-views/nodegrapheditor').NodeGraphEditor;
|
||||
const { ProjectModel } = require('@noodl-models/projectmodel');
|
||||
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
|
||||
const PopupLayer = require('@noodl-views/popuplayer');
|
||||
const DebugInspector = require('@noodl-utils/debuginspector');
|
||||
const ViewerConnection = require('../../src/editor/src/ViewerConnection');
|
||||
|
||||
describe('Node graph editor auto tests', function () {
|
||||
var c1, g1;
|
||||
|
||||
// Records and store mouse calls to the node graph editor
|
||||
var storeTimeout;
|
||||
var recordedEvents = [];
|
||||
|
||||
function storeEvents() {
|
||||
clearTimeout(storeTimeout);
|
||||
storeTimeout = setTimeout(function () {
|
||||
localStorage['recordedEvents'] = JSON.stringify(recordedEvents);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function recordEvents() {
|
||||
var types = ['mousedown', 'mousemove', 'mouseup'];
|
||||
|
||||
// mouse
|
||||
var old = NodeGraphEditor.instance.mouse;
|
||||
NodeGraphEditor.instance.mouse = function (type, pos, evt) {
|
||||
var now = +new Date();
|
||||
recordedEvents.push({
|
||||
type: type,
|
||||
time: now,
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
evt: evt
|
||||
? {
|
||||
button: evt.button,
|
||||
shiftKey: evt.shiftKey ? true : undefined,
|
||||
ctrlKey: evt.ctrlKey ? true : undefined,
|
||||
spaceKey: evt.spaceKey ? true : undefined
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
|
||||
storeEvents();
|
||||
|
||||
return old.call(this, type, pos, evt);
|
||||
};
|
||||
|
||||
// cut, copy, paste
|
||||
var oldCut = NodeGraphEditor.instance.cut;
|
||||
NodeGraphEditor.instance.cut = function () {
|
||||
var now = +new Date();
|
||||
recordedEvents.push({
|
||||
type: 'cut',
|
||||
time: now
|
||||
});
|
||||
return oldCut.call(this);
|
||||
};
|
||||
|
||||
var oldCopy = NodeGraphEditor.instance.copy;
|
||||
NodeGraphEditor.instance.copy = function () {
|
||||
var now = +new Date();
|
||||
recordedEvents.push({
|
||||
type: 'copy',
|
||||
time: now
|
||||
});
|
||||
return oldCopy.call(this);
|
||||
};
|
||||
|
||||
var oldPaste = NodeGraphEditor.instance.paste;
|
||||
NodeGraphEditor.instance.paste = function () {
|
||||
var now = +new Date();
|
||||
recordedEvents.push({
|
||||
type: 'paste',
|
||||
time: now
|
||||
});
|
||||
return oldPaste.call(this);
|
||||
};
|
||||
}
|
||||
|
||||
// Plays back previously stored mouse calls
|
||||
function playEvents(events, done, realtime) {
|
||||
var i = 0;
|
||||
|
||||
function play() {
|
||||
var event = events[i];
|
||||
var type = event.type;
|
||||
if (type === 'cut') {
|
||||
NodeGraphEditor.instance.cut();
|
||||
} else if (type === 'copy') {
|
||||
NodeGraphEditor.instance.copy();
|
||||
} else if (type === 'paste') {
|
||||
NodeGraphEditor.instance.paste();
|
||||
} else {
|
||||
NodeGraphEditor.instance.mouse(
|
||||
event.type,
|
||||
{
|
||||
x: event.x,
|
||||
y: event.y
|
||||
},
|
||||
event.evt
|
||||
);
|
||||
}
|
||||
|
||||
if (events[i + 1]) {
|
||||
setTimeout(function () {
|
||||
i++;
|
||||
play();
|
||||
}, events[i + 1].time - event.time);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
play();
|
||||
}
|
||||
|
||||
// Sets up a fresh node graph editor
|
||||
function setup() {
|
||||
$('body').append(
|
||||
'<div id="node-graph-editor" style="position:absolute; top:0px; right:0px; width:400px; height:800px; background-color:white;"></div>'
|
||||
);
|
||||
|
||||
// Disable context menu
|
||||
$('body').on('contextmenu', function () {
|
||||
return false;
|
||||
});
|
||||
|
||||
// NodeLibrary.instance = new NodeLibrary();
|
||||
|
||||
ProjectModel.instance = ProjectModel.fromJSON(project);
|
||||
NodeLibrary.instance.registerModule(ProjectModel.instance);
|
||||
|
||||
c1 = ProjectModel.instance.getComponentWithName('Root');
|
||||
g1 = c1.graph;
|
||||
|
||||
// Mocking panel instance
|
||||
PopupLayer.instance = {
|
||||
showTooltip: function () {},
|
||||
hideTooltip: function () {},
|
||||
showToast: function () {},
|
||||
on: function () {},
|
||||
hidePopup: function () {},
|
||||
showPopup: function () {},
|
||||
isDragging: function () {
|
||||
return false;
|
||||
},
|
||||
showModal: function () {},
|
||||
hideModal: function () {},
|
||||
hideAllModalsAndPopups: function () {}
|
||||
};
|
||||
|
||||
// Mocking viewer connection
|
||||
ViewerConnection.instance = {
|
||||
on: function () {},
|
||||
sendNodeHighlighted: function () {}
|
||||
};
|
||||
|
||||
NodeGraphEditor.instance = new NodeGraphEditor({
|
||||
model: g1
|
||||
});
|
||||
NodeGraphEditor.instance.setPanAndScale({
|
||||
scale: 1,
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
NodeGraphEditor.instance.render();
|
||||
$('#node-graph-editor').append(NodeGraphEditor.instance.el);
|
||||
NodeGraphEditor.instance.resize({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 400,
|
||||
height: 800
|
||||
});
|
||||
}
|
||||
|
||||
var project = {
|
||||
components: [
|
||||
{
|
||||
name: 'Root',
|
||||
ports: [],
|
||||
visual: true,
|
||||
visualRootId: '14e31556-f569-21bb-e948-65af515ae574',
|
||||
canHaveVisualChildren: false,
|
||||
graph: {
|
||||
connections: [],
|
||||
roots: []
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function setupNodes() {
|
||||
g1.addRoot(
|
||||
NodeGraphNode.fromJSON({
|
||||
id: '14e31556-f569-21bb-e948-65af515ae574',
|
||||
type: 'group',
|
||||
label: 'group1',
|
||||
x: 49,
|
||||
y: 75
|
||||
})
|
||||
);
|
||||
|
||||
g1.addRoot(
|
||||
NodeGraphNode.fromJSON({
|
||||
id: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
|
||||
type: 'group',
|
||||
label: 'group2',
|
||||
x: 49,
|
||||
y: 164
|
||||
})
|
||||
);
|
||||
|
||||
g1.addRoot(
|
||||
NodeGraphNode.fromJSON({
|
||||
id: '34ea0053-3334-d2cb-3a31-de577030102e',
|
||||
type: 'group',
|
||||
label: 'group3',
|
||||
x: 49,
|
||||
y: 267
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function setupNodes2() {
|
||||
g1.addRoot(
|
||||
NodeGraphNode.fromJSON({
|
||||
id: 'A',
|
||||
type: 'group',
|
||||
label: 'group4',
|
||||
x: 49,
|
||||
y: 367
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function setupConnections() {
|
||||
g1.addConnection({
|
||||
fromId: '14e31556-f569-21bb-e948-65af515ae574',
|
||||
fromProperty: 'screenX',
|
||||
toId: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
|
||||
toProperty: 'x'
|
||||
});
|
||||
}
|
||||
|
||||
function setupConnections2() {
|
||||
g1.addConnection({
|
||||
fromId: '34ea0053-3334-d2cb-3a31-de577030102e',
|
||||
fromProperty: 'screenX',
|
||||
toId: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
|
||||
toProperty: 'x'
|
||||
});
|
||||
|
||||
g1.addConnection({
|
||||
fromId: '14e31556-f569-21bb-e948-65af515ae574',
|
||||
fromProperty: 'screenX',
|
||||
toId: '34ea0053-3334-d2cb-3a31-de577030102e',
|
||||
toProperty: 'y'
|
||||
});
|
||||
}
|
||||
|
||||
function teardownNodes() {
|
||||
g1.removeAllNodes();
|
||||
}
|
||||
|
||||
// Closes and tears the node graph editor down
|
||||
function teardown() {
|
||||
$('#node-graph-editor').remove();
|
||||
NodeGraphEditor.instance = undefined;
|
||||
}
|
||||
|
||||
it('can setup view', function () {
|
||||
setup();
|
||||
expect(NodeGraphEditor.instance).not.toBe(undefined);
|
||||
});
|
||||
|
||||
/* it('can record events',function(done) {
|
||||
setupNodes();
|
||||
// setupNodes2();
|
||||
recordEvents();
|
||||
});
|
||||
return;*/
|
||||
|
||||
// Multi re arrange
|
||||
xit('Multi rearrange nodes, attach, detach', function (done) {
|
||||
setupNodes();
|
||||
setupNodes2();
|
||||
|
||||
playEvents(require('../recordings/multirearrange.json'), function () {
|
||||
expect(g1.roots.length).toBe(2);
|
||||
|
||||
var n1 = g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574');
|
||||
var n2 = g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe');
|
||||
var n3 = g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e');
|
||||
var n4 = g1.findNodeWithId('A');
|
||||
|
||||
expect(n1.children[0]).toBe(n3);
|
||||
expect(n4.children[0]).toBe(n2);
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
xit('cut n paste', function (done) {
|
||||
setupNodes();
|
||||
|
||||
playEvents(require('../recordings/cutnpaste.json'), function () {
|
||||
expect(g1.roots.length).toBe(3);
|
||||
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).not.toBe(undefined);
|
||||
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')).not.toBe(undefined);
|
||||
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')).not.toBe(undefined);
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
xit('can undo attach, detach and move', function (done) {
|
||||
setupNodes();
|
||||
|
||||
playEvents(require('../recordings/undoarrange.json'), function () {
|
||||
// group3
|
||||
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').parent).toBe(undefined);
|
||||
|
||||
// group2 child to group1
|
||||
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe').parent).toBe(
|
||||
g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')
|
||||
);
|
||||
|
||||
NodeGraphEditor.instance.undo(); // Undo detach
|
||||
|
||||
// group3 child to group1
|
||||
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').parent).toBe(
|
||||
g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')
|
||||
);
|
||||
|
||||
NodeGraphEditor.instance.undo(); // Undo attach
|
||||
|
||||
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').parent).toBe(undefined);
|
||||
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe').parent).toBe(undefined);
|
||||
|
||||
NodeGraphEditor.instance.undo(); // Undo move
|
||||
|
||||
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').x).toBe(49);
|
||||
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e').y).toBe(267);
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Single re arrange
|
||||
xit('single rearrange nodes, attach, detach', function (done) {
|
||||
setupNodes();
|
||||
|
||||
playEvents(require('../recordings/singlerearrange.json'), function () {
|
||||
var n = g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574');
|
||||
expect(n.parent).toBe(undefined);
|
||||
|
||||
var n1 = g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe');
|
||||
var n2 = g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e');
|
||||
|
||||
expect(n1.parent).toBe(n);
|
||||
expect(n2.parent).toBe(n);
|
||||
|
||||
expect(n.children[0]).toBe(n2);
|
||||
expect(n.children[1]).toBe(n1);
|
||||
|
||||
expect(g1.roots.length).toBe(1);
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Multi select and mode nodes
|
||||
xit('can multiselect and move nodes', function (done) {
|
||||
setupNodes();
|
||||
|
||||
playEvents(require('../recordings/multimove.json'), function () {
|
||||
var n1 = NodeGraphEditor.instance.model.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574');
|
||||
var n2 = NodeGraphEditor.instance.model.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe');
|
||||
|
||||
expect(n1.x).toBe(50);
|
||||
expect(n1.y).toBe(365);
|
||||
|
||||
expect(n2.x).toBe(50);
|
||||
expect(n2.y).toBe(454);
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Drag to move nodes
|
||||
xit('can move nodes', function (done) {
|
||||
setupNodes();
|
||||
|
||||
playEvents(require('../recordings/movenode.json'), function () {
|
||||
var n = NodeGraphEditor.instance.model.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574');
|
||||
expect(n.x).toBe(47);
|
||||
expect(n.y).toBe(408);
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
xit('can undo cut and paste', function (done) {
|
||||
setupNodes();
|
||||
|
||||
playEvents(require('../recordings/undocutpaste.json'), function () {
|
||||
expect(g1.roots.length).toBe(3);
|
||||
expect(g1.roots[1].label).toBe('group2');
|
||||
expect(g1.roots[2].label).toBe('group3');
|
||||
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).toBe(undefined);
|
||||
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')).toBe(undefined);
|
||||
|
||||
NodeGraphEditor.instance.undo(); // Undo paste
|
||||
|
||||
expect(g1.roots.length).toBe(1);
|
||||
|
||||
NodeGraphEditor.instance.undo(); // Undo cut
|
||||
|
||||
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).not.toBe(undefined);
|
||||
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')).not.toBe(undefined);
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Undo cut children
|
||||
xit('Can undo cut children', function (done) {
|
||||
setupNodes();
|
||||
setupNodes2();
|
||||
|
||||
playEvents(require('../recordings/undocutchildren.json'), function () {
|
||||
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574').children[0]).toBe(g1.findNodeWithId('A'));
|
||||
|
||||
NodeGraphEditor.instance.undo();
|
||||
|
||||
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574').children[0]).toBe(
|
||||
g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')
|
||||
);
|
||||
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe').children[0]).toBe(
|
||||
g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')
|
||||
);
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Undo cut connections
|
||||
xit('Can undo cut connections', function (done) {
|
||||
setupNodes();
|
||||
setupConnections();
|
||||
setupConnections2();
|
||||
|
||||
playEvents(require('../recordings/undocutconnections.json'), function () {
|
||||
expect(g1.connections.length).toBe(0);
|
||||
|
||||
NodeGraphEditor.instance.undo();
|
||||
|
||||
expect(g1.connections.length).toBe(3);
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Can delete and undo connection
|
||||
xit('can delete and undo delete connection', function (done) {
|
||||
setupNodes();
|
||||
setupConnections();
|
||||
|
||||
playEvents(require('../recordings/deletecon.json'), function () {
|
||||
expect(g1.connections.length).toBe(0);
|
||||
NodeGraphEditor.instance.undo();
|
||||
expect(g1.connections.length).toBe(1);
|
||||
expect(g1.connections[0].fromId).toBe('14e31556-f569-21bb-e948-65af515ae574');
|
||||
|
||||
expect(NodeGraphEditor.instance.verifyWithModel()).toBe(true);
|
||||
teardownNodes();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Delete
|
||||
xit('can delete nodes and undo', function (done) {
|
||||
setupNodes();
|
||||
setupConnections();
|
||||
|
||||
playEvents(require('../recordings/deleteandundo.json'), function () {
|
||||
NodeGraphEditor.instance.delete();
|
||||
|
||||
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')).toBe(undefined);
|
||||
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).toBe(undefined);
|
||||
expect(g1.findNodeWithId('34ea0053-3334-d2cb-3a31-de577030102e')).not.toBe(undefined);
|
||||
expect(g1.connections.length).toBe(0);
|
||||
|
||||
NodeGraphEditor.instance.undo();
|
||||
|
||||
expect(g1.findNodeWithId('14e31556-f569-21bb-e948-65af515ae574')).not.toBe(undefined);
|
||||
expect(g1.findNodeWithId('33a2be5c-b341-27b4-292f-aee1b5bc30fe')).not.toBe(undefined);
|
||||
expect(g1.connections.length).toBe(1);
|
||||
teardownNodes();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
xit('Can create inspectors', function (done) {
|
||||
setupNodes();
|
||||
setupConnections();
|
||||
setupConnections2();
|
||||
|
||||
playEvents(
|
||||
require('../recordings/connectiondebugger.json'),
|
||||
function () {
|
||||
var model = DebugInspector.InspectorsModel.instanceForProject(ProjectModel.instance);
|
||||
var inspectors = model.getInspectors();
|
||||
expect(inspectors[1]).toEqual({
|
||||
connectionKey: '14e31556-f569-21bb-e948-65af515ae574screenX33a2be5c-b341-27b4-292f-aee1b5bc30fex',
|
||||
pinned: true,
|
||||
position: 0.390625,
|
||||
type: 'connection',
|
||||
connection: {
|
||||
fromId: '14e31556-f569-21bb-e948-65af515ae574',
|
||||
fromProperty: 'screenX',
|
||||
toId: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
|
||||
toProperty: 'x'
|
||||
}
|
||||
});
|
||||
expect(inspectors[0]).toEqual({
|
||||
connectionKey: '34ea0053-3334-d2cb-3a31-de577030102escreenX33a2be5c-b341-27b4-292f-aee1b5bc30fex',
|
||||
pinned: true,
|
||||
position: 0.515625,
|
||||
type: 'connection',
|
||||
connection: {
|
||||
fromId: '34ea0053-3334-d2cb-3a31-de577030102e',
|
||||
fromProperty: 'screenX',
|
||||
toId: '33a2be5c-b341-27b4-292f-aee1b5bc30fe',
|
||||
toProperty: 'x'
|
||||
}
|
||||
});
|
||||
expect(inspectors.length).toBe(2);
|
||||
|
||||
teardownNodes();
|
||||
done();
|
||||
},
|
||||
true
|
||||
); // Play in real time
|
||||
});
|
||||
|
||||
// Multi select and mode nodes
|
||||
it('can tear down', function () {
|
||||
teardown();
|
||||
expect(NodeGraphEditor.instance).toBe(undefined);
|
||||
});
|
||||
});
|
||||
173
packages/noodl-editor/tests/nodegraph/nodegraphmodel.js
Normal file
173
packages/noodl-editor/tests/nodegraph/nodegraphmodel.js
Normal file
@@ -0,0 +1,173 @@
|
||||
const NodeGraphModel = require('@noodl-models/nodegraphmodel').NodeGraphModel;
|
||||
const NodeGraphNode = require('@noodl-models/nodegraphmodel').NodeGraphNode;
|
||||
|
||||
describe('Node graph basic tests', function () {
|
||||
it('fires nodeAdded, nodeRemoved and connectionAdded, connectionRemoved events', function () {
|
||||
const g1 = new NodeGraphModel();
|
||||
|
||||
var handlers = jasmine.createSpyObj('handlers', [
|
||||
'nodeAdded',
|
||||
'nodeRemoved',
|
||||
'connectionAdded',
|
||||
'connectionRemoved'
|
||||
]);
|
||||
|
||||
g1.on('nodeAdded', handlers.nodeAdded);
|
||||
g1.on('nodeRemoved', handlers.nodeRemoved);
|
||||
g1.on('connectionAdded', handlers.connectionAdded);
|
||||
g1.on('connectionRemoved', handlers.connectionRemoved);
|
||||
|
||||
const n1 = NodeGraphNode.fromJSON({
|
||||
type: 'layer',
|
||||
id: 'A'
|
||||
});
|
||||
g1.addRoot(n1);
|
||||
|
||||
expect(handlers.nodeAdded.calls.argsFor(0)[0].model).toBe(n1);
|
||||
|
||||
const n2 = NodeGraphNode.fromJSON({
|
||||
type: 'layer',
|
||||
id: 'B'
|
||||
});
|
||||
g1.addRoot(n2);
|
||||
|
||||
expect(handlers.nodeAdded.calls.argsFor(1)[0].model).toBe(n2);
|
||||
|
||||
const con = {
|
||||
fromId: 'A',
|
||||
fromProperty: 'color',
|
||||
toId: 'B',
|
||||
toProperty: 'color'
|
||||
};
|
||||
g1.addConnection(con);
|
||||
expect(handlers.connectionAdded.calls.argsFor(0)[0].model).toBe(con);
|
||||
|
||||
g1.removeConnection(con);
|
||||
expect(handlers.connectionRemoved.calls.argsFor(0)[0].model).toBe(con);
|
||||
|
||||
g1.removeNode(n2);
|
||||
expect(handlers.nodeRemoved.calls.argsFor(0)[0].model).toBe(n2);
|
||||
});
|
||||
|
||||
it('can change the id on all nodes', () => {
|
||||
const g1 = new NodeGraphModel();
|
||||
const n1 = NodeGraphNode.fromJSON({
|
||||
type: 'layer',
|
||||
id: 'A'
|
||||
});
|
||||
g1.addRoot(n1);
|
||||
const n2 = NodeGraphNode.fromJSON({
|
||||
type: 'layer',
|
||||
id: 'B'
|
||||
});
|
||||
g1.addRoot(n2);
|
||||
|
||||
const con = {
|
||||
fromId: 'A',
|
||||
fromProperty: 'color',
|
||||
toId: 'B',
|
||||
toProperty: 'color'
|
||||
};
|
||||
g1.addConnection(con);
|
||||
|
||||
g1.rekeyAllIds();
|
||||
|
||||
expect(n1.id).not.toEqual('A');
|
||||
expect(n2.id).not.toEqual('B');
|
||||
expect(con.fromId).toEqual(n1.id);
|
||||
expect(con.toId).toEqual(n2.id);
|
||||
});
|
||||
|
||||
describe('tracks what nodes are in the graph correctly', () => {
|
||||
it('can add and remove one node', () => {
|
||||
const graph = new NodeGraphModel();
|
||||
|
||||
//add a root
|
||||
expect(graph.findNodeWithId('A')).toBeFalsy();
|
||||
const n1 = NodeGraphNode.fromJSON({
|
||||
type: 'test',
|
||||
id: 'A'
|
||||
});
|
||||
graph.addRoot(n1);
|
||||
expect(graph.findNodeWithId('A')).toBeTruthy();
|
||||
|
||||
//and then remove it
|
||||
graph.removeNode(n1);
|
||||
expect(graph.findNodeWithId('A')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('can add and remove a node with children', () => {
|
||||
const graph = new NodeGraphModel();
|
||||
|
||||
//add a new root with children and verify that both nodes are found
|
||||
const n2 = NodeGraphNode.fromJSON({
|
||||
type: 'test',
|
||||
id: 'A',
|
||||
children: [
|
||||
{
|
||||
type: 'test',
|
||||
id: 'B'
|
||||
}
|
||||
]
|
||||
});
|
||||
graph.addRoot(n2);
|
||||
|
||||
expect(graph.findNodeWithId('A')).toBe(n2);
|
||||
expect(graph.findNodeWithId('B')).toBe(n2.children[0]);
|
||||
|
||||
//remove the root and verify that both nodes are removed
|
||||
graph.removeNode(n2);
|
||||
expect(graph.findNodeWithId('A')).toBeFalsy();
|
||||
expect(graph.findNodeWithId('B')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('can add a child node to a root', () => {
|
||||
const graph = new NodeGraphModel();
|
||||
|
||||
const root = NodeGraphNode.fromJSON({
|
||||
type: 'test',
|
||||
id: 'A'
|
||||
});
|
||||
graph.addRoot(root);
|
||||
|
||||
const child = NodeGraphNode.fromJSON({
|
||||
type: 'test',
|
||||
id: 'B'
|
||||
});
|
||||
|
||||
root.addChild(child);
|
||||
|
||||
expect(graph.findNodeWithId('A')).toBe(root);
|
||||
expect(graph.findNodeWithId('B')).toBe(child);
|
||||
|
||||
const child2 = NodeGraphNode.fromJSON({
|
||||
type: 'test',
|
||||
id: 'C'
|
||||
});
|
||||
|
||||
root.insertChild(child2, 0);
|
||||
|
||||
expect(graph.findNodeWithId('C')).toBe(child2);
|
||||
});
|
||||
|
||||
it('can remove a child node to a root', () => {
|
||||
const graph = new NodeGraphModel();
|
||||
|
||||
const n2 = NodeGraphNode.fromJSON({
|
||||
type: 'test',
|
||||
id: 'A',
|
||||
children: [
|
||||
{
|
||||
type: 'test',
|
||||
id: 'B'
|
||||
}
|
||||
]
|
||||
});
|
||||
graph.addRoot(n2);
|
||||
graph.removeNode(n2.children[0]);
|
||||
|
||||
expect(graph.findNodeWithId('B')).toBeFalsy();
|
||||
expect(graph.findNodeWithId('A')).toBe(n2);
|
||||
});
|
||||
});
|
||||
});
|
||||
318
packages/noodl-editor/tests/nodegraph/nodelibrary-spec.js
Normal file
318
packages/noodl-editor/tests/nodegraph/nodelibrary-spec.js
Normal file
@@ -0,0 +1,318 @@
|
||||
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
|
||||
const BasicNodeType = require('@noodl-models/nodelibrary/BasicNodeType').BasicNodeType;
|
||||
const UnknownNodeType = require('@noodl-models/nodelibrary/UnknownNodeType').UnknownNodeType;
|
||||
|
||||
const { ComponentModel } = require('@noodl-models/componentmodel');
|
||||
const { ProjectModel } = require('@noodl-models/projectmodel');
|
||||
|
||||
describe('Node library tests', function () {
|
||||
beforeEach(() => {
|
||||
//reload fresh library for every test
|
||||
window.NodeLibraryData = require('../nodegraph/nodelibrary');
|
||||
for (const module of NodeLibrary.instance.modules) {
|
||||
NodeLibrary.instance.unregisterModule(module);
|
||||
}
|
||||
|
||||
NodeLibrary.instance.loadLibrary();
|
||||
});
|
||||
|
||||
it('can detect compatibale port types', function () {
|
||||
// Equal types
|
||||
expect(NodeLibrary.instance.canCastPortTypes('string', 'string')).toBe(true);
|
||||
expect(
|
||||
NodeLibrary.instance.canCastPortTypes(
|
||||
{
|
||||
name: 'string'
|
||||
},
|
||||
'string'
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
NodeLibrary.instance.canCastPortTypes('string', {
|
||||
name: 'string'
|
||||
})
|
||||
).toBe(true);
|
||||
|
||||
// Not equal and cannot cast
|
||||
expect(
|
||||
NodeLibrary.instance.canCastPortTypes('string', {
|
||||
name: 'signal'
|
||||
})
|
||||
).toBe(false);
|
||||
|
||||
// Star types are compatible with everything
|
||||
expect(
|
||||
NodeLibrary.instance.canCastPortTypes('*', {
|
||||
name: 'number'
|
||||
})
|
||||
).toBe(true);
|
||||
|
||||
// Casting
|
||||
expect(
|
||||
NodeLibrary.instance.canCastPortTypes('number', {
|
||||
name: 'string'
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('can find compatible port types', function () {
|
||||
// Single type
|
||||
expect(
|
||||
NodeLibrary.instance.findCompatiblePortType([
|
||||
{
|
||||
direction: 'to',
|
||||
type: 'string'
|
||||
}
|
||||
])
|
||||
).toEqual({
|
||||
name: 'string'
|
||||
});
|
||||
|
||||
// Multiple connections, same type, with modifiers
|
||||
expect(
|
||||
NodeLibrary.instance.findCompatiblePortType([
|
||||
{
|
||||
direction: 'to',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
direction: 'from',
|
||||
type: {
|
||||
name: 'string',
|
||||
multiline: true
|
||||
}
|
||||
}
|
||||
])
|
||||
).toEqual({
|
||||
name: 'string',
|
||||
multiline: true
|
||||
});
|
||||
|
||||
// Is enums merged
|
||||
expect(
|
||||
NodeLibrary.instance.findCompatiblePortType([
|
||||
{
|
||||
direction: 'to',
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: ['a', 'b']
|
||||
}
|
||||
},
|
||||
{
|
||||
direction: 'from',
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: ['b', 'c']
|
||||
}
|
||||
}
|
||||
])
|
||||
).toEqual({
|
||||
name: 'enum',
|
||||
enums: ['a', 'b', 'c']
|
||||
});
|
||||
|
||||
// Find compatible type and merge
|
||||
expect(
|
||||
NodeLibrary.instance.findCompatiblePortType([
|
||||
{
|
||||
direction: 'to',
|
||||
type: {
|
||||
name: 'number',
|
||||
allowConnectionsOnly: true
|
||||
}
|
||||
},
|
||||
{
|
||||
direction: 'to',
|
||||
type: 'boolean'
|
||||
}
|
||||
])
|
||||
).toEqual({
|
||||
name: 'number',
|
||||
allowConnectionsOnly: true
|
||||
});
|
||||
|
||||
// Find compatible type, mixed direction
|
||||
expect(
|
||||
NodeLibrary.instance.findCompatiblePortType([
|
||||
{
|
||||
direction: 'to',
|
||||
type: {
|
||||
name: 'number',
|
||||
allowEditOnly: true
|
||||
}
|
||||
},
|
||||
{
|
||||
direction: 'to',
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
direction: 'from',
|
||||
type: 'number'
|
||||
}
|
||||
])
|
||||
).toEqual({
|
||||
name: 'number',
|
||||
allowEditOnly: true
|
||||
});
|
||||
|
||||
// Find compatible type, mixed direction
|
||||
expect(
|
||||
NodeLibrary.instance.findCompatiblePortType([
|
||||
{
|
||||
direction: 'to',
|
||||
type: {
|
||||
name: 'number'
|
||||
}
|
||||
},
|
||||
{
|
||||
direction: 'to',
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
direction: 'from',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
direction: 'to',
|
||||
type: 'color'
|
||||
}
|
||||
])
|
||||
).toEqual({
|
||||
name: 'string'
|
||||
});
|
||||
|
||||
// Can't find compatible type
|
||||
expect(
|
||||
NodeLibrary.instance.findCompatiblePortType([
|
||||
{
|
||||
direction: 'to',
|
||||
type: {
|
||||
name: 'number',
|
||||
allowEditOnly: true
|
||||
}
|
||||
},
|
||||
{
|
||||
direction: 'to',
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
direction: 'from',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
direction: 'to',
|
||||
type: 'referece'
|
||||
}
|
||||
])
|
||||
).toBe(undefined);
|
||||
});
|
||||
|
||||
it('can reload library', function () {
|
||||
var p = ProjectModel.fromJSON(getProject());
|
||||
NodeLibrary.instance.registerModule(p);
|
||||
|
||||
var n1 = p.components[0].graph.findNodeWithId('A');
|
||||
var n2 = p.components[0].graph.findNodeWithId('B');
|
||||
var n3 = p.components[0].graph.findNodeWithId('C');
|
||||
var n4 = p.components[0].graph.findNodeWithId('D');
|
||||
|
||||
expect(n1.type instanceof BasicNodeType).toBe(true);
|
||||
expect(n1.type.name).toBe('image');
|
||||
expect(n2.type instanceof UnknownNodeType).toBe(true);
|
||||
|
||||
// Update library
|
||||
NodeLibraryData.nodetypes.push({
|
||||
name: 'newimage',
|
||||
allowAsChild: true,
|
||||
allowAsExclusiveRootOnly: true,
|
||||
category: 'visuals',
|
||||
ports: [
|
||||
{
|
||||
name: 'x',
|
||||
type: {
|
||||
name: 'number'
|
||||
},
|
||||
plug: 'input'
|
||||
}
|
||||
]
|
||||
});
|
||||
NodeLibrary.instance.reload();
|
||||
|
||||
// Both should be OK now
|
||||
expect(n1.type instanceof BasicNodeType).toBe(true);
|
||||
expect(n1.type.name).toBe('image');
|
||||
|
||||
expect(n2.type instanceof BasicNodeType).toBe(true);
|
||||
expect(n2.type.name).toBe('newimage');
|
||||
|
||||
NodeLibrary.instance.unregisterModule(p);
|
||||
});
|
||||
|
||||
it('can get types by name', () => {
|
||||
expect(NodeLibrary.instance.getNodeTypeWithName('comp1')).toBeUndefined();
|
||||
//get basic type from node library
|
||||
expect(NodeLibrary.instance.getNodeTypeWithName('image')).toBeInstanceOf(BasicNodeType);
|
||||
|
||||
//get component type from a project
|
||||
const p = ProjectModel.fromJSON(getProject());
|
||||
NodeLibrary.instance.registerModule(p);
|
||||
expect(NodeLibrary.instance.getNodeTypeWithName('comp1')).toBeInstanceOf(ComponentModel);
|
||||
|
||||
//add a new component and get the type
|
||||
const newComponent = ComponentModel.fromJSON({
|
||||
name: 'comp2',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'B',
|
||||
type: 'image'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
p.addComponent(newComponent);
|
||||
expect(NodeLibrary.instance.getNodeTypeWithName('comp2')).toBe(newComponent);
|
||||
|
||||
//remove the component again
|
||||
p.removeComponent(newComponent);
|
||||
expect(NodeLibrary.instance.getNodeTypeWithName('comp2')).toBeUndefined();
|
||||
|
||||
//unregister project and make sure the components aren't available anymore
|
||||
NodeLibrary.instance.unregisterModule(p);
|
||||
expect(NodeLibrary.instance.getNodeTypeWithName('comp1')).toBeUndefined();
|
||||
expect(NodeLibrary.instance.getNodeTypeWithName('comp2')).toBeUndefined();
|
||||
});
|
||||
|
||||
function getProject() {
|
||||
return {
|
||||
components: [
|
||||
{
|
||||
name: 'comp1',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'A',
|
||||
type: 'image'
|
||||
},
|
||||
{
|
||||
id: 'B',
|
||||
type: 'newimage'
|
||||
},
|
||||
{
|
||||
id: 'C',
|
||||
type: 'Event Sender',
|
||||
parameters: {
|
||||
channel: 'Channel0'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'D',
|
||||
type: 'Event Receiver'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
751
packages/noodl-editor/tests/nodegraph/nodelibrary.js
Normal file
751
packages/noodl-editor/tests/nodegraph/nodelibrary.js
Normal file
@@ -0,0 +1,751 @@
|
||||
function createTestNodeLibrary() {
|
||||
return {
|
||||
projectsettings: {
|
||||
dynamicports: [],
|
||||
ports: [
|
||||
{
|
||||
name: 'settingIgnoredInExport',
|
||||
type: 'string',
|
||||
ignoreInExport: true
|
||||
},
|
||||
{
|
||||
name: 'someSetting',
|
||||
type: 'string',
|
||||
ignoreInExport: false
|
||||
},
|
||||
{
|
||||
name: 'someSetting2',
|
||||
type: 'string'
|
||||
}
|
||||
]
|
||||
},
|
||||
typecasts: [
|
||||
{
|
||||
from: 'boolean',
|
||||
to: []
|
||||
},
|
||||
{
|
||||
from: 'number',
|
||||
to: ['string', 'boolean']
|
||||
},
|
||||
{
|
||||
from: 'string',
|
||||
to: ['number', 'boolean', 'color']
|
||||
}
|
||||
],
|
||||
dynamicports: [
|
||||
{
|
||||
type: 'conditionalports',
|
||||
name: 'basic'
|
||||
},
|
||||
{
|
||||
type: 'expand',
|
||||
name: 'basic'
|
||||
},
|
||||
{
|
||||
type: 'portchannel',
|
||||
name: 'event-sender-channel',
|
||||
channelPortname: 'channel',
|
||||
typename: 'Event Sender',
|
||||
ignorePorts: ['channel']
|
||||
},
|
||||
{
|
||||
type: 'numbered',
|
||||
name: 'basic-number',
|
||||
port: {
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'regexp',
|
||||
name: 'expression-js',
|
||||
filters: [
|
||||
{
|
||||
type: 'replace',
|
||||
comment: 'Removed javascript style comments',
|
||||
regexp: '((\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/))',
|
||||
args: 'mg',
|
||||
with: ''
|
||||
},
|
||||
{
|
||||
type: 'replace',
|
||||
regexp: '"[^"]+"',
|
||||
args: 'g',
|
||||
with: ''
|
||||
},
|
||||
{
|
||||
type: 'replace',
|
||||
regexp: "'[^']+'",
|
||||
args: 'g',
|
||||
with: ''
|
||||
},
|
||||
{
|
||||
type: 'replace',
|
||||
regexp:
|
||||
'(break|case|class|catch|const|continue|debugger|default|delete|do|else|export|extends|finally|for|function|if|import|in|instanceof|let|new|return|super|switch|this|throw|try|typeof|var|void|while|with|yield)',
|
||||
args: 'g',
|
||||
with: ''
|
||||
},
|
||||
{
|
||||
type: 'replace',
|
||||
regexp: '\\s',
|
||||
args: 'g',
|
||||
with: ''
|
||||
},
|
||||
{
|
||||
type: 'ignore',
|
||||
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\()'
|
||||
},
|
||||
{
|
||||
type: 'ignore',
|
||||
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\=)'
|
||||
},
|
||||
{
|
||||
type: 'ports',
|
||||
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\:boolean)',
|
||||
args: 'g',
|
||||
port: {
|
||||
type: {
|
||||
name: 'boolean'
|
||||
},
|
||||
plug: 'input'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'replace',
|
||||
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*\\:boolean',
|
||||
args: 'g'
|
||||
},
|
||||
{
|
||||
type: 'ports',
|
||||
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\:string)',
|
||||
args: 'g',
|
||||
port: {
|
||||
type: {
|
||||
name: 'string'
|
||||
},
|
||||
plug: 'input'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'replace',
|
||||
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*\\:string',
|
||||
args: 'g'
|
||||
},
|
||||
{
|
||||
type: 'ports',
|
||||
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*(?=\\:number)',
|
||||
args: 'g',
|
||||
port: {
|
||||
type: {
|
||||
name: 'number'
|
||||
},
|
||||
plug: 'input'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'replace',
|
||||
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*\\:number',
|
||||
args: 'g'
|
||||
},
|
||||
{
|
||||
type: 'ports',
|
||||
regexp: '([a-z]|[A-Z])([a-z]|\\.|[A-Z]|[0-9])*',
|
||||
args: 'g',
|
||||
port: {
|
||||
type: {
|
||||
name: '=',
|
||||
default: 'string',
|
||||
allowedTypes: ['string', 'boolean', 'number']
|
||||
},
|
||||
plug: 'input'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
nodetypes: [
|
||||
{
|
||||
name: 'Component Outputs',
|
||||
haveComponentPorts: true,
|
||||
color: 'component',
|
||||
ports: []
|
||||
},
|
||||
{
|
||||
name: 'Component Inputs',
|
||||
color: 'component',
|
||||
haveComponentPorts: true,
|
||||
ports: []
|
||||
},
|
||||
{
|
||||
name: 'Component Children',
|
||||
color: 'component',
|
||||
category: 'visuals',
|
||||
allowSingleInstanceOnly: true,
|
||||
haveComponentChildren: ['visuals']
|
||||
},
|
||||
{
|
||||
name: 'Component Modifier Children',
|
||||
color: 'component',
|
||||
category: 'modifiers',
|
||||
allowSingleInstanceOnly: true,
|
||||
haveComponentChildren: ['modifiers']
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
version: 2,
|
||||
allowAsChild: true,
|
||||
allowAsExportRoot: true,
|
||||
category: 'visuals',
|
||||
allowChildrenWithCategory: ['visuals'],
|
||||
ports: [
|
||||
{
|
||||
name: 'compref',
|
||||
type: 'component',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
group: 'test',
|
||||
name: 'x',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', '%']
|
||||
},
|
||||
default: {
|
||||
value: 10,
|
||||
unit: '%'
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
group: 'test',
|
||||
name: 'y',
|
||||
type: {
|
||||
name: 'number'
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'opacity',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
displayName: 'AlignX',
|
||||
name: 'alignX',
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: [
|
||||
{
|
||||
value: 'left',
|
||||
label: 'Left'
|
||||
},
|
||||
{
|
||||
value: 'center',
|
||||
label: 'Center'
|
||||
},
|
||||
{
|
||||
value: 'right',
|
||||
label: 'Right'
|
||||
}
|
||||
]
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'alignY',
|
||||
type: {
|
||||
name: 'string',
|
||||
enums: ['top', 'center', 'bottom']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'scaleX',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'scaleY',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'height',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'fill',
|
||||
type: {
|
||||
name: 'string',
|
||||
enums: ['parent', 'width', 'height', 'aspectFill', 'aspectFit']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'pivotX',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'pivotY',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'layoutX',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'layoutY',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'depth',
|
||||
type: {
|
||||
name: 'number',
|
||||
casts: ['boolean']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'rotationX',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'rotationY',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'rotationZ',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'touchPropagation',
|
||||
type: {
|
||||
name: 'boolean',
|
||||
casts: ['number']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'layout',
|
||||
type: {
|
||||
name: 'string',
|
||||
enums: ['stackVertical', 'stackHorizontal', 'flowVertical', 'flowHorizontal', 'none']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'clip',
|
||||
type: 'boolean',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
group: 'test',
|
||||
name: 'screenX',
|
||||
type: 'number',
|
||||
plug: 'output'
|
||||
},
|
||||
{
|
||||
group: 'test',
|
||||
name: 'screenY',
|
||||
type: 'number',
|
||||
plug: 'output'
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'number',
|
||||
plug: 'output'
|
||||
},
|
||||
{
|
||||
name: 'height',
|
||||
type: 'number',
|
||||
plug: 'output'
|
||||
},
|
||||
{
|
||||
name: 'this',
|
||||
type: 'reference',
|
||||
plug: 'output'
|
||||
},
|
||||
{
|
||||
name: 'clipOut',
|
||||
type: 'boolean',
|
||||
plug: 'output'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
allowAsChild: true,
|
||||
allowAsExclusiveRootOnly: true,
|
||||
category: 'visuals',
|
||||
ports: [
|
||||
{
|
||||
name: 'x',
|
||||
type: {
|
||||
name: 'number'
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'y',
|
||||
type: {
|
||||
name: 'number'
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'opacity',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'alignX',
|
||||
type: {
|
||||
name: 'string',
|
||||
enums: ['left', 'center', 'right']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'alignY',
|
||||
type: {
|
||||
name: 'string',
|
||||
enums: ['top', 'center', 'bottom']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'scaleX',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'scaleY',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'height',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'fill',
|
||||
type: {
|
||||
name: 'string',
|
||||
enums: ['parent', 'width', 'height', 'aspectFill', 'aspectFit']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'pivotX',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'pivotY',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'layoutX',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'layoutY',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'depth',
|
||||
type: {
|
||||
name: 'number',
|
||||
casts: ['boolean']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'rotationX',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'rotationY',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'rotationZ',
|
||||
type: 'number',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'touchPropagation',
|
||||
type: {
|
||||
name: 'boolean',
|
||||
casts: ['number']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'blendMode',
|
||||
type: {
|
||||
name: 'string',
|
||||
enums: ['normal', 'solid', 'additive', 'multiply']
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
type: 'color',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: {
|
||||
name: 'string'
|
||||
},
|
||||
allowEditOnly: true,
|
||||
plug: 'input/output'
|
||||
},
|
||||
{
|
||||
name: 'image2',
|
||||
type: {
|
||||
name: 'image'
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'font',
|
||||
type: {
|
||||
name: 'font'
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'css',
|
||||
type: {
|
||||
name: 'string',
|
||||
codeeditor: 'css'
|
||||
},
|
||||
allowEditOnly: true,
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'shader',
|
||||
type: 'shader',
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
group: 'gruppen',
|
||||
name: 'screenX',
|
||||
type: 'number',
|
||||
plug: 'output'
|
||||
},
|
||||
{
|
||||
name: 'screenY',
|
||||
type: 'number',
|
||||
plug: 'output'
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'number',
|
||||
plug: 'output'
|
||||
},
|
||||
{
|
||||
name: 'height',
|
||||
type: 'number',
|
||||
plug: 'output'
|
||||
},
|
||||
{
|
||||
name: 'this',
|
||||
type: 'reference',
|
||||
plug: 'output'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'animation'
|
||||
},
|
||||
{
|
||||
name: 'scaleModifier',
|
||||
category: 'modifiers'
|
||||
},
|
||||
{
|
||||
name: 'nodeWithNumberedPorts',
|
||||
dynamicports: [
|
||||
{
|
||||
name: 'numbered/basic-number',
|
||||
prefix: 'my number',
|
||||
displayPrefix: 'My number',
|
||||
port: {
|
||||
group: 'My group'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'nodeWithNumberedPortsAndSelectors',
|
||||
dynamicports: [
|
||||
{
|
||||
name: 'numbered/basic-number',
|
||||
prefix: 'my number',
|
||||
displayPrefix: 'My number',
|
||||
port: {
|
||||
group: 'My group'
|
||||
},
|
||||
selectors: [
|
||||
{
|
||||
name: 'startAt',
|
||||
displayName: 'Start at',
|
||||
group: 'My selectors'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'javascript expression',
|
||||
usePortAsLabel: 'expression',
|
||||
dynamicports: [
|
||||
{
|
||||
name: 'regexp/expression-js',
|
||||
port: 'expression'
|
||||
}
|
||||
],
|
||||
ports: [
|
||||
{
|
||||
name: 'expression',
|
||||
type: {
|
||||
name: 'string',
|
||||
multiline: true
|
||||
},
|
||||
plug: 'input'
|
||||
},
|
||||
{
|
||||
name: 'result',
|
||||
type: '=',
|
||||
plug: 'output'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Event Sender',
|
||||
ports: [
|
||||
{
|
||||
name: 'channel',
|
||||
type: 'string',
|
||||
plug: 'input'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Event Receiver',
|
||||
dynamicports: [
|
||||
{
|
||||
name: 'portchannel/event-sender-channel',
|
||||
channelPort: {
|
||||
name: 'channel',
|
||||
displayName: 'Channel',
|
||||
plug: 'input'
|
||||
},
|
||||
port: {
|
||||
type: '*',
|
||||
plug: 'output'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Anim',
|
||||
dynamicports: [
|
||||
{
|
||||
name: 'conditionalports/basic',
|
||||
condition: 'type = typeA',
|
||||
ports: [
|
||||
{
|
||||
name: 'from',
|
||||
plug: 'input',
|
||||
type: 'number'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
name: 'conditionalports/basic',
|
||||
condition: 'type != typeA',
|
||||
ports: [
|
||||
{
|
||||
name: 'to',
|
||||
plug: 'input',
|
||||
type: 'number'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
ports: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'string',
|
||||
plug: 'input'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
name: 'ExpandPorts',
|
||||
dynamicports: [
|
||||
{
|
||||
name: 'expand/basic',
|
||||
indexStep: 100,
|
||||
template: [
|
||||
{
|
||||
name: '{{portname}}.A',
|
||||
plug: 'input',
|
||||
type: 'number',
|
||||
index: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'expand/basic',
|
||||
indexStep: 100,
|
||||
condition: "'{{portname}}.A' = test OR '{{portname}}.A' NOT SET",
|
||||
template: [
|
||||
{
|
||||
name: '{{portname}}.B',
|
||||
plug: 'input',
|
||||
type: 'number',
|
||||
index: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = createTestNodeLibrary();
|
||||
92
packages/noodl-editor/tests/nodegraph/propertyeditor.js
Normal file
92
packages/noodl-editor/tests/nodegraph/propertyeditor.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const { PropertyEditor } = require('@noodl-views/panels/propertyeditor/propertyeditor');
|
||||
const { NodeGraphNodeRename } = require('@noodl-views/panels/propertyeditor');
|
||||
|
||||
const { ProjectModel } = require('@noodl-models/projectmodel');
|
||||
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
|
||||
const { UndoQueue } = require('@noodl-models/undo-queue-model');
|
||||
|
||||
describe('Property editor panel unit tests', function () {
|
||||
var pe, n, c;
|
||||
|
||||
beforeEach(() => {
|
||||
ProjectModel.instance = ProjectModel.fromJSON(project);
|
||||
NodeLibrary.instance.registerModule(ProjectModel.instance);
|
||||
|
||||
c = ProjectModel.instance.getComponentWithName('Root');
|
||||
n = c.graph.findNodeWithId('A');
|
||||
pe = new PropertyEditor({
|
||||
model: n
|
||||
});
|
||||
pe.render();
|
||||
});
|
||||
|
||||
afterEach((done) => {
|
||||
//some tests schedule renders with a setTimeout
|
||||
//so schedule one ourselves before we clean up so the console
|
||||
//isn't filled with errors
|
||||
setTimeout(() => {
|
||||
ProjectModel.instance = undefined;
|
||||
done();
|
||||
}, 1);
|
||||
});
|
||||
|
||||
it('can delete node and undo', function () {
|
||||
pe.performDelete();
|
||||
|
||||
expect(c.graph.findNodeWithId('A')).toBe(undefined);
|
||||
expect(c.graph.connections.length).toBe(0);
|
||||
|
||||
UndoQueue.instance.undo();
|
||||
|
||||
expect(c.graph.findNodeWithId('A')).not.toBe(undefined);
|
||||
expect(c.graph.connections.length).toBe(1);
|
||||
});
|
||||
|
||||
it('can rename and undo', function () {
|
||||
NodeGraphNodeRename(n, 'test');
|
||||
|
||||
expect(c.graph.findNodeWithId('A').label).toBe('test');
|
||||
|
||||
UndoQueue.instance.undo();
|
||||
|
||||
expect(c.graph.findNodeWithId('A').label).toBe('group');
|
||||
});
|
||||
|
||||
it('can edit parameter and undo', function () {
|
||||
pe.portsView.setParameter('alpha', 0.5);
|
||||
|
||||
expect(c.graph.findNodeWithId('A').parameters['alpha']).toBe(0.5);
|
||||
|
||||
UndoQueue.instance.undo();
|
||||
|
||||
expect(c.graph.findNodeWithId('A').parameters['alpha']).toBe(undefined);
|
||||
});
|
||||
|
||||
var project = {
|
||||
components: [
|
||||
{
|
||||
name: 'Root',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'A',
|
||||
type: 'group'
|
||||
},
|
||||
{
|
||||
id: 'B',
|
||||
type: 'group'
|
||||
}
|
||||
],
|
||||
connections: [
|
||||
{
|
||||
fromId: 'A',
|
||||
toId: 'B',
|
||||
fromProperty: 'x',
|
||||
toProperty: 'y'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
234
packages/noodl-editor/tests/nodegraph/typechangepropagation.js
Normal file
234
packages/noodl-editor/tests/nodegraph/typechangepropagation.js
Normal file
@@ -0,0 +1,234 @@
|
||||
const { ProjectModel } = require('@noodl-models/projectmodel');
|
||||
const NodeLibrary = require('@noodl-models/nodelibrary').NodeLibrary;
|
||||
const { ComponentModel } = require('@noodl-models/componentmodel');
|
||||
|
||||
describe('Type change propagation', function () {
|
||||
var p;
|
||||
|
||||
it('can load project', function () {
|
||||
p = ProjectModel.fromJSON(project1);
|
||||
NodeLibrary.instance.registerModule(p);
|
||||
expect(p).not.toBe(undefined);
|
||||
});
|
||||
|
||||
xit('can remove connections and type will propagate', function () {
|
||||
// Remove connections
|
||||
var comp2 = p.getComponentWithName('/comp2');
|
||||
comp2.graph.removeConnection(comp2.graph.connections[0]);
|
||||
comp2.graph.removeConnection(comp2.graph.connections[0]);
|
||||
|
||||
// Component types should now have propagated and been set to undefined
|
||||
var comp1 = p.getComponentWithName('/comp1');
|
||||
expect(NodeLibrary.nameForPortType(comp1.findPortWithName('in1').type)).toBe(undefined);
|
||||
expect(NodeLibrary.nameForPortType(comp1.findPortWithName('out2').type)).toBe(undefined);
|
||||
});
|
||||
|
||||
xit('can reconnect and type will propagate', function () {
|
||||
// Add connections
|
||||
var comp2 = p.getComponentWithName('/comp2');
|
||||
var comp1 = p.getComponentWithName('/comp1');
|
||||
|
||||
comp2.graph.addConnection({
|
||||
fromId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
|
||||
fromProperty: 'screenX',
|
||||
toId: '6783bee9-1225-5840-354f-348a3d270b6d',
|
||||
toProperty: 'out2'
|
||||
});
|
||||
comp2.graph.addConnection({
|
||||
fromId: 'b1a816da-5023-18a2-59ba-9c58be5cd073',
|
||||
fromProperty: 'in1',
|
||||
toId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
|
||||
toProperty: 'image'
|
||||
});
|
||||
|
||||
// The types should have propagetd to comp1
|
||||
expect(NodeLibrary.nameForPortType(comp1.findPortWithName('in1').type)).toBe('string');
|
||||
expect(NodeLibrary.nameForPortType(comp1.findPortWithName('out2').type)).toBe('number');
|
||||
});
|
||||
|
||||
xit('removing a type results in undefined type', function () {
|
||||
var n1 = p.getComponentWithName('/comp').graph.findNodeWithId('A');
|
||||
p.getComponentWithName('/comp').graph.evaluateHealth();
|
||||
|
||||
expect(n1.getHealth().healthy).toBe(false);
|
||||
expect(n1.type.localName).toBe('notfound');
|
||||
expect(n1.type.fullName).toBe('/notfound');
|
||||
|
||||
p.addComponent(
|
||||
ComponentModel.fromJSON({
|
||||
name: '/notfound',
|
||||
graph: {}
|
||||
})
|
||||
);
|
||||
p.getComponentWithName('/comp').graph.evaluateHealth();
|
||||
|
||||
expect(n1.getHealth().healthy).toBe(true); // Node type should now be found
|
||||
});
|
||||
|
||||
xit('can remove type and create new one with new name', function () {
|
||||
var n1 = p.getComponentWithName('/comp').graph.findNodeWithId('A');
|
||||
|
||||
var comp = p.getComponentWithName('/notfound');
|
||||
p.removeComponent(comp);
|
||||
p.getComponentWithName('/comp').graph.evaluateHealth();
|
||||
|
||||
expect(n1.getHealth().healthy).toBe(false); // Health should be bad again
|
||||
var comp2 = ComponentModel.fromJSON({
|
||||
name: '/notfound',
|
||||
graph: {}
|
||||
});
|
||||
|
||||
p.addComponent(comp2);
|
||||
p.getComponentWithName('/comp').graph.evaluateHealth();
|
||||
expect(n1.getHealth().healthy).toBe(true); // The new type should be resolved
|
||||
expect(n1.type).toBe(comp2);
|
||||
expect(n1.toJSON().type).toBe('/notfound');
|
||||
});
|
||||
|
||||
xit('can rename components, type is changed', function () {
|
||||
var n1 = p.getComponentWithName('/comp').graph.findNodeWithId('A');
|
||||
|
||||
// rename the type
|
||||
p.renameComponentWithName('/notfound', '/notfound2');
|
||||
expect(n1.getHealth().healthy).toBe(true); // The new type should be resolved
|
||||
expect(n1.typename).toBe('/notfound2');
|
||||
});
|
||||
|
||||
var project1 = {
|
||||
components: [
|
||||
{
|
||||
name: '/comp',
|
||||
graph: {
|
||||
roots: [
|
||||
{
|
||||
id: 'A',
|
||||
type: '/notfound'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/comp1',
|
||||
graph: {
|
||||
connections: [
|
||||
{
|
||||
fromId: '7f6b3382-b5c9-ae85-3614-3a3d0b31e4ce',
|
||||
fromProperty: 'out2',
|
||||
toId: 'd72cfb77-8515-8612-efed-c92e3f533b8b',
|
||||
toProperty: 'out2'
|
||||
},
|
||||
{
|
||||
fromId: '15a67805-e2ee-1fa4-b86a-4efc04e7028d',
|
||||
fromProperty: 'in1',
|
||||
toId: '7f6b3382-b5c9-ae85-3614-3a3d0b31e4ce',
|
||||
toProperty: 'in1'
|
||||
}
|
||||
],
|
||||
roots: [
|
||||
{
|
||||
id: 'd72cfb77-8515-8612-efed-c92e3f533b8b',
|
||||
type: 'Component Outputs',
|
||||
x: 274,
|
||||
y: 185,
|
||||
parameters: {},
|
||||
children: [],
|
||||
ports: [
|
||||
{
|
||||
name: 'out2',
|
||||
type: '*',
|
||||
plug: 'input'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '7f6b3382-b5c9-ae85-3614-3a3d0b31e4ce',
|
||||
type: '/comp2',
|
||||
x: 530,
|
||||
y: 185,
|
||||
parameters: {},
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: '15a67805-e2ee-1fa4-b86a-4efc04e7028d',
|
||||
type: 'Component Inputs',
|
||||
x: 792,
|
||||
y: 186,
|
||||
parameters: {},
|
||||
children: [],
|
||||
ports: [
|
||||
{
|
||||
name: 'in1',
|
||||
type: '*',
|
||||
plug: 'output'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '/comp2',
|
||||
visual: true,
|
||||
visualRootId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
|
||||
canHaveVisualChildren: false,
|
||||
graph: {
|
||||
connections: [
|
||||
{
|
||||
fromId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
|
||||
fromProperty: 'this',
|
||||
toId: '6783bee9-1225-5840-354f-348a3d270b6d',
|
||||
toProperty: 'out2'
|
||||
},
|
||||
{
|
||||
fromId: 'b1a816da-5023-18a2-59ba-9c58be5cd073',
|
||||
fromProperty: 'in1',
|
||||
toId: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
|
||||
toProperty: 'x'
|
||||
}
|
||||
],
|
||||
roots: [
|
||||
{
|
||||
id: '6783bee9-1225-5840-354f-348a3d270b6d',
|
||||
type: 'Component Outputs',
|
||||
x: 686,
|
||||
y: 150,
|
||||
parameters: {},
|
||||
children: [],
|
||||
ports: [
|
||||
{
|
||||
name: 'out2',
|
||||
type: '*',
|
||||
plug: 'input'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'a05abb49-625e-8bd6-7183-7ea07a46e1d4',
|
||||
type: 'image',
|
||||
x: 426,
|
||||
y: 147,
|
||||
parameters: {},
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 'b1a816da-5023-18a2-59ba-9c58be5cd073',
|
||||
type: 'Component Inputs',
|
||||
x: 169,
|
||||
y: 148,
|
||||
parameters: {},
|
||||
children: [],
|
||||
ports: [
|
||||
{
|
||||
name: 'in1',
|
||||
type: '*',
|
||||
plug: 'output'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
name: 'proj1'
|
||||
};
|
||||
});
|
||||
32
packages/noodl-editor/tests/nodegraph/warnings-model-spec.js
Normal file
32
packages/noodl-editor/tests/nodegraph/warnings-model-spec.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const WarningsModel = require('@noodl-models/warningsmodel').WarningsModel;
|
||||
|
||||
describe('Warnings model', function () {
|
||||
beforeEach(() => {
|
||||
//since warningsmodel is global there can be warnings left over from other tests, make sure to clear them
|
||||
WarningsModel.instance.clearAllWarnings();
|
||||
});
|
||||
|
||||
it('can remove warnings using a callback for matching', function () {
|
||||
WarningsModel.instance.setWarning(
|
||||
{
|
||||
key: 'test-warning-remove'
|
||||
},
|
||||
{
|
||||
message: 'test warning'
|
||||
}
|
||||
);
|
||||
WarningsModel.instance.setWarning(
|
||||
{
|
||||
key: 'test-warning'
|
||||
},
|
||||
{
|
||||
message: 'test warning'
|
||||
}
|
||||
);
|
||||
|
||||
expect(WarningsModel.instance.getTotalNumberOfWarnings()).toBe(2);
|
||||
|
||||
WarningsModel.instance.clearWarningsForRefMatching((ref) => ref.key.includes('remove'));
|
||||
expect(WarningsModel.instance.getTotalNumberOfWarnings()).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user