Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/01-command_bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ body:
label: Which command is this bug report for?
options:
- build
- create
- dev
- generate
- help
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/02-feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ body:
label: Which command is this feature request for?
options:
- build
- create
- dev
- generate
- help
Expand Down
24 changes: 24 additions & 0 deletions .github/issue-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ command:build:
- "### Which command is this (bug
report|feature request) for\\?\\n\\nbuild\\n"

command:create:
- "### Which command is this (bug
report|feature request) for\\?\\n\\ncreate\\n"

command:dev:
- "### Which command is this (bug
report|feature request) for\\?\\n\\ndev\\n"

command:generate:
- "### Which command is this (bug
report|feature request) for\\?\\n\\ngenerate\\n"
Expand All @@ -14,10 +22,26 @@ command:install:
- "### Which command is this (bug
report|feature request) for\\?\\n\\ninstall\\n"

command:login:
- "### Which command is this (bug
report|feature request) for\\?\\n\\nlogin\\n"

command:logout:
- "### Which command is this (bug
report|feature request) for\\?\\n\\nlogout\\n"

command:new:
- "### Which command is this (bug
report|feature request) for\\?\\n\\nnew\\n"

command:publish:
- "### Which command is this (bug
report|feature request) for\\?\\n\\npublish\\n"

command:start:
- "### Which command is this (bug
report|feature request) for\\?\\n\\nstart\\n"

command:unpublish:
- "### Which command is this (bug
report|feature request) for\\?\\n\\nunpublish\\n"
6 changes: 6 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ command:build:
- src/command/commands/build.command.ts
- src/action/actions/build.action.ts

command:create:
- changed-files:
- any-glob-to-any-file:
- src/command/commands/create.command.ts
- src/action/actions/create.action.ts

command:dev:
- changed-files:
- any-glob-to-any-file:
Expand Down
4 changes: 4 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
description: "Related to build command"
color: "aaa3dc"

- name: "command:create"
description: "Related to create command"
color: "aaa3dc"

- name: "command:dev"
description: "Related to dev command"
color: "aaa3dc"
Expand Down
258 changes: 258 additions & 0 deletions e2e/cli-create.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { resolve } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";

import { runCli } from "./helpers/run-cli";

const tmpDir = resolve(__dirname, "../.tmp-e2e-create");

beforeAll(() => {
mkdirSync(tmpDir, { recursive: true });
});

afterAll(() => {
rmSync(tmpDir, { recursive: true, force: true });
});

describe("nf create (TypeScript, client component)", () => {
const projectDir = resolve(tmpDir, "create-ts-client");
const appDir = resolve(projectDir, "create-app");

beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });

await runCli([
"new",
"--name",
"create-app",
"--language",
"ts",
"--package-manager",
"npm",
"--strict",
"--no-server",
"--no-init-functions",
"--skip-install",
"--no-docker",
"-d",
projectDir,
]);
});

it("should create a component successfully", async () => {
const { stdout, exitCode } = await runCli([
"create",
"component",
"--name",
"player",
"-d",
appDir,
]);

expect(exitCode).toBe(0);
expect(stdout).toContain("NanoForge Component/System Creation");
expect(stdout).toContain("Element successfully created");
});

it("should generate a component file in client/components", () => {
expect(existsSync(resolve(appDir, "client/components/player.component.ts"))).toBe(true);
});

it("should create a system successfully", async () => {
const { stdout, exitCode } = await runCli([
"create",
"system",
"--name",
"movement",
"-d",
appDir,
]);

expect(exitCode).toBe(0);
expect(stdout).toContain("Element successfully created");
});

it("should generate a system file in client/systems", () => {
expect(existsSync(resolve(appDir, "client/systems/movement.system.ts"))).toBe(true);
});

it("should accept --config option", async () => {
const { exitCode } = await runCli([
"create",
"component",
"--name",
"health",
"--config",
"nanoforge.config.json",
"-d",
appDir,
]);

expect(exitCode).toBe(0);
});

it("should create a component with a custom path via --path", async () => {
const { exitCode } = await runCli([
"create",
"component",
"--name",
"custom",
"--path",
"client/custom-components",
"-d",
appDir,
]);

expect(exitCode).toBe(0);
expect(existsSync(resolve(appDir, "client/custom-components/custom.component.ts"))).toBe(true);
});
});

describe("nf create (TypeScript, server component)", () => {
const projectDir = resolve(tmpDir, "create-ts-server");
const appDir = resolve(projectDir, "create-server-app");

beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });

await runCli([
"new",
"--name",
"create-server-app",
"--language",
"ts",
"--package-manager",
"npm",
"--no-strict",
"--server",
"--no-init-functions",
"--skip-install",
"--no-docker",
"-d",
projectDir,
]);
});

