fragment-ts 1.0.19 โ†’ 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.
@@ -36,6 +36,12 @@ export class TestRunner {
36
36
  private suites: TestSuite[] = [];
37
37
  private passed = 0;
38
38
  private failed = 0;
39
+ private errors: Array<{
40
+ suite: string;
41
+ test: string;
42
+ error: string;
43
+ stack?: string;
44
+ }> = [];
39
45
 
40
46
  describe(name: string, fn: () => void): void {
41
47
  const suite: TestSuite = {
@@ -71,56 +77,144 @@ export class TestRunner {
71
77
  }
72
78
 
73
79
  async run(): Promise<void> {
74
- console.log("\nRunning Fragment Tests\n");
80
+ console.log("\n๐Ÿงช Running Fragment Tests\n");
81
+
82
+ if (this.suites.length === 0) {
83
+ console.log("No test suites found");
84
+ return;
85
+ }
75
86
 
76
87
  for (const suite of this.suites) {
77
- console.log(`\nSuite: ${suite.name}`);
88
+ console.log(`\n๐Ÿ“ฆ ${suite.name}`);
89
+
90
+ if (suite.tests.length === 0) {
91
+ console.log(" (no tests)");
92
+ continue;
93
+ }
78
94
 
79
95
  for (const test of suite.tests) {
80
96
  try {
97
+ // Run beforeEach hooks
81
98
  for (const hook of suite.beforeEachHooks) {
82
99
  await hook();
83
100
  }
84
101
 
102
+ // Run the test
85
103
  await test.fn();
86
104
 
105
+ // Run afterEach hooks
87
106
  for (const hook of suite.afterEachHooks) {
88
107
  await hook();
89
108
  }
90
109
 
91
- console.log(` PASS ${test.name}`);
110
+ console.log(` โœ“ ${test.name}`);
92
111
  this.passed++;
93
112
  } catch (error: any) {
94
- console.log(` FAIL ${test.name}`);
113
+ console.log(` โœ— ${test.name}`);
95
114
  console.error(` ${error?.message ?? error}`);
96
115
  this.failed++;
116
+ this.errors.push({
117
+ suite: suite.name,
118
+ test: test.name,
119
+ error: error?.message ?? String(error),
120
+ stack: error?.stack,
121
+ });
97
122
  }
98
123
  }
99
124
  }
100
125
 
101
- console.log(`\nResults: ${this.passed} passed, ${this.failed} failed\n`);
126
+ // Print summary
127
+ console.log(
128
+ `\n\n๐Ÿ“Š Results: ${this.passed} passed, ${this.failed} failed\n`,
129
+ );
102
130
 
103
131
  if (this.failed > 0) {
132
+ console.log("โŒ Failed Tests:\n");
133
+ this.errors.forEach((err) => {
134
+ console.log(` ${err.suite} > ${err.test}`);
135
+ console.log(` ${err.error}\n`);
136
+ });
104
137
  process.exit(1);
138
+ } else {
139
+ console.log("โœ… All tests passed!\n");
140
+ process.exit(0);
105
141
  }
106
142
  }
107
143
 
108
144
  async loadTestFiles(pattern: string): Promise<void> {
109
- const files = await glob(pattern);
145
+ const files = await glob(pattern, { cwd: process.cwd() });
146
+
147
+ if (files.length === 0) {
148
+ console.log(`No test files found matching pattern: ${pattern}`);
149
+ return;
150
+ }
151
+
152
+ console.log(`Found ${files.length} test file(s)\n`);
153
+
154
+ // Set the global runner before loading any files
155
+ G.__testRunner = this;
110
156
 
111
157
  for (const file of files) {
112
- require(path.resolve(file));
158
+ const fullPath = path.resolve(file);
159
+
160
+ // Clear require cache for this file to allow hot reloading
161
+ delete require.cache[fullPath];
162
+
163
+ try {
164
+ require(fullPath);
165
+ } catch (error: any) {
166
+ console.error(`Error loading test file ${file}:`, error.message);
167
+ throw error;
168
+ }
113
169
  }
114
170
  }
115
171
  }
116
172
 
173
+ /* ======================================================
174
+ * Singleton instance
175
+ * ====================================================== */
176
+ let runnerInstance: TestRunner | null = null;
177
+
178
+ export function getTestRunner(): TestRunner {
179
+ if (!runnerInstance) {
180
+ runnerInstance = new TestRunner();
181
+ G.__testRunner = runnerInstance;
182
+ }
183
+ return runnerInstance;
184
+ }
185
+
186
+ export function initTestRunner(): TestRunner {
187
+ return getTestRunner();
188
+ }
189
+
190
+ /* ======================================================
191
+ * Auto-initialization for test environment
192
+ * ====================================================== */
193
+ // Auto-initialize when in test environment
194
+ if (
195
+ process.env.NODE_ENV === "test" ||
196
+ process.argv.some((arg) => arg.includes("test"))
197
+ ) {
198
+ if (!G.__testRunner) {
199
+ getTestRunner();
200
+ }
201
+ }
202
+
117
203
  /* ======================================================
118
204
  * Global test helpers
119
205
  * ====================================================== */
120
206
  export function describe(name: string, fn: () => void): void {
121
207
  if (!G.__testRunner) {
122
- throw new Error("TestRunner not initialized");
208
+ // Try to auto-initialize
209
+ getTestRunner();
123
210
  }
211
+
212
+ if (!G.__testRunner) {
213
+ throw new Error(
214
+ "TestRunner not initialized. Make sure to call initTestRunner() or run tests via 'fragment test' command",
215
+ );
216
+ }
217
+
124
218
  G.__testRunner.describe(name, fn);
125
219
  }
126
220
 
@@ -176,21 +270,29 @@ export function expect(actual: any) {
176
270
  }
177
271
  },
178
272
 
179
- toThrow() {
273
+ toThrow(expectedError?: string) {
180
274
  if (typeof actual !== "function") {
181
275
  throw new Error("toThrow expects a function");
182
276
  }
183
277
 
184
278
  let threw = false;
279
+ let error = null;
185
280
  try {
186
281
  actual();
187
- } catch {
282
+ } catch (e: any) {
188
283
  threw = true;
284
+ error = e;
189
285
  }
190
286
 
191
287
  if (!threw) {
192
288
  throw new Error("Expected function to throw an error");
193
289
  }
290
+
291
+ if (expectedError && error?.message !== expectedError) {
292
+ throw new Error(
293
+ `Expected error message "${expectedError}" but got "${error?.message}"`,
294
+ );
295
+ }
194
296
  },
195
297
 
196
298
  toBeInstanceOf(expected: any) {
@@ -209,9 +311,15 @@ export function expect(actual: any) {
209
311
  }
210
312
  },
211
313
 
212
- toHaveProperty(prop: string) {
213
- if (actual == null || !(prop in actual)) {
214
- throw new Error(`Expected object to have property "${prop}"`);
314
+ toHaveProperty(property: string, value?: any) {
315
+ if (actual == null || !(property in actual)) {
316
+ throw new Error(`Expected object to have property "${property}"`);
317
+ }
318
+
319
+ if (value !== undefined && actual[property] !== value) {
320
+ throw new Error(
321
+ `Expected property "${property}" to be ${value} but got ${actual[property]}`,
322
+ );
215
323
  }
216
324
  },
217
325
 
@@ -240,17 +348,24 @@ export function expect(actual: any) {
240
348
  }
241
349
 
242
350
  /* ======================================================
243
- * CLI entry
351
+ * CLI entry (when this file is run directly)
244
352
  * ====================================================== */
245
353
  if (require.main === module) {
246
- const runner = new TestRunner();
247
- G.__testRunner = runner;
248
-
249
- runner
250
- .loadTestFiles("dist/**/*.spec.js")
251
- .then(() => runner.run())
252
- .catch((err) => {
253
- console.error("Failed to run tests:", err);
354
+ (async () => {
355
+ try {
356
+ const runner = getTestRunner();
357
+
358
+ // Auto-detect TypeScript based on arguments
359
+ const isTsNode = process.argv.some(
360
+ (arg) => arg.includes("ts-node") || arg.includes("ts-node/register"),
361
+ );
362
+ const pattern = isTsNode ? "src/**/*.spec.ts" : "dist/**/*.spec.js";
363
+
364
+ await runner.loadTestFiles(pattern);
365
+ await runner.run();
366
+ } catch (err: any) {
367
+ console.error("Failed to run tests:", err.message);
254
368
  process.exit(1);
255
- });
369
+ }
370
+ })();
256
371
  }
@@ -20,6 +20,7 @@ export class FragmentWebApplication {
20
20
  this.container = DIContainer.getInstance();
21
21
  this.metadataStorage = MetadataStorage.getInstance();
22
22
  this.setupMiddleware();
23
+ this
23
24
  }
24
25
 
25
26
  private setupMiddleware(): void {