Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: setup #10

Merged
merged 1 commit into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,13 @@ import tseslint from 'typescript-eslint';
import configs from 'rete-cli/configs/eslint.mjs';

export default tseslint.config(
...configs
...configs,
{
files: ['**/*.test.ts'],
rules: {
'eslint-disable': 'off',
'init-declarations': 'off',
'max-statements': 'off',
}
}
)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"scripts": {
"build": "rete build -c rete.config.ts",
"lint": "rete lint",
"test": "rete test",
"doc": "rete doc"
},
"author": "Vitaliy Stoliarov",
Expand Down
53 changes: 53 additions & 0 deletions test/control-flow-engine.test.ts
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()
})
})
141 changes: 141 additions & 0 deletions test/control-flow.test.ts
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')
})
})
170 changes: 170 additions & 0 deletions test/dataflow-engine.test.ts
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)
})
})
Loading
Loading