it("should create a server component successfully", async () => {
const { exitCode } = await runCli([
"create",
"component",
"--name",
"enemy",
"--server",
"-d",
appDir,
]);

expect(exitCode).toBe(0);
});

it("should generate a component file in server/components", () => {
expect(existsSync(resolve(appDir, "server/components/enemy.component.ts"))).toBe(true);
});

it("should create a server system successfully", async () => {
const { exitCode } = await runCli([
"create",
"system",
"--name",
"physics",
"--server",
"-d",
appDir,
]);

expect(exitCode).toBe(0);
});

it("should generate a system file in server/systems", () => {
expect(existsSync(resolve(appDir, "server/systems/physics.system.ts"))).toBe(true);
});

it("should create a server component with a custom path via --path", async () => {
const { exitCode } = await runCli([
"create",
"component",
"--name",
"network",
"--server",
"--path",
"server/custom-components",
"-d",
appDir,
]);

expect(exitCode).toBe(0);
expect(existsSync(resolve(appDir, "server/custom-components/network.component.ts"))).toBe(true);
});
});

describe("nf create (JavaScript)", () => {
const projectDir = resolve(tmpDir, "create-js");
const appDir = resolve(projectDir, "create-js-app");

beforeAll(async () => {
mkdirSync(projectDir, { recursive: true });

await runCli([
"new",
"--name",
"create-js-app",
"--language",
"js",
"--package-manager",
"npm",
"--no-strict",
"--no-server",
"--no-init-functions",
"--skip-install",
"--no-docker",
"-d",
projectDir,
]);
});

it("should create a JavaScript component successfully", async () => {
const { exitCode } = await runCli(["create", "component", "--name", "sprite", "-d", appDir]);

expect(exitCode).toBe(0);
});

it("should generate a .js component file", () => {
expect(existsSync(resolve(appDir, "client/components/sprite.component.js"))).toBe(true);
});

it("should create a JavaScript system successfully", async () => {
const { exitCode } = await runCli(["create", "system", "--name", "render", "-d", appDir]);

expect(exitCode).toBe(0);
});

it("should generate a .js system file", () => {
expect(existsSync(resolve(appDir, "client/systems/render.system.js"))).toBe(true);
});
});

describe("nf create (error cases)", () => {
it("should fail when directory does not exist", async () => {
const { exitCode } = await runCli([
"create",
"component",
"--name",
"test",
"-d",
resolve(tmpDir, "nonexistent"),
]);

expect(exitCode).not.toBe(0);
});

it("should fail with an invalid type", async () => {
const projectDir = resolve(tmpDir, "create-ts-client");
const appDir = resolve(projectDir, "create-app");

const { exitCode } = await runCli(["create", "invalid-type", "--name", "test", "-d", appDir]);

expect(exitCode).not.toBe(0);
});
});
22 changes: 10 additions & 12 deletions e2e/cli-generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ describe("nf generate (TypeScript, no server)", () => {
// Entity creation and component attachment
expect(content).toContain("const exampleEntity = registry.spawnEntity()");
expect(content).toContain(
'registry.addComponent(exampleEntity, new ExampleComponent("example", 10))',
'registry.addComponent(exampleEntity, new ExampleComponent("example", 10, undefined))',
);

// System registration
Expand All @@ -164,21 +164,19 @@ describe("nf generate (TypeScript, no server)", () => {
save.components.push({
name: "HealthComponent",
path: "./components/health.component",
paramsNames: ["health"],
});

save.entities[0].components.push({
name: "HealthComponent",
params: ["100"],
});
save.entities[0].components["HealthComponent"] = { health: 100 };

save.entities.push({
id: "exampleEntity2",
components: [
{
name: "ExampleComponent",
params: ['"example2"', "15"],
components: {
ExampleComponent: {
paramA: "example2",
paramB: 15,
},
],
},
});

writeFileSync(savePath, JSON.stringify(save, null, 2));
Expand All @@ -193,15 +191,15 @@ describe("nf generate (TypeScript, no server)", () => {

expect(content).toContain("const exampleEntity2 = registry.spawnEntity()");
expect(content).toContain(
'registry.addComponent(exampleEntity2, new ExampleComponent("example2", 15))',
'registry.addComponent(exampleEntity2, new ExampleComponent("example2", 15, undefined))',
);

// Original component should still be present

expect(content).toContain('import { ExampleComponent } from "./components/example.component"');
expect(content).toContain("const exampleEntity = registry.spawnEntity()");
expect(content).toContain(
'registry.addComponent(exampleEntity, new ExampleComponent("example", 10))',
'registry.addComponent(exampleEntity, new ExampleComponent("example", 10, undefined))',
);
});

Expand Down
Loading
Loading