@memberjunction/react-test-harness 2.97.0 ā 2.99.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 +537 -130
- package/dist/lib/component-linter.js.map +1 -1
- package/dist/lib/component-runner.d.ts +2 -2
- package/dist/lib/component-runner.d.ts.map +1 -1
- package/dist/lib/component-runner.js +394 -45
- package/dist/lib/component-runner.js.map +1 -1
- package/dist/lib/library-lint-cache.d.ts +4 -0
- package/dist/lib/library-lint-cache.d.ts.map +1 -1
- package/dist/lib/library-lint-cache.js +61 -3
- package/dist/lib/library-lint-cache.js.map +1 -1
- package/dist/lib/test-harness.d.ts.map +1 -1
- package/dist/lib/test-harness.js +5 -2
- package/dist/lib/test-harness.js.map +1 -1
- package/package.json +3 -3
|
@@ -22,7 +22,6 @@ export interface ComponentExecutionResult {
|
|
|
22
22
|
html: string;
|
|
23
23
|
errors: Violation[];
|
|
24
24
|
warnings: Violation[];
|
|
25
|
-
criticalWarnings: string[];
|
|
26
25
|
console: {
|
|
27
26
|
type: string;
|
|
28
27
|
text: string;
|
|
@@ -59,8 +58,9 @@ export declare class ComponentRunner {
|
|
|
59
58
|
private loadComponentLibraries;
|
|
60
59
|
/**
|
|
61
60
|
* Set up error tracking in the page
|
|
61
|
+
* @deprecated Moved inline to page.evaluate after library loading to avoid false positives
|
|
62
62
|
*/
|
|
63
|
-
private
|
|
63
|
+
private setupErrorTracking_DEPRECATED;
|
|
64
64
|
/**
|
|
65
65
|
* Collect runtime errors from the page
|
|
66
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;AAMrD,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,
|
|
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;AAMrD,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;IA62B7F;;;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"}
|
|
@@ -52,7 +52,6 @@ class ComponentRunner {
|
|
|
52
52
|
const startTime = Date.now();
|
|
53
53
|
const errors = [];
|
|
54
54
|
const warnings = [];
|
|
55
|
-
const criticalWarnings = [];
|
|
56
55
|
const consoleLogs = [];
|
|
57
56
|
const dataErrors = []; // Track data access errors from RunView/RunQuery
|
|
58
57
|
let renderCount = 0;
|
|
@@ -119,10 +118,10 @@ class ComponentRunner {
|
|
|
119
118
|
if (!runtimeCheck.hasMJRuntime) {
|
|
120
119
|
throw new Error('Failed to inject MJReactRuntime into page context');
|
|
121
120
|
}
|
|
122
|
-
//
|
|
123
|
-
|
|
121
|
+
// NOTE: Error tracking setup moved to after library loading to avoid false positives
|
|
122
|
+
// during library initialization (e.g., antd's UMD bundle setup)
|
|
124
123
|
// Set up console logging
|
|
125
|
-
this.setupConsoleLogging(page, consoleLogs, warnings
|
|
124
|
+
this.setupConsoleLogging(page, consoleLogs, warnings);
|
|
126
125
|
// Expose MJ utilities to the page
|
|
127
126
|
await this.exposeMJUtilities(page, options, dataErrors, debug);
|
|
128
127
|
if (debug) {
|
|
@@ -182,6 +181,21 @@ class ComponentRunner {
|
|
|
182
181
|
// Create runtime context
|
|
183
182
|
// Note: Component libraries are loaded by the ComponentCompiler itself
|
|
184
183
|
// via loadRequiredLibraries, so we don't need to pass them here
|
|
184
|
+
// Diagnostic: Check if React is available before creating context
|
|
185
|
+
if (!window.React) {
|
|
186
|
+
console.error('š“ CRITICAL: React is NULL when creating runtimeContext!');
|
|
187
|
+
console.error('Window keys:', Object.keys(window).filter(k => k.toLowerCase().includes('react')));
|
|
188
|
+
throw new Error('React is not available in window context');
|
|
189
|
+
}
|
|
190
|
+
if (debug) {
|
|
191
|
+
console.log('ā
React is available:', typeof window.React);
|
|
192
|
+
console.log('ā
React hooks check:', {
|
|
193
|
+
useState: typeof window.React?.useState,
|
|
194
|
+
useEffect: typeof window.React?.useEffect,
|
|
195
|
+
useRef: typeof window.React?.useRef,
|
|
196
|
+
useMemo: typeof window.React?.useMemo
|
|
197
|
+
});
|
|
198
|
+
}
|
|
185
199
|
const runtimeContext = {
|
|
186
200
|
React: window.React,
|
|
187
201
|
ReactDOM: window.ReactDOM,
|
|
@@ -315,6 +329,94 @@ class ComponentRunner {
|
|
|
315
329
|
}
|
|
316
330
|
// Note: Library components are now handled by the runtime's compiler
|
|
317
331
|
// which loads them into the appropriate context/closure
|
|
332
|
+
// NOW set up enhanced error tracking - AFTER libraries are loaded
|
|
333
|
+
// This avoids false positives from library initialization code (e.g., antd)
|
|
334
|
+
if (!window.__testHarnessErrorTrackingSetup) {
|
|
335
|
+
window.__testHarnessErrorTrackingSetup = true;
|
|
336
|
+
// Initialize error tracking arrays if not already done
|
|
337
|
+
window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
|
|
338
|
+
window.__testHarnessConsoleErrors = window.__testHarnessConsoleErrors || [];
|
|
339
|
+
window.__testHarnessConsoleWarnings = window.__testHarnessConsoleWarnings || [];
|
|
340
|
+
window.__testHarnessTestFailed = window.__testHarnessTestFailed || false;
|
|
341
|
+
window.__testHarnessRenderCount = window.__testHarnessRenderCount || 0;
|
|
342
|
+
// Wrap React.createElement to detect invalid element types
|
|
343
|
+
const originalCreateElement = window.React?.createElement;
|
|
344
|
+
if (originalCreateElement) {
|
|
345
|
+
window.React.createElement = function (type, props, ...children) {
|
|
346
|
+
window.__testHarnessRenderCount++;
|
|
347
|
+
// Enhanced error detection for invalid element types
|
|
348
|
+
if (type !== null && type !== undefined) {
|
|
349
|
+
const typeOf = typeof type;
|
|
350
|
+
// Check for the common "object instead of component" error
|
|
351
|
+
if (typeOf === 'object' && !window.React.isValidElement(type)) {
|
|
352
|
+
// Try to get a meaningful name for the object
|
|
353
|
+
let objectInfo = 'unknown object';
|
|
354
|
+
try {
|
|
355
|
+
if (type.constructor && type.constructor.name) {
|
|
356
|
+
objectInfo = type.constructor.name;
|
|
357
|
+
}
|
|
358
|
+
else if (type.name) {
|
|
359
|
+
objectInfo = type.name;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// Try to show what properties it has
|
|
363
|
+
const keys = Object.keys(type).slice(0, 5);
|
|
364
|
+
if (keys.length > 0) {
|
|
365
|
+
objectInfo = `object with properties: ${keys.join(', ')}`;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch (e) {
|
|
370
|
+
// Ignore errors in trying to get object info
|
|
371
|
+
}
|
|
372
|
+
// Generate helpful error message
|
|
373
|
+
const errorMsg = [
|
|
374
|
+
`Invalid JSX element type: React received an object (${objectInfo}) instead of a React component function.`,
|
|
375
|
+
'',
|
|
376
|
+
'This often occurs when JSX elements or React.createElement receive an object instead of a valid component function.',
|
|
377
|
+
'',
|
|
378
|
+
'Inspect all instances where you are using JSX elements that come from libraries or components to ensure they are properly referenced.',
|
|
379
|
+
'',
|
|
380
|
+
'The exact fix depends on the specific library or component structure.'
|
|
381
|
+
].join('\\n');
|
|
382
|
+
// Log to both console and error tracking
|
|
383
|
+
console.error('š“ Invalid JSX Element Type Detected:', errorMsg);
|
|
384
|
+
// Store the error for later collection
|
|
385
|
+
window.__testHarnessRuntimeErrors.push({
|
|
386
|
+
message: errorMsg,
|
|
387
|
+
type: 'invalid-element-type',
|
|
388
|
+
phase: 'createElement',
|
|
389
|
+
source: 'enhanced-detection',
|
|
390
|
+
elementInfo: objectInfo
|
|
391
|
+
});
|
|
392
|
+
// Still try to call the original to get React's error too
|
|
393
|
+
// This will provide the component stack trace
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else if (type === undefined) {
|
|
397
|
+
// Undefined component - likely a failed destructure or missing import
|
|
398
|
+
const errorMsg = [
|
|
399
|
+
'Invalid JSX element type: component is undefined.',
|
|
400
|
+
'',
|
|
401
|
+
'This occurs when a JSX element references a component that is undefined at runtime.',
|
|
402
|
+
'',
|
|
403
|
+
'Inspect how this component is being accessed - it may not exist in the expected location or may have a different name.',
|
|
404
|
+
'',
|
|
405
|
+
'Check that the component exists in your dependencies or libraries and is properly referenced.'
|
|
406
|
+
].join('\\n');
|
|
407
|
+
console.error('š“ Undefined JSX Component:', errorMsg);
|
|
408
|
+
window.__testHarnessRuntimeErrors.push({
|
|
409
|
+
message: errorMsg,
|
|
410
|
+
type: 'undefined-component',
|
|
411
|
+
phase: 'createElement',
|
|
412
|
+
source: 'enhanced-detection'
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
// Call original createElement
|
|
416
|
+
return originalCreateElement.apply(this, [type, props, ...children]);
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
318
420
|
// Render the component
|
|
319
421
|
const rootElement = document.getElementById('root');
|
|
320
422
|
if (!rootElement) {
|
|
@@ -496,7 +598,15 @@ class ComponentRunner {
|
|
|
496
598
|
renderCount = await page.evaluate(() => window.__testHarnessRenderCount || 0);
|
|
497
599
|
// Collect all errors with source information
|
|
498
600
|
const runtimeErrorsWithSource = await this.collectRuntimeErrors(page);
|
|
499
|
-
|
|
601
|
+
// Filter out JSX element type errors when adding to errors array
|
|
602
|
+
errors.push(...runtimeErrorsWithSource
|
|
603
|
+
.filter(e => {
|
|
604
|
+
// Skip JSX element type errors from error count
|
|
605
|
+
const isJSXError = e.type === 'invalid-element-type' ||
|
|
606
|
+
e.type === 'undefined-component';
|
|
607
|
+
return !isJSXError;
|
|
608
|
+
})
|
|
609
|
+
.map(e => e.message)); // Extract messages for backward compat
|
|
500
610
|
// Collect warnings (separate from errors)
|
|
501
611
|
const collectedWarnings = await this.collectWarnings(page);
|
|
502
612
|
warnings.push(...collectedWarnings);
|
|
@@ -504,12 +614,18 @@ class ComponentRunner {
|
|
|
504
614
|
const asyncWaitTime = options.asyncErrorWaitTime || 1000;
|
|
505
615
|
await page.waitForTimeout(asyncWaitTime);
|
|
506
616
|
const asyncErrors = await this.collectRuntimeErrors(page);
|
|
507
|
-
// Only add new errors
|
|
617
|
+
// Only add new errors (excluding JSX element type errors)
|
|
508
618
|
asyncErrors.forEach(err => {
|
|
509
|
-
|
|
619
|
+
const isJSXError = err.type === 'invalid-element-type' ||
|
|
620
|
+
err.type === 'undefined-component';
|
|
621
|
+
if (!isJSXError && !errors.includes(err.message)) {
|
|
510
622
|
errors.push(err.message);
|
|
511
623
|
runtimeErrorsWithSource.push(err); // Keep the structured version too
|
|
512
624
|
}
|
|
625
|
+
else if (isJSXError && !runtimeErrorsWithSource.some(e => e.message === err.message)) {
|
|
626
|
+
// Still track JSX errors for logging but don't add to errors array
|
|
627
|
+
runtimeErrorsWithSource.push(err);
|
|
628
|
+
}
|
|
513
629
|
});
|
|
514
630
|
// Also check for new warnings
|
|
515
631
|
const asyncWarnings = await this.collectWarnings(page);
|
|
@@ -529,17 +645,29 @@ class ComponentRunner {
|
|
|
529
645
|
}
|
|
530
646
|
// Determine success
|
|
531
647
|
const success = errors.length === 0 &&
|
|
532
|
-
criticalWarnings.length === 0 &&
|
|
533
648
|
!hasRenderLoop &&
|
|
534
649
|
!hasTimeout &&
|
|
535
650
|
executionResult.success;
|
|
536
651
|
// Combine runtime errors with data errors
|
|
537
652
|
const allErrors = [...errors, ...dataErrors];
|
|
538
|
-
// Map runtime errors with source info
|
|
539
|
-
|
|
653
|
+
// Map runtime errors with source info and specific rules
|
|
654
|
+
// Filter out JSX element type errors - they're too noisy and often false positives
|
|
655
|
+
const errorViolations = runtimeErrorsWithSource
|
|
656
|
+
.filter(e => {
|
|
657
|
+
// Skip JSX element type errors - still logged but not reported as violations
|
|
658
|
+
const isJSXError = e.rule === 'invalid-jsx-element' ||
|
|
659
|
+
e.rule === 'undefined-jsx-component' ||
|
|
660
|
+
e.type === 'invalid-element-type' ||
|
|
661
|
+
e.type === 'undefined-component';
|
|
662
|
+
if (isJSXError && debug) {
|
|
663
|
+
console.log('š JSX element error detected but not reported as violation:', e.message);
|
|
664
|
+
}
|
|
665
|
+
return !isJSXError;
|
|
666
|
+
})
|
|
667
|
+
.map(e => ({
|
|
540
668
|
message: e.message,
|
|
541
669
|
severity: 'critical',
|
|
542
|
-
rule: 'runtime-error',
|
|
670
|
+
rule: e.rule || 'runtime-error', // Use specific rule from collectRuntimeErrors
|
|
543
671
|
line: 0,
|
|
544
672
|
column: 0,
|
|
545
673
|
source: e.source
|
|
@@ -577,18 +705,39 @@ class ComponentRunner {
|
|
|
577
705
|
source: 'user-component' // Data errors are from user's data access code
|
|
578
706
|
});
|
|
579
707
|
});
|
|
708
|
+
// Check warnings for critical patterns and move them to errors
|
|
709
|
+
const criticalWarningViolations = [];
|
|
710
|
+
const regularWarnings = [];
|
|
711
|
+
warnings.forEach(w => {
|
|
712
|
+
if (ComponentRunner.CRITICAL_WARNING_PATTERNS.some(pattern => pattern.test(w))) {
|
|
713
|
+
// This is a critical warning - add to errors
|
|
714
|
+
criticalWarningViolations.push({
|
|
715
|
+
message: w,
|
|
716
|
+
severity: 'critical',
|
|
717
|
+
rule: 'critical-react-warning',
|
|
718
|
+
line: 0,
|
|
719
|
+
column: 0,
|
|
720
|
+
source: 'react-framework'
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
// Regular warning
|
|
725
|
+
regularWarnings.push(w);
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
// Combine all error violations
|
|
729
|
+
const allErrorViolations = [...errorViolations, ...criticalWarningViolations];
|
|
580
730
|
const result = {
|
|
581
|
-
success: success && dataErrors.length === 0, // Fail
|
|
731
|
+
success: success && dataErrors.length === 0 && criticalWarningViolations.length === 0, // Fail on critical warnings too
|
|
582
732
|
html,
|
|
583
|
-
errors:
|
|
584
|
-
warnings:
|
|
733
|
+
errors: allErrorViolations,
|
|
734
|
+
warnings: regularWarnings.map(w => ({
|
|
585
735
|
message: w,
|
|
586
736
|
severity: 'low',
|
|
587
737
|
rule: 'warning',
|
|
588
738
|
line: 0,
|
|
589
739
|
column: 0
|
|
590
740
|
})),
|
|
591
|
-
criticalWarnings,
|
|
592
741
|
console: consoleLogs,
|
|
593
742
|
screenshot,
|
|
594
743
|
executionTime: Date.now() - startTime,
|
|
@@ -636,7 +785,6 @@ class ComponentRunner {
|
|
|
636
785
|
line: 0,
|
|
637
786
|
column: 0
|
|
638
787
|
})),
|
|
639
|
-
criticalWarnings,
|
|
640
788
|
console: consoleLogs,
|
|
641
789
|
executionTime: Date.now() - startTime,
|
|
642
790
|
renderCount
|
|
@@ -834,21 +982,93 @@ class ComponentRunner {
|
|
|
834
982
|
}
|
|
835
983
|
/**
|
|
836
984
|
* Set up error tracking in the page
|
|
985
|
+
* @deprecated Moved inline to page.evaluate after library loading to avoid false positives
|
|
837
986
|
*/
|
|
838
|
-
async
|
|
839
|
-
await page.evaluate(() => {
|
|
987
|
+
async setupErrorTracking_DEPRECATED(page, componentSpec, allLibraries) {
|
|
988
|
+
await page.evaluate(({ spec, availableLibraries }) => {
|
|
840
989
|
// Initialize error tracking
|
|
841
990
|
window.__testHarnessRuntimeErrors = [];
|
|
842
991
|
window.__testHarnessConsoleErrors = [];
|
|
843
992
|
window.__testHarnessConsoleWarnings = [];
|
|
844
993
|
window.__testHarnessTestFailed = false;
|
|
845
994
|
window.__testHarnessRenderCount = 0;
|
|
846
|
-
// Track renders
|
|
995
|
+
// Track renders and detect invalid element types
|
|
847
996
|
const originalCreateElement = window.React?.createElement;
|
|
848
997
|
if (originalCreateElement) {
|
|
849
|
-
window.React.createElement = function (...
|
|
998
|
+
window.React.createElement = function (type, props, ...children) {
|
|
850
999
|
window.__testHarnessRenderCount++;
|
|
851
|
-
|
|
1000
|
+
// Enhanced error detection for invalid element types
|
|
1001
|
+
if (type !== null && type !== undefined) {
|
|
1002
|
+
const typeOf = typeof type;
|
|
1003
|
+
// Check for the common "object instead of component" error
|
|
1004
|
+
if (typeOf === 'object' && !window.React.isValidElement(type)) {
|
|
1005
|
+
// Try to get a meaningful name for the object
|
|
1006
|
+
let objectInfo = 'unknown object';
|
|
1007
|
+
try {
|
|
1008
|
+
if (type.constructor && type.constructor.name) {
|
|
1009
|
+
objectInfo = type.constructor.name;
|
|
1010
|
+
}
|
|
1011
|
+
else if (type.name) {
|
|
1012
|
+
objectInfo = type.name;
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
// Try to show what properties it has
|
|
1016
|
+
const keys = Object.keys(type).slice(0, 5);
|
|
1017
|
+
if (keys.length > 0) {
|
|
1018
|
+
objectInfo = `object with properties: ${keys.join(', ')}`;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
catch (e) {
|
|
1023
|
+
// Ignore errors in trying to get object info
|
|
1024
|
+
}
|
|
1025
|
+
// Generate helpful error message
|
|
1026
|
+
const errorMsg = [
|
|
1027
|
+
`Invalid JSX element type: React received an object (${objectInfo}) instead of a React component function.`,
|
|
1028
|
+
'',
|
|
1029
|
+
'This often occurs when JSX elements or React.createElement receive an object instead of a valid component function.',
|
|
1030
|
+
'',
|
|
1031
|
+
'Inspect all instances where you are using JSX elements that come from libraries or components to ensure they are properly referenced.',
|
|
1032
|
+
'',
|
|
1033
|
+
'The exact fix depends on the specific library or component structure.'
|
|
1034
|
+
].join('\\n');
|
|
1035
|
+
// Log to both console and error tracking
|
|
1036
|
+
console.error('š“ Invalid JSX Element Type Detected:', errorMsg);
|
|
1037
|
+
// Store the error for later collection
|
|
1038
|
+
window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
|
|
1039
|
+
window.__testHarnessRuntimeErrors.push({
|
|
1040
|
+
message: errorMsg,
|
|
1041
|
+
type: 'invalid-element-type',
|
|
1042
|
+
phase: 'createElement',
|
|
1043
|
+
source: 'enhanced-detection',
|
|
1044
|
+
elementInfo: objectInfo
|
|
1045
|
+
});
|
|
1046
|
+
// Still try to call the original to get React's error too
|
|
1047
|
+
// This will provide the component stack trace
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
else if (type === undefined) {
|
|
1051
|
+
// Undefined component - likely a failed destructure or missing import
|
|
1052
|
+
const errorMsg = [
|
|
1053
|
+
'Invalid JSX element type: component is undefined.',
|
|
1054
|
+
'',
|
|
1055
|
+
'This occurs when a JSX element references a component that is undefined at runtime.',
|
|
1056
|
+
'',
|
|
1057
|
+
'Inspect how this component is being accessed - it may not exist in the expected location or may have a different name.',
|
|
1058
|
+
'',
|
|
1059
|
+
'Check that the component exists in your dependencies or libraries and is properly referenced.'
|
|
1060
|
+
].join('\\n');
|
|
1061
|
+
console.error('š“ Undefined JSX Component:', errorMsg);
|
|
1062
|
+
window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
|
|
1063
|
+
window.__testHarnessRuntimeErrors.push({
|
|
1064
|
+
message: errorMsg,
|
|
1065
|
+
type: 'undefined-component',
|
|
1066
|
+
phase: 'createElement',
|
|
1067
|
+
source: 'enhanced-detection'
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
// Call original createElement
|
|
1071
|
+
return originalCreateElement.call(this, type, props, ...children);
|
|
852
1072
|
};
|
|
853
1073
|
}
|
|
854
1074
|
// Override console.error
|
|
@@ -877,9 +1097,82 @@ class ComponentRunner {
|
|
|
877
1097
|
}
|
|
878
1098
|
originalConsoleError.apply(console, args);
|
|
879
1099
|
};
|
|
1100
|
+
// Helper function to analyze undefined identifiers
|
|
1101
|
+
const analyzeUndefinedIdentifier = (identifier, spec, availableLibraries) => {
|
|
1102
|
+
const result = {
|
|
1103
|
+
isInSpecLibraries: false,
|
|
1104
|
+
isInSpecDependencies: false,
|
|
1105
|
+
isAvailableLibrary: false,
|
|
1106
|
+
matchedLibrary: null,
|
|
1107
|
+
specLibraries: spec?.libraries || [],
|
|
1108
|
+
specDependencies: spec?.dependencies || []
|
|
1109
|
+
};
|
|
1110
|
+
// Check if it's in spec libraries
|
|
1111
|
+
result.isInSpecLibraries = result.specLibraries.some((lib) => lib.globalVariable === identifier);
|
|
1112
|
+
// Check if it's in spec dependencies
|
|
1113
|
+
result.isInSpecDependencies = result.specDependencies.some((dep) => dep.name === identifier);
|
|
1114
|
+
// Check against ALL available libraries (case-insensitive)
|
|
1115
|
+
if (availableLibraries) {
|
|
1116
|
+
const availableLib = availableLibraries.find((lib) => lib.GlobalVariable &&
|
|
1117
|
+
lib.GlobalVariable.toLowerCase() === identifier.toLowerCase());
|
|
1118
|
+
if (availableLib) {
|
|
1119
|
+
result.isAvailableLibrary = true;
|
|
1120
|
+
result.matchedLibrary = availableLib;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return result;
|
|
1124
|
+
};
|
|
1125
|
+
// Helper function to generate guidance message
|
|
1126
|
+
const generateGuidance = (identifier, analysis) => {
|
|
1127
|
+
// Case 1: Trying to use a library not in their spec
|
|
1128
|
+
if (analysis.isAvailableLibrary && !analysis.isInSpecLibraries) {
|
|
1129
|
+
const libList = analysis.specLibraries.length > 0
|
|
1130
|
+
? analysis.specLibraries.map((l) => l.globalVariable || l.name).filter(Boolean).join(', ')
|
|
1131
|
+
: 'no third-party libraries';
|
|
1132
|
+
return `${identifier} is not defined. It appears you're trying to use the ${analysis.matchedLibrary.Name} library. ` +
|
|
1133
|
+
`You do NOT have access to this library. ` +
|
|
1134
|
+
`Your architect gave you access to: ${libList}. ` +
|
|
1135
|
+
`You must work within these constraints and cannot load additional libraries.`;
|
|
1136
|
+
}
|
|
1137
|
+
// Case 2: Should be a component but not properly accessed
|
|
1138
|
+
if (analysis.isInSpecDependencies) {
|
|
1139
|
+
return `${identifier} is not defined. This component exists in your dependencies. ` +
|
|
1140
|
+
`Ensure you've destructured it: const { ${identifier} } = components; ` +
|
|
1141
|
+
`or accessed it as: components.${identifier}`;
|
|
1142
|
+
}
|
|
1143
|
+
// Case 3: Not a valid library or component
|
|
1144
|
+
const libList = analysis.specLibraries.length > 0
|
|
1145
|
+
? `Available libraries: ${analysis.specLibraries.map((l) => l.globalVariable || l.name).filter(Boolean).join(', ')}`
|
|
1146
|
+
: 'No third-party libraries are available';
|
|
1147
|
+
const depList = analysis.specDependencies.length > 0
|
|
1148
|
+
? `Available components: ${analysis.specDependencies.map((d) => d.name).join(', ')}`
|
|
1149
|
+
: 'No component dependencies are available';
|
|
1150
|
+
return `${identifier} is not defined. This is not a valid library or component in your specification. ` +
|
|
1151
|
+
`${libList}. ${depList}. ` +
|
|
1152
|
+
`You must only use the libraries and components specified in your component specification.`;
|
|
1153
|
+
};
|
|
880
1154
|
// Global error handler
|
|
881
1155
|
window.addEventListener('error', (event) => {
|
|
882
|
-
//
|
|
1156
|
+
// Check for "X is not defined" errors
|
|
1157
|
+
const notDefinedMatch = event.message?.match(/^(\w+) is not defined$/);
|
|
1158
|
+
if (notDefinedMatch) {
|
|
1159
|
+
const identifier = notDefinedMatch[1];
|
|
1160
|
+
// Analyze what this identifier might be
|
|
1161
|
+
const analysis = analyzeUndefinedIdentifier(identifier, spec, availableLibraries);
|
|
1162
|
+
// Generate specific guidance
|
|
1163
|
+
const guidance = generateGuidance(identifier, analysis);
|
|
1164
|
+
// Store enhanced error with specific guidance
|
|
1165
|
+
window.__testHarnessRuntimeErrors.push({
|
|
1166
|
+
message: guidance,
|
|
1167
|
+
stack: event.error?.stack,
|
|
1168
|
+
type: 'undefined-identifier',
|
|
1169
|
+
source: 'user-component',
|
|
1170
|
+
identifier: identifier
|
|
1171
|
+
});
|
|
1172
|
+
window.__testHarnessTestFailed = true;
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
// Handle other errors as before
|
|
883
1176
|
let source = 'user-component'; // Default to user component
|
|
884
1177
|
if (event.message && event.message.includes('Script error')) {
|
|
885
1178
|
source = 'runtime-wrapper';
|
|
@@ -903,7 +1196,7 @@ class ComponentRunner {
|
|
|
903
1196
|
window.__testHarnessTestFailed = true;
|
|
904
1197
|
event.preventDefault();
|
|
905
1198
|
});
|
|
906
|
-
});
|
|
1199
|
+
}, { spec: componentSpec, availableLibraries: allLibraries || [] });
|
|
907
1200
|
}
|
|
908
1201
|
/**
|
|
909
1202
|
* Collect runtime errors from the page
|
|
@@ -916,7 +1209,8 @@ class ComponentRunner {
|
|
|
916
1209
|
testFailed: window.__testHarnessTestFailed || false
|
|
917
1210
|
};
|
|
918
1211
|
});
|
|
919
|
-
|
|
1212
|
+
// Track unique errors and their counts
|
|
1213
|
+
const errorMap = new Map();
|
|
920
1214
|
// Check if we have any specific React render errors
|
|
921
1215
|
const hasSpecificReactError = errorData.runtimeErrors.some((error) => error.type === 'react-render-error' &&
|
|
922
1216
|
!error.message.includes('Script error'));
|
|
@@ -928,19 +1222,84 @@ class ComponentRunner {
|
|
|
928
1222
|
error.message === 'Script error.') {
|
|
929
1223
|
return; // Skip this generic error
|
|
930
1224
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1225
|
+
// Map error types to specific rule names
|
|
1226
|
+
let rule = 'runtime-error';
|
|
1227
|
+
switch (error.type) {
|
|
1228
|
+
case 'invalid-element-type':
|
|
1229
|
+
rule = 'invalid-jsx-element';
|
|
1230
|
+
break;
|
|
1231
|
+
case 'undefined-component':
|
|
1232
|
+
rule = 'undefined-jsx-component';
|
|
1233
|
+
break;
|
|
1234
|
+
case 'undefined-identifier':
|
|
1235
|
+
rule = 'undefined-identifier';
|
|
1236
|
+
break;
|
|
1237
|
+
case 'react-render-error':
|
|
1238
|
+
rule = 'react-render-error';
|
|
1239
|
+
break;
|
|
1240
|
+
case 'render-loop':
|
|
1241
|
+
rule = 'infinite-render-loop';
|
|
1242
|
+
break;
|
|
1243
|
+
case 'registration-error':
|
|
1244
|
+
rule = 'component-registration-error';
|
|
1245
|
+
break;
|
|
1246
|
+
case 'execution-error':
|
|
1247
|
+
rule = 'execution-error';
|
|
1248
|
+
break;
|
|
1249
|
+
case 'runtime':
|
|
1250
|
+
rule = 'runtime-error';
|
|
1251
|
+
break;
|
|
1252
|
+
case 'promise-rejection':
|
|
1253
|
+
rule = 'unhandled-promise-rejection';
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
// Create a key for deduplication based on message and type
|
|
1257
|
+
const key = `${error.type}:${error.message}`;
|
|
1258
|
+
if (errorMap.has(key)) {
|
|
1259
|
+
// Increment count for duplicate
|
|
1260
|
+
errorMap.get(key).count++;
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
// Add new error
|
|
1264
|
+
errorMap.set(key, {
|
|
1265
|
+
error: {
|
|
1266
|
+
message: error.message,
|
|
1267
|
+
source: error.source,
|
|
1268
|
+
type: error.type,
|
|
1269
|
+
rule: rule
|
|
1270
|
+
},
|
|
1271
|
+
count: 1
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
937
1274
|
});
|
|
938
|
-
//
|
|
1275
|
+
// Process console errors
|
|
939
1276
|
errorData.consoleErrors.forEach((error) => {
|
|
940
|
-
const
|
|
1277
|
+
const key = `console-error:${error}`;
|
|
1278
|
+
if (errorMap.has(key)) {
|
|
1279
|
+
errorMap.get(key).count++;
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
errorMap.set(key, {
|
|
1283
|
+
error: {
|
|
1284
|
+
message: error,
|
|
1285
|
+
source: 'react-framework',
|
|
1286
|
+
type: 'console-error',
|
|
1287
|
+
rule: 'console-error'
|
|
1288
|
+
},
|
|
1289
|
+
count: 1
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
// Convert map to array with occurrence counts
|
|
1294
|
+
const errors = [];
|
|
1295
|
+
errorMap.forEach(({ error, count }) => {
|
|
1296
|
+
// Append count if > 1
|
|
1297
|
+
const message = count > 1
|
|
1298
|
+
? `${error.message} (occurred ${count} times)`
|
|
1299
|
+
: error.message;
|
|
941
1300
|
errors.push({
|
|
942
|
-
|
|
943
|
-
|
|
1301
|
+
...error,
|
|
1302
|
+
message
|
|
944
1303
|
});
|
|
945
1304
|
});
|
|
946
1305
|
return errors;
|
|
@@ -965,7 +1324,7 @@ class ComponentRunner {
|
|
|
965
1324
|
/**
|
|
966
1325
|
* Set up console logging
|
|
967
1326
|
*/
|
|
968
|
-
setupConsoleLogging(page, consoleLogs, warnings
|
|
1327
|
+
setupConsoleLogging(page, consoleLogs, warnings) {
|
|
969
1328
|
page.on('console', (msg) => {
|
|
970
1329
|
const type = msg.type();
|
|
971
1330
|
const text = msg.text();
|
|
@@ -976,10 +1335,6 @@ class ComponentRunner {
|
|
|
976
1335
|
if (!warnings.includes(text)) {
|
|
977
1336
|
warnings.push(text);
|
|
978
1337
|
}
|
|
979
|
-
// Check if it's a critical warning that should fail the test
|
|
980
|
-
if (ComponentRunner.CRITICAL_WARNING_PATTERNS.some(pattern => pattern.test(text))) {
|
|
981
|
-
criticalWarnings.push(text);
|
|
982
|
-
}
|
|
983
1338
|
}
|
|
984
1339
|
});
|
|
985
1340
|
page.on('pageerror', (error) => {
|
|
@@ -1440,12 +1795,6 @@ class ComponentRunner {
|
|
|
1440
1795
|
console.log(` ${i + 1}. ${warn.message}`);
|
|
1441
1796
|
});
|
|
1442
1797
|
}
|
|
1443
|
-
if (result.criticalWarnings && result.criticalWarnings.length > 0) {
|
|
1444
|
-
console.log('\nš“ Critical Warnings:', result.criticalWarnings.length);
|
|
1445
|
-
result.criticalWarnings.forEach((warn, i) => {
|
|
1446
|
-
console.log(` ${i + 1}. ${warn}`);
|
|
1447
|
-
});
|
|
1448
|
-
}
|
|
1449
1798
|
console.log('\n========================================\n');
|
|
1450
1799
|
}
|
|
1451
1800
|
}
|