fragment-ts 1.0.18 → 1.0.20
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/changes/1.md +113 -64
- package/dist/cli/commands/init.command.js +1 -1
- package/dist/cli/commands/test.command.d.ts +1 -0
- package/dist/cli/commands/test.command.d.ts.map +1 -1
- package/dist/cli/commands/test.command.js +149 -173
- package/dist/cli/commands/test.command.js.map +1 -1
- package/dist/testing/runner.d.ts +14 -1
- package/dist/testing/runner.d.ts.map +1 -1
- package/dist/testing/runner.js +199 -19
- package/dist/testing/runner.js.map +1 -1
- package/dist/web/application.d.ts.map +1 -1
- package/dist/web/application.js +1 -0
- package/dist/web/application.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/init.command.ts +1 -1
- package/src/cli/commands/test.command.ts +188 -173
- package/src/testing/runner.ts +253 -25
- package/src/web/application.ts +1 -0
|
@@ -17,6 +17,7 @@ export class TestCommand {
|
|
|
17
17
|
)
|
|
18
18
|
.option("--pattern <pattern>", "Test file pattern", "**/*.spec.ts")
|
|
19
19
|
.option("--coverage", "Generate coverage report")
|
|
20
|
+
.option("--no-color", "Disable colored output")
|
|
20
21
|
.action(async (options) => {
|
|
21
22
|
await this.runTests(options);
|
|
22
23
|
});
|
|
@@ -57,7 +58,7 @@ export class TestCommand {
|
|
|
57
58
|
useTypeScript = false;
|
|
58
59
|
mode = "production (dist/)";
|
|
59
60
|
basePath = "dist";
|
|
60
|
-
pattern = options.pattern.replace(
|
|
61
|
+
pattern = options.pattern.replace(/\.ts$/, ".js") || "**/*.spec.js";
|
|
61
62
|
} else {
|
|
62
63
|
// Auto-detect
|
|
63
64
|
if (hasSource) {
|
|
@@ -69,7 +70,7 @@ export class TestCommand {
|
|
|
69
70
|
useTypeScript = false;
|
|
70
71
|
mode = "auto-detected production (dist/)";
|
|
71
72
|
basePath = "dist";
|
|
72
|
-
pattern = options.pattern.replace(
|
|
73
|
+
pattern = options.pattern.replace(/\.ts$/, ".js") || "**/*.spec.js";
|
|
73
74
|
} else {
|
|
74
75
|
console.log(
|
|
75
76
|
chalk.red("No src/ or dist/ directory found. Run: fragment init"),
|
|
@@ -78,189 +79,62 @@ export class TestCommand {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
console.log(chalk.gray(` Mode: ${mode}
|
|
82
|
+
console.log(chalk.gray(` Mode: ${mode}`));
|
|
83
|
+
console.log(chalk.gray(` Pattern: ${basePath}/${pattern}`));
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const { glob } = require('glob');
|
|
88
|
-
const path = require('path');
|
|
89
|
-
|
|
90
|
-
// Global test state
|
|
91
|
-
const testState = {
|
|
92
|
-
suites: [],
|
|
93
|
-
currentSuite: null,
|
|
94
|
-
passed: 0,
|
|
95
|
-
failed: 0,
|
|
96
|
-
errors: []
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Global test functions
|
|
100
|
-
global.describe = function(name, fn) {
|
|
101
|
-
const suite = { name, tests: [] };
|
|
102
|
-
testState.suites.push(suite);
|
|
103
|
-
testState.currentSuite = suite;
|
|
104
|
-
fn();
|
|
105
|
-
testState.currentSuite = null;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
global.it = function(name, fn) {
|
|
109
|
-
if (!testState.currentSuite) {
|
|
110
|
-
throw new Error('it() must be called inside describe()');
|
|
111
|
-
}
|
|
112
|
-
testState.currentSuite.tests.push({ name, fn });
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
global.expect = function(actual) {
|
|
116
|
-
return {
|
|
117
|
-
toBe(expected) {
|
|
118
|
-
if (actual !== expected) {
|
|
119
|
-
throw new Error(\`Expected \${actual} to be \${expected}\`);
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
toEqual(expected) {
|
|
123
|
-
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
124
|
-
throw new Error(\`Expected \${JSON.stringify(actual)} to equal \${JSON.stringify(expected)}\`);
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
toBeTruthy() {
|
|
128
|
-
if (!actual) {
|
|
129
|
-
throw new Error(\`Expected \${actual} to be truthy\`);
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
toBeFalsy() {
|
|
133
|
-
if (actual) {
|
|
134
|
-
throw new Error(\`Expected \${actual} to be falsy\`);
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
toThrow(expectedError) {
|
|
138
|
-
let threw = false;
|
|
139
|
-
let error = null;
|
|
140
|
-
try {
|
|
141
|
-
actual();
|
|
142
|
-
} catch (e) {
|
|
143
|
-
threw = true;
|
|
144
|
-
error = e;
|
|
145
|
-
}
|
|
146
|
-
if (!threw) {
|
|
147
|
-
throw new Error('Expected function to throw');
|
|
148
|
-
}
|
|
149
|
-
if (expectedError && error.message !== expectedError) {
|
|
150
|
-
throw new Error(\`Expected error message "\${expectedError}" but got "\${error.message}"\`);
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
toBeInstanceOf(expected) {
|
|
154
|
-
if (!(actual instanceof expected)) {
|
|
155
|
-
throw new Error(\`Expected \${actual} to be instance of \${expected.name}\`);
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
toContain(expected) {
|
|
159
|
-
if (!actual.includes(expected)) {
|
|
160
|
-
throw new Error(\`Expected \${actual} to contain \${expected}\`);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
toHaveProperty(property, value) {
|
|
164
|
-
if (!(property in actual)) {
|
|
165
|
-
throw new Error(\`Expected object to have property \${property}\`);
|
|
166
|
-
}
|
|
167
|
-
if (value !== undefined && actual[property] !== value) {
|
|
168
|
-
throw new Error(\`Expected property \${property} to be \${value} but got \${actual[property]}\`);
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
toBeNull() {
|
|
172
|
-
if (actual !== null) {
|
|
173
|
-
throw new Error(\`Expected \${actual} to be null\`);
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
toBeUndefined() {
|
|
177
|
-
if (actual !== undefined) {
|
|
178
|
-
throw new Error(\`Expected \${actual} to be undefined\`);
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
toHaveLength(expected) {
|
|
182
|
-
if (actual.length !== expected) {
|
|
183
|
-
throw new Error(\`Expected length \${expected} but got \${actual.length}\`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
async function runTests() {
|
|
190
|
-
try {
|
|
191
|
-
// Find and load test files
|
|
192
|
-
const files = await glob('${basePath}/${pattern}', { cwd: process.cwd() });
|
|
193
|
-
|
|
194
|
-
if (files.length === 0) {
|
|
195
|
-
console.log('No test files found matching pattern: ${basePath}/${pattern}');
|
|
196
|
-
process.exit(0);
|
|
197
|
-
}
|
|
85
|
+
// Check if we need to use ts-node for TypeScript files
|
|
86
|
+
const tsConfigPath = path.join(cwd, "tsconfig.json");
|
|
87
|
+
const hasTsConfig = fs.existsSync(tsConfigPath);
|
|
198
88
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
for (const test of suite.tests) {
|
|
211
|
-
try {
|
|
212
|
-
await test.fn();
|
|
213
|
-
console.log(\` ✓ \${test.name}\`);
|
|
214
|
-
testState.passed++;
|
|
215
|
-
} catch (error) {
|
|
216
|
-
console.log(\` ✗ \${test.name}\`);
|
|
217
|
-
console.error(\` \${error.message}\`);
|
|
218
|
-
testState.failed++;
|
|
219
|
-
testState.errors.push({
|
|
220
|
-
suite: suite.name,
|
|
221
|
-
test: test.name,
|
|
222
|
-
error: error.message,
|
|
223
|
-
stack: error.stack
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Print summary
|
|
230
|
-
console.log(\`\\n\\n📊 Results: \${testState.passed} passed, \${testState.failed} failed\\n\`);
|
|
231
|
-
|
|
232
|
-
if (testState.failed > 0) {
|
|
233
|
-
console.log('\\n❌ Failed Tests:\\n');
|
|
234
|
-
testState.errors.forEach(err => {
|
|
235
|
-
console.log(\` \${err.suite} > \${err.test}\`);
|
|
236
|
-
console.log(\` \${err.error}\\n\`);
|
|
237
|
-
});
|
|
238
|
-
process.exit(1);
|
|
239
|
-
} else {
|
|
240
|
-
console.log('✅ All tests passed!\\n');
|
|
241
|
-
process.exit(0);
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
console.error('Error running tests:', error);
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
runTests();
|
|
250
|
-
`;
|
|
89
|
+
// Create a simple runner script that uses our test runner module
|
|
90
|
+
const scriptContent = this.generateRunnerScript({
|
|
91
|
+
useTypeScript,
|
|
92
|
+
basePath,
|
|
93
|
+
pattern,
|
|
94
|
+
hasTsConfig,
|
|
95
|
+
tsConfigPath,
|
|
96
|
+
watchMode: options.watch,
|
|
97
|
+
coverage: options.coverage,
|
|
98
|
+
noColor: options.color === false,
|
|
99
|
+
});
|
|
251
100
|
|
|
252
101
|
const scriptPath = path.join(cwd, ".fragment-test-runner.js");
|
|
253
102
|
|
|
254
103
|
try {
|
|
255
104
|
fs.writeFileSync(scriptPath, scriptContent);
|
|
256
105
|
|
|
106
|
+
// Make the script executable
|
|
107
|
+
fs.chmodSync(scriptPath, "755");
|
|
108
|
+
|
|
257
109
|
const nodeArgs = [scriptPath];
|
|
258
110
|
const env = {
|
|
259
111
|
...process.env,
|
|
260
112
|
TS_NODE_TRANSPILE_ONLY: "true",
|
|
261
113
|
NODE_ENV: "test",
|
|
114
|
+
// Only set TS_NODE_PROJECT if tsconfig exists
|
|
115
|
+
...(hasTsConfig && useTypeScript
|
|
116
|
+
? { TS_NODE_PROJECT: tsConfigPath }
|
|
117
|
+
: {}),
|
|
118
|
+
// Handle color output
|
|
119
|
+
FORCE_COLOR: options.color === false ? "0" : "1",
|
|
262
120
|
};
|
|
263
121
|
|
|
122
|
+
// Add --watch support
|
|
123
|
+
if (options.watch) {
|
|
124
|
+
console.log(
|
|
125
|
+
chalk.yellow(
|
|
126
|
+
"\n⚠️ Watch mode is not yet implemented. Running once.\n",
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Add --coverage support
|
|
132
|
+
if (options.coverage) {
|
|
133
|
+
console.log(
|
|
134
|
+
chalk.yellow("\n⚠️ Coverage reporting is not yet implemented.\n"),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
264
138
|
const proc = spawn("node", nodeArgs, {
|
|
265
139
|
cwd,
|
|
266
140
|
stdio: "inherit",
|
|
@@ -270,7 +144,9 @@ runTests();
|
|
|
270
144
|
proc.on("close", (code) => {
|
|
271
145
|
try {
|
|
272
146
|
fs.unlinkSync(scriptPath);
|
|
273
|
-
} catch {
|
|
147
|
+
} catch (e) {
|
|
148
|
+
// Ignore cleanup errors
|
|
149
|
+
}
|
|
274
150
|
process.exit(code || 0);
|
|
275
151
|
});
|
|
276
152
|
|
|
@@ -278,7 +154,9 @@ runTests();
|
|
|
278
154
|
console.error(chalk.red("Failed to run tests:"), error);
|
|
279
155
|
try {
|
|
280
156
|
fs.unlinkSync(scriptPath);
|
|
281
|
-
} catch {
|
|
157
|
+
} catch (e) {
|
|
158
|
+
// Ignore cleanup errors
|
|
159
|
+
}
|
|
282
160
|
process.exit(1);
|
|
283
161
|
});
|
|
284
162
|
} catch (error: any) {
|
|
@@ -286,4 +164,141 @@ runTests();
|
|
|
286
164
|
process.exit(1);
|
|
287
165
|
}
|
|
288
166
|
}
|
|
289
|
-
|
|
167
|
+
|
|
168
|
+
private static generateRunnerScript(options: {
|
|
169
|
+
useTypeScript: boolean;
|
|
170
|
+
basePath: string;
|
|
171
|
+
pattern: string;
|
|
172
|
+
hasTsConfig: boolean;
|
|
173
|
+
tsConfigPath: string;
|
|
174
|
+
watchMode: boolean;
|
|
175
|
+
coverage: boolean;
|
|
176
|
+
noColor: boolean;
|
|
177
|
+
}): string {
|
|
178
|
+
const {
|
|
179
|
+
useTypeScript,
|
|
180
|
+
basePath,
|
|
181
|
+
pattern,
|
|
182
|
+
hasTsConfig,
|
|
183
|
+
tsConfigPath,
|
|
184
|
+
noColor,
|
|
185
|
+
} = options;
|
|
186
|
+
|
|
187
|
+
// Determine the runner path - try to find the actual test runner
|
|
188
|
+
// This assumes the test runner is installed as a dependency
|
|
189
|
+
let runnerImportPath = "fragment-ts/testing";
|
|
190
|
+
|
|
191
|
+
// Try to find the local test runner first for development
|
|
192
|
+
const possiblePaths = [
|
|
193
|
+
path.join(process.cwd(), "node_modules", "fragment-ts", "testing"),
|
|
194
|
+
path.join(
|
|
195
|
+
process.cwd(),
|
|
196
|
+
"node_modules",
|
|
197
|
+
"fragment-ts",
|
|
198
|
+
"dist",
|
|
199
|
+
"testing",
|
|
200
|
+
),
|
|
201
|
+
path.join(__dirname, "..", "..", "testing"), // If we're in the fragment-ts package itself
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
let resolvedRunnerPath = runnerImportPath;
|
|
205
|
+
for (const testPath of possiblePaths) {
|
|
206
|
+
if (fs.existsSync(testPath + ".ts") || fs.existsSync(testPath + ".js")) {
|
|
207
|
+
resolvedRunnerPath = testPath;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return `
|
|
213
|
+
"use strict";
|
|
214
|
+
|
|
215
|
+
${useTypeScript ? "require('ts-node/register/transpile-only');" : ""}
|
|
216
|
+
require('reflect-metadata');
|
|
217
|
+
|
|
218
|
+
// Set environment
|
|
219
|
+
process.env.NODE_ENV = 'test';
|
|
220
|
+
${noColor ? "process.env.FORCE_COLOR = '0';" : "process.env.FORCE_COLOR = '1';"}
|
|
221
|
+
|
|
222
|
+
// Set up TypeScript config if available
|
|
223
|
+
${
|
|
224
|
+
hasTsConfig && useTypeScript
|
|
225
|
+
? `
|
|
226
|
+
try {
|
|
227
|
+
const tsNode = require('ts-node');
|
|
228
|
+
tsNode.register({
|
|
229
|
+
project: '${tsConfigPath}',
|
|
230
|
+
transpileOnly: true,
|
|
231
|
+
compilerOptions: {
|
|
232
|
+
module: 'commonjs',
|
|
233
|
+
target: 'ES2020',
|
|
234
|
+
esModuleInterop: true,
|
|
235
|
+
skipLibCheck: true
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
} catch (error) {
|
|
239
|
+
// Ignore, ts-node might not be available
|
|
240
|
+
}`
|
|
241
|
+
: ""
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let testRunner;
|
|
245
|
+
try {
|
|
246
|
+
// Try to import the test runner
|
|
247
|
+
const testModule = require('${resolvedRunnerPath}');
|
|
248
|
+
|
|
249
|
+
// Handle different export patterns
|
|
250
|
+
if (testModule.getTestRunner) {
|
|
251
|
+
testRunner = testModule.getTestRunner();
|
|
252
|
+
} else if (testModule.TestRunner) {
|
|
253
|
+
testRunner = new testModule.TestRunner();
|
|
254
|
+
} else if (testModule.default && testModule.default.getTestRunner) {
|
|
255
|
+
testRunner = testModule.default.getTestRunner();
|
|
256
|
+
} else {
|
|
257
|
+
// Fallback to creating a new instance
|
|
258
|
+
testRunner = testModule;
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error('Failed to load test runner:', error.message);
|
|
262
|
+
console.error('Make sure fragment-ts is installed as a dependency');
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function runTests() {
|
|
267
|
+
try {
|
|
268
|
+
console.log('Looking for test files...');
|
|
269
|
+
|
|
270
|
+
// Load test files with the runner
|
|
271
|
+
await testRunner.loadTestFiles('${basePath}/${pattern}');
|
|
272
|
+
|
|
273
|
+
// Run tests
|
|
274
|
+
await testRunner.run();
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.error('\\n❌ Error running tests:', error.message);
|
|
277
|
+
|
|
278
|
+
if (error.stack && process.env.DEBUG) {
|
|
279
|
+
console.error(error.stack);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Handle process termination
|
|
287
|
+
process.on('SIGINT', () => {
|
|
288
|
+
console.log('\\n\\nTest run interrupted');
|
|
289
|
+
process.exit(130);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
process.on('SIGTERM', () => {
|
|
293
|
+
console.log('\\n\\nTest run terminated');
|
|
294
|
+
process.exit(143);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Run the tests
|
|
298
|
+
runTests().catch(error => {
|
|
299
|
+
console.error('Unhandled error:', error);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
});
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
304
|
+
}
|