Skip to content

Commit d593579

Browse files
authored
test: add unit and e2e tests (#244)
* test: add unit and e2e tests * test: add cli to deps * test: remove start from test as not stable
1 parent eccf6e2 commit d593579

53 files changed

Lines changed: 4456 additions & 860 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
uses: ./.github/actions/prepare
2121

2222
- name: "Run tests"
23-
run: pnpm test:unit
23+
run: pnpm test
2424

2525
- name: "Run linter"
2626
run: pnpm lint

.husky/pre-push

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
pnpm lint
2-
pnpm build
3-
pnpm test:unit
1+
pnpm run build
2+
pnpm run lint
3+
pnpm run test

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ packages/
1010
example/
1111
utils/
1212
releases/
13+
.nanoforge/
1314

1415
.turbo/
1516
node_modules/

e2e/game.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { execSync } from "child_process";
2+
import { existsSync, rmSync } from "fs";
3+
import { dirname, join } from "path";
4+
import { fileURLToPath } from "url";
5+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
6+
7+
const PROJECT_DIR = join(dirname(fileURLToPath(import.meta.url)), "./game");
8+
9+
describe("E2E Game", () => {
10+
beforeAll(() => {
11+
rmSync(join(PROJECT_DIR, ".nanoforge"), { recursive: true, force: true });
12+
execSync("pnpm run build", {
13+
cwd: PROJECT_DIR,
14+
stdio: "pipe",
15+
timeout: 120_000,
16+
});
17+
}, 130_000);
18+
19+
afterAll(() => {
20+
rmSync(join(PROJECT_DIR, ".nanoforge"), { recursive: true, force: true });
21+
}, 130_000);
22+
23+
describe("Build", () => {
24+
it("should produce a server bundle", () => {
25+
expect(existsSync(join(PROJECT_DIR, ".nanoforge/server/main.js"))).toBe(true);
26+
});
27+
28+
it("should produce a client bundle directory", () => {
29+
expect(existsSync(join(PROJECT_DIR, ".nanoforge/client"))).toBe(true);
30+
});
31+
32+
it("should include the WASM file in the server bundle", () => {
33+
expect(existsSync(join(PROJECT_DIR, ".nanoforge/server/libecs.wasm"))).toBe(true);
34+
});
35+
});
36+
});

e2e/game/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.nanoforge/
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { type EditorComponentManifest } from "@nanoforge-dev/ecs-client";
2+
3+
export class ExampleComponent {
4+
name = this.constructor.name;
5+
6+
constructor(
7+
public paramA: string,
8+
public paramB: number,
9+
public paramC: boolean = false,
10+
) {}
11+
12+
get foo() {
13+
return "bar";
14+
}
15+
16+
get paramAOrDefault() {
17+
return this.paramC ? this.paramA : "default";
18+
}
19+
20+
addOne() {
21+
this.paramB += 1;
22+
}
23+
}
24+
25+
// * Required to generate code
26+
export default ExampleComponent.name;
27+
28+
// * Required for the editor to display the component and generate code
29+
export const EDITOR_COMPONENT_MANIFEST: EditorComponentManifest = {
30+
name: "Example",
31+
description: "Example component description",
32+
params: {
33+
paramA: {
34+
type: "string",
35+
name: "Param A",
36+
description: "Param A description",
37+
example: "Example value",
38+
},
39+
paramB: {
40+
type: "number",
41+
name: "Param B",
42+
description: "Param B description",
43+
example: 3,
44+
},
45+
paramC: {
46+
type: "boolean",
47+
name: "Param C",
48+
description: "Param C description",
49+
example: true,
50+
default: false,
51+
// Not required because it has a default value
52+
optional: true,
53+
},
54+
},
55+
};

e2e/game/client/main.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { AssetManagerLibrary } from "@nanoforge-dev/asset-manager";
2+
import { type IRunOptions } from "@nanoforge-dev/common";
3+
import { NanoforgeFactory } from "@nanoforge-dev/core";
4+
import { ECSClientLibrary } from "@nanoforge-dev/ecs-client";
5+
import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d";
6+
import { InputLibrary } from "@nanoforge-dev/input";
7+
import { MusicLibrary } from "@nanoforge-dev/music";
8+
import { NetworkClientLibrary } from "@nanoforge-dev/network-client";
9+
import { SoundLibrary } from "@nanoforge-dev/sound";
10+
11+
import { ExampleComponent } from "./components/example.component";
12+
import { exampleSystem } from "./systems/example.system";
13+
14+
export async function main(options: IRunOptions) {
15+
const app = NanoforgeFactory.createClient({
16+
tickRate: 60,
17+
environment: {
18+
serverAddress: "127.0.0.1",
19+
serverTcpPort: "4445",
20+
serverUdpPort: "4444",
21+
},
22+
});
23+
24+
const assetManager = new AssetManagerLibrary();
25+
const ecs = new ECSClientLibrary();
26+
const graphics = new Graphics2DLibrary();
27+
const input = new InputLibrary();
28+
const music = new MusicLibrary();
29+
const network = new NetworkClientLibrary();
30+
const sound = new SoundLibrary();
31+
32+
app.useAssetManager(assetManager);
33+
app.useComponentSystem(ecs);
34+
app.useGraphics(graphics);
35+
app.useInput(input);
36+
app.useSound(sound);
37+
app.useNetwork(network);
38+
app.use(Symbol("music"), music);
39+
40+
await app.init(options);
41+
42+
const registry = ecs.registry;
43+
44+
const exampleEntity = registry.spawnEntity();
45+
registry.addComponent(exampleEntity, new ExampleComponent("example", 10));
46+
47+
registry.addSystem(exampleSystem);
48+
49+
await app.run();
50+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { type Context } from "@nanoforge-dev/common";
2+
import { type EditorSystemManifest, type Registry } from "@nanoforge-dev/ecs-client";
3+
4+
import { ExampleComponent } from "../components/example.component";
5+
6+
export const exampleSystem = (registry: Registry, ctx: Context) => {
7+
const entities = registry.getZipper([ExampleComponent]);
8+
9+
entities.forEach((entity) => {
10+
if (entity.ExampleComponent.paramA === "end") {
11+
ctx.app.setIsRunning(false);
12+
return;
13+
}
14+
15+
if (entity.ExampleComponent.paramB === 0) entity.ExampleComponent.paramA = "end";
16+
17+
if (entity.ExampleComponent.paramB >= 0)
18+
entity.ExampleComponent.paramB = entity.ExampleComponent.paramB - 1;
19+
});
20+
};
21+
22+
// * Required to generate code
23+
export default exampleSystem.name;
24+
25+
// * Required for the editor to display the system and generate code
26+
export const EDITOR_SYSTEM_MANIFEST: EditorSystemManifest = {
27+
name: "Example",
28+
description:
29+
"This system end the game when paramB reaches 0 for any entity with ExampleComponent",
30+
dependencies: ["ExampleComponent"],
31+
};

e2e/game/nanoforge.config.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"client": {
3+
"build": {
4+
"entryFile": "client/main.ts",
5+
"outDir": ".nanoforge/client"
6+
},
7+
"runtime": {
8+
"dir": ".nanoforge/client"
9+
}
10+
},
11+
"server": {
12+
"enable": true,
13+
"build": {
14+
"entryFile": "server/main.ts",
15+
"outDir": ".nanoforge/server"
16+
},
17+
"runtime": {
18+
"dir": ".nanoforge/server"
19+
}
20+
}
21+
}

e2e/game/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"$schema": "https://json.schemastore.org/package.json",
3+
"name": "@nanoforge-dev/e2e-game",
4+
"version": "0.0.0",
5+
"description": "NanoForge Engine - End-to-End test game",
6+
"type": "module",
7+
"scripts": {
8+
"build": "nf build",
9+
"start": "nf start"
10+
},
11+
"devDependencies": {
12+
"@nanoforge-dev/asset-manager": "workspace:*",
13+
"@nanoforge-dev/cli": "latest",
14+
"@nanoforge-dev/common": "workspace:*",
15+
"@nanoforge-dev/core": "workspace:*",
16+
"@nanoforge-dev/ecs-client": "workspace:*",
17+
"@nanoforge-dev/ecs-server": "workspace:*",
18+
"@nanoforge-dev/graphics-2d": "workspace:*",
19+
"@nanoforge-dev/input": "workspace:*",
20+
"@nanoforge-dev/music": "workspace:*",
21+
"@nanoforge-dev/network-client": "workspace:*",
22+
"@nanoforge-dev/network-server": "workspace:*",
23+
"@nanoforge-dev/sound": "workspace:*",
24+
"typescript": "catalog:core",
25+
"vitest": "catalog:test"
26+
},
27+
"private": true,
28+
"engines": {
29+
"node": "25"
30+
}
31+
}

0 commit comments

Comments
 (0)