feat: Added initial tests 58a36cbe
Steve Simkins · 2025-08-02 15:23 7 file(s) · +182 −1
TESTING.md (added) +60 −0
1 +
# Testing Guide
2 +
3 +
This project uses [Bun Test](https://bun.sh/docs/cli/test) for focused, high-value testing of core business logic.
4 +
5 +
## Running Tests
6 +
7 +
```bash
8 +
# Run all tests
9 +
bun test
10 +
11 +
# Run tests in watch mode (for development)
12 +
bun test --watch
13 +
14 +
# Run tests with coverage report
15 +
bun test --coverage
16 +
```
17 +
18 +
## Test Philosophy
19 +
20 +
This project follows a **focused testing approach** that prioritizes:
21 +
22 +
1. **High Value**: Test core business logic and data validation
23 +
2. **Fast Execution**: No slow I/O operations or complex mocking
24 +
3. **Maintainability**: Simple, reliable tests that won't break with dependencies
25 +
4. **Clarity**: Each test has a clear purpose and validates real behavior
26 +
27 +
## What We Test ✅
28 +
29 +
### **Core Utilities** (100% Coverage)
30 +
- `src/utils/try-catch.test.ts` - Error handling wrapper functionality
31 +
- `src/utils/templates.test.ts` - Template configuration and Hono code generation
32 +
- `src/utils/constants.test.ts` - Application constants validation
33 +
- `src/types.test.ts` - TypeScript type definitions and interfaces
34 +
35 +
## What We DON'T Test ❌
36 +
37 +
**CLI Functions with External Dependencies:**
38 +
- File system operations (degit, fs-extra)
39 +
- Interactive prompts (consola)
40 +
- System commands (git, bun install)
41 +
- Process management (process.exit)
42 +
43 +
**Why:** These functions are integration points with the OS and external tools. Testing them would require complex mocking that provides little value and high maintenance overhead.
44 +
45 +
## Test Configuration
46 +
47 +
### `bunfig.toml`
48 +
```toml
49 +
[test]
50 +
coverage = true
51 +
timeout = 5000
52 +
53 +
[test.env]
54 +
NODE_ENV = "test"
55 +
```
56 +
57 +
### Package Scripts
58 +
- `bun test` - Run all tests
59 +
- `bun test --watch` - Watch mode for development
60 +
- `bun test --coverage` - Generate coverage reports
bunfig.toml (added) +8 −0
1 +
[test]
2 +
# Test configuration for Bun
3 +
coverage = true
4 +
timeout = 5000
5 +
6 +
# Environment variables for tests
7 +
[test.env]
8 +
NODE_ENV = "test"
package.json +4 −1
13 13
  ],
14 14
  "scripts": {
15 15
    "build": "bun build src/index.ts --outdir dist --target node",
16 -
    "start": "bun ./dist/index.js"
16 +
    "start": "bun ./dist/index.js",
17 +
    "test": "bun test",
18 +
    "test:watch": "bun test --watch",
19 +
    "test:coverage": "bun test --coverage"
17 20
  },
