-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
633 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { describe, expect, it, jest } from '@jest/globals' | ||
import { ClassicPreset, NodeEditor } from 'rete' | ||
|
||
import { ControlFlowEngine, ControlFlowEngineScheme } from '../src/control-flow-engine' | ||
|
||
class Node extends ClassicPreset.Node { | ||
constructor(public label: string) { | ||
super(label) | ||
|
||
this.addInput('input', new ClassicPreset.Input(new ClassicPreset.Socket('input'))) | ||
this.addOutput('output', new ClassicPreset.Output(new ClassicPreset.Socket('output'))) | ||
} | ||
|
||
execute(input: string, forward: (output: string) => void): void { | ||
forward('output') | ||
} | ||
} | ||
|
||
class Connection<N extends Node> extends ClassicPreset.Connection<N, N> { | ||
constructor(public node1: N, public output: string, public node2: N, public input: string) { | ||
super(node1, output, node2, input) | ||
} | ||
} | ||
|
||
describe('ControlFlow', () => { | ||
let editor!: NodeEditor<ControlFlowEngineScheme> | ||
let controlFlow: ControlFlowEngine<ControlFlowEngineScheme> | ||
|
||
beforeEach(() => { | ||
editor = new NodeEditor<ControlFlowEngineScheme>() | ||
controlFlow = new ControlFlowEngine() | ||
|
||
editor.use(controlFlow) | ||
}) | ||
|
||
it('executes sequence of nodes', async () => { | ||
const node1 = new Node('label1') | ||
const node2 = new Node('label2') | ||
|
||
await editor.addNode(node1) | ||
await editor.addNode(node2) | ||
|
||
await editor.addConnection(new Connection(node1, 'output', node2, 'input')) | ||
|
||
const spy1 = jest.spyOn(node1, 'execute') | ||
const spy2 = jest.spyOn(node2, 'execute') | ||
|
||
controlFlow.execute(node1.id) | ||
|
||
expect(spy1).toHaveBeenCalled() | ||
expect(spy2).toHaveBeenCalled() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import { describe, expect, it } from '@jest/globals' | ||
import { ClassicPreset, NodeEditor } from 'rete' | ||
|
||
import { ControlFlow } from '../src/control-flow' | ||
import { ClassicScheme } from '../src/types' | ||
|
||
describe('ControlFlow', () => { | ||
let editor!: NodeEditor<ClassicScheme> | ||
let controlFlow: ControlFlow<ClassicScheme> | ||
|
||
beforeEach(() => { | ||
editor = new NodeEditor<ClassicScheme>() | ||
controlFlow = new ControlFlow(editor) | ||
}) | ||
|
||
it('adds a node to the control flow', () => { | ||
const node = new ClassicPreset.Node('label') | ||
|
||
controlFlow.add(node, { | ||
inputs: () => [], | ||
outputs: () => [], | ||
execute: () => null | ||
}) | ||
|
||
expect(controlFlow.setups.has(node.id)).toBe(true) | ||
}) | ||
|
||
it('removes a node from the control flow', () => { | ||
const node = new ClassicPreset.Node('label') | ||
|
||
controlFlow.add(node, { | ||
inputs: () => [], | ||
outputs: () => [], | ||
execute: () => null | ||
}) | ||
|
||
controlFlow.remove(node.id) | ||
|
||
expect(controlFlow.setups.has(node.id)).toBe(false) | ||
}) | ||
|
||
it('executes a node', () => { | ||
const node = new ClassicPreset.Node('label') | ||
const fn1 = jest.fn() | ||
|
||
controlFlow.add(node, { | ||
inputs: () => [], | ||
outputs: () => [], | ||
execute: fn1 | ||
}) | ||
|
||
expect(fn1).not.toHaveBeenCalled() | ||
controlFlow.execute(node.id) | ||
expect(fn1).toHaveBeenCalled() | ||
}) | ||
|
||
it('executes sequence of nodes', async () => { | ||
const node1 = new ClassicPreset.Node('label') | ||
const node2 = new ClassicPreset.Node('label') | ||
|
||
node1.addOutput('out', new ClassicPreset.Output(new ClassicPreset.Socket('number'))) | ||
node2.addInput('in', new ClassicPreset.Input(new ClassicPreset.Socket('number'))) | ||
|
||
await editor.addConnection(new ClassicPreset.Connection(node1, 'out', node2, 'in')) | ||
|
||
const fn1 = jest.fn() | ||
const fn2 = jest.fn() | ||
|
||
controlFlow.add(node1, { | ||
inputs: () => [], | ||
outputs: () => ['out'], | ||
execute: (input, forward) => { | ||
fn1() | ||
forward('out') | ||
} | ||
}) | ||
controlFlow.add(node2, { | ||
inputs: () => ['in'], | ||
outputs: () => [], | ||
execute: fn2 | ||
}) | ||
|
||
expect(fn1).not.toHaveBeenCalled() | ||
expect(fn2).not.toHaveBeenCalled() | ||
controlFlow.execute(node1.id) | ||
expect(fn1).toHaveBeenCalled() | ||
expect(fn2).toHaveBeenCalled() | ||
}) | ||
|
||
it('throws error when node is not initialized', () => { | ||
const node = new ClassicPreset.Node('label') | ||
|
||
expect(() => controlFlow.execute(node.id)).toThrowError('node is not initialized') | ||
}) | ||
|
||
it('throws error when trying to add a node more than once', () => { | ||
const node = new ClassicPreset.Node('label') | ||
|
||
controlFlow.add(node, { | ||
inputs: () => [], | ||
outputs: () => [], | ||
execute: () => null | ||
}) | ||
|
||
expect(() => controlFlow.add(node, { | ||
inputs: () => [], | ||
outputs: () => [], | ||
execute: () => null | ||
})).toThrowError('already processed') | ||
}) | ||
|
||
it('throws error when trying to remove a node that does not exist', () => { | ||
const node = new ClassicPreset.Node('label') | ||
|
||
expect(() => controlFlow.remove(node.id)).not.toThrow() | ||
}) | ||
|
||
it('throws error when input is not specified', () => { | ||
const node = new ClassicPreset.Node('label') | ||
|
||
controlFlow.add(node, { | ||
inputs: () => [], | ||
outputs: () => [], | ||
execute: () => null | ||
}) | ||
|
||
expect(() => controlFlow.execute(node.id, 'input')).toThrowError('inputs don\'t have a key') | ||
}) | ||
|
||
it('throws error when output is not specified', () => { | ||
const node = new ClassicPreset.Node('label') | ||
|
||
controlFlow.add(node, { | ||
inputs: () => [], | ||
outputs: () => [], | ||
execute: (inputs, forward) => forward('output') | ||
}) | ||
|
||
expect(() => controlFlow.execute(node.id)).toThrowError('outputs don\'t have a key') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { describe, expect, it, jest } from '@jest/globals' | ||
import { ClassicPreset, NodeEditor } from 'rete' | ||
|
||
import { DataflowEngine, DataflowEngineScheme, DataflowNode } from '../src/dataflow-engine' | ||
|
||
class Node extends ClassicPreset.Node implements DataflowNode { | ||
constructor(public label: string, private produces: string) { | ||
super(label) | ||
|
||
this.addInput('input', new ClassicPreset.Input(new ClassicPreset.Socket('input'))) | ||
this.addOutput('output', new ClassicPreset.Output(new ClassicPreset.Socket('output'))) | ||
} | ||
|
||
data(): { output: string } { | ||
return { | ||
output: this.produces | ||
} | ||
} | ||
} | ||
|
||
class Connection<N extends Node> extends ClassicPreset.Connection<N, N> { | ||
constructor(public node1: N, public output: string, public node2: N, public input: string) { | ||
super(node1, output, node2, input) | ||
} | ||
} | ||
|
||
describe('ControlFlow', () => { | ||
let editor!: NodeEditor<DataflowEngineScheme> | ||
let dataflow: DataflowEngine<DataflowEngineScheme> | ||
|
||
beforeEach(() => { | ||
editor = new NodeEditor<DataflowEngineScheme>() | ||
dataflow = new DataflowEngine() | ||
|
||
editor.use(dataflow) | ||
}) | ||
|
||
it('collects input data from the predecessor nodes', async () => { | ||
const node1 = new Node('label1', 'data1') | ||
const node2 = new Node('label2', 'data2') | ||
const node3 = new Node('label3', 'data3') | ||
|
||
await editor.addNode(node1) | ||
await editor.addNode(node2) | ||
await editor.addNode(node3) | ||
|
||
await editor.addConnection(new Connection(node1, 'output', node3, 'input')) | ||
await editor.addConnection(new Connection(node2, 'output', node3, 'input')) | ||
|
||
const spy1 = jest.spyOn(node1, 'data') | ||
const spy2 = jest.spyOn(node2, 'data') | ||
const spy3 = jest.spyOn(node3, 'data') | ||
|
||
expect(spy1).not.toHaveBeenCalled() | ||
expect(spy2).not.toHaveBeenCalled() | ||
expect(spy3).not.toHaveBeenCalled() | ||
|
||
await dataflow.fetchInputs(node3.id) | ||
|
||
expect(spy1).toHaveBeenCalled() | ||
expect(spy2).toHaveBeenCalled() | ||
expect(spy3).not.toHaveBeenCalled() | ||
|
||
expect(spy1).toHaveReturnedWith({ output: 'data1' }) | ||
expect(spy2).toHaveReturnedWith({ output: 'data2' }) | ||
}) | ||
|
||
it('fetches data from the leaf node', async () => { | ||
const node1 = new Node('label1', 'data1') | ||
const node2 = new Node('label2', 'data2') | ||
const node3 = new Node('label3', 'data3') | ||
|
||
await editor.addNode(node1) | ||
await editor.addNode(node2) | ||
await editor.addNode(node3) | ||
|
||
await editor.addConnection(new Connection(node1, 'output', node3, 'input')) | ||
await editor.addConnection(new Connection(node2, 'output', node3, 'input')) | ||
|
||
const spy1 = jest.spyOn(node1, 'data') | ||
const spy2 = jest.spyOn(node2, 'data') | ||
const spy3 = jest.spyOn(node3, 'data') | ||
|
||
expect(spy1).not.toHaveBeenCalled() | ||
expect(spy2).not.toHaveBeenCalled() | ||
expect(spy3).not.toHaveBeenCalled() | ||
|
||
await dataflow.fetch(node3.id) | ||
|
||
expect(spy1).toHaveBeenCalled() | ||
expect(spy2).toHaveBeenCalled() | ||
expect(spy3).toHaveBeenCalled() | ||
|
||
expect(spy1).toHaveReturnedWith({ output: 'data1' }) | ||
expect(spy2).toHaveReturnedWith({ output: 'data2' }) | ||
expect(spy3).toHaveReturnedWith({ output: 'data3' }) | ||
}) | ||
|
||
it('caches the data', async () => { | ||
const node = new Node('label', 'data') | ||
|
||
await editor.addNode(node) | ||
|
||
const spy = jest.spyOn(node, 'data') | ||
|
||
expect(spy).not.toHaveBeenCalled() | ||
|
||
await dataflow.fetch(node.id) | ||
|
||
expect(spy).toHaveBeenCalledTimes(1) | ||
|
||
await dataflow.fetch(node.id) | ||
|
||
expect(spy).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
it('clears the cache', async () => { | ||
const node = new Node('label', 'data') | ||
|
||
await editor.addNode(node) | ||
|
||
const spy = jest.spyOn(node, 'data') | ||
|
||
expect(spy).not.toHaveBeenCalled() | ||
|
||
await dataflow.fetch(node.id) | ||
|
||
expect(spy).toHaveBeenCalledTimes(1) | ||
|
||
dataflow.reset(node.id) | ||
await dataflow.fetch(node.id) | ||
|
||
expect(spy).toHaveBeenCalledTimes(2) | ||
}) | ||
|
||
it('resets the cache of the node and all its successors', async () => { | ||
const node1 = new Node('label1', 'data1') | ||
const node2 = new Node('label2', 'data2') | ||
const node3 = new Node('label3', 'data3') | ||
|
||
await editor.addNode(node1) | ||
await editor.addNode(node2) | ||
await editor.addNode(node3) | ||
|
||
await editor.addConnection(new Connection(node1, 'output', node2, 'input')) | ||
await editor.addConnection(new Connection(node2, 'output', node3, 'input')) | ||
|
||
const spy1 = jest.spyOn(node1, 'data') | ||
const spy3 = jest.spyOn(node3, 'data') | ||
|
||
expect(spy1).not.toHaveBeenCalled() | ||
expect(spy3).not.toHaveBeenCalled() | ||
|
||
await dataflow.fetch(node3.id) | ||
|
||
expect(spy1).toHaveBeenCalled() | ||
expect(spy3).toHaveBeenCalledTimes(1) | ||
|
||
await dataflow.fetch(node3.id) | ||
|
||
expect(spy1).toHaveBeenCalledTimes(1) | ||
expect(spy3).toHaveBeenCalledTimes(1) | ||
|
||
dataflow.reset(node2.id) | ||
await dataflow.fetch(node3.id) | ||
|
||
expect(spy1).toHaveBeenCalledTimes(1) | ||
expect(spy3).toHaveBeenCalledTimes(2) | ||
}) | ||
}) |
Oops, something went wrong.