@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.
@@ -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 setupErrorTracking;
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,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,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;IA6sB7F;;;OAGG;YACW,oBAAoB;IA0ElC;;OAEG;YACW,sBAAsB;IAqHpC;;OAEG;YACW,kBAAkB;IAiFhC;;OAEG;YACW,oBAAoB;IA8ClC;;OAEG;YACW,eAAe;IAkB7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;YA+Bb,qBAAqB;cAqBnB,uBAAuB,CAAC,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IA2JtF;;OAEG;YACW,iBAAiB;IAsT/B;;OAEG;IACH,OAAO,CAAC,aAAa;CA6BtB"}
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
- // Set up error tracking
123
- await this.setupErrorTracking(page);
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, criticalWarnings);
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
- errors.push(...runtimeErrorsWithSource.map(e => e.message)); // Extract messages for backward compat
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
- if (!errors.includes(err.message)) {
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, data errors don't have source
539
- const errorViolations = runtimeErrorsWithSource.map(e => ({
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 if we have data errors
731
+ success: success && dataErrors.length === 0 && criticalWarningViolations.length === 0, // Fail on critical warnings too
582
732
  html,
583
- errors: errorViolations,
584
- warnings: warnings.map(w => ({
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 setupErrorTracking(page) {
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 (...args) {
998
+ window.React.createElement = function (type, props, ...children) {
850
999
  window.__testHarnessRenderCount++;
851
- return originalCreateElement.apply(this, args);
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
- // Determine source based on error content
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
- const errors = [];
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
- const phase = error.phase ? ` (during ${error.phase})` : '';
932
- const errorMsg = `${error.type} error: ${error.message}${phase}`;
933
- errors.push({
934
- message: errorMsg,
935
- source: error.source
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
- // Console errors don't have source info
1275
+ // Process console errors
939
1276
  errorData.consoleErrors.forEach((error) => {
940
- const errorMsg = `Console error: ${error}`;
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
- message: errorMsg,
943
- source: 'react-framework' // Console errors from React are framework level
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, criticalWarnings) {
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
  }