18 21
  "keywords": [
19 22
    "bun",
src/types.test.ts (added) +28 −0
1 +
import { describe, it, expect } from "bun:test";
2 +
import type { TemplateInfo, ProjectOptions, ProjectResult } from "./types";
3 +
4 +
describe("TypeScript types", () => {
5 +
  it("should define correct type structures", () => {
6 +
    // Test that types can be instantiated correctly
7 +
    const templateInfo: TemplateInfo = {
8 +
      branch: "main",
9 +
      description: "Test template"
10 +
    };
11 +
12 +
    const options: ProjectOptions = {
13 +
      projectName: "test-project",
14 +
      linter: "biome"
15 +
    };
16 +
17 +
    const result: ProjectResult = {
18 +
      projectName: "test-project",
19 +
      gitInitialized: true,
20 +
      dependenciesInstalled: false,
21 +
      template: "default"
22 +
    };
23 +
24 +
    expect(templateInfo.branch).toBe("main");
25 +
    expect(options.linter).toBe("biome");
26 +
    expect(result.projectName).toBe("test-project");
27 +
  });
28 +
});
src/utils/constants.test.ts (added) +16 −0
1 +
import { describe, it, expect } from "bun:test";
2 +
import { DEFAULT_REPO } from "./constants";
3 +
4 +
describe("constants", () => {
5 +
  it("should have correct default repository", () => {
6 +
    expect(DEFAULT_REPO).toBe("stevedylandev/bhvr");
7 +
  });
8 +
9 +
  it("should be a string", () => {
10 +
    expect(typeof DEFAULT_REPO).toBe("string");
11 +
  });
12 +
13 +
  it("should follow GitHub repo format", () => {
14 +
    expect(DEFAULT_REPO).toMatch(/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+$/);
15 +
  });
16 +
});
src/utils/templates.test.ts (added) +25 −0
1 +
import { describe, it, expect } from "bun:test";
2 +
import { TEMPLATES, honoRpcTemplate, honoClientTemplate } from "./templates";
3 +
4 +
describe("Templates", () => {
5 +
	it("should have all required templates with correct structure", () => {
6 +
		const expectedTemplates = ["default", "tailwind", "shadcn"];
7 +
8 +
		expectedTemplates.forEach((templateName) => {
9 +
			expect(TEMPLATES).toHaveProperty(templateName);
10 +
			const template = TEMPLATES[templateName];
11 +
			expect(template).toHaveProperty("branch");
12 +
			expect(template).toHaveProperty("description");
13 +
			expect(typeof template?.branch).toBe("string");
14 +
			expect(typeof template?.description).toBe("string");
15 +
		});
16 +
	});
17 +
18 +
	it("should have valid Hono templates", () => {
19 +
		expect(honoRpcTemplate).toContain("import { Hono }");
20 +
		expect(honoRpcTemplate).toContain("export const app = new Hono()");
21 +
22 +
		expect(honoClientTemplate).toContain("import { hc }");
23 +
		expect(honoClientTemplate).toContain("export const hcWithType");
24 +
	});
25 +
});
src/utils/try-catch.test.ts (added) +41 −0
1 +
import { describe, it, expect } from "bun:test";
2 +
import { tryCatch } from "./try-catch";
3 +
4 +
describe("tryCatch", () => {
5 +
  it("should return success result for resolved promise", async () => {
6 +
    const result = await tryCatch(Promise.resolve("success"));
7 +
    
8 +
    expect(result.data).toBe("success");
9 +
    expect(result.error).toBeNull();
10 +
  });
11 +
12 +
  it("should return failure result for rejected promise", async () => {
13 +
    const error = new Error("test error");
14 +
    const result = await tryCatch(Promise.reject(error));
15 +
    
16 +
    expect(result.data).toBeNull();
17 +
    expect(result.error).toBe(error);
18 +
  });
19 +
20 +
  it("should handle async function that throws", async () => {
21 +
    const asyncFunction = async () => {
22 +
      throw new Error("async error");
23 +
    };
24 +
    
25 +
    const result = await tryCatch(asyncFunction());
26 +
    
27 +
    expect(result.data).toBeNull();
28 +
    expect(result.error).toBeInstanceOf(Error);
29 +
    expect((result.error as Error).message).toBe("async error");
30 +
  });
31 +
32 +
  it("should handle different data types", async () => {
33 +
    const objectResult = await tryCatch(Promise.resolve({ id: 1, name: "test" }));
34 +
    const numberResult = await tryCatch(Promise.resolve(42));
35 +
    const booleanResult = await tryCatch(Promise.resolve(true));
36 +
    
37 +
    expect(objectResult.data).toEqual({ id: 1, name: "test" });
38 +
    expect(numberResult.data).toBe(42);
39 +
    expect(booleanResult.data).toBe(true);
40 +
  });
41 +
});