Skip to content

Commit

Permalink
add isEmpty node (#479)
Browse files Browse the repository at this point in the history
* add isEmpty node

* fix formating, add tests

* Update typing

* Update changeset

---------

Co-authored-by: SorsOps <[email protected]>
  • Loading branch information
mck and SorsOps authored Aug 2, 2024
1 parent 93ebb0a commit 02620af
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-starfishes-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tokens-studio/graph-engine": minor
---

Add typing/hasValue node that lets you check if a value is present
33 changes: 33 additions & 0 deletions packages/graph-engine/src/nodes/typing/hasValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { AnySchema, BooleanSchema } from '../../schemas/index.js';
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';

export default class NodeDefinition<T> extends Node {
static title = 'Has Value';
static type = 'studio.tokens.typing.hasValue';
static description =
'Checks if a value is defined. Returns true if undefined or null.';

declare inputs: ToInput<{
value: T;
}>;
declare outputs: ToOutput<{
undefined: boolean;
}>;

constructor(props: INodeDefinition) {
super(props);
this.addInput('value', {
type: AnySchema
});

this.addOutput('undefined', {
type: BooleanSchema
});
}

execute(): void | Promise<void> {
const { value } = this.getAllInputs();
this.setOutput('undefined', value === null || value === undefined);
}
}
3 changes: 2 additions & 1 deletion packages/graph-engine/src/nodes/typing/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assertDefined from './assertDefined.js';
import hasValue from './hasValue.js';
import parseUnit from './parseUnit.js';
import passUnit from './passUnit.js';

export const nodes = [assertDefined, passUnit, parseUnit];
export const nodes = [assertDefined, hasValue, passUnit, parseUnit];
44 changes: 44 additions & 0 deletions packages/graph-engine/tests/suites/nodes/array/arrify.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { AnySchema, StringSchema } from '@/index.js';
import { Graph } from '@/graph/graph.js';
import { arrayOf } from '@/schemas/utils.js';
import { describe, expect, test } from 'vitest';
import ConstantSourceNode from '@/nodes/generic/constant.js';
import Node from '@/nodes/array/arrify.js';

describe('array/arrify', () => {
test('exports an array by default', async () => {
const graph = new Graph();
const node = new Node({ graph });

await node.execute();

const actual = node.outputs.value.value;

expect(actual).to.eql([]);
expect(node.outputs.value.type).to.contains(arrayOf(AnySchema));
});
test('produces the expected array', async () => {
const graph = new Graph();
const node = new Node({ graph });

const constantA = new ConstantSourceNode({ graph });
constantA.inputs.value.setValue('hello', {
type: StringSchema
});

const constantB = new ConstantSourceNode({ graph });
constantB.inputs.value.setValue('world', {
type: StringSchema
});

constantA.outputs.value.connect(node.inputs.items);
constantB.outputs.value.connect(node.inputs.items);

await node.execute();

const actual = node.outputs.value.value;

expect(actual).to.eql(['hello', 'world']);
expect(node.outputs.value.type).to.contains(arrayOf(StringSchema));
});
});
39 changes: 39 additions & 0 deletions packages/graph-engine/tests/suites/nodes/array/flatten.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Graph } from '@/graph/graph.js';
import { NumberSchema } from '@/index.js';
import { arrayOf } from '@/schemas/utils.js';
import { describe, expect, test } from 'vitest';
import Node from '@/nodes/array/flatten.js';

describe('array/flatten', () => {
test('does not work without inputs', async () => {
const graph = new Graph();
const node = new Node({ graph });

try {
await node.execute();
} catch (err) {
expect(err.message).to.be.eql('Input array must be an array of arrays');
}
});

test('flattens arrays as expected', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.array.setValue(
[
[1, 2],
[3, 4]
],
{
type: arrayOf(arrayOf(NumberSchema))
}
);

await node.execute();

const actual = node.outputs.array.value;

expect(actual).to.eql([1, 2, 3, 4]);
});
});
82 changes: 82 additions & 0 deletions packages/graph-engine/tests/suites/nodes/typing/hasValue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Graph } from '../../../../src/graph/graph.js';
import { describe, expect, test } from 'vitest';
import Node from '../../../../src/nodes/typing/hasValue.js';

describe('typing/hasValue', () => {
test('should return true for null value', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue(null);

await node.execute();

expect(node.outputs.undefined.value).to.be.true;
});

test('should return true for undefined value', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue(undefined);

await node.execute();

expect(node.outputs.undefined.value).to.be.true;
});

test('should return false for empty string', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue('');

await node.execute();

expect(node.outputs.undefined.value).to.be.false;
});

test('should return false for non-empty string', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue('Hello');

await node.execute();

expect(node.outputs.undefined.value).to.be.false;
});

test('should return false for number', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue(0);

await node.execute();

expect(node.outputs.undefined.value).to.be.false;
});

test('should return false for non-empty array', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue([1, 2, 3]);

await node.execute();

expect(node.outputs.undefined.value).to.be.false;
});

test('should return false for non-empty object', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue({ key: 'value' });

await node.execute();

expect(node.outputs.undefined.value).to.be.false;
});
});
5 changes: 4 additions & 1 deletion packages/graph-engine/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"types": ["node"],
"baseUrl": "."
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["stories/**/*", "src/**/*", "tests/**/*"],
"exclude": ["node_modules", "cypress/**"]
Expand Down
10 changes: 10 additions & 0 deletions packages/graph-engine/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
test: {
// ... Specify options here.
},
plugins: [tsconfigPaths()]
});

0 comments on commit 02620af

Please sign in to comment.