tkeron 4.0.0-beta.15 → 4.0.0-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +5 -3
- package/changelog.md +57 -0
- package/docs/components-typescript.md +25 -4
- package/docs/mcp-server.md +5 -25
- package/docs/testing.md +331 -0
- package/index.ts +2 -2
- package/mcp-server.ts +147 -446
- package/package.json +2 -2
- package/src/build.ts +8 -0
- package/src/buildWrapper.ts +21 -0
- package/src/processCom.ts +101 -85
- package/src/processComTs.ts +149 -134
package/bun.lock
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "tkeron",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
8
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
9
9
|
"@tkeron/commands": "0.3.1",
|
|
10
10
|
"@tkeron/html-parser": "1.5.3",
|
|
11
11
|
"@tkeron/tools": "^0.2.2",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"packages": {
|
|
21
21
|
"@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="],
|
|
22
22
|
|
|
23
|
-
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.
|
|
23
|
+
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="],
|
|
24
24
|
|
|
25
25
|
"@tkeron/commands": ["@tkeron/commands@0.3.1", "", { "peerDependencies": { "typescript": "^5.9.3" } }, "sha512-b7YpdpwVrq8dwnvztpFYCNhjYjiQhX5xix1XXL1Dvt2oHcWiZ3RnG/6yzH0woL3Vbc7shpRNfIexlxmtEkhnIQ=="],
|
|
26
26
|
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
|
|
87
87
|
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
|
88
88
|
|
|
89
|
-
"express-rate-limit": ["express-rate-limit@
|
|
89
|
+
"express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="],
|
|
90
90
|
|
|
91
91
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
|
92
92
|
|
|
@@ -118,6 +118,8 @@
|
|
|
118
118
|
|
|
119
119
|
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
|
120
120
|
|
|
121
|
+
"ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="],
|
|
122
|
+
|
|
121
123
|
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
|
122
124
|
|
|
123
125
|
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
|
package/changelog.md
CHANGED
|
@@ -1,3 +1,60 @@
|
|
|
1
|
+
# v4.0.0-beta.17
|
|
2
|
+
|
|
3
|
+
## MCP Server Updates
|
|
4
|
+
|
|
5
|
+
### API Migration
|
|
6
|
+
|
|
7
|
+
- **Changed**: Migrated MCP server to new SDK API (`McpServer`, `registerResource`, `registerTool`)
|
|
8
|
+
- **Removed**: Legacy API (`Server`, `setRequestHandler`)
|
|
9
|
+
- **Benefit**: Better compatibility with MCP SDK 1.26.0, cleaner code structure
|
|
10
|
+
|
|
11
|
+
### Tools Simplification
|
|
12
|
+
|
|
13
|
+
- **Removed**: `tkeron_init`, `tkeron_build`, `tkeron_develop` tools
|
|
14
|
+
- **Rationale**: MCP server now focuses on documentation and examples only
|
|
15
|
+
- **Alternative**: AI agents should use terminal commands (`tk init`, `tk build`, `tk dev`) instead
|
|
16
|
+
- **Kept**: `list_examples` and `get_example` tools (read-only)
|
|
17
|
+
- **Annotations**: Added `readOnlyHint: true` to remaining tools
|
|
18
|
+
|
|
19
|
+
### New Testing Documentation
|
|
20
|
+
|
|
21
|
+
- **New**: `tkeron://testing` resource with comprehensive testing guide
|
|
22
|
+
- **Location**: `docs/testing.md`
|
|
23
|
+
- **Coverage**:
|
|
24
|
+
- How to use `getBuildResult()` for testing tkeron projects
|
|
25
|
+
- Proper test structure with `beforeAll()` for concurrent safety
|
|
26
|
+
- Testing patterns: file existence, DOM elements, bounded content, transformations
|
|
27
|
+
- Complete examples with best practices
|
|
28
|
+
- Rules summary (8 key rules)
|
|
29
|
+
- **Purpose**: Help users write reliable tests for their tkeron projects
|
|
30
|
+
|
|
31
|
+
### Documentation Updates
|
|
32
|
+
|
|
33
|
+
- **Updated**: `docs/mcp-server.md` to reflect new tools approach
|
|
34
|
+
- **Clarified**: AI agents can access examples but must use terminal for project operations
|
|
35
|
+
- **Updated**: MCP server tests to include new `testing` resource
|
|
36
|
+
|
|
37
|
+
### Tests
|
|
38
|
+
|
|
39
|
+
- **New**: `examples/basic_build/basic_build.test.ts` - complete test suite demonstrating testing patterns
|
|
40
|
+
- **Purpose**: Reference implementation for testing tkeron projects
|
|
41
|
+
|
|
42
|
+
## Dependency Updates
|
|
43
|
+
|
|
44
|
+
- **Updated**: `@modelcontextprotocol/sdk` from `^1.25.3` to `^1.26.0`
|
|
45
|
+
|
|
46
|
+
# v4.0.0-beta.16
|
|
47
|
+
|
|
48
|
+
## Improvements
|
|
49
|
+
|
|
50
|
+
### Component Naming Validation
|
|
51
|
+
|
|
52
|
+
- **New**: Build now validates that component names contain at least one hyphen (custom element requirement)
|
|
53
|
+
- **Error message**: Clear error with file path and rename suggestion when invalid names are detected
|
|
54
|
+
- **Example**: `greeting.com.html` → shows error suggesting `tk-greeting.com.html`
|
|
55
|
+
- **Applies to**: Both `.com.html` and `.com.ts` components
|
|
56
|
+
- **Documentation**: Updated TypeScript components docs to clarify naming rules
|
|
57
|
+
|
|
1
58
|
# v4.0.0-beta.15
|
|
2
59
|
|
|
3
60
|
## Bug Fixes
|
|
@@ -7,7 +7,7 @@ TypeScript components (`.com.ts` files) let you create dynamic, logic-driven com
|
|
|
7
7
|
Create a `.com.ts` file:
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
// greeting.com.ts
|
|
10
|
+
// user-greeting.com.ts
|
|
11
11
|
const name = com.getAttribute("name") || "World";
|
|
12
12
|
com.innerHTML = `<h1>Hello, ${name}!</h1>`;
|
|
13
13
|
```
|
|
@@ -15,9 +15,9 @@ com.innerHTML = `<h1>Hello, ${name}!</h1>`;
|
|
|
15
15
|
Use it with attributes:
|
|
16
16
|
|
|
17
17
|
```html
|
|
18
|
-
<greeting name="Alice"></greeting>
|
|
19
|
-
<greeting name="Bob"></greeting>
|
|
20
|
-
<greeting></greeting>
|
|
18
|
+
<user-greeting name="Alice"></user-greeting>
|
|
19
|
+
<user-greeting name="Bob"></user-greeting>
|
|
20
|
+
<user-greeting></user-greeting>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Build output:
|
|
@@ -58,6 +58,27 @@ Browser sees only:
|
|
|
58
58
|
<div class="card">John</div>
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
### Component Naming
|
|
62
|
+
|
|
63
|
+
Component filenames must:
|
|
64
|
+
|
|
65
|
+
- End with `.com.ts`
|
|
66
|
+
- Match the custom element name (without `.com.ts`)
|
|
67
|
+
- **Use lowercase with hyphens** (custom element requirement)
|
|
68
|
+
|
|
69
|
+
**Examples:**
|
|
70
|
+
|
|
71
|
+
| Filename | Custom Element | Valid? |
|
|
72
|
+
| --------------------- | --------------- | ------------------------- |
|
|
73
|
+
| `user-card.com.ts` | `<user-card>` | ✅ Yes |
|
|
74
|
+
| `nav-menu.com.ts` | `<nav-menu>` | ✅ Yes |
|
|
75
|
+
| `greeting.com.ts` | `<greeting>` | ❌ No (no hyphen) |
|
|
76
|
+
| `UserCard.com.ts` | `<user-card>` | ✅ Yes (case-insensitive) |
|
|
77
|
+
|
|
78
|
+
**Why hyphens are required:**
|
|
79
|
+
|
|
80
|
+
Custom elements in HTML must contain at least one hyphen to distinguish them from standard HTML elements. This is a web standard requirement. Tkeron validates this at build time and shows a clear error message if a component name doesn't follow this rule.
|
|
81
|
+
|
|
61
82
|
### Component Resolution
|
|
62
83
|
|
|
63
84
|
Same as HTML components:
|
package/docs/mcp-server.md
CHANGED
|
@@ -99,6 +99,7 @@ The MCP server exposes the following documentation resources:
|
|
|
99
99
|
| `tkeron://pre-rendering` | Transform HTML at build time with .pre.ts files |
|
|
100
100
|
| `tkeron://cli-reference` | Complete command-line interface documentation |
|
|
101
101
|
| `tkeron://best-practices` | Patterns, anti-patterns, and limitations |
|
|
102
|
+
| `tkeron://testing` | How to test tkeron projects with getBuildResult() |
|
|
102
103
|
|
|
103
104
|
## What AI Agents Can Do
|
|
104
105
|
|
|
@@ -111,25 +112,19 @@ With access to the MCP server, AI agents can:
|
|
|
111
112
|
- ✅ Provide accurate CLI commands and options
|
|
112
113
|
- ✅ Debug common issues with components and pre-rendering
|
|
113
114
|
- ✅ Recommend best practices and avoid anti-patterns
|
|
114
|
-
- ✅ **Initialize new Tkeron projects directly**
|
|
115
|
-
- ✅ **Build and develop Tkeron projects**
|
|
116
115
|
- ✅ **List and explore example projects**
|
|
117
116
|
- ✅ **Retrieve example code and structures**
|
|
117
|
+
- ✅ **Initialize, build, and develop projects via terminal** (`tk init`, `tk build`, `tk dev`)
|
|
118
118
|
|
|
119
119
|
## Available Tools
|
|
120
120
|
|
|
121
121
|
The MCP server provides the following tools for AI agents:
|
|
122
122
|
|
|
123
|
-
### Documentation Tool
|
|
124
|
-
|
|
125
|
-
- **`get_tkeron_docs`**: Retrieve specific documentation topics
|
|
126
|
-
- Parameters: `topic` (overview, getting-started, components-html, etc.)
|
|
127
|
-
- Returns the full markdown content of the requested documentation
|
|
128
|
-
|
|
129
123
|
### Example Management Tools
|
|
130
124
|
|
|
131
125
|
- **`list_examples`**: List all available Tkeron example projects
|
|
132
|
-
-
|
|
126
|
+
- No parameters required
|
|
127
|
+
- Returns example names, descriptions, and paths
|
|
133
128
|
- Helps users discover what's possible with Tkeron
|
|
134
129
|
|
|
135
130
|
- **`get_example`**: Get the complete source code of an example
|
|
@@ -137,22 +132,7 @@ The MCP server provides the following tools for AI agents:
|
|
|
137
132
|
- Returns file structure and all source code
|
|
138
133
|
- Useful for learning patterns and bootstrapping projects
|
|
139
134
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
- **`tkeron_init`**: Initialize a new Tkeron project
|
|
143
|
-
- Parameters: `projectPath` (absolute path), `force` (optional boolean)
|
|
144
|
-
- Creates the standard Tkeron structure (websrc/ with sample files)
|
|
145
|
-
- AI agents can create projects without user intervention
|
|
146
|
-
|
|
147
|
-
- **`tkeron_build`**: Build a Tkeron project
|
|
148
|
-
- Parameters: `sourceDir`, `targetDir` (optional)
|
|
149
|
-
- Processes all `.pre.ts`, `.com.html`, and `.com.ts` files
|
|
150
|
-
- Generates production-ready output
|
|
151
|
-
|
|
152
|
-
- **`tkeron_develop`**: Start the development server
|
|
153
|
-
- Parameters: `sourceDir`, `outputDir`, `port`, `host` (all optional)
|
|
154
|
-
- Launches hot-reload development server
|
|
155
|
-
- AI agents can test projects immediately after creation
|
|
135
|
+
> **Note:** The MCP server only provides documentation and examples. It does NOT modify files. The AI agent should use terminal commands (`tk init`, `tk build`, `tk dev`) to manage projects.
|
|
156
136
|
|
|
157
137
|
## Benefits
|
|
158
138
|
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# Testing Tkeron Projects
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Tkeron provides a `getBuildResult()` function that lets you test your built projects programmatically. This is useful for verifying that your components, pre-rendering scripts, and build output work correctly.
|
|
6
|
+
|
|
7
|
+
**Important:** These tests validate YOUR project's build output — not tkeron internals. You're testing that your HTML, components, and scripts produce the expected result after the build.
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
Install tkeron as a dependency in your project:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun add -d tkeron
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { getBuildResult, type BuildResult } from "tkeron";
|
|
21
|
+
|
|
22
|
+
const result: BuildResult = await getBuildResult("./websrc");
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### BuildResult
|
|
26
|
+
|
|
27
|
+
`getBuildResult()` returns a `Record<string, FileInfo>` indexed by relative path:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
type BuildResult = Record<string, FileInfo>;
|
|
31
|
+
|
|
32
|
+
interface FileInfo {
|
|
33
|
+
fileName: string; // "index.html"
|
|
34
|
+
filePath: string; // "styles" or "" for root files
|
|
35
|
+
path: string; // "styles/main.css" (the key)
|
|
36
|
+
type: string; // MIME type: "text/html", "text/css", etc.
|
|
37
|
+
size: number; // size in bytes
|
|
38
|
+
fileHash: string; // SHA-256 hash
|
|
39
|
+
getContentAsString?: () => string; // text files only
|
|
40
|
+
dom?: Document; // HTML files only - parsed DOM
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `dom` is only present on `.html` files. It provides full DOM API: `querySelector()`, `getElementById()`, `querySelectorAll()`, etc.
|
|
45
|
+
- `getContentAsString()` is only present on text files (html, css, js, json, txt, svg, xml, ts, mjs, cjs, md, map).
|
|
46
|
+
- Binary files (images, fonts) only have metadata — no `dom` or `getContentAsString`.
|
|
47
|
+
|
|
48
|
+
## Test Structure
|
|
49
|
+
|
|
50
|
+
### Single Build with beforeAll
|
|
51
|
+
|
|
52
|
+
**Always** build once and share the result across tests. This is:
|
|
53
|
+
|
|
54
|
+
- Concurrent-safe (`bun test --concurrent`)
|
|
55
|
+
- Efficient (1 build instead of N builds)
|
|
56
|
+
- Reliable (no race conditions)
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { describe, it, expect, beforeAll } from "bun:test";
|
|
60
|
+
import { getBuildResult, type BuildResult } from "tkeron";
|
|
61
|
+
import { join } from "path";
|
|
62
|
+
|
|
63
|
+
describe("my project", () => {
|
|
64
|
+
const sourcePath = join(import.meta.dir, "src");
|
|
65
|
+
let result: BuildResult;
|
|
66
|
+
|
|
67
|
+
beforeAll(async () => {
|
|
68
|
+
result = await getBuildResult(sourcePath);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should generate index.html", () => {
|
|
72
|
+
expect(result?.["index.html"]).toBeDefined();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Never** call `getBuildResult()` inside each `it()` — this causes race conditions in concurrent mode.
|
|
78
|
+
|
|
79
|
+
## Testing Patterns
|
|
80
|
+
|
|
81
|
+
### 1. Verify File Existence
|
|
82
|
+
|
|
83
|
+
Check that expected output files exist:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
it("should generate expected files", () => {
|
|
87
|
+
expect(result?.["index.html"]).toBeDefined();
|
|
88
|
+
expect(result?.["index.js"]).toBeDefined();
|
|
89
|
+
expect(result?.["styles/main.css"]).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. Verify Element Existence (Always Check First)
|
|
94
|
+
|
|
95
|
+
**Rule:** Always verify an element exists BEFORE checking its properties.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// ✅ CORRECT: check existence first, then properties
|
|
99
|
+
it("should have login button", () => {
|
|
100
|
+
const dom = result?.["index.html"]?.dom;
|
|
101
|
+
|
|
102
|
+
const button = dom?.querySelector("#login-button");
|
|
103
|
+
expect(button).toBeDefined();
|
|
104
|
+
expect(button!.textContent).toBe("Log In");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ❌ WRONG: accessing properties without checking existence
|
|
108
|
+
it("should have login button", () => {
|
|
109
|
+
const dom = result?.["index.html"]?.dom;
|
|
110
|
+
expect(dom!.querySelector("#login-button")!.textContent).toBe("Log In");
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Verify Bounded Content
|
|
115
|
+
|
|
116
|
+
Test specific, short, predictable values — not full output strings.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// ✅ GOOD: specific, bounded values
|
|
120
|
+
it("should have correct title", () => {
|
|
121
|
+
const dom = result?.["index.html"]?.dom;
|
|
122
|
+
|
|
123
|
+
const title = dom?.querySelector("title");
|
|
124
|
+
expect(title).toBeDefined();
|
|
125
|
+
expect(title!.textContent).toBe("My App");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ✅ GOOD: keyword presence in compiled JS
|
|
129
|
+
it("should contain event handling logic", () => {
|
|
130
|
+
const js = result?.["index.js"]?.getContentAsString!();
|
|
131
|
+
expect(js).toContain("addEventListener");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ❌ BAD: comparing full minified JS output (fragile, breaks on any optimization change)
|
|
135
|
+
it("should match exact JS", () => {
|
|
136
|
+
const js = result?.["index.js"]?.getContentAsString!();
|
|
137
|
+
expect(js).toBe('var t=0,e=document.querySelector("button")...');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ❌ BAD: comparing full HTML output
|
|
141
|
+
it("should match exact HTML", () => {
|
|
142
|
+
const html = result?.["index.html"]?.getContentAsString!();
|
|
143
|
+
expect(html).toBe("<!doctype html><html>...");
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Why bounded:** Build output changes with optimizations, minification, and bundling. Test the **meaning**, not the **exact format**.
|
|
148
|
+
|
|
149
|
+
### 4. Verify DOM Elements (Specific Queries)
|
|
150
|
+
|
|
151
|
+
Test specific elements that matter to your project:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
it("should have navigation with correct links", () => {
|
|
155
|
+
const dom = result?.["index.html"]?.dom;
|
|
156
|
+
|
|
157
|
+
const homeLink = dom?.querySelector('nav a[href="/"]');
|
|
158
|
+
expect(homeLink).toBeDefined();
|
|
159
|
+
expect(homeLink!.textContent).toBe("Home");
|
|
160
|
+
|
|
161
|
+
const aboutLink = dom?.querySelector('nav a[href="/about"]');
|
|
162
|
+
expect(aboutLink).toBeDefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should have user profile section", () => {
|
|
166
|
+
const dom = result?.["index.html"]?.dom;
|
|
167
|
+
|
|
168
|
+
const profile = dom?.querySelector("#user-profile");
|
|
169
|
+
expect(profile).toBeDefined();
|
|
170
|
+
|
|
171
|
+
const avatar = profile?.querySelector("img.avatar");
|
|
172
|
+
expect(avatar).toBeDefined();
|
|
173
|
+
expect(avatar!.getAttribute("alt")).toBe("User avatar");
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 5. Verify Tkeron Transformations
|
|
178
|
+
|
|
179
|
+
Test that tkeron's build features work correctly in your project:
|
|
180
|
+
|
|
181
|
+
**Script injection (TypeScript → JavaScript):**
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
it("should have compiled script in head", () => {
|
|
185
|
+
const dom = result?.["index.html"]?.dom;
|
|
186
|
+
|
|
187
|
+
const script = dom?.querySelector('script[type="module"]');
|
|
188
|
+
expect(script).toBeDefined();
|
|
189
|
+
expect(script!.getAttribute("src")).toBe("./index.js");
|
|
190
|
+
expect(script!.parentElement!.tagName).toBe("HEAD");
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Component substitution (.com.html):**
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
it("should have processed nav component", () => {
|
|
198
|
+
const dom = result?.["index.html"]?.dom;
|
|
199
|
+
|
|
200
|
+
// The <nav-menu> tag should be replaced by its .com.html content
|
|
201
|
+
const nav = dom?.querySelector("nav");
|
|
202
|
+
expect(nav).toBeDefined();
|
|
203
|
+
|
|
204
|
+
const links = dom?.querySelectorAll("nav a");
|
|
205
|
+
expect(links).toBeDefined();
|
|
206
|
+
expect(links!.length).toBeGreaterThan(0);
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**TypeScript component (.com.ts):**
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
it("should have dynamically generated list", () => {
|
|
214
|
+
const dom = result?.["index.html"]?.dom;
|
|
215
|
+
|
|
216
|
+
const list = dom?.querySelector("#item-list");
|
|
217
|
+
expect(list).toBeDefined();
|
|
218
|
+
|
|
219
|
+
const items = list?.querySelectorAll("li");
|
|
220
|
+
expect(items).toBeDefined();
|
|
221
|
+
expect(items!.length).toBe(5);
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Pre-rendering (.pre.ts):**
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
it("should have pre-rendered content", () => {
|
|
229
|
+
const dom = result?.["index.html"]?.dom;
|
|
230
|
+
|
|
231
|
+
const generated = dom?.querySelector("#generated-content");
|
|
232
|
+
expect(generated).toBeDefined();
|
|
233
|
+
expect(generated!.textContent).not.toBe("");
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 6. Verify File Metadata
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
it("should have correct metadata", () => {
|
|
241
|
+
expect(result?.["index.html"]?.type).toBe("text/html");
|
|
242
|
+
expect(result?.["index.html"]?.size).toBeGreaterThan(0);
|
|
243
|
+
expect(result?.["index.html"]?.fileHash).toBeDefined();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("should not have DOM on non-HTML files", () => {
|
|
247
|
+
expect(result?.["index.js"]?.dom).toBeUndefined();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should have correct path info for nested files", () => {
|
|
251
|
+
expect(result?.["pages/about.html"]?.fileName).toBe("about.html");
|
|
252
|
+
expect(result?.["pages/about.html"]?.filePath).toBe("pages");
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Complete Example
|
|
257
|
+
|
|
258
|
+
Here's a complete test file for a tkeron project with components:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { describe, it, expect, beforeAll } from "bun:test";
|
|
262
|
+
import { getBuildResult, type BuildResult } from "tkeron";
|
|
263
|
+
import { join } from "path";
|
|
264
|
+
|
|
265
|
+
describe("my-website build", () => {
|
|
266
|
+
const sourcePath = join(import.meta.dir, "websrc");
|
|
267
|
+
let result: BuildResult;
|
|
268
|
+
|
|
269
|
+
beforeAll(async () => {
|
|
270
|
+
result = await getBuildResult(sourcePath);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("should generate expected files", () => {
|
|
274
|
+
expect(result?.["index.html"]).toBeDefined();
|
|
275
|
+
expect(result?.["index.js"]).toBeDefined();
|
|
276
|
+
expect(result?.["about.html"]).toBeDefined();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("should have page title", () => {
|
|
280
|
+
const dom = result?.["index.html"]?.dom;
|
|
281
|
+
|
|
282
|
+
const title = dom?.querySelector("title");
|
|
283
|
+
expect(title).toBeDefined();
|
|
284
|
+
expect(title!.textContent).toBe("My Website");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should have navigation from component", () => {
|
|
288
|
+
const dom = result?.["index.html"]?.dom;
|
|
289
|
+
|
|
290
|
+
const nav = dom?.querySelector("nav");
|
|
291
|
+
expect(nav).toBeDefined();
|
|
292
|
+
|
|
293
|
+
const homeLink = dom?.querySelector('nav a[href="/"]');
|
|
294
|
+
expect(homeLink).toBeDefined();
|
|
295
|
+
expect(homeLink!.textContent).toBe("Home");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("should have footer from component", () => {
|
|
299
|
+
const dom = result?.["index.html"]?.dom;
|
|
300
|
+
|
|
301
|
+
const footer = dom?.querySelector("footer");
|
|
302
|
+
expect(footer).toBeDefined();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should have compiled JavaScript", () => {
|
|
306
|
+
const js = result?.["index.js"]?.getContentAsString!();
|
|
307
|
+
|
|
308
|
+
expect(js).toBeDefined();
|
|
309
|
+
expect(js!.length).toBeGreaterThan(0);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should have script injected in head", () => {
|
|
313
|
+
const dom = result?.["index.html"]?.dom;
|
|
314
|
+
|
|
315
|
+
const script = dom?.querySelector('head script[type="module"]');
|
|
316
|
+
expect(script).toBeDefined();
|
|
317
|
+
expect(script!.getAttribute("src")).toBe("./index.js");
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Rules Summary
|
|
323
|
+
|
|
324
|
+
1. **Build once** with `beforeAll()` — never inside individual tests
|
|
325
|
+
2. **Check existence first** — always `expect(element).toBeDefined()` before accessing properties
|
|
326
|
+
3. **Use optional chaining** (`?.`) for safe property access on result entries
|
|
327
|
+
4. **Test bounded content** — specific values, keywords, attributes; not full output strings
|
|
328
|
+
5. **Test specific elements** — query for elements that matter to your project
|
|
329
|
+
6. **Verify transformations** — components processed, scripts injected, pre-rendering applied
|
|
330
|
+
7. **Don't test the parser** — you're testing your project, not tkeron internals
|
|
331
|
+
8. **Concurrent-safe** — tests must work with `bun test --concurrent`
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { getCommands } from "@tkeron/commands";
|
|
3
|
-
import {
|
|
3
|
+
import { buildWrapper } from "./src/buildWrapper";
|
|
4
4
|
import { develop } from "./src/develop";
|
|
5
5
|
import { initWrapper } from "./src/initWrapper";
|
|
6
6
|
import { showBanner } from "./src/banner";
|
|
@@ -17,7 +17,7 @@ getCommands()
|
|
|
17
17
|
.addDescription("Build the project")
|
|
18
18
|
.addPositionedArgument("sourceDir")
|
|
19
19
|
.addPositionedArgument("targetDir")
|
|
20
|
-
.setCallback(
|
|
20
|
+
.setCallback(buildWrapper)
|
|
21
21
|
|
|
22
22
|
.commands()
|
|
23
23
|
|