Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 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
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 }));
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);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

One note for the future - currently the unit tests cannot call nodeParserRegistry.loadPlugins();, because the webpack require.context is not available inside the test.
You will get an error like:

    TypeError: require.context is not a function

      71 |
      72 |     public loadPlugins(): void {
    > 73 |         const pluginContext = (require as any).context('../../plugins', true, /\.plugin\.ts$/);
         |                                                ^

example:

https://github.com/Joined-Forces/klee/actions/runs/18507939361/job/52741425985?pr=8

I avoid this for now by just not calling that function inside the test.
But if we want to add tests for loadPlugins() in the future, we would need to fix this.

One possible way would be to patch and mock require.context before the tests run. This might require changing some code, as currently adding a mock in setupFiles happens too late.

But: this PR will add test coverage on a number of other areas, and we can always refactor later.
Let me know what you think.

});

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);
});

});

8 changes: 8 additions & 0 deletions src/parser/pin-property.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,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