From 396003563658178d0b8afe050c52a6d09179d2ae Mon Sep 17 00:00:00 2001 From: Ni55aN Date: Sat, 20 Apr 2024 14:45:16 +0300 Subject: [PATCH 1/3] chore(tests): remove unused modules --- test/data/add-numbers.ts | 68 --------------------- test/data/components.ts | 31 ---------- test/data/recursive.ts | 122 -------------------------------------- test/utils/render-mock.ts | 10 ---- test/utils/throwsAsync.ts | 13 ---- 5 files changed, 244 deletions(-) delete mode 100644 test/data/add-numbers.ts delete mode 100644 test/data/components.ts delete mode 100644 test/data/recursive.ts delete mode 100644 test/utils/render-mock.ts delete mode 100644 test/utils/throwsAsync.ts diff --git a/test/data/add-numbers.ts b/test/data/add-numbers.ts deleted file mode 100644 index 785c6754..00000000 --- a/test/data/add-numbers.ts +++ /dev/null @@ -1,68 +0,0 @@ -export default { - 'id': 'test@0.0.1', - 'nodes': { - '1': { - 'id': 1, - 'data': { - 'num': 2 - }, - 'inputs': {}, - 'outputs': { - 'num': { - 'connections': [{ - 'node': 3, - 'input': 'num1', - 'data': {} - }] - } - }, - 'position': [80, 200], - 'name': 'Number' - }, - '2': { - 'id': 2, - 'data': { - 'num': 0 - }, - 'inputs': {}, - 'outputs': { - 'num': { - 'connections': [{ - 'node': 3, - 'input': 'num2', - 'data': {} - }] - } - }, - 'position': [80, 400], - 'name': 'Number' - }, - '3': { - 'id': 3, - 'data': {}, - 'inputs': { - 'num1': { - 'connections': [{ - 'node': 1, - 'output': 'num', - 'data': {} - }] - }, - 'num2': { - 'connections': [{ - 'node': 2, - 'output': 'num', - 'data': {} - }] - } - }, - 'outputs': { - 'num': { - 'connections': [] - } - }, - 'position': [500, 240], - 'name': 'Add' - } - } -} \ No newline at end of file diff --git a/test/data/components.ts b/test/data/components.ts deleted file mode 100644 index f6d9eb73..00000000 --- a/test/data/components.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Component, Input, Node, Output, Socket } from '../../src'; - -const socketNum = new Socket('Number'); - -export class Comp1 extends Component { - - constructor() { - super('Number'); - } - - async builder(node: Node) { - node.addOutput(new Output('num', 'Name', socketNum)) - } - - worker() { } -} - -export class Comp2 extends Component { - - constructor() { - super('Add'); - } - - async builder(node: Node) { - node.addInput(new Input('num1', 'Name', socketNum)); - node.addInput(new Input('num2', 'Name', socketNum)); - node.addOutput(new Output('num', 'Name', socketNum)) - } - - worker() { } -} \ No newline at end of file diff --git a/test/data/recursive.ts b/test/data/recursive.ts deleted file mode 100644 index 17160246..00000000 --- a/test/data/recursive.ts +++ /dev/null @@ -1,122 +0,0 @@ -export default { - 'id' : 'test@0.0.1', - 'nodes' : { - '1': { - 'id': 1, - 'data': { - 'num': 2 - }, - 'group': null, - 'inputs': [], - 'outputs': [ - { - 'connections': [ - { - 'node': 3, - 'input': 0 - } - ] - } - ], - 'position': [ - 80, 200 - ], - 'name': 'name' - }, - '2': { - 'id': 2, - 'data': { - 'num': 1 - }, - 'group': null, - 'inputs': [], - 'outputs': [ - { - 'connections': [ - { - 'node': 4, - 'input': 1 - } - ] - } - ], - 'position': [ - 105.55555555555556, 516.6666666666666 - ], - 'name': 'name' - }, - '3': { - 'id': 3, - 'data': {}, - 'group': null, - 'inputs': [ - { - 'connections': [ - { - 'node': 1, - 'output': 0 - } - ] - }, { - 'connections': [ - { - 'node': 4, - 'output': 0 - } - ] - } - ], - 'outputs': [ - { - 'connections': [ - { - 'node': 4, - 'input': 0 - } - ] - } - ], - 'position': [ - 454.44444444444446, 108.88888888888889 - ], - 'name': 'Add' - }, - '4': { - 'id': 4, - 'data': {}, - 'group': null, - 'inputs': [ - { - 'connections': [ - { - 'node': 3, - 'output': 0 - } - ] - }, { - 'connections': [ - { - 'node': 2, - 'output': 0 - } - ] - } - ], - 'outputs': [ - { - 'connections': [ - { - 'node': 3, - 'input': 1 - } - ] - } - ], - 'position': [ - 781.6666666666663, 260.0000000000001 - ], - 'name': 'Add' - } - }, - 'groups' : {} -} \ No newline at end of file diff --git a/test/utils/render-mock.ts b/test/utils/render-mock.ts deleted file mode 100644 index 21d83cd3..00000000 --- a/test/utils/render-mock.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function renderMock(editor) { - editor.on('rendernode', ({ node, bindSocket }) => { - Array.from(node.inputs.values()).forEach(i => { - bindSocket(document.createElement('div'), 'input', i); - }); - Array.from(node.outputs.values()).forEach(o => { - bindSocket(document.createElement('div'), 'output', o); - }); - }); -} diff --git a/test/utils/throwsAsync.ts b/test/utils/throwsAsync.ts deleted file mode 100644 index aad5eccc..00000000 --- a/test/utils/throwsAsync.ts +++ /dev/null @@ -1,13 +0,0 @@ -import assert from 'assert'; - -export default async function(fn, msg) { - let f = () => {}; - - try { - await fn(); - } catch (e) { - f = () => { throw new Error(e) }; - } finally { - assert.throws(f, Error, msg); - } -} \ No newline at end of file From c0d43c695fa2db5e1fd4ee93aca6c7a14b393c14 Mon Sep 17 00:00:00 2001 From: Ni55aN Date: Sat, 20 Apr 2024 16:09:52 +0300 Subject: [PATCH 2/3] test: add tests for scope, utils and preset --- test/index.test.ts | 39 +++++++- test/mocks/crypto.ts | 24 +++++ test/presets/classic.test.ts | 173 +++++++++++++++++++++++++++++++++++ test/scope.test.ts | 162 ++++++++++++++++++++++++++++++++ test/utils.test.ts | 32 +++++++ 5 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 test/mocks/crypto.ts create mode 100644 test/presets/classic.test.ts create mode 100644 test/scope.test.ts create mode 100644 test/utils.test.ts diff --git a/test/index.test.ts b/test/index.test.ts index 7e2fe361..741bbdae 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,4 +1,3 @@ -// import jest globals import { describe, expect, it } from '@jest/globals' import { NodeEditor } from '../src/editor' @@ -52,5 +51,43 @@ describe('NodeEditor', () => { await expect(() => editor.addConnection(connectionData)).rejects.toThrowError() }) + + it('removeNode should remove a node', async () => { + const editor = new NodeEditor() + const nodeData = { id: '1', label: 'Node 1' } + + await editor.addNode(nodeData) + await editor.removeNode('1') + const nodes = editor.getNodes() + + expect(nodes).toHaveLength(0) + }) + + it('removeConnection should remove a connection', async () => { + const editor = new NodeEditor() + const connectionData = { id: '1', source: '1', target: '2' } + + await editor.addNode({ id: '1' }) + await editor.addNode({ id: '2' }) + await editor.addConnection(connectionData) + await editor.removeConnection('1') + const connections = editor.getConnections() + + expect(connections).toHaveLength(0) + }) + + it('should clear all nodes and connections', async () => { + const editor = new NodeEditor() + + await editor.addNode({ id: '1' }) + await editor.addNode({ id: '2' }) + await editor.addConnection({ id: '1', source: '1', target: '2' }) + await editor.clear() + const nodes = editor.getNodes() + const connections = editor.getConnections() + + expect(nodes).toHaveLength(0) + expect(connections).toHaveLength(0) + }) }) diff --git a/test/mocks/crypto.ts b/test/mocks/crypto.ts new file mode 100644 index 00000000..e248dbb6 --- /dev/null +++ b/test/mocks/crypto.ts @@ -0,0 +1,24 @@ +import { jest } from '@jest/globals' +import { Buffer } from 'buffer' + +export function mockCrypto(object: Record) { + // eslint-disable-next-line no-undef + globalThis.crypto = object as unknown as Crypto +} + +export function mockCryptoFromArray(array: Uint8Array) { + mockCrypto({ + getRandomValues: jest.fn().mockReturnValue(array) + }) +} + +export function mockCryptoFromBuffer(buffer: Buffer) { + mockCrypto({ + randomBytes: jest.fn().mockReturnValue(buffer) + }) +} + +export function resetCrypto() { + // eslint-disable-next-line no-undef, no-undefined + globalThis.crypto = undefined as unknown as Crypto +} diff --git a/test/presets/classic.test.ts b/test/presets/classic.test.ts new file mode 100644 index 00000000..7f29b205 --- /dev/null +++ b/test/presets/classic.test.ts @@ -0,0 +1,173 @@ +import { describe, expect, it } from '@jest/globals' + +import { mockCryptoFromArray, resetCrypto } from '../mocks/crypto' + +describe('ClassicPreset', () => { + // eslint-disable-next-line init-declarations + let preset!: typeof import('../../src/presets/classic') + + beforeEach(async () => { + mockCryptoFromArray(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])) + preset = await import('../../src/presets/classic') + }) + + afterEach(() => { + resetCrypto() + }) + + describe('Node', () => { + it('is instantiable', () => { + expect(new preset.Node('A')).toBeInstanceOf(preset.Node) + }) + + it('should have an id', () => { + const node = new preset.Node('A') + + expect(node.id).toBeDefined() + }) + + it('should have a label', () => { + const node = new preset.Node('A') + + expect(node.label).toBe('A') + }) + + it('adds Input', () => { + const node = new preset.Node('A') + const input = new preset.Input(new preset.Socket('a')) + + node.addInput('a', input) + + expect(node.hasInput('a')).toBeTruthy() + expect(node.inputs['a']).toBe(input) + }) + + it('throws error if Input already exists', () => { + const node = new preset.Node('A') + + node.addInput('a', new preset.Input(new preset.Socket('a'))) + + expect(() => node.addInput('a', new preset.Input(new preset.Socket('a')))).toThrow() + }) + + it('removes Input', () => { + const node = new preset.Node('A') + + node.addInput('a', new preset.Input(new preset.Socket('a'))) + node.removeInput('a') + + expect(node.hasInput('a')).toBeFalsy() + }) + + it('adds Output', () => { + const node = new preset.Node('A') + const output = new preset.Output(new preset.Socket('a')) + + node.addOutput('a', output) + + expect(node.hasOutput('a')).toBeTruthy() + expect(node.outputs['a']).toBe(output) + }) + + it('throws error if Output already exists', () => { + const node = new preset.Node('A') + + node.addOutput('a', new preset.Output(new preset.Socket('a'))) + + expect(() => node.addOutput('a', new preset.Output(new preset.Socket('a')))).toThrow() + }) + + it('removes Output', () => { + const node = new preset.Node('A') + + node.addOutput('a', new preset.Output(new preset.Socket('a'))) + node.removeOutput('a') + + expect(node.hasOutput('a')).toBeFalsy() + }) + }) + + describe('Connection', () => { + it('Connection throws error if input not found', () => { + const a = new preset.Node('A') + const b = new preset.Node('B') + + a.addOutput('a', new preset.Output(new preset.Socket('a'))) + + expect(() => new preset.Connection(a, 'a', b, 'b')).toThrow() + }) + + it('Connection throws error if output not found', () => { + const a = new preset.Node('A') + const b = new preset.Node('B') + + b.addInput('b', new preset.Input(new preset.Socket('b'))) + + expect(() => new preset.Connection(a, 'a', b, 'b')).toThrow() + }) + + it('Connection is instantiable', () => { + const a = new preset.Node('A') + const b = new preset.Node('B') + const output = new preset.Output(new preset.Socket('b')) + const input = new preset.Input(new preset.Socket('a')) + + a.addOutput('a', output) + b.addInput('b', input) + + expect(new preset.Connection(a, 'a', b, 'b')).toBeInstanceOf(preset.Connection) + }) + }) + + describe('Control', () => { + it('adds Control to Node', () => { + const node = new preset.Node('A') + + node.addControl('ctrl', new preset.Control()) + + expect(node.hasControl('ctrl')).toBeTruthy() + }) + + it('throws error if Control already exists', () => { + const node = new preset.Node('A') + + node.addControl('ctrl', new preset.Control()) + + expect(() => node.addControl('ctrl', new preset.Control())).toThrow() + }) + + it('removes Control from Node', () => { + const node = new preset.Node('A') + + node.addControl('ctrl', new preset.Control()) + node.removeControl('ctrl') + + expect(node.hasControl('ctrl')).toBeFalsy() + }) + + it('adds Control to Input', () => { + const input = new preset.Input(new preset.Socket('a')) + + input.addControl(new preset.Control()) + + expect(input.control).toBeTruthy() + }) + + it('throws error if Control in Input already exists', () => { + const input = new preset.Input(new preset.Socket('a')) + + input.addControl(new preset.Control()) + + expect(() => input.addControl(new preset.Control())).toThrow() + }) + + it('removes Control from Input', () => { + const input = new preset.Input(new preset.Socket('a')) + + input.addControl(new preset.Control()) + input.removeControl() + + expect(input.control).toBeFalsy() + }) + }) +}) diff --git a/test/scope.test.ts b/test/scope.test.ts new file mode 100644 index 00000000..33d860d6 --- /dev/null +++ b/test/scope.test.ts @@ -0,0 +1,162 @@ +import { describe, expect, it, jest } from '@jest/globals' + +import { Scope } from '../src/scope' + +type Parent = { parent: string } +type Child = { child: number } + +describe('Scope', () => { + it('should create a new Scope instance', () => { + const scope = new Scope('test') + + expect(scope).toBeInstanceOf(Scope) + }) + + it('doesnt have a parent by default', () => { + const scope = new Scope('test') + + expect(scope.hasParent()).toBeFalsy() + }) + + describe('parent-child', () => { + it('should set a parent scope', () => { + const parent = new Scope('parent') + const child = new Scope('child') + + child.setParent(parent) + + expect(child.parentScope()).toBe(parent) + }) + + it('should use a nested scope', () => { + const parent = new Scope('parent') + const child = new Scope('child') + + parent.use(child) + expect(child.hasParent()).toBeTruthy() + expect(child.parentScope()).toBe(parent) + }) + + it('should throw an error when using a non-Scope instance', () => { + const parent = new Scope('parent') + const child = { signal: { emit: jest.fn() } } + + expect(() => parent.use(child as any)).toThrowError('cannot use non-Scope instance') + }) + + it('should throw an error when trying to access a parent without one', () => { + const scope = new Scope('test') + + expect(() => scope.parentScope()).toThrowError('cannot find parent') + }) + + it('should throw an error when trying to access a parent with the wrong type', () => { + class WrongScope extends Scope { } + const parent = new Scope('parent') + const child = new Scope('child') + + parent.use(child) + + expect(() => child.parentScope(WrongScope)).toThrowError('actual parent is not instance of type') + }) + }) + + describe('addPipe', () => { + it('should emit a signal', async () => { + const scope = new Scope('test') + const pipe = jest.fn<() => Parent>() + + scope.addPipe(pipe) + await scope.emit({ parent: 'test' }) + + expect(pipe).toHaveBeenCalledWith({ parent: 'test' }) + }) + + it('should return a promise from emit', async () => { + const scope = new Scope('test') + const signal = jest.fn<() => Parent>() + + scope.addPipe(signal) + const result = scope.emit({ parent: 'test' }) + + expect(result).toBeInstanceOf(Promise) + }) + + it('should return the result of the signal', async () => { + const scope = new Scope('test') + const signal = jest.fn<() => Parent>().mockReturnValue({ parent: 'test-result' }) + + scope.addPipe(signal) + const result = await scope.emit({ parent: 'test' }) + + expect(result).toEqual({ parent: 'test-result' }) + }) + + it('should return undefined if the signal returns undefined', async () => { + const scope = new Scope('test') + // eslint-disable-next-line no-undefined + const signal = jest.fn().mockReturnValue(undefined) + + scope.addPipe(signal) + const result = await scope.emit('test') + + expect(result).toBeUndefined() + }) + + it('should return the result of the signal with a parent', async () => { + const parent = new Scope('parent') + const child = new Scope('child') + const signal = jest.fn<() => Parent>().mockReturnValue({ parent: 'test-parent' }) + + parent.addPipe(signal) + parent.use(child) + const result = await child.emit({ child: 1 }) + + expect(result).toEqual({ child: 1 }) + }) + + it('should return the result of the signal with a parent and child', async () => { + const parent = new Scope('parent') + const child = new Scope('child') + const signal = jest.fn<() => Child>().mockReturnValue({ child: 1 }) + + parent.use(child) + child.addPipe(signal) + const result = await child.emit({ child: 2 }) + + expect(result).toEqual({ child: 1 }) + }) + + it('should transfer signals from parent to child', async () => { + const parent = new Scope('parent') + const child = new Scope('child') + const parentSignal = jest.fn<() => Parent>().mockReturnValue({ parent: 'test-parent' }) + const childSignal = jest.fn<() => Child>() + + parent.addPipe(parentSignal) + child.addPipe(childSignal) + parent.use(child) + + await parent.emit({ parent: 'test-parent' }) + + expect(childSignal).toHaveBeenCalledWith({ parent: 'test-parent' }) + }) + + it('should prevent execution of child signal if parent signal returns undefined', async () => { + const parent = new Scope('parent') + const child = new Scope('child') + // eslint-disable-next-line no-undefined + const parentSignal = jest.fn<() => Parent | undefined>().mockReturnValue(undefined) + const childSignal = jest.fn<() => Child>() + + parent.addPipe(parentSignal) + child.addPipe(childSignal) + parent.use(child) + + await parent.emit({ parent: 'test-parent' }) + + expect(childSignal).not.toHaveBeenCalled() + }) + }) +}) + diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 00000000..ae40b9f1 --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it, jest } from '@jest/globals' +import { Buffer } from 'buffer' + +import { mockCryptoFromArray, mockCryptoFromBuffer, resetCrypto } from './mocks/crypto' + +describe('getUID', () => { + beforeEach(() => { + jest.resetModules() + }) + + afterEach(() => { + resetCrypto() + }) + + it('should return a unique id based on crypto.getRandomValues', async () => { + mockCryptoFromArray(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])) + + const { getUID } = await import('../src/utils') + const uid = getUID() + + expect(uid).toHaveLength(16) + }) + + it('should return a unique id based on crypto.randomBytes', async () => { + mockCryptoFromBuffer(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8])) + + const { getUID } = await import('../src/utils') + const uid = getUID() + + expect(uid).toHaveLength(16) + }) +}) From 56b3b0bc4c54fb7e4bed01dea3802ce27d6135f6 Mon Sep 17 00:00:00 2001 From: Ni55aN Date: Sat, 20 Apr 2024 16:09:22 +0300 Subject: [PATCH 3/3] build(deps): update rete cli --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9994db02..98def4cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "jest-environment-jsdom": "^29.1.2", - "rete-cli": "^1.0.1", + "rete-cli": "^1.0.2", "typescript": "4.8.4" } }, @@ -6424,9 +6424,9 @@ } }, "node_modules/rete-cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rete-cli/-/rete-cli-1.0.1.tgz", - "integrity": "sha512-mh7Voj+4FjJovCcXNHVXkTQewr8Yf1cyarayRUCbkSwkKxFjnl0HHOy2D17i6LCYAmnXIn3ShzwH3IWnw1MJPw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rete-cli/-/rete-cli-1.0.2.tgz", + "integrity": "sha512-oHI2ui5kRs2aCLvlklNc9VNhHQZgGGZfFUe9Z2WHEe8bdXM24gjwcZc4MEkUvlFocPxyY65rfz3eEK7AiQnxEg==", "dev": true, "dependencies": { "@babel/plugin-transform-runtime": "^7.21.4", diff --git a/package.json b/package.json index b47119f3..1cd72da8 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "devDependencies": { "jest-environment-jsdom": "^29.1.2", - "rete-cli": "^1.0.1", + "rete-cli": "^1.0.2", "typescript": "4.8.4" }, "dependencies": {