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:
@@ -0,0 +1,55 @@
|
||||
const ActiveWarnings = require('./editorconnection.activewarnings');
|
||||
|
||||
describe('Tracks active warnings that are sent to the editor', ()=>{
|
||||
let activeWarnings;
|
||||
|
||||
beforeEach(async ()=>{
|
||||
activeWarnings = new ActiveWarnings();
|
||||
});
|
||||
|
||||
|
||||
test('Set and clear a warning', () => {
|
||||
expect(activeWarnings.setWarning('testId', 'testKey', 'testWarning')).toBe(true);
|
||||
|
||||
//these warnings shouldnt exist
|
||||
expect(activeWarnings.clearWarning('testId', 'otherKey')).toEqual(false);
|
||||
expect(activeWarnings.clearWarning('otherId', 'testKey')).toEqual(false);
|
||||
|
||||
//this warning is the one we set
|
||||
expect(activeWarnings.clearWarning('testId', 'testKey')).toEqual(true);
|
||||
|
||||
//and now the warning should be gone
|
||||
expect(activeWarnings.clearWarning('testId', 'testKey')).toEqual(false);
|
||||
});
|
||||
|
||||
test('Set and clear multiple warning on one node', () => {
|
||||
expect(activeWarnings.setWarning('testId', 'testKey1', 'testWarning')).toBe(true);
|
||||
expect(activeWarnings.setWarning('testId', 'testKey2', 'testWarning')).toBe(true);
|
||||
|
||||
expect(activeWarnings.clearWarning('testId', 'testKey1')).toEqual(true);
|
||||
expect(activeWarnings.clearWarning('testId', 'testKey1')).toEqual(false);
|
||||
|
||||
expect(activeWarnings.clearWarning('testId', 'testKey2')).toEqual(true);
|
||||
expect(activeWarnings.clearWarning('testId', 'testKey2')).toEqual(false);
|
||||
});
|
||||
|
||||
test('Clear multiple warnings at once', () => {
|
||||
expect(activeWarnings.setWarning('testId1', 'testKey1', 'testWarning')).toBe(true);
|
||||
expect(activeWarnings.setWarning('testId1', 'testKey2', 'testWarning')).toBe(true);
|
||||
expect(activeWarnings.setWarning('testId2', 'testKey1', 'testWarning')).toBe(true);
|
||||
|
||||
expect(activeWarnings.clearWarnings('testId3')).toEqual(false);
|
||||
|
||||
expect(activeWarnings.clearWarnings('testId1')).toEqual(true);
|
||||
expect(activeWarnings.clearWarnings('testId1')).toEqual(false);
|
||||
|
||||
expect(activeWarnings.clearWarnings('testId2')).toEqual(true);
|
||||
expect(activeWarnings.clearWarnings('testId2')).toEqual(false);
|
||||
});
|
||||
|
||||
test('Set same warning multiple times', () => {
|
||||
expect(activeWarnings.setWarning('testId', 'testKey', 'testWarning')).toBe(true);
|
||||
expect(activeWarnings.setWarning('testId', 'testKey', 'testWarning')).toBe(false);
|
||||
expect(activeWarnings.setWarning('testId', 'testKey', 'testWarning2')).toBe(true);
|
||||
});
|
||||
});
|
||||
98
packages/noodl-runtime/test/editormodeleventshandler.test.js
Normal file
98
packages/noodl-runtime/test/editormodeleventshandler.test.js
Normal file
@@ -0,0 +1,98 @@
|
||||
const handleEvent = require('./editormodeleventshandler').handleEvent;
|
||||
const GraphModel = require('./models/graphmodel');
|
||||
const NodeContext = require('./nodecontext');
|
||||
|
||||
describe('Component ports update when on the componentPortsUpdated event', ()=>{
|
||||
let graphModel;
|
||||
let nodeContext;
|
||||
|
||||
beforeEach(async ()=>{
|
||||
nodeContext = new NodeContext();
|
||||
graphModel = new GraphModel();
|
||||
await graphModel.importComponentFromEditorData({
|
||||
id: 'component',
|
||||
name: 'testComponent',
|
||||
ports: [
|
||||
{ name: "testInput1", plug: "input", type: 'string'},
|
||||
{ name: "testInput2", plug: "input", type: 'string'},
|
||||
{ name: "testOutput1", plug: "output", type: 'string'},
|
||||
{ name: "testOutput2", plug: "output", type: 'string'}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
function updateAndMatchPorts(newInputPorts, newOutputPorts) {
|
||||
|
||||
handleEvent(nodeContext, graphModel, {
|
||||
type: 'componentPortsUpdated',
|
||||
componentName: 'testComponent',
|
||||
ports: newInputPorts.concat(newOutputPorts)
|
||||
});
|
||||
|
||||
const componentModel = graphModel.getComponentWithName('testComponent');
|
||||
const inputPorts = componentModel.getInputPorts();
|
||||
for(const port of newInputPorts) {
|
||||
expect(inputPorts.hasOwnProperty(port.name)).toBeTruthy();
|
||||
expect(inputPorts[port.name].type).toEqual(port.type);
|
||||
}
|
||||
expect(Object.keys(inputPorts).length).toBe(newInputPorts.length);
|
||||
|
||||
const outputPorts = componentModel.getOutputPorts();
|
||||
for(const port of newOutputPorts) {
|
||||
expect(outputPorts.hasOwnProperty(port.name)).toBeTruthy();
|
||||
expect(outputPorts[port.name].type).toEqual(port.type);
|
||||
}
|
||||
expect(Object.keys(outputPorts).length).toBe(newOutputPorts.length);
|
||||
}
|
||||
|
||||
test('Input ports are added', () => {
|
||||
updateAndMatchPorts([
|
||||
{ name: "testInput1", plug: "input", type: 'string'},
|
||||
{ name: "testInput2", plug: "input", type: 'string'},
|
||||
],
|
||||
[
|
||||
{name: "testOutput1", plug: "output", type: 'string'}
|
||||
]);
|
||||
});
|
||||
|
||||
test('Input ports are removed', () => {
|
||||
updateAndMatchPorts([],
|
||||
[
|
||||
{name: "testOutput1", plug: "output", type: 'string'}
|
||||
]);
|
||||
});
|
||||
|
||||
test('Input port types are updated', () => {
|
||||
updateAndMatchPorts([
|
||||
{ name: "testInput1", plug: "input", type: 'boolean'},
|
||||
{ name: "testInput2", plug: "input", type: {name:'number'}}
|
||||
],
|
||||
[]);
|
||||
});
|
||||
|
||||
test('Output ports are added', () => {
|
||||
updateAndMatchPorts([
|
||||
{ name: "testInput1", plug: "input", type: 'string'}
|
||||
],
|
||||
[
|
||||
{name: "testOutput1", plug: "output", type: 'string'},
|
||||
{name: "testOutput2", plug: "output", type: 'string'}
|
||||
]);
|
||||
});
|
||||
|
||||
test('Output ports are removed', () => {
|
||||
updateAndMatchPorts([
|
||||
{ name: "testInput1", plug: "input", type: 'string'}
|
||||
],
|
||||
[
|
||||
]);
|
||||
});
|
||||
|
||||
test('Output port types are updated', () => {
|
||||
updateAndMatchPorts([],
|
||||
[
|
||||
{ name: "testOutput1", plug: "output", type: 'number'},
|
||||
{ name: "testOutput2", plug: "output", type: {name:'boolean'}}
|
||||
]);
|
||||
});
|
||||
});
|
||||
61
packages/noodl-runtime/test/eventsender.test.js
Normal file
61
packages/noodl-runtime/test/eventsender.test.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const EventSender = require('./eventsender');
|
||||
|
||||
describe('EventSender', ()=> {
|
||||
|
||||
let eventSender;
|
||||
|
||||
beforeEach(()=>{
|
||||
eventSender = new EventSender();
|
||||
});
|
||||
|
||||
it('can send events', ()=> {
|
||||
|
||||
const mockCallback = jest.fn(() => {});
|
||||
|
||||
eventSender.on('testEvent', mockCallback);
|
||||
|
||||
const someArgument = {lol:'troll'};
|
||||
eventSender.emit('testEvent', someArgument);
|
||||
|
||||
expect(mockCallback.mock.calls.length).toBe(1);
|
||||
expect(mockCallback.mock.calls[0][0]).toBe(someArgument);
|
||||
});
|
||||
|
||||
it('can remove a listener', ()=> {
|
||||
const mockCallback = jest.fn(() => {});
|
||||
const mockCallback2 = jest.fn(() => {});
|
||||
|
||||
const ref = {};
|
||||
const ref2 = {};
|
||||
|
||||
eventSender.on('testEvent', mockCallback, ref);
|
||||
eventSender.on('testEvent', mockCallback2, ref2);
|
||||
|
||||
eventSender.removeListenersWithRef(ref);
|
||||
eventSender.emit('testEvent');
|
||||
|
||||
expect(mockCallback.mock.calls.length).toBe(0);
|
||||
expect(mockCallback2.mock.calls.length).toBe(1);
|
||||
|
||||
});
|
||||
|
||||
it('can remove all listeners', ()=> {
|
||||
const mockCallback = jest.fn(() => {});
|
||||
const mockCallback2 = jest.fn(() => {});
|
||||
|
||||
const ref = {};
|
||||
const ref2 = {};
|
||||
|
||||
eventSender.on('testEvent', mockCallback, ref);
|
||||
eventSender.on('testEvent2', mockCallback2, ref2);
|
||||
|
||||
eventSender.removeAllListeners('testEvent');
|
||||
eventSender.emit('testEvent');
|
||||
eventSender.emit('testEvent2');
|
||||
|
||||
expect(mockCallback.mock.calls.length).toBe(0);
|
||||
expect(mockCallback2.mock.calls.length).toBe(1);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
67
packages/noodl-runtime/test/models/componentmodel.test.js
Normal file
67
packages/noodl-runtime/test/models/componentmodel.test.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const { ComponentModel } = require('./componentmodel');
|
||||
const NodeModel = require('./nodemodel');
|
||||
|
||||
test('Returns all nodes in the component', ()=>{
|
||||
const component = new ComponentModel('testComponent');
|
||||
|
||||
component.addNode(new NodeModel('id1', 'testNode'));
|
||||
let nodes = component.getAllNodes();
|
||||
expect(nodes.length).toBe(1);
|
||||
expect(nodes[0].id).toBe('id1');
|
||||
|
||||
component.addNode(new NodeModel('id2', 'testNode'));
|
||||
nodes = component.getAllNodes();
|
||||
expect(nodes.length).toBe(2);
|
||||
|
||||
//the order is not guaranteed, so let's just use find
|
||||
expect(nodes.find(n=>n.id==='id1')).not.toBe(undefined);
|
||||
expect(nodes.find(n=>n.id==='id2')).not.toBe(undefined);
|
||||
});
|
||||
|
||||
test('Connections can be added', ()=>{
|
||||
const component = new ComponentModel('testComponent');
|
||||
component.addNode(new NodeModel('id1', 'testNode'));
|
||||
component.addNode(new NodeModel('id2', 'testNode'));
|
||||
component.addConnection({
|
||||
sourceId: 'id1',
|
||||
sourcePort: 'test',
|
||||
targetId: 'id2',
|
||||
targetPort: 'test'
|
||||
});
|
||||
|
||||
const connections = component.getConnectionsFrom('id1');
|
||||
expect(connections.length).toBe(1);
|
||||
});
|
||||
|
||||
test('Connections can be removed', ()=>{
|
||||
const connection = {
|
||||
sourceId: 'id1',
|
||||
sourcePort: 'test',
|
||||
targetId: 'id2',
|
||||
targetPort: 'test'
|
||||
};
|
||||
|
||||
const component = new ComponentModel('testComponent');
|
||||
component.addNode(new NodeModel('id1', 'testNode'));
|
||||
component.addNode(new NodeModel('id2', 'testNode'));
|
||||
component.addConnection(connection);
|
||||
//test with a copy of the object, and not the same object, to verify it works then as well
|
||||
component.removeConnection(JSON.parse(JSON.stringify(connection)));
|
||||
expect(component.getAllConnections().length).toBe(0);
|
||||
});
|
||||
|
||||
test('Removing non-existing connection should not throw', ()=>{
|
||||
const connection = {
|
||||
sourceId: 'id1',
|
||||
sourcePort: 'test',
|
||||
targetId: 'id2',
|
||||
targetPort: 'test'
|
||||
};
|
||||
|
||||
const component = new ComponentModel('testComponent');
|
||||
component.addNode(new NodeModel('id1', 'testNode'));
|
||||
component.addNode(new NodeModel('id2', 'testNode'));
|
||||
component.addConnection(connection);
|
||||
expect(()=>component.removeConnection({})).not.toThrow();
|
||||
expect(component.getAllConnections().length).toBe(1);
|
||||
});
|
||||
50
packages/noodl-runtime/test/nodecontext.test.js
Normal file
50
packages/noodl-runtime/test/nodecontext.test.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const NodeContext = require('./nodecontext');
|
||||
const NodeDefinition = require('./nodedefinition');
|
||||
const ComponentInstance = require('./nodes/componentinstance');
|
||||
const { ComponentModel } = require('./models/componentmodel');
|
||||
|
||||
describe('NodeContext', ()=>{
|
||||
|
||||
test("can detect cyclic updates", async () => {
|
||||
const testNodeDefinition = NodeDefinition.defineNode({
|
||||
name: 'Test Node',
|
||||
category: 'test',
|
||||
initialize() {this._internal.count = 0;},
|
||||
inputs: {
|
||||
input: {
|
||||
set() {
|
||||
this._internal.count++;
|
||||
this.flagOutputDirty('output');
|
||||
}
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
output: {type: 'number', get() {return Math.random()}}
|
||||
},
|
||||
})
|
||||
|
||||
const context = new NodeContext();
|
||||
context.nodeRegister.register(testNodeDefinition);
|
||||
|
||||
const componentModel = await ComponentModel.createFromExportData({
|
||||
name: 'testComponent',
|
||||
id: '1',
|
||||
nodes: [
|
||||
{ id: '2', type: 'Test Node', parameters: {input: true}},
|
||||
{ id: '3', type: 'Test Node'}
|
||||
],
|
||||
connections: [
|
||||
{sourceId: '2', sourcePort: 'output', targetId: '3', targetPort: 'input'},
|
||||
{sourceId: '3', sourcePort: 'output', targetId: '2', targetPort: 'input'}
|
||||
]
|
||||
});
|
||||
|
||||
const componentInstance = new ComponentInstance(context);
|
||||
await componentInstance.setComponentModel(componentModel);
|
||||
|
||||
context.update();
|
||||
|
||||
expect(componentInstance.nodeScope.getNodeWithId('2')._internal.count).toBeGreaterThan(50);
|
||||
expect(componentInstance.nodeScope.getNodeWithId('3')._internal.count).toBeGreaterThan(50);
|
||||
});
|
||||
});
|
||||
241
packages/noodl-runtime/test/nodes/componentinstance.test.js
Normal file
241
packages/noodl-runtime/test/nodes/componentinstance.test.js
Normal file
@@ -0,0 +1,241 @@
|
||||
const ComponentInstance = require('./componentinstance');
|
||||
const { ComponentModel } = require('../models/componentmodel');
|
||||
const GraphModel = require('../models/graphmodel');
|
||||
const NodeContext = require('../nodecontext');
|
||||
const ComponentInputs = require('./componentinputs');
|
||||
const ComponentOutputs = require('./componentoutputs');
|
||||
const NodeDefinition = require('../nodedefinition');
|
||||
|
||||
async function setupComponent() {
|
||||
const context = new NodeContext();
|
||||
context.nodeRegister.register(NodeDefinition.defineNode(ComponentInputs.node));
|
||||
context.nodeRegister.register(NodeDefinition.defineNode(ComponentOutputs.node));
|
||||
|
||||
const componentModel = await ComponentModel.createFromExportData({
|
||||
name: 'testComponent',
|
||||
id: 'loltroll2',
|
||||
nodes: [
|
||||
{
|
||||
id: 'loltroll',
|
||||
type: 'Component Inputs',
|
||||
ports: [{ name: 'textInput', plug: 'output', type: 'string' }]
|
||||
},
|
||||
{
|
||||
id: 'componentOutputs',
|
||||
type: 'Component Outputs',
|
||||
ports: [{ name: 'textOutput', plug: 'input', type: 'string' }]
|
||||
}
|
||||
],
|
||||
ports: [
|
||||
{ name: 'textInput', plug: 'input', type: 'string' },
|
||||
{ name: 'textOutput', plug: 'output', type: 'string' }
|
||||
],
|
||||
connections: []
|
||||
});
|
||||
|
||||
const componentInstance = new ComponentInstance(context);
|
||||
await componentInstance.setComponentModel(componentModel);
|
||||
return { componentInstance, componentModel };
|
||||
}
|
||||
|
||||
function createTestNodeDefinition() {
|
||||
return NodeDefinition.defineNode({
|
||||
name: 'Test Node',
|
||||
category: 'test',
|
||||
initialize() {
|
||||
this.inputHistory = [];
|
||||
},
|
||||
inputs: {
|
||||
input: {
|
||||
type: 'string',
|
||||
set(value) {
|
||||
this.testValue = value;
|
||||
this.flagOutputDirty('output');
|
||||
this.inputHistory.push(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
output: {
|
||||
type: 'string',
|
||||
get() {
|
||||
return this.testValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test('Component inputs are registered', async () => {
|
||||
const { componentInstance } = await setupComponent();
|
||||
expect(componentInstance.hasInput('textInput')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Internal component inputs are removed when a component input node is removed', async () => {
|
||||
const { componentInstance, componentModel } = await setupComponent();
|
||||
|
||||
expect(componentInstance._internal.componentInputs.length).toBe(1);
|
||||
await componentModel.removeNodeWithId('loltroll');
|
||||
expect(componentInstance._internal.componentInputs.length).toBe(0);
|
||||
});
|
||||
|
||||
test('Component outputs are registered', async () => {
|
||||
const { componentInstance } = await setupComponent();
|
||||
expect(componentInstance.hasOutput('textOutput')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Internal component outputs are removed when a component output node is removed', async () => {
|
||||
const { componentInstance, componentModel } = await setupComponent();
|
||||
|
||||
expect(componentInstance._internal.componentOutputs.length).toBe(1);
|
||||
await componentModel.removeNodeWithId('componentOutputs');
|
||||
expect(componentInstance._internal.componentOutputs.length).toBe(0);
|
||||
});
|
||||
|
||||
test('Parameters should be overwritten by connections', async () => {
|
||||
const context = new NodeContext();
|
||||
|
||||
context.nodeRegister.register(createTestNodeDefinition());
|
||||
|
||||
const componentModel = await ComponentModel.createFromExportData({
|
||||
name: 'rootComponent',
|
||||
id: 'testid',
|
||||
nodes: [
|
||||
{
|
||||
id: 'testNodeEnd',
|
||||
type: 'Test Node',
|
||||
parameters: { input: 'param-value' }
|
||||
},
|
||||
{
|
||||
id: 'testNodeStart',
|
||||
type: 'Test Node',
|
||||
parameters: { input: 'connection-value' }
|
||||
}
|
||||
],
|
||||
connections: [{ sourceId: 'testNodeStart', sourcePort: 'output', targetId: 'testNodeEnd', targetPort: 'input' }]
|
||||
});
|
||||
|
||||
const componentInstance = new ComponentInstance(context);
|
||||
await componentInstance.setComponentModel(componentModel);
|
||||
|
||||
const testnodeEnd = componentInstance.nodeScope.getNodeWithId('testNodeEnd');
|
||||
testnodeEnd.update();
|
||||
|
||||
expect(testnodeEnd.inputHistory.length).toBe(1);
|
||||
expect(testnodeEnd.inputHistory[0]).toBe('connection-value');
|
||||
});
|
||||
|
||||
test('Component inputs should not interfere with internal nodes', async () => {
|
||||
const context = new NodeContext();
|
||||
|
||||
context.nodeRegister.register(NodeDefinition.defineNode(ComponentInputs.node));
|
||||
context.nodeRegister.register(createTestNodeDefinition());
|
||||
|
||||
const componentModel = await ComponentModel.createFromExportData({
|
||||
name: 'rootComponent',
|
||||
id: 'testid',
|
||||
nodes: [
|
||||
{
|
||||
id: 'testnode',
|
||||
type: 'Test Node',
|
||||
parameters: { input: 'param-value' }
|
||||
},
|
||||
{
|
||||
id: 'compinput',
|
||||
type: 'Component Inputs',
|
||||
ports: [{ name: 'output', plug: 'output', type: 'string' }]
|
||||
}
|
||||
],
|
||||
connections: [{ sourceId: 'compinput', sourcePort: 'output', targetId: 'testnode', targetPort: 'input' }]
|
||||
});
|
||||
|
||||
const componentInstance = new ComponentInstance(context);
|
||||
await componentInstance.setComponentModel(componentModel);
|
||||
|
||||
const testnode = componentInstance.nodeScope.getNodeWithId('testnode');
|
||||
testnode.update();
|
||||
|
||||
expect(testnode.inputHistory.length).toBe(1);
|
||||
expect(testnode.inputHistory[0]).toBe('param-value');
|
||||
});
|
||||
|
||||
test('No delays in component inputs and outputs', async () => {
|
||||
const context = new NodeContext();
|
||||
|
||||
context.nodeRegister.register(NodeDefinition.defineNode(ComponentInputs.node));
|
||||
context.nodeRegister.register(NodeDefinition.defineNode(ComponentOutputs.node));
|
||||
context.nodeRegister.register(createTestNodeDefinition());
|
||||
|
||||
const graph = new GraphModel();
|
||||
|
||||
graph.on('componentAdded', (component) => context.registerComponentModel(component));
|
||||
|
||||
await graph.importEditorData({
|
||||
components: [
|
||||
{
|
||||
name: 'testComponent',
|
||||
nodes: [
|
||||
{
|
||||
id: 'compinput',
|
||||
type: 'Component Inputs',
|
||||
ports: [{ name: 'textInput', plug: 'output', type: 'string' }]
|
||||
},
|
||||
{
|
||||
id: 'testnode-inner',
|
||||
type: 'Test Node',
|
||||
parameters: { input: 'inner-value' }
|
||||
},
|
||||
{
|
||||
id: 'compoutput',
|
||||
type: 'Component Outputs',
|
||||
ports: [{ name: 'textOutput', plug: 'input', type: 'string' }]
|
||||
}
|
||||
],
|
||||
connections: [
|
||||
{ sourceId: 'compinput', sourcePort: 'textInput', targetId: 'testnode-inner', targetPort: 'input' },
|
||||
{ sourceId: 'testnode-inner', sourcePort: 'output', targetId: 'compoutput', targetPort: 'textOutput' }
|
||||
],
|
||||
ports: [
|
||||
{ name: 'textInput', plug: 'input', type: 'string' },
|
||||
{ name: 'textOutput', plug: 'output', type: 'string' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'rootComponent',
|
||||
nodes: [
|
||||
{
|
||||
id: 'testComponent',
|
||||
type: 'testComponent'
|
||||
},
|
||||
{
|
||||
id: 'testnodeEnd',
|
||||
type: 'Test Node'
|
||||
},
|
||||
{
|
||||
id: 'testNodeStart',
|
||||
type: 'Test Node',
|
||||
parameters: { input: 'outer-value' }
|
||||
}
|
||||
],
|
||||
connections: [
|
||||
{ sourceId: 'testComponent', sourcePort: 'textOutput', targetId: 'testnodeEnd', targetPort: 'input' },
|
||||
{ sourceId: 'testNodeStart', sourcePort: 'output', targetId: 'testComponent', targetPort: 'textInput' }
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const rootComponent = await context.createComponentInstanceNode('rootComponent');
|
||||
context.setRootComponent(rootComponent);
|
||||
|
||||
const testnodeEnd = rootComponent.nodeScope.getNodeWithId('testnodeEnd');
|
||||
const testnodeInner = rootComponent.nodeScope.getNodesWithIdRecursive('testnode-inner')[0];
|
||||
|
||||
context.update();
|
||||
|
||||
expect(testnodeEnd.inputHistory.length).toBe(1);
|
||||
expect(testnodeEnd.inputHistory[0]).toBe('outer-value');
|
||||
|
||||
expect(testnodeInner.inputHistory.length).toBe(1);
|
||||
expect(testnodeInner.inputHistory[0]).toBe('outer-value');
|
||||
});
|
||||
157
packages/noodl-runtime/test/nodescope.test.js
Normal file
157
packages/noodl-runtime/test/nodescope.test.js
Normal file
@@ -0,0 +1,157 @@
|
||||
const NodeScope = require('./nodescope');
|
||||
const NodeContext = require('./nodecontext');
|
||||
const NodeDefinition = require('./nodedefinition');
|
||||
const { ComponentModel } = require('./models/componentmodel');
|
||||
const GraphModel = require('./models/graphmodel');
|
||||
|
||||
function createTestNodeDefinition({onInit, onDelete}) {
|
||||
return NodeDefinition.defineNode({
|
||||
name: 'Test Node',
|
||||
category: 'test',
|
||||
initialize() {
|
||||
this.children = [];
|
||||
onInit && onInit.call(this);
|
||||
onDelete && this.addDeleteListener(onDelete);
|
||||
},
|
||||
methods: {
|
||||
addChild(child) {
|
||||
this.children.push(child);
|
||||
},
|
||||
removeChild(child) {
|
||||
this.children.splice(this.children.indexOf(child), 1);
|
||||
},
|
||||
getChildren() {
|
||||
return this.children;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Create a graph that includes a two levels of component children
|
||||
async function createTestRootComponent(args) {
|
||||
const context = new NodeContext();
|
||||
|
||||
context.nodeRegister.register(createTestNodeDefinition(args || {}));
|
||||
|
||||
const graph = new GraphModel();
|
||||
|
||||
graph.on("componentAdded", component => context.registerComponentModel(component) );
|
||||
|
||||
await graph.importEditorData( {
|
||||
components: [
|
||||
{
|
||||
name: 'rootComponent',
|
||||
nodes: [ {
|
||||
id: 'test-component',
|
||||
type: 'testComponent',
|
||||
children: [
|
||||
{ id: 'test-node-from-root', type: 'Test Node' },
|
||||
{
|
||||
id: 'test-component-child',
|
||||
type: 'testComponent',
|
||||
children: [
|
||||
{ id: 'test-node-in-component-child', type: 'Test Node' },
|
||||
]
|
||||
}
|
||||
]
|
||||
} ],
|
||||
},
|
||||
{
|
||||
name: 'testComponent',
|
||||
nodes: [ {
|
||||
id: 'test-node',
|
||||
type: 'Test Node',
|
||||
children: [
|
||||
{
|
||||
id: 'test-node-child',
|
||||
type: 'Test Node',
|
||||
children: [{id: 'component-children', type: 'Component Children'}]
|
||||
}
|
||||
]
|
||||
} ],
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const rootComponent = await context.createComponentInstanceNode("rootComponent");
|
||||
context.setRootComponent(rootComponent);
|
||||
|
||||
return rootComponent;
|
||||
}
|
||||
|
||||
test('find nodes by id', async () => {
|
||||
const nodeScope = (await createTestRootComponent()).nodeScope;
|
||||
|
||||
expect(nodeScope.hasNodeWithId('test-component')).toBe(true);
|
||||
|
||||
const testComponent = nodeScope.getNodeWithId('test-component');
|
||||
expect(testComponent.nodeScope.hasNodeWithId('test-node')).toBe(true);
|
||||
});
|
||||
|
||||
test('delete component', async () => {
|
||||
const nodeScope = (await createTestRootComponent()).nodeScope;
|
||||
|
||||
nodeScope.deleteNode(nodeScope.getNodeWithId('test-component'));
|
||||
expect(nodeScope.hasNodeWithId('test-node')).toBe(false);
|
||||
});
|
||||
|
||||
test('delete hierarchy', async () => {
|
||||
let testNodeCount = 0;
|
||||
const testComponent = await createTestRootComponent({
|
||||
onInit: () => testNodeCount++,
|
||||
onDelete: () => testNodeCount--
|
||||
});
|
||||
|
||||
const nodeScope = testComponent.nodeScope.getNodeWithId('test-component').nodeScope;
|
||||
|
||||
expect(testNodeCount).toBe(6);
|
||||
nodeScope.deleteNode(nodeScope.getNodeWithId('test-node'));
|
||||
expect(nodeScope.hasNodeWithId('test-node')).toBe(false);
|
||||
expect(nodeScope.hasNodeWithId('test-node-child')).toBe(false);
|
||||
expect(testNodeCount).toBe(3);
|
||||
});
|
||||
|
||||
test('delete child in a component children hierarchy', async () => {
|
||||
let testNodeCount = 0;
|
||||
const testComponent = await createTestRootComponent({
|
||||
onInit: () => testNodeCount++,
|
||||
onDelete: () => testNodeCount--
|
||||
});
|
||||
const nodeScope = testComponent.nodeScope;
|
||||
|
||||
expect(testNodeCount).toBe(6);
|
||||
expect(nodeScope.hasNodeWithId('test-node-from-root')).toBe(true);
|
||||
nodeScope.deleteNode(nodeScope.getNodeWithId('test-node-from-root'));
|
||||
expect(nodeScope.hasNodeWithId('test-node-from-root')).toBe(false);
|
||||
|
||||
expect(testNodeCount).toBe(5);
|
||||
});
|
||||
|
||||
test('delete component with component children', async () => {
|
||||
let testNodeCount = 0;
|
||||
const testComponent = await createTestRootComponent({
|
||||
onInit: () => testNodeCount++,
|
||||
onDelete: () => testNodeCount--
|
||||
});
|
||||
const nodeScope = testComponent.nodeScope;
|
||||
|
||||
expect(testNodeCount).toBe(6);
|
||||
const node = nodeScope.getNodeWithId('test-component-child');
|
||||
nodeScope.deleteNode(node);
|
||||
expect(testNodeCount).toBe(3);
|
||||
});
|
||||
|
||||
test('delete entire scope and nested scopes', async () => {
|
||||
|
||||
let testNodeCount = 0;
|
||||
|
||||
const testComponent = await createTestRootComponent({
|
||||
onInit: () => testNodeCount++,
|
||||
onDelete: () => testNodeCount--
|
||||
});
|
||||
const nodeScope = testComponent.nodeScope;
|
||||
|
||||
expect(testNodeCount).toBe(6);
|
||||
nodeScope.reset();
|
||||
expect(testNodeCount).toBe(0);
|
||||
});
|
||||
7
packages/noodl-runtime/test/outputproperty.test.js
Normal file
7
packages/noodl-runtime/test/outputproperty.test.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const OutputProperty = require('./outputproperty');
|
||||
|
||||
test('Throws an exception if no owner is specified', () => {
|
||||
expect(()=>{
|
||||
new OutputProperty();
|
||||
}).toThrow(Error);
|
||||
});
|
||||
Reference in New Issue
Block a user