Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7195288
install jest for unit tests
dave-artificial-agency Oct 9, 2025
d39ef4a
add test class to test nodes
dave-artificial-agency Sep 10, 2025
91e6709
add jest config file
dave-artificial-agency Sep 9, 2025
01a40ce
add github action file to run the tests in PRs
dave-artificial-agency Sep 9, 2025
2ea898a
tests - add tests for PinPropertyParser.bSerializeAsSinglePrecisionFloat
dave-artificial-agency Sep 10, 2025
a46ac14
tests - add tests for PinPropertyParser SubPins
dave-artificial-agency Sep 10, 2025
654d9c7
tests - add tests for PinPropertyParser ParentPin
dave-artificial-agency Sep 10, 2025
84dad2e
tests - add tests for PinPropertyParser DesiredPinDirection
dave-artificial-agency Sep 10, 2025
eb80ee5
tests - add test for GenericNodeParser UserDefinedPin
dave-artificial-agency Sep 10, 2025
e7b4697
tests - add null check tests for PinPropertyParser null subCategoryOb…
dave-artificial-agency Sep 10, 2025
1103eae
tests - add test for ColorUtils getPinColor() handles null subCategor…
dave-artificial-agency Sep 10, 2025
bce2bc3
tests - add test for ColorUtil real type
dave-artificial-agency Sep 10, 2025
8745b31
tests - add tests for PinPropertyParser INVTEXT
dave-artificial-agency Sep 10, 2025
d3e3496
tests - add test for CallFunctionNodeParser bDefaultsToPureFunc
dave-artificial-agency Sep 10, 2025
da200a8
fix error: Label.textAlign on load
dave-artificial-agency Sep 10, 2025
ad6125a
exclude tests from build
dave-artificial-agency Sep 10, 2025
223e047
add parser for INVTEXT in PinFriendlyName
dave-artificial-agency Sep 11, 2025
8feecc7
move repeated mock to global test setup
dave-artificial-agency Oct 9, 2025
ba612ca
add test config for better imports
dave-artificial-agency Sep 29, 2025
8f405f6
remove unneeded mock
dave-artificial-agency Sep 29, 2025
cf66358
readme - note how to run the tests
dave-artificial-agency Oct 9, 2025
04aed10
Merge branch 'main' of https://github.com/ai-startup/klee into add-tests
dave-artificial-agency Oct 14, 2025
6a2acbc
update tests to use new merged registry syntax
dave-artificial-agency Oct 14, 2025
a8514e7
add tests for PinPropertyParser regex cases
dave-artificial-agency Oct 17, 2025
ab63b1e
add tests for parsing escaped quotes in DefaultValue
dave-artificial-agency Oct 17, 2025
2c12ecb
improve regex to parse escaped quotes
dave-artificial-agency Oct 17, 2025
13d550f
add friendly name for Get Actor Location
dave-artificial-agency Oct 22, 2025
f8ab440
correctly render vector type Return Value as yellow
dave-artificial-agency Oct 22, 2025
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
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Tests

