@memberjunction/react-test-harness 2.98.0 → 2.100.0
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/lib/component-linter.d.ts.map +1 -1
- package/dist/lib/component-linter.js +674 -118
- package/dist/lib/component-linter.js.map +1 -1
- package/dist/lib/component-runner.d.ts +2 -1
- package/dist/lib/component-runner.d.ts.map +1 -1
- package/dist/lib/component-runner.js +260 -27
- package/dist/lib/component-runner.js.map +1 -1
- package/dist/lib/test-broken-9.d.ts +3 -0
- package/dist/lib/test-broken-9.d.ts.map +1 -0
- package/dist/lib/test-broken-9.js +122 -0
- package/dist/lib/test-broken-9.js.map +1 -0
- package/package.json +3 -3
|
@@ -58,8 +58,9 @@ export declare class ComponentRunner {
|
|
|
58
58
|
private loadComponentLibraries;
|
|
59
59
|
/**
|
|
60
60
|
* Set up error tracking in the page
|
|
61
|
+
* @deprecated Moved inline to page.evaluate after library loading to avoid false positives
|
|
61
62
|
*/
|
|
62
|
-
private
|
|
63
|
+
private setupErrorTracking_DEPRECATED;
|
|
63
64
|
/**
|
|
64
65
|
* Collect runtime errors from the page
|
|
65
66
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAiC,QAAQ,EAAyD,MAAM,sBAAsB,CAAC;AAC3I,OAAO,EAAmB,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,aAAa,EAMd,MAAM,6CAA6C,CAAC;
|
|
1
|
+
{"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAiC,QAAQ,EAAyD,MAAM,sBAAsB,CAAC;AAC3I,OAAO,EAAmB,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,aAAa,EAMd,MAAM,6CAA6C,CAAC;AAkCrD,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,aAAa,CAAC;IAC/D,WAAW,EAAE,QAAQ,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAChC;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,SAAS,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,qBAAa,eAAe;IAkBd,OAAO,CAAC,cAAc;IAhBlC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAS/C;IAKF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAE7B,cAAc,EAAE,cAAc;IAElD;;OAEG;IACG,aAAa,CACjB,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,aAAa,CAAC,EAAE,GAAG,EACnB,eAAe,CAAC,EAAE,OAAO,EACzB,WAAW,CAAC,EAAE,QAAQ,EACtB,OAAO,CAAC,EAAE,GAAG,GACZ,OAAO,CAAC;QAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAmBrD,gBAAgB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IA28B7F;;;OAGG;YACW,oBAAoB;IA0ElC;;OAEG;YACW,sBAAsB;IAqHpC;;;OAGG;YACW,6BAA6B;IA6P3C;;OAEG;YACW,oBAAoB;IAmHlC;;OAEG;YACW,eAAe;IAkB7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;YAyBb,qBAAqB;cAqBnB,uBAAuB,CAAC,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IA2JtF;;OAEG;YACW,iBAAiB;IAsT/B;;OAEG;IACH,OAAO,CAAC,aAAa;CAuBtB"}
|
|
@@ -29,6 +29,27 @@ const component_linter_1 = require("./component-linter");
|
|
|
29
29
|
const core_entities_1 = require("@memberjunction/core-entities");
|
|
30
30
|
const ai_vectors_memory_1 = require("@memberjunction/ai-vectors-memory");
|
|
31
31
|
const aiengine_1 = require("@memberjunction/aiengine");
|
|
32
|
+
/**
|
|
33
|
+
* Pre-resolve a component spec for browser execution
|
|
34
|
+
* Converts registry components to embedded format with all code included
|
|
35
|
+
*/
|
|
36
|
+
async function preResolveComponentSpec(spec, contextUser) {
|
|
37
|
+
// If already embedded with code, return as-is
|
|
38
|
+
if (spec.location === 'embedded' && spec.code) {
|
|
39
|
+
return spec;
|
|
40
|
+
}
|
|
41
|
+
// For registry components, we need to fetch and embed everything
|
|
42
|
+
if (spec.location === 'registry' && spec.registry) {
|
|
43
|
+
// In test harness context, we likely don't have GraphQL access
|
|
44
|
+
// Registry components would need to be pre-resolved before being passed to test harness
|
|
45
|
+
console.warn(`Registry component ${spec.name} cannot be resolved in test harness context - GraphQL not available`);
|
|
46
|
+
console.warn('Registry components should be pre-resolved before passing to test harness');
|
|
47
|
+
// Return the spec as-is, which will likely fail in browser but at least won't crash here
|
|
48
|
+
return spec;
|
|
49
|
+
}
|
|
50
|
+
// For other types, return as-is
|
|
51
|
+
return spec;
|
|
52
|
+
}
|
|
32
53
|
/**
|
|
33
54
|
* ComponentRunner that uses the actual React runtime via Playwright UMD bundle
|
|
34
55
|
*/
|
|
@@ -118,8 +139,8 @@ class ComponentRunner {
|
|
|
118
139
|
if (!runtimeCheck.hasMJRuntime) {
|
|
119
140
|
throw new Error('Failed to inject MJReactRuntime into page context');
|
|
120
141
|
}
|
|
121
|
-
//
|
|
122
|
-
|
|
142
|
+
// NOTE: Error tracking setup moved to after library loading to avoid false positives
|
|
143
|
+
// during library initialization (e.g., antd's UMD bundle setup)
|
|
123
144
|
// Set up console logging
|
|
124
145
|
this.setupConsoleLogging(page, consoleLogs, warnings);
|
|
125
146
|
// Expose MJ utilities to the page
|
|
@@ -143,6 +164,14 @@ class ComponentRunner {
|
|
|
143
164
|
// Total available libraries in metadata (for context only)
|
|
144
165
|
console.log(' - total available libraries in metadata:', allLibraries?.length || 0);
|
|
145
166
|
}
|
|
167
|
+
// // Pre-resolve the spec for browser execution (convert registry components to embedded)
|
|
168
|
+
// const resolvedSpec = await preResolveComponentSpec(options.componentSpec, options.contextUser);
|
|
169
|
+
// if (debug) {
|
|
170
|
+
// console.log('📦 Pre-resolved spec for browser execution:', {
|
|
171
|
+
// original: { location: options.componentSpec.location, registry: options.componentSpec.registry },
|
|
172
|
+
// resolved: { location: resolvedSpec.location, registry: resolvedSpec.registry, hasCode: !!resolvedSpec.code }
|
|
173
|
+
// });
|
|
174
|
+
// }
|
|
146
175
|
// Execute the component using the real React runtime with timeout (Recommendation #1)
|
|
147
176
|
const executionPromise = page.evaluate(async ({ spec, props, debug, componentLibraries }) => {
|
|
148
177
|
if (debug) {
|
|
@@ -157,7 +186,7 @@ class ComponentRunner {
|
|
|
157
186
|
if (!MJRuntime) {
|
|
158
187
|
throw new Error('React runtime not loaded');
|
|
159
188
|
}
|
|
160
|
-
const { ComponentCompiler, ComponentRegistry, ComponentHierarchyRegistrar, SetupStyles } = MJRuntime;
|
|
189
|
+
const { ComponentCompiler, ComponentRegistry, ComponentHierarchyRegistrar, ComponentManager, SetupStyles } = MJRuntime;
|
|
161
190
|
if (debug) {
|
|
162
191
|
console.log('🚀 Starting component execution with real runtime');
|
|
163
192
|
console.log('Available runtime classes:', Object.keys(MJRuntime));
|
|
@@ -181,6 +210,21 @@ class ComponentRunner {
|
|
|
181
210
|
// Create runtime context
|
|
182
211
|
// Note: Component libraries are loaded by the ComponentCompiler itself
|
|
183
212
|
// via loadRequiredLibraries, so we don't need to pass them here
|
|
213
|
+
// Diagnostic: Check if React is available before creating context
|
|
214
|
+
if (!window.React) {
|
|
215
|
+
console.error('🔴 CRITICAL: React is NULL when creating runtimeContext!');
|
|
216
|
+
console.error('Window keys:', Object.keys(window).filter(k => k.toLowerCase().includes('react')));
|
|
217
|
+
throw new Error('React is not available in window context');
|
|
218
|
+
}
|
|
219
|
+
if (debug) {
|
|
220
|
+
console.log('✅ React is available:', typeof window.React);
|
|
221
|
+
console.log('✅ React hooks check:', {
|
|
222
|
+
useState: typeof window.React?.useState,
|
|
223
|
+
useEffect: typeof window.React?.useEffect,
|
|
224
|
+
useRef: typeof window.React?.useRef,
|
|
225
|
+
useMemo: typeof window.React?.useMemo
|
|
226
|
+
});
|
|
227
|
+
}
|
|
184
228
|
const runtimeContext = {
|
|
185
229
|
React: window.React,
|
|
186
230
|
ReactDOM: window.ReactDOM,
|
|
@@ -192,7 +236,14 @@ class ComponentRunner {
|
|
|
192
236
|
// IMPORTANT: Configure the LibraryRegistry in the browser context
|
|
193
237
|
// This is needed for the compiler to know about approved libraries
|
|
194
238
|
if (window.MJReactRuntime && window.MJReactRuntime.LibraryRegistry) {
|
|
195
|
-
const { LibraryRegistry } = window.MJReactRuntime;
|
|
239
|
+
const { LibraryRegistry, LibraryLoader } = window.MJReactRuntime;
|
|
240
|
+
// Enable progressive delay for library initialization in test harness
|
|
241
|
+
if (LibraryLoader) {
|
|
242
|
+
LibraryLoader.enableProgressiveDelay = true;
|
|
243
|
+
if (debug) {
|
|
244
|
+
console.log('⚙️ Enabled progressive delay for library initialization');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
196
247
|
// Configure the registry with the component libraries
|
|
197
248
|
// Note: LibraryRegistry.Config expects ComponentLibraryEntity[]
|
|
198
249
|
await LibraryRegistry.Config(false, componentLibraries || []);
|
|
@@ -201,7 +252,9 @@ class ComponentRunner {
|
|
|
201
252
|
}
|
|
202
253
|
}
|
|
203
254
|
const registry = new ComponentRegistry();
|
|
204
|
-
|
|
255
|
+
// NEW: Use ComponentManager instead of ComponentHierarchyRegistrar
|
|
256
|
+
const manager = new ComponentManager(compiler, registry, runtimeContext, { debug: true, enableUsageTracking: false } // Force debug on for better diagnostics
|
|
257
|
+
);
|
|
205
258
|
// Use the utilities we already created with mock metadata
|
|
206
259
|
// Don't call createRuntimeUtilities() as it tries to create new Metadata() which fails
|
|
207
260
|
const utilities = window.__mjUtilities;
|
|
@@ -249,13 +302,57 @@ class ComponentRunner {
|
|
|
249
302
|
}
|
|
250
303
|
}
|
|
251
304
|
let registrationResult;
|
|
305
|
+
let loadResult; // Declare loadResult in outer scope
|
|
252
306
|
try {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
307
|
+
if (debug) {
|
|
308
|
+
console.log('📋 [BROWSER] Spec before loadHierarchy:', {
|
|
309
|
+
name: spec.name,
|
|
310
|
+
location: spec.location,
|
|
311
|
+
registry: spec.registry,
|
|
312
|
+
hasCode: !!spec.code,
|
|
313
|
+
codeLength: spec.code?.length,
|
|
314
|
+
libraries: spec.libraries,
|
|
315
|
+
dependencies: spec.dependencies?.map((d) => ({
|
|
316
|
+
name: d.name,
|
|
317
|
+
location: d.location,
|
|
318
|
+
hasCode: !!d.code
|
|
319
|
+
}))
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
// NEW: Use ComponentManager.loadHierarchy instead of registrar.registerHierarchy
|
|
323
|
+
// Note: In browser context, we don't have access to contextUser or database
|
|
324
|
+
// This is fine for embedded components which are self-contained
|
|
325
|
+
loadResult = await manager.loadHierarchy(spec, {
|
|
326
|
+
contextUser: undefined, // No user context in browser
|
|
327
|
+
defaultNamespace: 'Global',
|
|
328
|
+
defaultVersion: 'v1',
|
|
329
|
+
returnType: 'both',
|
|
330
|
+
resolutionMode: 'embed', // Convert to embedded format for browser execution
|
|
331
|
+
allLibraries: componentLibraries || [] // Pass libraries for compiler
|
|
258
332
|
});
|
|
333
|
+
if (debug) {
|
|
334
|
+
console.log('📋 [BROWSER] LoadHierarchy result:', {
|
|
335
|
+
success: loadResult.success,
|
|
336
|
+
rootComponent: !!loadResult.rootComponent,
|
|
337
|
+
resolvedSpec: loadResult.resolvedSpec ? {
|
|
338
|
+
name: loadResult.resolvedSpec.name,
|
|
339
|
+
location: loadResult.resolvedSpec.location,
|
|
340
|
+
registry: loadResult.resolvedSpec.registry,
|
|
341
|
+
libraries: loadResult.resolvedSpec.libraries,
|
|
342
|
+
hasCode: !!loadResult.resolvedSpec.code
|
|
343
|
+
} : null,
|
|
344
|
+
loadedComponents: loadResult.loadedComponents,
|
|
345
|
+
errors: loadResult.errors
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
// Convert to old format for compatibility
|
|
349
|
+
registrationResult = {
|
|
350
|
+
success: loadResult.success,
|
|
351
|
+
registeredComponents: loadResult.loadedComponents,
|
|
352
|
+
errors: loadResult.errors.map((e) => ({ componentName: e.componentName || '', error: e.message, phase: e.phase })),
|
|
353
|
+
warnings: [],
|
|
354
|
+
resolvedSpec: loadResult.resolvedSpec
|
|
355
|
+
};
|
|
259
356
|
}
|
|
260
357
|
catch (registrationError) {
|
|
261
358
|
// Capture the actual error before it gets obscured
|
|
@@ -288,32 +385,140 @@ class ComponentRunner {
|
|
|
288
385
|
// Note: ComponentRegistry doesn't expose internal components Map directly
|
|
289
386
|
// We can see what was registered through the registrationResult
|
|
290
387
|
}
|
|
291
|
-
//
|
|
292
|
-
|
|
388
|
+
// NEW: With ComponentManager, we already have the components from loadResult
|
|
389
|
+
if (!loadResult) {
|
|
390
|
+
throw new Error('Component loading failed - no result returned');
|
|
391
|
+
}
|
|
392
|
+
const RootComponentObject = loadResult.rootComponent;
|
|
293
393
|
if (!RootComponentObject) {
|
|
294
394
|
// Enhanced error message with debugging info
|
|
295
|
-
console.error('Failed to
|
|
296
|
-
console.error('
|
|
297
|
-
throw new Error('Root component not
|
|
395
|
+
console.error('Failed to load component:', spec.name);
|
|
396
|
+
console.error('Load errors:', loadResult.errors);
|
|
397
|
+
throw new Error('Root component not loaded: ' + spec.name);
|
|
298
398
|
}
|
|
299
399
|
// Extract the React component from the ComponentObject
|
|
300
400
|
const RootComponent = RootComponentObject.component;
|
|
301
401
|
if (!RootComponent || typeof RootComponent !== 'function') {
|
|
302
402
|
throw new Error('Component object does not contain a valid React component');
|
|
303
403
|
}
|
|
304
|
-
// Get all
|
|
305
|
-
|
|
306
|
-
const components = {};
|
|
307
|
-
for (const [name, componentObj] of Object.entries(componentObjects)) {
|
|
308
|
-
// ComponentObject has a component property that's the React component
|
|
309
|
-
components[name] = componentObj.component;
|
|
310
|
-
}
|
|
404
|
+
// Get all loaded components from the result
|
|
405
|
+
// ComponentManager now returns unwrapped components directly
|
|
406
|
+
const components = loadResult.components || {};
|
|
311
407
|
if (debug) {
|
|
312
408
|
console.log('📚 Registered components for dependencies:', Object.keys(components));
|
|
313
409
|
console.log('📋 Component spec dependencies:', spec.dependencies?.map((d) => d.name) || []);
|
|
410
|
+
// Check what libraries are actually available in global scope
|
|
411
|
+
console.log('🌍 [BROWSER] Global library check after loading:', {
|
|
412
|
+
ApexCharts: typeof window.ApexCharts,
|
|
413
|
+
antd: typeof window.antd,
|
|
414
|
+
React: typeof window.React,
|
|
415
|
+
ReactDOM: typeof window.ReactDOM,
|
|
416
|
+
windowKeys: Object.keys(window).filter(k => k.toLowerCase().includes('apex') ||
|
|
417
|
+
k.toLowerCase().includes('antd') ||
|
|
418
|
+
k === 'ApexCharts' ||
|
|
419
|
+
k === 'antd')
|
|
420
|
+
});
|
|
421
|
+
// If libraries were supposed to be loaded, check their actual presence
|
|
422
|
+
if (spec.libraries && spec.libraries.length > 0) {
|
|
423
|
+
console.log('🔍 [BROWSER] Checking required libraries:');
|
|
424
|
+
for (const lib of spec.libraries) {
|
|
425
|
+
const globalVar = lib.globalVariable;
|
|
426
|
+
const exists = !!window[globalVar];
|
|
427
|
+
const type = typeof window[globalVar];
|
|
428
|
+
console.log(` - ${lib.name} (${globalVar}): ${exists ? `✅ Present (${type})` : '❌ Missing'}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
314
431
|
}
|
|
315
432
|
// Note: Library components are now handled by the runtime's compiler
|
|
316
433
|
// which loads them into the appropriate context/closure
|
|
434
|
+
// NOW set up enhanced error tracking - AFTER libraries are loaded
|
|
435
|
+
// This avoids false positives from library initialization code (e.g., antd)
|
|
436
|
+
if (!window.__testHarnessErrorTrackingSetup) {
|
|
437
|
+
window.__testHarnessErrorTrackingSetup = true;
|
|
438
|
+
// Initialize error tracking arrays if not already done
|
|
439
|
+
window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
|
|
440
|
+
window.__testHarnessConsoleErrors = window.__testHarnessConsoleErrors || [];
|
|
441
|
+
window.__testHarnessConsoleWarnings = window.__testHarnessConsoleWarnings || [];
|
|
442
|
+
window.__testHarnessTestFailed = window.__testHarnessTestFailed || false;
|
|
443
|
+
window.__testHarnessRenderCount = window.__testHarnessRenderCount || 0;
|
|
444
|
+
// Wrap React.createElement to detect invalid element types
|
|
445
|
+
const originalCreateElement = window.React?.createElement;
|
|
446
|
+
if (originalCreateElement) {
|
|
447
|
+
window.React.createElement = function (type, props, ...children) {
|
|
448
|
+
window.__testHarnessRenderCount++;
|
|
449
|
+
// Enhanced error detection for invalid element types
|
|
450
|
+
if (type !== null && type !== undefined) {
|
|
451
|
+
const typeOf = typeof type;
|
|
452
|
+
// Check for the common "object instead of component" error
|
|
453
|
+
if (typeOf === 'object' && !window.React.isValidElement(type)) {
|
|
454
|
+
// Try to get a meaningful name for the object
|
|
455
|
+
let objectInfo = 'unknown object';
|
|
456
|
+
try {
|
|
457
|
+
if (type.constructor && type.constructor.name) {
|
|
458
|
+
objectInfo = type.constructor.name;
|
|
459
|
+
}
|
|
460
|
+
else if (type.name) {
|
|
461
|
+
objectInfo = type.name;
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
// Try to show what properties it has
|
|
465
|
+
const keys = Object.keys(type).slice(0, 5);
|
|
466
|
+
if (keys.length > 0) {
|
|
467
|
+
objectInfo = `object with properties: ${keys.join(', ')}`;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
// Ignore errors in trying to get object info
|
|
473
|
+
}
|
|
474
|
+
// Generate helpful error message
|
|
475
|
+
const errorMsg = [
|
|
476
|
+
`Invalid JSX element type: React received an object (${objectInfo}) instead of a React component function.`,
|
|
477
|
+
'',
|
|
478
|
+
'This often occurs when JSX elements or React.createElement receive an object instead of a valid component function.',
|
|
479
|
+
'',
|
|
480
|
+
'Inspect all instances where you are using JSX elements that come from libraries or components to ensure they are properly referenced.',
|
|
481
|
+
'',
|
|
482
|
+
'The exact fix depends on the specific library or component structure.'
|
|
483
|
+
].join('\\n');
|
|
484
|
+
// Log to both console and error tracking
|
|
485
|
+
console.error('🔴 Invalid JSX Element Type Detected:', errorMsg);
|
|
486
|
+
// Store the error for later collection
|
|
487
|
+
window.__testHarnessRuntimeErrors.push({
|
|
488
|
+
message: errorMsg,
|
|
489
|
+
type: 'invalid-element-type',
|
|
490
|
+
phase: 'createElement',
|
|
491
|
+
source: 'enhanced-detection',
|
|
492
|
+
elementInfo: objectInfo
|
|
493
|
+
});
|
|
494
|
+
// Still try to call the original to get React's error too
|
|
495
|
+
// This will provide the component stack trace
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
else if (type === undefined) {
|
|
499
|
+
// Undefined component - likely a failed destructure or missing import
|
|
500
|
+
const errorMsg = [
|
|
501
|
+
'Invalid JSX element type: component is undefined.',
|
|
502
|
+
'',
|
|
503
|
+
'This occurs when a JSX element references a component that is undefined at runtime.',
|
|
504
|
+
'',
|
|
505
|
+
'Inspect how this component is being accessed - it may not exist in the expected location or may have a different name.',
|
|
506
|
+
'',
|
|
507
|
+
'Check that the component exists in your dependencies or libraries and is properly referenced.'
|
|
508
|
+
].join('\\n');
|
|
509
|
+
console.error('🔴 Undefined JSX Component:', errorMsg);
|
|
510
|
+
window.__testHarnessRuntimeErrors.push({
|
|
511
|
+
message: errorMsg,
|
|
512
|
+
type: 'undefined-component',
|
|
513
|
+
phase: 'createElement',
|
|
514
|
+
source: 'enhanced-detection'
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
// Call original createElement
|
|
518
|
+
return originalCreateElement.apply(this, [type, props, ...children]);
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
317
522
|
// Render the component
|
|
318
523
|
const rootElement = document.getElementById('root');
|
|
319
524
|
if (!rootElement) {
|
|
@@ -495,7 +700,15 @@ class ComponentRunner {
|
|
|
495
700
|
renderCount = await page.evaluate(() => window.__testHarnessRenderCount || 0);
|
|
496
701
|
// Collect all errors with source information
|
|
497
702
|
const runtimeErrorsWithSource = await this.collectRuntimeErrors(page);
|
|
498
|
-
|
|
703
|
+
// Filter out JSX element type errors when adding to errors array
|
|
704
|
+
errors.push(...runtimeErrorsWithSource
|
|
705
|
+
.filter(e => {
|
|
706
|
+
// Skip JSX element type errors from error count
|
|
707
|
+
const isJSXError = e.type === 'invalid-element-type' ||
|
|
708
|
+
e.type === 'undefined-component';
|
|
709
|
+
return !isJSXError;
|
|
710
|
+
})
|
|
711
|
+
.map(e => e.message)); // Extract messages for backward compat
|
|
499
712
|
// Collect warnings (separate from errors)
|
|
500
713
|
const collectedWarnings = await this.collectWarnings(page);
|
|
501
714
|
warnings.push(...collectedWarnings);
|
|
@@ -503,12 +716,18 @@ class ComponentRunner {
|
|
|
503
716
|
const asyncWaitTime = options.asyncErrorWaitTime || 1000;
|
|
504
717
|
await page.waitForTimeout(asyncWaitTime);
|
|
505
718
|
const asyncErrors = await this.collectRuntimeErrors(page);
|
|
506
|
-
// Only add new errors
|
|
719
|
+
// Only add new errors (excluding JSX element type errors)
|
|
507
720
|
asyncErrors.forEach(err => {
|
|
508
|
-
|
|
721
|
+
const isJSXError = err.type === 'invalid-element-type' ||
|
|
722
|
+
err.type === 'undefined-component';
|
|
723
|
+
if (!isJSXError && !errors.includes(err.message)) {
|
|
509
724
|
errors.push(err.message);
|
|
510
725
|
runtimeErrorsWithSource.push(err); // Keep the structured version too
|
|
511
726
|
}
|
|
727
|
+
else if (isJSXError && !runtimeErrorsWithSource.some(e => e.message === err.message)) {
|
|
728
|
+
// Still track JSX errors for logging but don't add to errors array
|
|
729
|
+
runtimeErrorsWithSource.push(err);
|
|
730
|
+
}
|
|
512
731
|
});
|
|
513
732
|
// Also check for new warnings
|
|
514
733
|
const asyncWarnings = await this.collectWarnings(page);
|
|
@@ -534,7 +753,20 @@ class ComponentRunner {
|
|
|
534
753
|
// Combine runtime errors with data errors
|
|
535
754
|
const allErrors = [...errors, ...dataErrors];
|
|
536
755
|
// Map runtime errors with source info and specific rules
|
|
537
|
-
|
|
756
|
+
// Filter out JSX element type errors - they're too noisy and often false positives
|
|
757
|
+
const errorViolations = runtimeErrorsWithSource
|
|
758
|
+
.filter(e => {
|
|
759
|
+
// Skip JSX element type errors - still logged but not reported as violations
|
|
760
|
+
const isJSXError = e.rule === 'invalid-jsx-element' ||
|
|
761
|
+
e.rule === 'undefined-jsx-component' ||
|
|
762
|
+
e.type === 'invalid-element-type' ||
|
|
763
|
+
e.type === 'undefined-component';
|
|
764
|
+
if (isJSXError && debug) {
|
|
765
|
+
console.log('📝 JSX element error detected but not reported as violation:', e.message);
|
|
766
|
+
}
|
|
767
|
+
return !isJSXError;
|
|
768
|
+
})
|
|
769
|
+
.map(e => ({
|
|
538
770
|
message: e.message,
|
|
539
771
|
severity: 'critical',
|
|
540
772
|
rule: e.rule || 'runtime-error', // Use specific rule from collectRuntimeErrors
|
|
@@ -852,8 +1084,9 @@ class ComponentRunner {
|
|
|
852
1084
|
}
|
|
853
1085
|
/**
|
|
854
1086
|
* Set up error tracking in the page
|
|
1087
|
+
* @deprecated Moved inline to page.evaluate after library loading to avoid false positives
|
|
855
1088
|
*/
|
|
856
|
-
async
|
|
1089
|
+
async setupErrorTracking_DEPRECATED(page, componentSpec, allLibraries) {
|
|
857
1090
|
await page.evaluate(({ spec, availableLibraries }) => {
|
|
858
1091
|
// Initialize error tracking
|
|
859
1092
|
window.__testHarnessRuntimeErrors = [];
|