elit 3.3.3 → 3.3.5
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/dist/build.d.mts +1 -1
- package/dist/build.js +1 -0
- package/dist/build.js.map +1 -0
- package/dist/build.mjs +1 -0
- package/dist/build.mjs.map +1 -0
- package/dist/chokidar.js +1 -0
- package/dist/chokidar.js.map +1 -0
- package/dist/chokidar.mjs +1 -0
- package/dist/chokidar.mjs.map +1 -0
- package/dist/cli.js +4720 -37
- package/dist/config.d.mts +3 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -0
- package/dist/config.mjs +1 -0
- package/dist/config.mjs.map +1 -0
- package/dist/coverage.d.mts +85 -0
- package/dist/coverage.d.ts +76 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +1549 -0
- package/dist/coverage.js.map +1 -0
- package/dist/coverage.mjs +1520 -0
- package/dist/coverage.mjs.map +1 -0
- package/dist/database.d.mts +31 -6
- package/dist/database.d.ts +31 -6
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +60 -33
- package/dist/database.js.map +1 -0
- package/dist/database.mjs +60 -33
- package/dist/database.mjs.map +1 -0
- package/dist/dom.js +1 -0
- package/dist/dom.js.map +1 -0
- package/dist/dom.mjs +1 -0
- package/dist/dom.mjs.map +1 -0
- package/dist/el.js +1 -0
- package/dist/el.js.map +1 -0
- package/dist/el.mjs +1 -0
- package/dist/el.mjs.map +1 -0
- package/dist/fs.js +1 -0
- package/dist/fs.js.map +1 -0
- package/dist/fs.mjs +1 -0
- package/dist/fs.mjs.map +1 -0
- package/dist/hmr.js +1 -0
- package/dist/hmr.js.map +1 -0
- package/dist/hmr.mjs +1 -0
- package/dist/hmr.mjs.map +1 -0
- package/dist/http.js +1 -0
- package/dist/http.js.map +1 -0
- package/dist/http.mjs +1 -0
- package/dist/http.mjs.map +1 -0
- package/dist/https.d.mts +1 -1
- package/dist/https.js +1 -0
- package/dist/https.js.map +1 -0
- package/dist/https.mjs +1 -0
- package/dist/https.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mime-types.js +1 -0
- package/dist/mime-types.js.map +1 -0
- package/dist/mime-types.mjs +1 -0
- package/dist/mime-types.mjs.map +1 -0
- package/dist/path.js +1 -0
- package/dist/path.js.map +1 -0
- package/dist/path.mjs +1 -0
- package/dist/path.mjs.map +1 -0
- package/dist/router.js +1 -0
- package/dist/router.js.map +1 -0
- package/dist/router.mjs +1 -0
- package/dist/router.mjs.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/runtime.js.map +1 -0
- package/dist/runtime.mjs +1 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/{server-Cz3z-5ls.d.mts → server-BFTzgJpO.d.mts} +62 -1
- package/dist/{server-BG2CaVMh.d.ts → server-CIXtexNS.d.ts} +62 -1
- package/dist/server.d.mts +1 -1
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +45 -3
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +45 -3
- package/dist/server.mjs.map +1 -0
- package/dist/state.d.mts +1 -1
- package/dist/state.js +1 -0
- package/dist/state.js.map +1 -0
- package/dist/state.mjs +1 -0
- package/dist/state.mjs.map +1 -0
- package/dist/style.js +1 -0
- package/dist/style.js.map +1 -0
- package/dist/style.mjs +1 -0
- package/dist/style.mjs.map +1 -0
- package/dist/test-globals.d.ts +184 -0
- package/dist/test-reporter.d.mts +77 -0
- package/dist/test-reporter.d.ts +73 -0
- package/dist/test-reporter.d.ts.map +1 -0
- package/dist/test-reporter.js +726 -0
- package/dist/test-reporter.js.map +1 -0
- package/dist/test-reporter.mjs +696 -0
- package/dist/test-reporter.mjs.map +1 -0
- package/dist/test-runtime.d.mts +122 -0
- package/dist/test-runtime.d.ts +120 -0
- package/dist/test-runtime.d.ts.map +1 -0
- package/dist/test-runtime.js +1292 -0
- package/dist/test-runtime.js.map +1 -0
- package/dist/test-runtime.mjs +1269 -0
- package/dist/test-runtime.mjs.map +1 -0
- package/dist/test.d.mts +39 -0
- package/dist/test.d.ts +38 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +4966 -0
- package/dist/test.js.map +1 -0
- package/dist/test.mjs +4944 -0
- package/dist/test.mjs.map +1 -0
- package/dist/types.d.mts +62 -1
- package/dist/types.d.ts +52 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +1 -0
- package/dist/types.mjs.map +1 -0
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +24 -2
- package/dist/ws.js.map +1 -0
- package/dist/ws.mjs +24 -2
- package/dist/ws.mjs.map +1 -0
- package/dist/wss.js +24 -2
- package/dist/wss.js.map +1 -0
- package/dist/wss.mjs +24 -2
- package/dist/wss.mjs.map +1 -0
- package/package.json +37 -5
- package/src/cli.ts +165 -1
- package/src/config.ts +3 -1
- package/src/coverage.ts +1479 -0
- package/src/database.ts +71 -35
- package/src/server.ts +25 -1
- package/src/test-globals.d.ts +184 -0
- package/src/test-reporter.ts +609 -0
- package/src/test-runtime.ts +1359 -0
- package/src/test.ts +368 -0
- package/src/types.ts +59 -0
- package/src/ws.ts +32 -2
package/src/test.ts
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest-compatible Test Runner powered by esbuild
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for running tests with Jest-like API.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readdirSync } from './fs';
|
|
8
|
+
import { join, relative } from './path';
|
|
9
|
+
import { runTests, setupGlobals, clearGlobals, resetCoveredFiles, getCoveredFiles } from './test-runtime';
|
|
10
|
+
import { TestReporter, DotReporter, JsonReporter, VerboseReporter } from './test-reporter';
|
|
11
|
+
|
|
12
|
+
export interface TestOptions {
|
|
13
|
+
files?: string[];
|
|
14
|
+
include?: string[];
|
|
15
|
+
exclude?: string[];
|
|
16
|
+
reporter?: 'default' | 'dot' | 'json' | 'verbose';
|
|
17
|
+
timeout?: number;
|
|
18
|
+
bail?: boolean;
|
|
19
|
+
run?: boolean;
|
|
20
|
+
watch?: boolean;
|
|
21
|
+
endToEnd?: boolean;
|
|
22
|
+
colors?: boolean;
|
|
23
|
+
globals?: boolean;
|
|
24
|
+
describePattern?: string;
|
|
25
|
+
testPattern?: string;
|
|
26
|
+
coverage?: {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
provider: 'v8' | 'istanbul';
|
|
29
|
+
reporter?: ('text' | 'html' | 'lcov' | 'json' | 'coverage-final.json' | 'clover')[];
|
|
30
|
+
include?: string[];
|
|
31
|
+
exclude?: string[];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convert glob pattern to regex (safe from ReDoS)
|
|
37
|
+
*/
|
|
38
|
+
function globToRegex(pattern: string): RegExp {
|
|
39
|
+
// Handle brace expansion: {ts,js} -> (ts|js)
|
|
40
|
+
// Use string operations instead of regex to avoid ReDoS
|
|
41
|
+
let expanded = pattern;
|
|
42
|
+
const openBraceIndex = pattern.indexOf('{');
|
|
43
|
+
if (openBraceIndex !== -1) {
|
|
44
|
+
const closeBraceIndex = pattern.indexOf('}', openBraceIndex);
|
|
45
|
+
if (closeBraceIndex !== -1) {
|
|
46
|
+
const options = pattern.slice(openBraceIndex + 1, closeBraceIndex).split(',');
|
|
47
|
+
const before = pattern.slice(0, openBraceIndex);
|
|
48
|
+
const after = pattern.slice(closeBraceIndex + 1);
|
|
49
|
+
expanded = before + '(' + options.join('|') + ')' + after;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Convert to regex using character-by-character processing
|
|
54
|
+
// This avoids using .replace() with regex on user input
|
|
55
|
+
let regexStr = '^';
|
|
56
|
+
for (let i = 0; i < expanded.length; i++) {
|
|
57
|
+
const char = expanded[i];
|
|
58
|
+
switch (char) {
|
|
59
|
+
case '.':
|
|
60
|
+
regexStr += '\\.';
|
|
61
|
+
break;
|
|
62
|
+
case '*':
|
|
63
|
+
regexStr += '.*';
|
|
64
|
+
break;
|
|
65
|
+
case '?':
|
|
66
|
+
regexStr += '.';
|
|
67
|
+
break;
|
|
68
|
+
case '+':
|
|
69
|
+
case '^':
|
|
70
|
+
case '$':
|
|
71
|
+
case '|':
|
|
72
|
+
case '(':
|
|
73
|
+
case ')':
|
|
74
|
+
case '[':
|
|
75
|
+
case ']':
|
|
76
|
+
case '{':
|
|
77
|
+
case '}':
|
|
78
|
+
case '\\':
|
|
79
|
+
regexStr += '\\' + char;
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
regexStr += char;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
regexStr += '$';
|
|
86
|
+
|
|
87
|
+
return new RegExp(regexStr);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if a file path matches a pattern (safe from ReDoS)
|
|
92
|
+
*/
|
|
93
|
+
function matchesPattern(relativePath: string, pattern: string): boolean {
|
|
94
|
+
// Handle brace expansion: {test,spec} or {ts,js,tsx,etc}
|
|
95
|
+
// Use string operations instead of regex to avoid ReDoS
|
|
96
|
+
const openBraceIndex = pattern.indexOf('{');
|
|
97
|
+
|
|
98
|
+
if (openBraceIndex !== -1) {
|
|
99
|
+
const closeBraceIndex = pattern.indexOf('}', openBraceIndex);
|
|
100
|
+
if (closeBraceIndex !== -1) {
|
|
101
|
+
// Expand braces to multiple patterns
|
|
102
|
+
const options = pattern.slice(openBraceIndex + 1, closeBraceIndex).split(',');
|
|
103
|
+
const before = pattern.slice(0, openBraceIndex);
|
|
104
|
+
const after = pattern.slice(closeBraceIndex + 1);
|
|
105
|
+
|
|
106
|
+
for (const option of options) {
|
|
107
|
+
const testPattern = before + option + after;
|
|
108
|
+
if (matchesPattern(relativePath, testPattern)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Simple glob to regex conversion
|
|
117
|
+
const regex = globToRegex(pattern);
|
|
118
|
+
return regex.test(relativePath);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Find all test files matching patterns
|
|
123
|
+
*/
|
|
124
|
+
function findTestFiles(
|
|
125
|
+
root: string,
|
|
126
|
+
include: string[],
|
|
127
|
+
exclude: string[]
|
|
128
|
+
): string[] {
|
|
129
|
+
const files: string[] = [];
|
|
130
|
+
|
|
131
|
+
// Normalize path to use forward slashes for pattern matching
|
|
132
|
+
// This ensures cross-platform compatibility
|
|
133
|
+
function normalizePathForPattern(path: string): string {
|
|
134
|
+
return path.replace(/\\/g, '/');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function scanDir(dir: string) {
|
|
138
|
+
try {
|
|
139
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
140
|
+
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
if (typeof entry === 'string') continue;
|
|
143
|
+
|
|
144
|
+
const fullPath = join(dir, entry.name);
|
|
145
|
+
|
|
146
|
+
if (entry.isDirectory()) {
|
|
147
|
+
// Check if directory should be excluded
|
|
148
|
+
const relativePath = normalizePathForPattern(relative(root, fullPath));
|
|
149
|
+
if (exclude.some(pattern => matchesPattern(relativePath, pattern))) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
scanDir(fullPath);
|
|
153
|
+
} else if (entry.isFile()) {
|
|
154
|
+
// Check if file matches include patterns
|
|
155
|
+
const relativePath = normalizePathForPattern(relative(root, fullPath));
|
|
156
|
+
// First check if file should be excluded
|
|
157
|
+
if (exclude.some(pattern => matchesPattern(relativePath, pattern))) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
for (const pattern of include) {
|
|
161
|
+
if (matchesPattern(relativePath, pattern)) {
|
|
162
|
+
files.push(fullPath);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
// Ignore permission errors
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
scanDir(root);
|
|
174
|
+
return files;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Run tests with Jest-compatible interface
|
|
179
|
+
*/
|
|
180
|
+
export async function runJestTests(options: TestOptions = {}) {
|
|
181
|
+
const {
|
|
182
|
+
include = ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
183
|
+
exclude = ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**/.elit-tests-temp/**'],
|
|
184
|
+
reporter = 'default',
|
|
185
|
+
timeout = 5000,
|
|
186
|
+
bail = false,
|
|
187
|
+
globals = true,
|
|
188
|
+
} = options;
|
|
189
|
+
|
|
190
|
+
const root = process.cwd();
|
|
191
|
+
const files = options.files || findTestFiles(root, include, exclude);
|
|
192
|
+
|
|
193
|
+
if (files.length === 0) {
|
|
194
|
+
console.log('\n No test files found\n');
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
passed: 0,
|
|
198
|
+
failed: 0,
|
|
199
|
+
total: 0,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Setup globals if enabled
|
|
204
|
+
if (globals) {
|
|
205
|
+
setupGlobals();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Create reporter
|
|
209
|
+
let testReporter;
|
|
210
|
+
switch (reporter) {
|
|
211
|
+
case 'dot':
|
|
212
|
+
testReporter = new DotReporter();
|
|
213
|
+
break;
|
|
214
|
+
case 'json':
|
|
215
|
+
testReporter = new JsonReporter();
|
|
216
|
+
break;
|
|
217
|
+
case 'verbose':
|
|
218
|
+
testReporter = new VerboseReporter();
|
|
219
|
+
break;
|
|
220
|
+
default:
|
|
221
|
+
testReporter = new TestReporter({ colors: true });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Notify start
|
|
225
|
+
if ('onRunStart' in testReporter) {
|
|
226
|
+
testReporter.onRunStart(files);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Reset coverage tracking before running tests
|
|
230
|
+
resetCoveredFiles();
|
|
231
|
+
|
|
232
|
+
// Run tests
|
|
233
|
+
const results = await runTests({
|
|
234
|
+
files,
|
|
235
|
+
timeout,
|
|
236
|
+
bail,
|
|
237
|
+
describePattern: options.describePattern,
|
|
238
|
+
testPattern: options.testPattern,
|
|
239
|
+
});
|
|
240
|
+
// Notify individual test results
|
|
241
|
+
if ('onTestResult' in testReporter) {
|
|
242
|
+
for (const result of results.results) {
|
|
243
|
+
testReporter.onTestResult(result);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Notify end
|
|
248
|
+
if ('onRunEnd' in testReporter) {
|
|
249
|
+
testReporter.onRunEnd(results.results);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Clear globals
|
|
253
|
+
if (globals) {
|
|
254
|
+
clearGlobals();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Generate coverage if enabled
|
|
258
|
+
if (options.coverage?.enabled) {
|
|
259
|
+
await generateCoverage(options.coverage, results.results);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
success: results.failed === 0,
|
|
264
|
+
passed: results.passed,
|
|
265
|
+
failed: results.failed,
|
|
266
|
+
total: results.passed + results.failed + results.skipped + results.todo,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Generate coverage report
|
|
272
|
+
*/
|
|
273
|
+
async function generateCoverage(
|
|
274
|
+
options: {
|
|
275
|
+
provider: 'v8' | 'istanbul';
|
|
276
|
+
reporter?: ('text' | 'html' | 'lcov' | 'json' | 'coverage-final.json' | 'clover')[];
|
|
277
|
+
include?: string[];
|
|
278
|
+
exclude?: string[];
|
|
279
|
+
},
|
|
280
|
+
testResults?: any[]
|
|
281
|
+
) {
|
|
282
|
+
const { processCoverage, generateTextReport, generateHtmlReport, generateCoverageFinalJson, generateCloverXml } = await import('./coverage');
|
|
283
|
+
|
|
284
|
+
// Get covered files from test execution
|
|
285
|
+
const coveredFilesForCoverage = getCoveredFiles();
|
|
286
|
+
|
|
287
|
+
const coverageMap = await processCoverage({
|
|
288
|
+
reportsDirectory: './coverage',
|
|
289
|
+
include: options.include || ['**/*.ts', '**/*.js'],
|
|
290
|
+
exclude: options.exclude || ['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**'],
|
|
291
|
+
reporter: options.reporter || ['text', 'html'],
|
|
292
|
+
coveredFiles: coveredFilesForCoverage,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const reporters = options.reporter || ['text', 'html'];
|
|
296
|
+
if (reporters.includes('text')) {
|
|
297
|
+
console.log('\n' + generateTextReport(coverageMap, testResults));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (reporters.includes('html')) {
|
|
301
|
+
generateHtmlReport(coverageMap, './coverage');
|
|
302
|
+
console.log(`\n Coverage report: coverage/index.html\n`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (reporters.includes('coverage-final.json')) {
|
|
306
|
+
generateCoverageFinalJson(coverageMap, './coverage');
|
|
307
|
+
console.log(`\n Coverage report: coverage/coverage-final.json\n`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (reporters.includes('clover')) {
|
|
311
|
+
generateCloverXml(coverageMap, './coverage');
|
|
312
|
+
console.log(`\n Coverage report: coverage/clover.xml\n`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// Watch Mode
|
|
318
|
+
// ============================================================================
|
|
319
|
+
|
|
320
|
+
export async function runWatchMode(options: TestOptions = {}) {
|
|
321
|
+
const chokidar = await import('chokidar');
|
|
322
|
+
|
|
323
|
+
console.log('\n � watch mode - files will be re-run on change\n');
|
|
324
|
+
|
|
325
|
+
let isRunning = false;
|
|
326
|
+
let needsRerun = false;
|
|
327
|
+
|
|
328
|
+
const runTests = async () => {
|
|
329
|
+
if (isRunning) {
|
|
330
|
+
needsRerun = true;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
isRunning = true;
|
|
335
|
+
needsRerun = false;
|
|
336
|
+
|
|
337
|
+
console.clear();
|
|
338
|
+
await runJestTests(options);
|
|
339
|
+
|
|
340
|
+
isRunning = false;
|
|
341
|
+
|
|
342
|
+
if (needsRerun) {
|
|
343
|
+
await runTests();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Initial run
|
|
348
|
+
await runTests();
|
|
349
|
+
|
|
350
|
+
const { include, exclude } = options;
|
|
351
|
+
const watchPatterns = include || ['**/*.test.ts', '**/*.test.js', '**/*.spec.ts', '**/*.spec.js'];
|
|
352
|
+
const ignoredPatterns = exclude || ['**/node_modules/**', '**/dist/**', '**/coverage/**'];
|
|
353
|
+
|
|
354
|
+
const watcher = chokidar.default.watch(watchPatterns, {
|
|
355
|
+
ignored: ignoredPatterns,
|
|
356
|
+
persistent: true,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
watcher.on('change', async (path) => {
|
|
360
|
+
console.log(`\n 📄 ${path} changed\n`);
|
|
361
|
+
await runTests();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
watcher.on('add', async (path) => {
|
|
365
|
+
console.log(`\n 📄 ${path} added\n`);
|
|
366
|
+
await runTests();
|
|
367
|
+
});
|
|
368
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -268,3 +268,62 @@ export interface PreviewOptions {
|
|
|
268
268
|
/** Environment variables to inject (prefix with VITE_ for client access) */
|
|
269
269
|
env?: Record<string, string>;
|
|
270
270
|
}
|
|
271
|
+
|
|
272
|
+
// ===== Test Types =====
|
|
273
|
+
|
|
274
|
+
export type TestEnvironment = 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime';
|
|
275
|
+
|
|
276
|
+
export type TestCoverageProvider = 'v8' | 'istanbul';
|
|
277
|
+
|
|
278
|
+
export type TestCoverageReporter = 'text' | 'json' | 'html' | 'lcov' | 'lcovonly' | 'coverage-final.json' | 'clover';
|
|
279
|
+
|
|
280
|
+
export interface TestCoverageOptions {
|
|
281
|
+
provider?: TestCoverageProvider;
|
|
282
|
+
reporter?: TestCoverageReporter[];
|
|
283
|
+
dir?: string;
|
|
284
|
+
include?: string[];
|
|
285
|
+
exclude?: string[];
|
|
286
|
+
thresholds?: {
|
|
287
|
+
lines?: number;
|
|
288
|
+
functions?: number;
|
|
289
|
+
branches?: number;
|
|
290
|
+
statements?: number;
|
|
291
|
+
};
|
|
292
|
+
all?: boolean;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface TestOptions {
|
|
296
|
+
environment?: TestEnvironment;
|
|
297
|
+
globals?: boolean;
|
|
298
|
+
setupFiles?: string[];
|
|
299
|
+
include?: string[];
|
|
300
|
+
exclude?: string[];
|
|
301
|
+
testTimeout?: number;
|
|
302
|
+
isolate?: boolean;
|
|
303
|
+
pool?: string;
|
|
304
|
+
poolOptions?: {
|
|
305
|
+
threads?: {
|
|
306
|
+
singleThread?: boolean;
|
|
307
|
+
minThreads?: number;
|
|
308
|
+
maxThreads?: number;
|
|
309
|
+
isolate?: boolean;
|
|
310
|
+
};
|
|
311
|
+
forks?: {
|
|
312
|
+
singleFork?: boolean;
|
|
313
|
+
minForks?: number;
|
|
314
|
+
maxForks?: number;
|
|
315
|
+
isolate?: boolean;
|
|
316
|
+
};
|
|
317
|
+
};
|
|
318
|
+
coverage?: TestCoverageOptions;
|
|
319
|
+
watch?: boolean;
|
|
320
|
+
ui?: boolean;
|
|
321
|
+
reporter?: 'verbose' | 'dot' | 'json' | 'tap';
|
|
322
|
+
bail?: number | boolean;
|
|
323
|
+
pattern?: string | RegExp;
|
|
324
|
+
colors?: boolean;
|
|
325
|
+
retry?: number;
|
|
326
|
+
includeSrc?: string[];
|
|
327
|
+
excludeSrc?: string[];
|
|
328
|
+
env?: Record<string, string>;
|
|
329
|
+
}
|
package/src/ws.ts
CHANGED
|
@@ -348,15 +348,45 @@ export class WebSocketServer extends EventEmitter {
|
|
|
348
348
|
});
|
|
349
349
|
|
|
350
350
|
socket.on('error', (error: Error) => {
|
|
351
|
+
// Silently ignore connection errors (ECONNABORTED, ECONNRESET, etc.)
|
|
352
|
+
// These are normal when clients disconnect during HMR
|
|
353
|
+
const errorCode = (error as any).code;
|
|
354
|
+
if (errorCode === 'ECONNABORTED' || errorCode === 'ECONNRESET' || errorCode === 'EPIPE') {
|
|
355
|
+
// Silently ignore - connection was closed by client
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
// Only emit other errors
|
|
351
359
|
client.emit('error', error);
|
|
352
360
|
});
|
|
353
361
|
|
|
354
362
|
// Override send method
|
|
355
363
|
client.send = (data: Data, _options?: any, callback?: (err?: Error) => void) => {
|
|
364
|
+
// Check if socket is still writable
|
|
365
|
+
if (!socket.writable || client.readyState !== ReadyState.OPEN) {
|
|
366
|
+
const err = new Error('WebSocket is not open');
|
|
367
|
+
(err as any).code = 'WS_NOT_OPEN';
|
|
368
|
+
queueCallback(callback, err);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
356
372
|
try {
|
|
357
373
|
const frame = this._createFrame(data);
|
|
358
|
-
socket.write(frame)
|
|
359
|
-
|
|
374
|
+
socket.write(frame, (err?: Error) => {
|
|
375
|
+
// Handle async write errors (ECONNABORTED, ECONNRESET, etc.)
|
|
376
|
+
if (err) {
|
|
377
|
+
const errorCode = (err as any).code;
|
|
378
|
+
// Silently ignore connection errors - these are normal during HMR
|
|
379
|
+
if (errorCode !== 'ECONNABORTED' && errorCode !== 'ECONNRESET' && errorCode !== 'EPIPE') {
|
|
380
|
+
queueCallback(callback, err);
|
|
381
|
+
} else {
|
|
382
|
+
// Connection closed - mark client as closed
|
|
383
|
+
client.readyState = ReadyState.CLOSED;
|
|
384
|
+
queueCallback(callback); // Call without error for graceful handling
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
queueCallback(callback);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
360
390
|
} catch (error) {
|
|
361
391
|
queueCallback(callback, error as Error);
|
|
362
392
|
}
|