on:
pull_request:
branches: [ main]
push:
branches: [ main]

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run Jest
run: npx jest
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ npm install
npm run dev
```

### Run the tests

```bash
npx jest
```
15 changes: 15 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'js'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.test.ts'
],
clearMocks: true,
setupFiles: ['<rootDir>/src/jest.setup.ts'],
};
6,051 changes: 4,776 additions & 1,275 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@
"Pascal Aregger <pascal.aregger97@gmail.com>"
],
"devDependencies": {
"@types/jest": "^30.0.0",
"ifdef-loader": "^2.3.2",
"jest": "^30.2.0",
"ts-jest": "^29.4.4",
"ts-loader": "^9.4.1",
"typescript": "^4.9.3",
"typescript": "^4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0",
"webpack-dev-server": "^4.11.1",
Expand Down
1 change: 1 addition & 0 deletions src/controls/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class Label extends UserControl {
this.text = text;
this.font = (font || Constants.NODE_FONT);
this.color = (color || Constants.NODE_TEXT_COLOR);
this.textAlign = 'left';

this.padding = { top: 0, right: 0, bottom: 0, left: 0 }
}
Expand Down
14 changes: 14 additions & 0 deletions src/controls/tests/label.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// <reference types="jest" />

import { Label } from '../label';

describe('Label', () => {
test('constructor sets default values', () => {
const label = new Label('Test Text');

expect(label.text).toBe('Test Text');
expect(label.textAlign).toBeDefined();
expect(label.font).toBeDefined();
expect(label.color).toBeDefined();
});
});
33 changes: 33 additions & 0 deletions src/controls/utils/tests/color-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// <reference types="jest" />

import { ColorUtils } from '../color-utils';
import { PinProperty } from '../../../data/pin/pin-property';
import { PinCategory } from '../../../data/pin/pin-category';

describe('ColorUtils', () => {
test('getPinColor handles null subCategoryObject', () => {
const pin = new PinProperty('TestNode');
pin.category = PinCategory.object;
pin.subCategoryObject = undefined;

const color = ColorUtils.getPinColor(pin);

expect(color).toBeDefined();
expect(typeof color).toBe('string');
});

test('getPinColor returns correct color for real category', () => {
const realPin = new PinProperty('RealPin');
realPin.category = PinCategory.real;
const realColor = ColorUtils.getPinColor(realPin);

const floatPin = new PinProperty('FloatPin');
floatPin.category = PinCategory.float;
const floatColor = ColorUtils.getPinColor(floatPin);

expect(realColor).toBeDefined();
expect(typeof realColor).toBe('string');
expect(realColor).toBe(floatColor);
});
});

4 changes: 4 additions & 0 deletions src/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// <reference types="jest" />

// Mock browser APIs that aren't available in Node.js
global.Path2D = jest.fn().mockImplementation((path) => ({ path }));
3 changes: 2 additions & 1 deletion src/parser/node-friendly-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export const NodeFriendlyNames: { [name: string]: string; } = {
'K2_GetTimerElapsedTimeHandle': 'Get Timer Elapsed Time by Handle',
'K2_InvalidateTimerHandle': 'Invalidate',
'K2_IsTimerActiveHandle': 'Is Timer Active by Handle',
'K2_DestroyActor': 'Destroy Actor'
'K2_DestroyActor': 'Destroy Actor',
'K2_GetActorLocation': 'Get Actor Location',
}
32 changes: 32 additions & 0 deletions src/parser/node-parsers/tests/call-function-node-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/// <reference types="jest" />

import { CallFunctionNodeParser } from '../call-function-node.parser';
import { ParsingNodeData } from '../../parsing-node-data';
import { CallFunctionNode } from '../../../data/nodes/call-function.node';
import { Vector2 } from '../../../math/vector2';

describe('CallFunctionNodeParser', () => {
test('bDefaultsToPureFunc=True sets isPureFunc to true', () => {
const parser = new CallFunctionNodeParser();

const node: CallFunctionNode = {
pos: new Vector2(0, 0),
title: 'Test',
subTitles: [],
customProperties: [],
isPureFunc: false,
isConstFunc: false,
functionReference: null
} as CallFunctionNode;

const parsingData = new ParsingNodeData([
'Begin Object',
'bDefaultsToPureFunc=True',
'End Object'
]);

parsingData.node = node;
parser.parse(parsingData);
expect(node.isPureFunc).toBe(true);
});
});
52 changes: 52 additions & 0 deletions src/parser/node-parsers/tests/generic-node-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/// <reference types="jest" />

import { GenericNodeParser } from '../generic-node.parser';
import { NodeParserRegistry } from "../../node-parser-registry";
import { ParsingNodeData } from '../../parsing-node-data';
import { PinDirection } from '../../../data/pin/pin-direction';
import { TestableNodeControl } from '../../../tests/testable-node-control';


// Mock UnrealNodeClass to reduce expensive constructor loop
jest.mock('../../../data/classes/unreal-node-class', () => ({
UnrealNodeClass: {
CUSTOM_EVENT: '/Script/BlueprintGraph.K2Node_CustomEvent'
}
}));

describe('GenericNodeParser', () => {
let parser: GenericNodeParser;

beforeEach(() => {
let nodeParserRegistry = new NodeParserRegistry();
parser = new GenericNodeParser(nodeParserRegistry);
});

test('Can parse CustomProperties UserDefinedPin', () => {
const lines = [
'Begin Object Class=/Script/BlueprintGraph.K2Node_CustomEvent Name="K2Node_CustomEvent_4"',
' NodeGuid=7E57DA7A000000000000000000000000',
'CustomProperties UserDefinedPin (PinName="Context",PinType=(PinCategory="object"),DesiredPinDirection=EGPD_Output)',
'End Object'
];

const parsingData = new ParsingNodeData(lines);
const nodeControl = parser.parse(parsingData);
const result = new TestableNodeControl(nodeControl);

expect(result).toBeDefined();
expect(result.node).toBeDefined();
expect(result.node.customProperties).toBeDefined();

const userDefinedPin = result.node.customProperties.find((prop: any) =>
prop.constructor.name === 'PinProperty' && prop.name === 'Context'
) as any;

expect(userDefinedPin).toBeDefined();
expect(userDefinedPin.name).toBe('Context');
expect(userDefinedPin.category).toBe('object');
expect(userDefinedPin.direction).toBe(PinDirection.EGPD_Output);
});

});

39 changes: 30 additions & 9 deletions src/parser/pin-property.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,14 @@ export class PinPropertyParser implements CustomPropertyParser {

// ([a-zA-Z0-9_.]+) Capture key (similar to \w, but also allows dots)
// \s*=\s* Equal sign between optional white spaces
// (("[^"]*")|(\([^\)]*\))|([^,]*)) Captures a value implemented in one of 4 types
// (("[^"]*")|(\([^\)]*\))|([^,]*)) Captures a value implemented in one of several types
// ("[^"]*") Type 1: capture quoted values e.g.: PinName="self"
// (\([^\)]*\)) Type 2: capture values set in brackets e.g.: LinkedTo=(K2Node_CallFunction_0 6A3D6AD94697B8938F5061A6BA9D5FF2,)
// (\w*\(\w*(?:[^\(]*\([^\)]*\))*\)) Type 3: capture multilevel loctext e.g.: PinFriendlyName=LOCGEN_FORMAT_NAMED(NSLOCTEXT("KismetSchema", "SplitPinFriendlyNameFormat", "{PinDisplayName} {ProtoPinDisplayName}"), "PinDisplayName", NSLOCTEXT("", "E767B2BA4B1D5DFDD5E21E953300AB1E", "Settings"), "ProtoPinDisplayName", NSLOCTEXT("", "182F932842DA4BEA8624D89F6CD70FDA", "Attenuation Settings"))
// (\w*\([^\)]*\)) Type 4: capture method values e.g.: PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target")
// ([^,]*) Type 5: capture pure values e.g.: PinType.bIsConst=False
const matches = propertyData.matchAll(/([a-zA-Z0-9_.]+)\s*=\s*(("[^"]*")|(\([^)]*\))|(\w*\(\w*(?:[^(]*\([^)]*(?:"[^"]*")\))*\))|(\w*\([^)]*\([^)]*\)[^)]*\))|(\w*\([^)]*\))|([^,]*))/g);
// ("(?:[^"\\]|\\.)*\([^)]*\)") Type 6: capture struct values with escaped quotes e.g.: DefaultValue="(ExecutionFunction=\"\",CallbackTarget=None)"
const matches = propertyData.matchAll(/([a-zA-Z0-9_.]+)\s*=\s*(("(?:[^"\\]|\\.)*\([^)]*\)")|("[^"]*")|(\([^)]*\))|(\w*\(\w*(?:[^(]*\([^)]*(?:"[^"]*")\))*\))|(\w*\([^)]*\([^)]*\)[^)]*\))|(\w*\([^)]*\))|([^,]*))/g);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this regex change , several of these regex comments are no longer accurate. I can update them, or delete them so we don't have to maintain anything if that's better.


for (const [fullMatch, key, value] of matches) {
if(!fullMatch || !key) { console.warn(`Skipped property attribute because invalid key: '${fullMatch}'`); continue; }
Expand Down Expand Up @@ -141,14 +142,26 @@ export class PinPropertyParser implements CustomPropertyParser {


private static parseSubCategoryObject(value: string): PinSubCategoryObject {
let className = value;
let type = value.substring(0, value.indexOf("'"));
let matches = value.matchAll(/'"(.*)"'/g);
if (matches) {
let match = matches.next();
// Remove only the outer double quotes, preserve inner single quotes
if (value.startsWith('"') && value.endsWith('"')) {
value = value.substring(1, value.length - 1);
}

if (match && match.value) {
className = match.value[1];
let className = value;
let type = '';

// Check if there's a wrapper format like:
// /Script/CoreUObject.ScriptStruct'/Script/CoreUObject.Vector'
// Split by single quote to extract the parts
const quoteIndex = value.indexOf("'");
if (quoteIndex !== -1) {
// Extract the type (everything before the first single quote)
type = value.substring(0, quoteIndex);
// Extract the inner class path (between the single quotes)
const remainingPart = value.substring(quoteIndex + 1);
const endQuoteIndex = remainingPart.lastIndexOf("'");
if (endQuoteIndex !== -1) {
className = remainingPart.substring(0, endQuoteIndex);
}
}

Expand All @@ -158,6 +171,14 @@ export class PinPropertyParser implements CustomPropertyParser {
private static parsePinFriendlyName(value: string): string {
let name:string = "";

if (value.startsWith("INVTEXT")) {
let prefixLength = 'INVTEXT('.length;
value = value.substr(prefixLength, value.length - prefixLength - 1);

name = value.replace(/"/g, '');
return name;
}

if (value.startsWith("NSLOCTEXT")) {
let prefixLength = 'NSLOCTEXT('.length - 1;
value = value.substr(prefixLength, value.length - prefixLength - 1);
Expand Down
Loading