@memberjunction/react-test-harness 2.90.0 → 2.91.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.
@@ -1,16 +1,38 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.ComponentRunner = void 0;
4
- const react_runtime_1 = require("@memberjunction/react-runtime");
5
27
  const core_1 = require("@memberjunction/core");
6
28
  const component_linter_1 = require("./component-linter");
29
+ const core_entities_1 = require("@memberjunction/core-entities");
30
+ /**
31
+ * ComponentRunner that uses the actual React runtime via Playwright UMD bundle
32
+ */
7
33
  class ComponentRunner {
8
34
  constructor(browserManager) {
9
35
  this.browserManager = browserManager;
10
- this.compiler = new react_runtime_1.ComponentCompiler();
11
- this.registry = new react_runtime_1.ComponentRegistry();
12
- // Set up runtime context (will be populated in browser)
13
- this.runtimeContext = {};
14
36
  }
15
37
  /**
16
38
  * Lint component code before execution
@@ -30,65 +52,427 @@ class ComponentRunner {
30
52
  const warnings = [];
31
53
  const criticalWarnings = [];
32
54
  const consoleLogs = [];
55
+ const dataErrors = []; // Track data access errors from RunView/RunQuery
33
56
  let renderCount = 0;
34
- // Default debug to true
35
- const debug = options.debug !== false;
57
+ const debug = options.debug !== false; // Default to true for debugging
58
+ const globalTimeout = options.timeout || 30000; // Default 30 seconds total timeout
36
59
  if (debug) {
37
60
  console.log('\nšŸ” === Component Execution Debug Mode ===');
38
61
  console.log('Component:', options.componentSpec.name);
39
62
  console.log('Props:', JSON.stringify(options.props || {}, null, 2));
40
63
  }
64
+ // Get a fresh page for this test execution
65
+ const page = await this.browserManager.getPage();
66
+ // Set default timeout for all page operations (Recommendation #2)
67
+ page.setDefaultTimeout(globalTimeout);
68
+ // Load component metadata and libraries first (needed for library loading)
69
+ await core_entities_1.ComponentMetadataEngine.Instance.Config(false, options.contextUser);
70
+ const allLibraries = core_entities_1.ComponentMetadataEngine.Instance.ComponentLibraries.map(c => c.GetAll());
41
71
  try {
42
- const page = await this.browserManager.getPage();
43
- // Clear the page before each test for isolation
72
+ // Navigate to a blank page FIRST, then load runtime
44
73
  await page.goto('about:blank');
45
- // Expose MJ utilities to the page
46
- await this.exposeMJUtilities(page, options.contextUser);
47
- // Then set up monitoring
74
+ // Set up the basic HTML structure
75
+ await page.setContent(`
76
+ <!DOCTYPE html>
77
+ <html>
78
+ <head>
79
+ <meta charset="utf-8">
80
+ <title>React Component Test (V2)</title>
81
+ <style>
82
+ body {
83
+ margin: 0;
84
+ padding: 20px;
85
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
86
+ }
87
+ #root {
88
+ min-height: 100vh;
89
+ background-color: white;
90
+ padding: 20px;
91
+ }
92
+ </style>
93
+ </head>
94
+ <body>
95
+ <div id="root" data-testid="react-root"></div>
96
+ </body>
97
+ </html>
98
+ `);
99
+ // Always load runtime libraries after setting content
100
+ // This ensures they persist in the current page context
101
+ // allLibraries is loaded above from ComponentMetadataEngine
102
+ await this.loadRuntimeLibraries(page, options.componentSpec, allLibraries, debug);
103
+ // Verify the runtime is actually loaded
104
+ const runtimeCheck = await page.evaluate(() => {
105
+ return {
106
+ hasMJRuntime: typeof window.MJReactRuntime !== 'undefined',
107
+ hasReact: typeof window.React !== 'undefined',
108
+ hasReactDOM: typeof window.ReactDOM !== 'undefined',
109
+ hasBabel: typeof window.Babel !== 'undefined',
110
+ mjRuntimeKeys: window.MJReactRuntime ? Object.keys(window.MJReactRuntime) : []
111
+ };
112
+ });
113
+ if (debug) {
114
+ console.log('Runtime loaded successfully');
115
+ }
116
+ if (!runtimeCheck.hasMJRuntime) {
117
+ throw new Error('Failed to inject MJReactRuntime into page context');
118
+ }
119
+ // Set up error tracking
120
+ await this.setupErrorTracking(page);
121
+ // Set up console logging
48
122
  this.setupConsoleLogging(page, consoleLogs, warnings, criticalWarnings);
49
- this.setupErrorHandling(page, errors);
50
- await this.injectRenderTracking(page);
51
- // Create and load the component
52
- const htmlContent = this.createHTMLTemplate(options);
53
- await page.goto(`data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`);
54
- // Wait for render with timeout detection
55
- const renderSuccess = await this.waitForRender(page, options, errors);
123
+ // Expose MJ utilities to the page
124
+ await this.exposeMJUtilities(page, options.contextUser, dataErrors, debug);
125
+ if (debug) {
126
+ console.log('šŸ“¤ NODE: About to call page.evaluate with:');
127
+ console.log(' - spec.name:', options.componentSpec.name);
128
+ console.log(' - spec.code length:', options.componentSpec.code?.length || 0);
129
+ console.log(' - props:', JSON.stringify(options.props || {}, null, 2));
130
+ console.log(' - componentLibraries count:', allLibraries?.length || 0);
131
+ if (allLibraries && allLibraries.length > 0) {
132
+ console.log(' - First few libraries:', allLibraries.slice(0, 3).map(lib => ({
133
+ Name: lib.Name,
134
+ GlobalVariable: lib.GlobalVariable
135
+ })));
136
+ }
137
+ }
138
+ // Execute the component using the real React runtime with timeout (Recommendation #1)
139
+ const executionPromise = page.evaluate(async ({ spec, props, debug, componentLibraries }) => {
140
+ if (debug) {
141
+ console.log('šŸŽÆ Starting component execution');
142
+ console.log('šŸ“š BROWSER: Received componentLibraries:', componentLibraries?.length || 0);
143
+ if (componentLibraries?.length > 0) {
144
+ console.log(' First library:', componentLibraries[0]);
145
+ }
146
+ }
147
+ // Declare renderCheckInterval at the top scope for cleanup
148
+ let renderCheckInterval;
149
+ try {
150
+ // Access the real runtime classes
151
+ const MJRuntime = window.MJReactRuntime;
152
+ if (!MJRuntime) {
153
+ throw new Error('React runtime not loaded');
154
+ }
155
+ const { ComponentCompiler, ComponentRegistry, ComponentHierarchyRegistrar, SetupStyles } = MJRuntime;
156
+ if (debug) {
157
+ console.log('šŸš€ Starting component execution with real runtime');
158
+ console.log('Available runtime classes:', Object.keys(MJRuntime));
159
+ }
160
+ // Initialize LibraryRegistry if needed
161
+ // Note: In test environment, we may not have full database access
162
+ // so libraries are handled by the runtime internally
163
+ // Build libraries object from loaded libraries
164
+ const loadedLibraries = {};
165
+ if (spec.libraries && componentLibraries) {
166
+ for (const specLib of spec.libraries) {
167
+ // Find the library definition
168
+ const libDef = componentLibraries.find(l => l.Name.toLowerCase() === specLib.name.toLowerCase());
169
+ if (libDef && libDef.GlobalVariable) {
170
+ // Check if the library is available as a global
171
+ const libraryValue = window[libDef.GlobalVariable];
172
+ if (libraryValue) {
173
+ loadedLibraries[libDef.GlobalVariable] = libraryValue;
174
+ if (debug) {
175
+ console.log(`āœ… Added ${libDef.Name} to runtime context as ${libDef.GlobalVariable}`);
176
+ }
177
+ }
178
+ else {
179
+ console.warn(`āš ļø Library ${libDef.Name} not found as window.${libDef.GlobalVariable}`);
180
+ }
181
+ }
182
+ }
183
+ }
184
+ // Create runtime context with loaded libraries
185
+ const runtimeContext = {
186
+ React: window.React,
187
+ ReactDOM: window.ReactDOM,
188
+ libraries: loadedLibraries
189
+ };
190
+ // Create instances
191
+ const compiler = new ComponentCompiler();
192
+ compiler.setBabelInstance(window.Babel);
193
+ // IMPORTANT: Configure the LibraryRegistry in the browser context
194
+ // This is needed for the compiler to know about approved libraries
195
+ if (window.MJReactRuntime && window.MJReactRuntime.LibraryRegistry) {
196
+ const { LibraryRegistry } = window.MJReactRuntime;
197
+ // Configure the registry with the component libraries
198
+ // Note: LibraryRegistry.Config expects ComponentLibraryEntity[]
199
+ await LibraryRegistry.Config(false, componentLibraries || []);
200
+ if (debug) {
201
+ console.log('āœ… Configured LibraryRegistry with', componentLibraries?.length || 0, 'libraries');
202
+ }
203
+ }
204
+ const registry = new ComponentRegistry();
205
+ const registrar = new ComponentHierarchyRegistrar(compiler, registry, runtimeContext);
206
+ // Use the utilities we already created with mock metadata
207
+ // Don't call createRuntimeUtilities() as it tries to create new Metadata() which fails
208
+ const utilities = window.__mjUtilities;
209
+ if (!utilities) {
210
+ throw new Error('Utilities not found - exposeMJUtilities may have failed');
211
+ }
212
+ const styles = SetupStyles();
213
+ if (debug) {
214
+ console.log('šŸ“¦ Registering component hierarchy...');
215
+ }
216
+ // CRITICAL: Ensure spec.libraries have globalVariable set from componentLibraries
217
+ // The spec might not have globalVariable, but we need it for the runtime to work
218
+ if (spec.libraries && componentLibraries) {
219
+ for (const specLib of spec.libraries) {
220
+ if (!specLib.globalVariable) {
221
+ const libDef = componentLibraries.find(l => l.Name.toLowerCase() === specLib.name.toLowerCase());
222
+ if (libDef && libDef.GlobalVariable) {
223
+ specLib.globalVariable = libDef.GlobalVariable;
224
+ if (debug) {
225
+ console.log(` šŸ“ Enhanced spec library ${specLib.name} with globalVariable: ${libDef.GlobalVariable}`);
226
+ }
227
+ }
228
+ }
229
+ }
230
+ if (debug) {
231
+ console.log('šŸ” Spec libraries after enhancement:', spec.libraries.map((l) => ({
232
+ name: l.name,
233
+ globalVariable: l.globalVariable
234
+ })));
235
+ }
236
+ }
237
+ // Register the component hierarchy
238
+ // IMPORTANT: Pass component libraries for library loading to work
239
+ if (debug) {
240
+ console.log('šŸ“š Registering component with', componentLibraries?.length || 0, 'libraries');
241
+ if (componentLibraries?.length > 0) {
242
+ console.log(' Passing libraries to registrar:', componentLibraries.slice(0, 2).map((l) => l.Name));
243
+ }
244
+ }
245
+ const registrationResult = await registrar.registerHierarchy(spec, {
246
+ styles,
247
+ namespace: 'Global',
248
+ version: 'v1', // Use v1 to match the registry defaults
249
+ allLibraries: componentLibraries || [] // Pass the component libraries for LibraryRegistry
250
+ });
251
+ if (debug && !registrationResult.success) {
252
+ console.log('āŒ Registration failed:', registrationResult.errors);
253
+ }
254
+ if (!registrationResult.success) {
255
+ throw new Error('Component registration failed: ' + JSON.stringify(registrationResult.errors));
256
+ }
257
+ if (debug) {
258
+ console.log('āœ… Registered components:', registrationResult.registeredComponents);
259
+ // Note: ComponentRegistry doesn't expose internal components Map directly
260
+ // We can see what was registered through the registrationResult
261
+ }
262
+ // Get the root component - explicitly pass namespace and version
263
+ const RootComponent = registry.get(spec.name, 'Global', 'v1');
264
+ if (!RootComponent) {
265
+ // Enhanced error message with debugging info
266
+ console.error('Failed to find component:', spec.name);
267
+ console.error('Registry keys:', Array.from(registry.components.keys()));
268
+ throw new Error('Root component not found: ' + spec.name);
269
+ }
270
+ // Get all registered components for prop passing
271
+ const components = registry.getAll('Global', 'v1');
272
+ // Add all loaded library exports to the components object
273
+ // This allows generated code to use components.PieChart, components.ResponsiveContainer, etc.
274
+ // for libraries that export components (like Recharts)
275
+ for (const [globalVar, libraryValue] of Object.entries(loadedLibraries)) {
276
+ if (typeof libraryValue === 'object' && libraryValue !== null) {
277
+ // If the library exports an object with multiple components, spread them
278
+ Object.assign(components, libraryValue);
279
+ if (debug) {
280
+ console.log(`āœ… Added ${globalVar} exports to components object`);
281
+ }
282
+ }
283
+ }
284
+ // Render the component
285
+ const rootElement = document.getElementById('root');
286
+ if (!rootElement) {
287
+ throw new Error('Root element not found');
288
+ }
289
+ const root = window.ReactDOM.createRoot(rootElement);
290
+ // Set up render count protection (Recommendation #5)
291
+ const MAX_RENDERS_ALLOWED = 500; // Reasonable limit for complex components
292
+ if (typeof window !== 'undefined') {
293
+ renderCheckInterval = setInterval(() => {
294
+ const currentRenderCount = window.__testHarnessRenderCount || 0;
295
+ if (currentRenderCount > MAX_RENDERS_ALLOWED) {
296
+ clearInterval(renderCheckInterval);
297
+ // Mark test as failed due to excessive renders
298
+ window.__testHarnessTestFailed = true;
299
+ window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
300
+ window.__testHarnessRuntimeErrors.push({
301
+ message: `Excessive re-renders detected: ${currentRenderCount} renders (max: ${MAX_RENDERS_ALLOWED})`,
302
+ type: 'render-loop'
303
+ });
304
+ // Try to unmount to stop the madness
305
+ try {
306
+ root.unmount();
307
+ }
308
+ catch (e) {
309
+ console.error('Failed to unmount after render loop:', e);
310
+ }
311
+ throw new Error(`Excessive re-renders: ${currentRenderCount} renders detected`);
312
+ }
313
+ }, 100); // Check every 100ms
314
+ }
315
+ // Build complete props
316
+ const componentProps = {
317
+ ...props,
318
+ utilities,
319
+ styles,
320
+ components,
321
+ savedUserSettings: {},
322
+ onSaveUserSettings: (settings) => {
323
+ console.log('User settings saved:', settings);
324
+ }
325
+ };
326
+ if (debug) {
327
+ console.log('šŸŽØ Rendering component with props:', Object.keys(componentProps));
328
+ }
329
+ // Create error boundary wrapper
330
+ const ErrorBoundary = class extends window.React.Component {
331
+ constructor(props) {
332
+ super(props);
333
+ this.state = { hasError: false, error: null };
334
+ }
335
+ static getDerivedStateFromError(error) {
336
+ window.__testHarnessTestFailed = true;
337
+ return { hasError: true, error };
338
+ }
339
+ componentDidCatch(error, errorInfo) {
340
+ console.error('React Error Boundary caught:', error, errorInfo);
341
+ window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
342
+ window.__testHarnessRuntimeErrors.push({
343
+ message: error.message,
344
+ stack: error.stack,
345
+ componentStack: errorInfo.componentStack,
346
+ type: 'react-error-boundary'
347
+ });
348
+ window.__testHarnessTestFailed = true;
349
+ }
350
+ render() {
351
+ if (this.state.hasError) {
352
+ // Re-throw to fail hard
353
+ throw this.state.error;
354
+ }
355
+ return this.props.children;
356
+ }
357
+ };
358
+ // Render with error boundary
359
+ root.render(window.React.createElement(ErrorBoundary, null, window.React.createElement(RootComponent, componentProps)));
360
+ // Clear the render check interval since we succeeded
361
+ if (renderCheckInterval) {
362
+ clearInterval(renderCheckInterval);
363
+ }
364
+ if (debug) {
365
+ console.log('āœ… Component rendered successfully');
366
+ }
367
+ return {
368
+ success: true,
369
+ componentCount: registrationResult.registeredComponents.length
370
+ };
371
+ }
372
+ catch (error) {
373
+ // Clean up render check interval if it exists
374
+ if (typeof renderCheckInterval !== 'undefined' && renderCheckInterval) {
375
+ clearInterval(renderCheckInterval);
376
+ }
377
+ console.error('šŸ”“ BROWSER: Component execution failed:', error);
378
+ console.error('šŸ”“ BROWSER: Error stack:', error.stack);
379
+ console.error('šŸ”“ BROWSER: Error type:', typeof error);
380
+ console.error('šŸ”“ BROWSER: Error stringified:', JSON.stringify(error, null, 2));
381
+ window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
382
+ window.__testHarnessRuntimeErrors.push({
383
+ message: error.message || String(error),
384
+ stack: error.stack,
385
+ type: 'execution-error'
386
+ });
387
+ window.__testHarnessTestFailed = true;
388
+ return {
389
+ success: false,
390
+ error: error.message || String(error)
391
+ };
392
+ }
393
+ }, {
394
+ spec: options.componentSpec,
395
+ props: options.props,
396
+ debug,
397
+ componentLibraries: allLibraries || []
398
+ });
399
+ // Create timeout promise (Recommendation #1)
400
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Component execution timeout after ${globalTimeout}ms`)), globalTimeout));
401
+ // Race between execution and timeout
402
+ let executionResult;
403
+ try {
404
+ executionResult = await Promise.race([executionPromise, timeoutPromise]);
405
+ }
406
+ catch (timeoutError) {
407
+ // Handle timeout gracefully
408
+ errors.push(`Component execution timed out after ${globalTimeout}ms`);
409
+ executionResult = {
410
+ success: false,
411
+ error: timeoutError instanceof Error ? timeoutError.message : 'Execution timeout'
412
+ };
413
+ }
414
+ if (debug) {
415
+ console.log('Execution result:', executionResult);
416
+ }
417
+ // Wait for render completion
418
+ const renderWaitTime = options.renderWaitTime || 500;
419
+ await page.waitForTimeout(renderWaitTime);
56
420
  // Get render count
57
- renderCount = await this.getRenderCount(page);
58
- // Collect runtime errors
421
+ renderCount = await page.evaluate(() => window.__testHarnessRenderCount || 0);
422
+ // Collect all errors
59
423
  const runtimeErrors = await this.collectRuntimeErrors(page);
60
424
  errors.push(...runtimeErrors);
61
- // Capture async errors only if wait time is specified
62
- if (options.asyncErrorWaitTime && options.asyncErrorWaitTime > 0) {
63
- const asyncErrors = await this.captureAsyncErrors(page, options.asyncErrorWaitTime);
64
- errors.push(...asyncErrors);
65
- }
66
- // Perform deep render validation
67
- const deepRenderErrors = await this.validateDeepRender(page);
68
- errors.push(...deepRenderErrors);
425
+ // Collect warnings (separate from errors)
426
+ const collectedWarnings = await this.collectWarnings(page);
427
+ warnings.push(...collectedWarnings);
428
+ // Capture async errors
429
+ const asyncWaitTime = options.asyncErrorWaitTime || 1000;
430
+ await page.waitForTimeout(asyncWaitTime);
431
+ const asyncErrors = await this.collectRuntimeErrors(page);
432
+ // Only add new errors
433
+ asyncErrors.forEach(err => {
434
+ if (!errors.includes(err)) {
435
+ errors.push(err);
436
+ }
437
+ });
438
+ // Also check for new warnings
439
+ const asyncWarnings = await this.collectWarnings(page);
440
+ asyncWarnings.forEach(warn => {
441
+ if (!warnings.includes(warn)) {
442
+ warnings.push(warn);
443
+ }
444
+ });
69
445
  // Get the rendered HTML
70
446
  const html = await page.content();
71
- // Take screenshot if needed
447
+ // Take screenshot
72
448
  const screenshot = await page.screenshot();
73
- // Determine success and collect any additional errors
74
- const { success, additionalErrors } = this.determineSuccess(errors, criticalWarnings, renderCount, !renderSuccess);
75
- // Add any additional errors
76
- errors.push(...additionalErrors);
449
+ // Determine success
450
+ const success = errors.length === 0 &&
451
+ criticalWarnings.length === 0 &&
452
+ renderCount <= ComponentRunner.MAX_RENDER_COUNT &&
453
+ executionResult.success;
454
+ if (renderCount > ComponentRunner.MAX_RENDER_COUNT) {
455
+ errors.push(`Excessive render count: ${renderCount} renders detected`);
456
+ }
457
+ // Combine runtime errors with data errors
458
+ const allErrors = [...errors, ...dataErrors];
77
459
  const result = {
78
- success,
460
+ success: success && dataErrors.length === 0, // Fail if we have data errors
79
461
  html,
80
- errors: errors.map(e => {
81
- return {
82
- message: e,
83
- severity: 'critical'
84
- }; // Ensure Violation type
85
- }),
86
- warnings: warnings.map(w => {
87
- return {
88
- message: w,
89
- severity: 'low'
90
- }; // Ensure Violation type
91
- }),
462
+ errors: allErrors.map(e => ({
463
+ message: e,
464
+ severity: 'critical',
465
+ rule: 'runtime-error',
466
+ line: 0,
467
+ column: 0
468
+ })),
469
+ warnings: warnings.map(w => ({
470
+ message: w,
471
+ severity: 'low',
472
+ rule: 'warning',
473
+ line: 0,
474
+ column: 0
475
+ })),
92
476
  criticalWarnings,
93
477
  console: consoleLogs,
94
478
  screenshot,
@@ -102,21 +486,25 @@ class ComponentRunner {
102
486
  }
103
487
  catch (error) {
104
488
  errors.push(error instanceof Error ? error.message : String(error));
489
+ // Combine runtime errors with data errors
490
+ const allErrors = [...errors, ...dataErrors];
105
491
  const result = {
106
492
  success: false,
107
493
  html: '',
108
- errors: errors.map(e => {
109
- return {
110
- message: e,
111
- severity: 'critical'
112
- }; // Ensure Violation type
113
- }),
114
- warnings: warnings.map(w => {
115
- return {
116
- message: w,
117
- severity: 'low'
118
- }; // Ensure Violation type
119
- }),
494
+ errors: allErrors.map(e => ({
495
+ message: e,
496
+ severity: 'critical',
497
+ rule: 'runtime-error',
498
+ line: 0,
499
+ column: 0
500
+ })),
501
+ warnings: warnings.map(w => ({
502
+ message: w,
503
+ severity: 'low',
504
+ rule: 'warning',
505
+ line: 0,
506
+ column: 0
507
+ })),
120
508
  criticalWarnings,
121
509
  console: consoleLogs,
122
510
  executionTime: Date.now() - startTime,
@@ -128,1011 +516,554 @@ class ComponentRunner {
128
516
  }
129
517
  return result;
130
518
  }
519
+ finally {
520
+ // Clean up: close the page after each test execution
521
+ // This is important because getPage() now creates a new page each time
522
+ // Closing the page ensures clean isolation between test runs
523
+ try {
524
+ await page.close();
525
+ }
526
+ catch (closeError) {
527
+ // Ignore errors when closing the page
528
+ if (debug) {
529
+ console.log('Note: Error closing page (this is usually harmless):', closeError);
530
+ }
531
+ }
532
+ }
131
533
  }
132
534
  /**
133
- * Dumps debug information to console for easier troubleshooting
535
+ * Load runtime libraries into the page
134
536
  */
135
- dumpDebugInfo(result) {
136
- console.log('\nšŸ“Š === Component Execution Results ===');
137
- console.log('Success:', result.success ? 'āœ…' : 'āŒ');
138
- console.log('Execution time:', result.executionTime + 'ms');
139
- console.log('Render count:', result.renderCount);
140
- if (result.console && result.console.length > 0) {
141
- console.log('\nšŸ“ Console Output:');
142
- result.console.forEach(log => {
143
- const icon = log.type === 'error' ? 'āŒ' :
144
- log.type === 'warning' ? 'āš ļø' :
145
- log.type === 'log' ? 'šŸ“' : 'šŸ’¬';
146
- console.log(` ${icon} [${log.type}] ${log.text}`);
147
- });
148
- }
149
- if (result.errors && result.errors.length > 0) {
150
- console.log('\nāŒ Errors:', result.errors.length);
151
- result.errors.forEach((err, i) => {
152
- const message = typeof err === 'string' ? err : err.message;
153
- console.log(` ${i + 1}. ${message}`);
154
- });
537
+ async loadRuntimeLibraries(page, componentSpec, allLibraries, debug = false) {
538
+ // Helper function to load scripts with timeout (Recommendation #3)
539
+ const loadScriptWithTimeout = async (url, timeout = 10000) => {
540
+ try {
541
+ await Promise.race([
542
+ page.addScriptTag({ url }),
543
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Script load timeout: ${url}`)), timeout))
544
+ ]);
545
+ }
546
+ catch (error) {
547
+ throw new Error(`Failed to load script ${url}: ${error instanceof Error ? error.message : String(error)}`);
548
+ }
549
+ };
550
+ // Load React and ReactDOM with timeout protection
551
+ await loadScriptWithTimeout('https://unpkg.com/react@18/umd/react.development.js');
552
+ await loadScriptWithTimeout('https://unpkg.com/react-dom@18/umd/react-dom.development.js');
553
+ // Load Babel for JSX transformation with timeout
554
+ await loadScriptWithTimeout('https://unpkg.com/@babel/standalone/babel.min.js');
555
+ // Load the real MemberJunction React Runtime UMD bundle
556
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
557
+ // Resolve the path to the UMD bundle
558
+ const runtimePath = require.resolve('@memberjunction/react-runtime/dist/runtime.umd.js');
559
+ const runtimeBundle = fs.readFileSync(runtimePath, 'utf-8');
560
+ // Inject the UMD bundle into the page
561
+ await page.addScriptTag({ content: runtimeBundle });
562
+ // The UMD bundle should have created window.MJReactRuntime
563
+ // Let's verify and potentially add any test-harness specific overrides
564
+ await page.evaluate(() => {
565
+ // Check if MJReactRuntime was loaded from the UMD bundle
566
+ if (typeof window.MJReactRuntime === 'undefined') {
567
+ throw new Error('MJReactRuntime UMD bundle did not load correctly');
568
+ }
569
+ // The real runtime is now available!
570
+ });
571
+ // Verify everything loaded
572
+ const loaded = await page.evaluate(() => {
573
+ return {
574
+ React: typeof window.React !== 'undefined',
575
+ ReactDOM: typeof window.ReactDOM !== 'undefined',
576
+ Babel: typeof window.Babel !== 'undefined',
577
+ MJRuntime: typeof window.MJReactRuntime !== 'undefined'
578
+ };
579
+ });
580
+ // All libraries loaded successfully
581
+ if (!loaded.React || !loaded.ReactDOM || !loaded.Babel || !loaded.MJRuntime) {
582
+ throw new Error('Failed to load required libraries');
155
583
  }
156
- if (result.warnings && result.warnings.length > 0) {
157
- console.log('\nāš ļø Warnings:', result.warnings.length);
158
- result.warnings.forEach((warn, i) => {
159
- const message = typeof warn === 'string' ? warn : warn.message;
160
- console.log(` ${i + 1}. ${message}`);
161
- });
584
+ // Load component-specific libraries from CDN
585
+ if (componentSpec?.libraries && allLibraries) {
586
+ await this.loadComponentLibraries(page, componentSpec.libraries, allLibraries, debug);
162
587
  }
163
- if (result.criticalWarnings && result.criticalWarnings.length > 0) {
164
- console.log('\nšŸ”“ Critical Warnings:', result.criticalWarnings.length);
165
- result.criticalWarnings.forEach((warn, i) => {
166
- console.log(` ${i + 1}. ${warn}`);
588
+ }
589
+ /**
590
+ * Load component-specific libraries from CDN
591
+ */
592
+ async loadComponentLibraries(page, specLibraries, allLibraries, debug = false) {
593
+ if (debug) {
594
+ console.log('šŸ“š Loading component libraries:', {
595
+ count: specLibraries.length,
596
+ libraries: specLibraries.map(l => l.name)
167
597
  });
168
598
  }
169
- if (result.html) {
170
- const htmlPreview = result.html.substring(0, 200);
171
- console.log('\nšŸ“„ HTML Preview:', htmlPreview + (result.html.length > 200 ? '...' : ''));
599
+ // Create a map of library definitions from allLibraries
600
+ const libraryMap = new Map();
601
+ for (const lib of allLibraries) {
602
+ libraryMap.set(lib.Name.toLowerCase(), lib);
172
603
  }
173
- console.log('\n========================================\n');
174
- }
175
- createHTMLTemplate(options) {
176
- const propsJson = JSON.stringify(options.props || {});
177
- const specJson = JSON.stringify(options.componentSpec);
178
- // Set configuration if provided
179
- if (options.libraryConfiguration) {
180
- react_runtime_1.StandardLibraryManager.setConfiguration(options.libraryConfiguration);
181
- }
182
- // Get all enabled libraries from configuration
183
- const enabledLibraries = react_runtime_1.StandardLibraryManager.getEnabledLibraries();
184
- // Separate runtime and component libraries
185
- const runtimeLibraries = enabledLibraries.filter((lib) => lib.category === 'runtime');
186
- const componentLibraries = enabledLibraries.filter((lib) => lib.category !== 'runtime');
187
- // Generate script tags for runtime libraries
188
- const runtimeScripts = runtimeLibraries
189
- .map((lib) => ` <script crossorigin src="${lib.cdnUrl}"></script>`)
190
- .join('\n');
191
- // Generate script tags for component libraries
192
- const componentScripts = componentLibraries
193
- .map((lib) => ` <script src="${lib.cdnUrl}"></script>`)
194
- .join('\n');
195
- // Generate CSS links
196
- const cssLinks = enabledLibraries
197
- .filter((lib) => lib.cdnCssUrl)
198
- .map((lib) => ` <link rel="stylesheet" href="${lib.cdnCssUrl}">`)
199
- .join('\n');
200
- // Include the ComponentCompiler class definition
201
- const componentCompilerCode = this.getComponentCompilerCode();
202
- return `
203
- <!DOCTYPE html>
204
- <html>
205
- <head>
206
- <meta charset="utf-8">
207
- <title>React Component Test</title>
208
- ${runtimeScripts}
209
- ${componentScripts}
210
- ${cssLinks}
211
- <style>
212
- body {
213
- margin: 0;
214
- padding: 20px;
215
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
216
- background-color: #f0f0f0; /* Light gray background to see contrast */
217
- }
218
- #root {
219
- min-height: 100vh;
220
- background-color: white;
221
- border: 2px solid #007bff;
222
- padding: 20px;
223
- box-shadow: 0 0 10px rgba(0,0,0,0.1);
224
- }
225
- .test-harness-header {
226
- background-color: #28a745;
227
- color: white;
228
- padding: 10px;
229
- margin-bottom: 20px;
230
- border-radius: 5px;
231
- font-weight: bold;
232
- text-align: center;
233
- }
234
- </style>
235
- <script>
236
- // Initialize error and console tracking
237
- window.__testHarnessRuntimeErrors = [];
238
- window.__testHarnessConsoleLogs = [];
239
-
240
- // Track console output for async error detection
241
- const originalConsoleError = console.error;
242
- console.error = function(...args) {
243
- window.__testHarnessConsoleLogs.push({
244
- type: 'error',
245
- text: args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ')
246
- });
247
- originalConsoleError.apply(console, args);
248
- };
249
-
250
- // Global error handler
251
- window.addEventListener('error', (event) => {
252
- console.error('Runtime error:', event.error);
253
- window.__testHarnessRuntimeErrors.push({
254
- message: event.error.message,
255
- stack: event.error.stack,
256
- type: 'runtime'
257
- });
258
- });
259
-
260
- // Unhandled promise rejection handler
261
- window.addEventListener('unhandledrejection', (event) => {
262
- console.error('Unhandled promise rejection:', event.reason);
263
- window.__testHarnessRuntimeErrors.push({
264
- message: 'Unhandled Promise Rejection: ' + (event.reason?.message || event.reason || 'Unknown reason'),
265
- stack: event.reason?.stack || 'No stack trace available',
266
- type: 'promise-rejection'
267
- });
268
- // Prevent the default handling (which would log to console)
269
- event.preventDefault();
270
- });
271
-
272
- // Render tracking injection
273
- (function() {
274
- let renderCounter = 0;
275
- window.__testHarnessRenderCount = 0;
276
-
277
- // Wait for React to be available
278
- const setupRenderTracking = () => {
279
- if (window.React && window.React.createElement) {
280
- const originalCreateElement = window.React.createElement.bind(window.React);
281
-
282
- window.React.createElement = function(type, props, ...children) {
283
- renderCounter++;
284
- window.__testHarnessRenderCount = renderCounter;
285
-
286
- if (renderCounter > 1000) {
287
- console.error('Excessive renders detected: ' + renderCounter + ' renders. Possible infinite loop.');
604
+ // Load each library the component needs
605
+ for (const specLib of specLibraries) {
606
+ const libDef = libraryMap.get(specLib.name.toLowerCase());
607
+ if (!libDef) {
608
+ console.warn(`āš ļø Library ${specLib.name} not found in metadata`);
609
+ continue;
288
610
  }
289
-
290
- return originalCreateElement(type, props, ...children);
291
- };
292
- }
293
- };
294
-
295
- // Try to set up immediately
296
- setupRenderTracking();
297
-
298
- // Also try after a delay in case React loads later
299
- setTimeout(setupRenderTracking, 100);
300
- })();
301
- </script>
302
- </head>
303
- <body>
304
- <div class="test-harness-header">
305
- 🧪 React Test Harness - Component Loaded Successfully
306
- </div>
307
- <div id="root" data-testid="react-root">
308
- <!-- Component will render here -->
309
- </div>
310
- <script type="text/babel">
311
- // Immediate debug message
312
- console.log('šŸš€ Test harness script started executing');
313
- document.getElementById('root').innerHTML = '<div style="background: lime; padding: 20px; color: black; font-size: 18px;">šŸ“ Script is running...</div>';
314
-
315
- ${options.setupCode || ''}
316
-
317
- // Create runtime context with dynamic libraries
318
- const componentLibraries = ${JSON.stringify(componentLibraries.map((lib) => ({
319
- globalVariable: lib.globalVariable,
320
- displayName: lib.displayName
321
- })))};
322
-
323
- const libraries = {};
324
- componentLibraries.forEach(lib => {
325
- if (window[lib.globalVariable]) {
326
- libraries[lib.globalVariable] = window[lib.globalVariable];
327
- }
328
- });
329
-
330
- const runtimeContext = {
331
- React: React,
332
- ReactDOM: ReactDOM,
333
- libraries: libraries
334
- };
335
-
336
- // Import the ComponentCompiler implementation
337
- ${componentCompilerCode}
338
-
339
- // Create component compiler instance
340
- const compiler = new ComponentCompiler();
341
- compiler.setBabelInstance(Babel);
342
-
343
- // Create component registry
344
- class SimpleRegistry {
345
- constructor() {
346
- this.components = new Map();
347
- }
348
-
349
- register(name, component, namespace = 'Global', version = 'v1') {
350
- const key = \`\${namespace}/\${version}/\${name}\`;
351
- this.components.set(key, component);
352
- }
353
-
354
- get(name, namespace = 'Global', version = 'v1') {
355
- const key = \`\${namespace}/\${version}/\${name}\`;
356
- return this.components.get(key);
357
- }
358
-
359
- getAll(namespace = 'Global', version = 'v1') {
360
- const components = {};
361
- const prefix = \`\${namespace}/\${version}/\`;
362
- this.components.forEach((component, key) => {
363
- if (key.startsWith(prefix)) {
364
- const name = key.substring(prefix.length);
365
- components[name] = component;
366
- }
367
- });
368
- return components;
369
- }
370
- }
371
-
372
- // Create registry instance
373
- const registry = new SimpleRegistry();
374
-
375
- // Create hierarchy registrar
376
- class SimpleHierarchyRegistrar {
377
- constructor(compiler, registry, runtimeContext) {
378
- this.compiler = compiler;
379
- this.registry = registry;
380
- this.runtimeContext = runtimeContext;
381
- }
382
-
383
- async registerHierarchy(rootSpec, options = {}) {
384
- const registeredComponents = [];
385
- const errors = [];
386
- const warnings = [];
387
-
388
- // Register components recursively
389
- const registerSpec = async (spec) => {
390
- // Register children first
391
- const children = spec.dependencies || [];
392
- for (const child of children) {
393
- await registerSpec(child);
394
- }
395
-
396
- // Register this component
397
- if (spec.code) {
398
- const result = await this.compiler.compile({
399
- componentName: spec.name,
400
- componentCode: spec.code
401
- });
402
-
403
- if (result.success) {
404
- const factory = result.component.component(this.runtimeContext, {});
405
- this.registry.register(spec.name, factory.component);
406
- registeredComponents.push(spec.name);
407
- } else {
408
- errors.push({
409
- componentName: spec.name,
410
- error: result.error.message,
411
- phase: 'compilation'
412
- });
611
+ if (debug) {
612
+ console.log(`šŸ“¦ Loading ${specLib.name}:`, {
613
+ cdnUrl: libDef.CDNUrl,
614
+ globalVariable: libDef.GlobalVariable
615
+ });
616
+ }
617
+ // Load CSS if available
618
+ if (libDef.CDNCssUrl) {
619
+ const cssUrls = libDef.CDNCssUrl.split(',').map(url => url.trim());
620
+ for (const cssUrl of cssUrls) {
621
+ if (cssUrl) {
622
+ await page.addStyleTag({ url: cssUrl });
623
+ if (debug) {
624
+ console.log(` āœ… Loaded CSS: ${cssUrl}`);
625
+ }
626
+ }
627
+ }
628
+ }
629
+ // Load the library script with timeout protection
630
+ if (libDef.CDNUrl) {
631
+ try {
632
+ // Add timeout for library loading (Recommendation #3)
633
+ await Promise.race([
634
+ page.addScriptTag({ url: libDef.CDNUrl }),
635
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Library load timeout: ${libDef.CDNUrl}`)), 10000))
636
+ ]);
637
+ // Verify the library loaded
638
+ const isLoaded = await page.evaluate((globalVar) => {
639
+ return typeof window[globalVar] !== 'undefined';
640
+ }, libDef.GlobalVariable);
641
+ if (isLoaded) {
642
+ if (debug) {
643
+ console.log(` āœ… Loaded ${specLib.name} as window.${libDef.GlobalVariable}`);
644
+ }
645
+ }
646
+ else {
647
+ console.error(` āŒ Failed to load ${specLib.name} - global variable ${libDef.GlobalVariable} not found`);
648
+ }
649
+ }
650
+ catch (error) {
651
+ console.error(` āŒ Error loading ${specLib.name} from ${libDef.CDNUrl}:`, error);
652
+ }
413
653
  }
414
- }
415
- };
416
-
417
- await registerSpec(rootSpec);
418
-
419
- return {
420
- success: errors.length === 0,
421
- registeredComponents,
422
- errors,
423
- warnings
424
- };
425
- }
426
- }
427
-
428
- const hierarchyRegistrar = new SimpleHierarchyRegistrar(compiler, registry, runtimeContext);
429
-
430
- // BuildUtilities function - uses real MJ utilities exposed via Playwright
431
- const BuildUtilities = () => {
432
- const utilities = {
433
- md: {
434
- entities: () => {
435
- return window.__mjGetEntities();
436
- },
437
- GetEntityObject: async (entityName) => {
438
- return await window.__mjGetEntityObject(entityName);
439
- }
440
- },
441
- rv: {
442
- RunView: async (params) => {
443
- return await window.__mjRunView(params);
444
- },
445
- RunViews: async (params) => {
446
- return await window.__mjRunViews(params);
447
- }
448
- },
449
- rq: {
450
- RunQuery: async (params) => {
451
- return await window.__mjRunQuery(params);
452
- }
453
- }
454
- };
455
- return utilities;
456
- };
457
-
458
- // SetupStyles function - copied from skip-chat implementation
459
- const SetupStyles = () => ({
460
- colors: {
461
- primary: '#5B4FE9',
462
- primaryHover: '#4940D4',
463
- primaryLight: '#E8E6FF',
464
- secondary: '#64748B',
465
- secondaryHover: '#475569',
466
- success: '#10B981',
467
- successLight: '#D1FAE5',
468
- warning: '#F59E0B',
469
- warningLight: '#FEF3C7',
470
- error: '#EF4444',
471
- errorLight: '#FEE2E2',
472
- info: '#3B82F6',
473
- infoLight: '#DBEAFE',
474
- background: '#FFFFFF',
475
- surface: '#F8FAFC',
476
- surfaceHover: '#F1F5F9',
477
- text: '#1E293B',
478
- textSecondary: '#64748B',
479
- textTertiary: '#94A3B8',
480
- textInverse: '#FFFFFF',
481
- border: '#E2E8F0',
482
- borderLight: '#F1F5F9',
483
- borderFocus: '#5B4FE9',
484
- shadow: 'rgba(0, 0, 0, 0.05)',
485
- shadowMedium: 'rgba(0, 0, 0, 0.1)',
486
- shadowLarge: 'rgba(0, 0, 0, 0.15)',
487
- },
488
- spacing: {
489
- xs: '4px',
490
- sm: '8px',
491
- md: '16px',
492
- lg: '24px',
493
- xl: '32px',
494
- xxl: '48px',
495
- xxxl: '64px',
496
- },
497
- typography: {
498
- fontFamily: '-apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif',
499
- fontSize: {
500
- xs: '11px',
501
- sm: '12px',
502
- md: '14px',
503
- lg: '16px',
504
- xl: '20px',
505
- xxl: '24px',
506
- xxxl: '32px',
507
- },
508
- fontWeight: {
509
- light: '300',
510
- regular: '400',
511
- medium: '500',
512
- semibold: '600',
513
- bold: '700',
514
- },
515
- lineHeight: {
516
- tight: '1.25',
517
- normal: '1.5',
518
- relaxed: '1.75',
519
- },
520
- },
521
- borders: {
522
- radius: {
523
- sm: '6px',
524
- md: '8px',
525
- lg: '12px',
526
- xl: '16px',
527
- full: '9999px',
528
- },
529
- width: {
530
- thin: '1px',
531
- medium: '2px',
532
- thick: '3px',
533
- },
534
- },
535
- shadows: {
536
- sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
537
- md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
538
- lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
539
- xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
540
- inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
541
- },
542
- transitions: {
543
- fast: '150ms ease-in-out',
544
- normal: '250ms ease-in-out',
545
- slow: '350ms ease-in-out',
546
- },
547
- overflow: 'auto'
548
- });
549
-
550
- // React Error Boundary component
551
- const ErrorBoundary = class extends React.Component {
552
- constructor(props) {
553
- super(props);
554
- this.state = { hasError: false, error: null };
555
- }
556
-
557
- static getDerivedStateFromError(error) {
558
- return { hasError: true, error };
559
- }
560
-
561
- componentDidCatch(error, errorInfo) {
562
- console.error('React Error Boundary caught:', error, errorInfo);
563
- window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
564
- window.__testHarnessRuntimeErrors.push({
565
- message: error.message,
566
- stack: error.stack,
567
- componentStack: errorInfo.componentStack,
568
- type: 'react'
569
- });
570
- }
571
-
572
- render() {
573
- if (this.state.hasError) {
574
- return React.createElement('div', { style: { color: 'red', padding: '20px' } },
575
- 'Component Error: ' + this.state.error.message
576
- );
577
- }
578
- return this.props.children;
579
- }
580
- };
581
-
582
- // Load component spec and register hierarchy
583
- const componentSpec = ${specJson};
584
- const props = ${propsJson};
585
-
586
- (async () => {
587
- console.log('šŸ“¦ Starting component initialization...');
588
- console.log('React available:', typeof React !== 'undefined');
589
- console.log('ReactDOM available:', typeof ReactDOM !== 'undefined');
590
- console.log('Babel available:', typeof Babel !== 'undefined');
591
-
592
- // Update the root to show progress
593
- document.getElementById('root').innerHTML = '<div style="background: cyan; padding: 20px; color: black;">šŸ”„ Initializing components...</div>';
594
-
595
- // First, test that React is working with a simple component
596
- const TestComponent = () => React.createElement('div', {
597
- style: {
598
- background: 'yellow',
599
- padding: '10px',
600
- margin: '10px 0',
601
- border: '2px solid orange',
602
- color: 'black',
603
- fontSize: '16px',
604
- fontWeight: 'bold'
605
654
  }
606
- }, 'šŸ”§ React is working! Now loading your component...');
607
-
608
- const testRoot = ReactDOM.createRoot(document.getElementById('root'));
609
- testRoot.render(React.createElement(TestComponent));
610
-
611
- // Wait a moment to show the test component
612
- await new Promise(resolve => setTimeout(resolve, 500));
613
-
614
- // Now proceed with the actual component
615
- // Register the component hierarchy
616
- const result = await hierarchyRegistrar.registerHierarchy(componentSpec);
617
-
618
- if (!result.success) {
619
- console.error('Failed to register components:', result.errors);
620
- document.getElementById('root').innerHTML = '<div style="color: red; padding: 20px;">Failed to register components: ' + JSON.stringify(result.errors) + '</div>';
621
- return;
622
- }
623
-
624
- // Get all registered components
625
- const components = registry.getAll();
626
-
627
- // Get the root component
628
- const RootComponent = registry.get(componentSpec.name);
629
-
630
- if (!RootComponent) {
631
- console.error('Root component not found:', componentSpec.name);
632
- return;
633
- }
634
-
635
- // Simple in-memory storage for user settings
636
- let savedUserSettings = {};
637
-
638
- // Create root for rendering
639
- const root = ReactDOM.createRoot(document.getElementById('root'));
640
-
641
- // Function to render with current settings
642
- const renderWithSettings = () => {
643
- console.log('šŸŽÆ Starting component render...');
644
- console.log('Props:', props);
645
- console.log('Root component found:', !!RootComponent);
646
-
647
- const enhancedProps = {
648
- ...props,
649
- components: components,
650
- utilities: BuildUtilities(),
651
- styles: SetupStyles(),
652
- savedUserSettings: savedUserSettings,
653
- onSaveUserSettings: (newSettings) => {
654
- console.log('User settings saved:', newSettings);
655
- // Update in-memory storage
656
- savedUserSettings = { ...newSettings };
657
- // Re-render with new settings
658
- renderWithSettings();
659
- }
660
- };
661
-
662
- console.log('Enhanced props created:', Object.keys(enhancedProps));
663
-
664
- try {
665
- root.render(
666
- React.createElement(ErrorBoundary, null,
667
- React.createElement(RootComponent, enhancedProps)
668
- )
669
- );
670
- console.log('āœ… Component rendered successfully');
671
- } catch (error) {
672
- console.error('āŒ Render error:', error);
673
- document.getElementById('root').innerHTML = '<div style="color: red; padding: 20px;">Render Error: ' + error.message + '</div>';
655
+ if (debug) {
656
+ // Log all available global variables that look like libraries
657
+ // Get all the global variables we expect from the spec
658
+ const expectedGlobals = specLibraries.map(lib => {
659
+ const libDef = libraryMap.get(lib.name.toLowerCase());
660
+ return libDef?.GlobalVariable;
661
+ }).filter(Boolean);
662
+ const globals = await page.evaluate((expected) => {
663
+ const relevantGlobals = {};
664
+ // Check the expected globals from the spec
665
+ for (const key of expected) {
666
+ if (window[key]) {
667
+ relevantGlobals[key] = typeof window[key];
668
+ }
669
+ else {
670
+ relevantGlobals[key] = 'NOT FOUND';
671
+ }
672
+ }
673
+ // Also check some common library globals
674
+ const commonKeys = ['Recharts', 'chroma', '_', 'moment', 'dayjs', 'Chart', 'GSAP', 'gsap', 'lottie'];
675
+ for (const key of commonKeys) {
676
+ if (!(key in relevantGlobals) && window[key]) {
677
+ relevantGlobals[key] = typeof window[key];
678
+ }
679
+ }
680
+ return relevantGlobals;
681
+ }, expectedGlobals);
682
+ console.log('šŸŒ Available library globals:', globals);
674
683
  }
675
- };
676
-
677
- // Initial render
678
- renderWithSettings();
679
-
680
- // Add a fallback message if nothing renders after a delay
681
- setTimeout(() => {
682
- const rootElement = document.getElementById('root');
683
- if (rootElement) {
684
- const hasContent = rootElement.innerHTML.trim().length > 0;
685
- const hasVisibleChildren = rootElement.querySelector('*');
686
-
687
- console.log('Root element check:', {
688
- hasContent,
689
- innerHTML: rootElement.innerHTML.substring(0, 100),
690
- hasVisibleChildren: !!hasVisibleChildren,
691
- childCount: rootElement.childNodes.length
692
- });
693
-
694
- if (!hasContent || !hasVisibleChildren) {
695
- rootElement.innerHTML = '<div style="color: red; font-size: 18px; padding: 20px; border: 2px dashed red; background: #ffe6e6;">āš ļø Component did not render any visible content</div>';
696
- } else {
697
- // Force visibility on all children as a test
698
- const allElements = rootElement.querySelectorAll('*');
699
- allElements.forEach(el => {
700
- if (el instanceof HTMLElement) {
701
- // Make sure elements are visible
702
- if (window.getComputedStyle(el).display === 'none') {
703
- el.style.display = 'block !important';
684
+ }
685
+ /**
686
+ * Set up error tracking in the page
687
+ */
688
+ async setupErrorTracking(page) {
689
+ await page.evaluate(() => {
690
+ // Initialize error tracking
691
+ window.__testHarnessRuntimeErrors = [];
692
+ window.__testHarnessConsoleErrors = [];
693
+ window.__testHarnessConsoleWarnings = [];
694
+ window.__testHarnessTestFailed = false;
695
+ window.__testHarnessRenderCount = 0;
696
+ // Track renders
697
+ const originalCreateElement = window.React?.createElement;
698
+ if (originalCreateElement) {
699
+ window.React.createElement = function (...args) {
700
+ window.__testHarnessRenderCount++;
701
+ return originalCreateElement.apply(this, args);
702
+ };
703
+ }
704
+ // Override console.error
705
+ const originalConsoleError = console.error;
706
+ console.error = function (...args) {
707
+ const errorText = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
708
+ // Check if this is a warning rather than an error
709
+ // React warnings typically start with "Warning:" or contain warning-related text
710
+ const isWarning = errorText.includes('Warning:') ||
711
+ errorText.includes('DevTools') ||
712
+ errorText.includes('deprecated') ||
713
+ errorText.includes('has been renamed') ||
714
+ errorText.includes('will be removed') ||
715
+ errorText.includes('Consider using') ||
716
+ errorText.includes('Please update') ||
717
+ (errorText.includes('React') && errorText.includes('recognize the')) || // Prop warnings
718
+ (errorText.includes('React') && errorText.includes('Invalid'));
719
+ if (isWarning) {
720
+ // Track as warning, don't fail the test
721
+ window.__testHarnessConsoleWarnings.push(errorText);
704
722
  }
705
- if (window.getComputedStyle(el).visibility === 'hidden') {
706
- el.style.visibility = 'visible !important';
723
+ else {
724
+ // Real error - track and fail the test
725
+ window.__testHarnessConsoleErrors.push(errorText);
726
+ window.__testHarnessTestFailed = true;
707
727
  }
708
- // Add a test border to see if elements exist
709
- el.style.border = '1px dotted red';
710
- }
728
+ originalConsoleError.apply(console, args);
729
+ };
730
+ // Global error handler
731
+ window.addEventListener('error', (event) => {
732
+ window.__testHarnessRuntimeErrors.push({
733
+ message: event.error?.message || event.message,
734
+ stack: event.error?.stack,
735
+ type: 'runtime'
736
+ });
737
+ window.__testHarnessTestFailed = true;
711
738
  });
712
- console.log('Applied debug borders to', allElements.length, 'elements');
713
- }
714
- }
715
- }, 2000);
716
- })();
717
- </script>
718
- </body>
719
- </html>`;
739
+ // Unhandled promise rejection handler
740
+ window.addEventListener('unhandledrejection', (event) => {
741
+ window.__testHarnessRuntimeErrors.push({
742
+ message: 'Unhandled Promise Rejection: ' + (event.reason?.message || event.reason),
743
+ stack: event.reason?.stack,
744
+ type: 'promise-rejection'
745
+ });
746
+ window.__testHarnessTestFailed = true;
747
+ event.preventDefault();
748
+ });
749
+ });
720
750
  }
721
751
  /**
722
- * Checks if a console message is a warning
752
+ * Collect runtime errors from the page
723
753
  */
724
- isWarning(type, text) {
725
- return type === 'warning' || text.startsWith('Warning:');
754
+ async collectRuntimeErrors(page) {
755
+ const errorData = await page.evaluate(() => {
756
+ return {
757
+ runtimeErrors: window.__testHarnessRuntimeErrors || [],
758
+ consoleErrors: window.__testHarnessConsoleErrors || [],
759
+ testFailed: window.__testHarnessTestFailed || false
760
+ };
761
+ });
762
+ const errors = [];
763
+ // Only add "test failed" message if there are actual errors
764
+ if (errorData.testFailed && (errorData.runtimeErrors.length > 0 || errorData.consoleErrors.length > 0)) {
765
+ errors.push('Test marked as failed by error handlers');
766
+ }
767
+ errorData.runtimeErrors.forEach((error) => {
768
+ const errorMsg = `${error.type} error: ${error.message}`;
769
+ if (!errors.includes(errorMsg)) {
770
+ errors.push(errorMsg);
771
+ }
772
+ });
773
+ errorData.consoleErrors.forEach((error) => {
774
+ const errorMsg = `Console error: ${error}`;
775
+ if (!errors.includes(errorMsg)) {
776
+ errors.push(errorMsg);
777
+ }
778
+ });
779
+ return errors;
726
780
  }
727
781
  /**
728
- * Checks if a warning is critical and should fail the test
782
+ * Collect warnings from the page (non-fatal issues)
729
783
  */
730
- isCriticalWarning(text) {
731
- return ComponentRunner.CRITICAL_WARNING_PATTERNS.some(pattern => pattern.test(text));
784
+ async collectWarnings(page) {
785
+ const warningData = await page.evaluate(() => {
786
+ return {
787
+ consoleWarnings: window.__testHarnessConsoleWarnings || []
788
+ };
789
+ });
790
+ const warnings = [];
791
+ warningData.consoleWarnings.forEach((warning) => {
792
+ if (!warnings.includes(warning)) {
793
+ warnings.push(warning);
794
+ }
795
+ });
796
+ return warnings;
732
797
  }
733
798
  /**
734
- * Sets up console logging with warning detection
799
+ * Set up console logging
735
800
  */
736
801
  setupConsoleLogging(page, consoleLogs, warnings, criticalWarnings) {
737
802
  page.on('console', (msg) => {
738
803
  const type = msg.type();
739
804
  const text = msg.text();
740
805
  consoleLogs.push({ type, text });
741
- if (this.isWarning(type, text)) {
742
- warnings.push(text);
743
- if (this.isCriticalWarning(text)) {
806
+ // Note: We're already handling warnings in our console.error override
807
+ // This catches any direct console.warn() calls
808
+ if (type === 'warning') {
809
+ if (!warnings.includes(text)) {
810
+ warnings.push(text);
811
+ }
812
+ // Check if it's a critical warning that should fail the test
813
+ if (ComponentRunner.CRITICAL_WARNING_PATTERNS.some(pattern => pattern.test(text))) {
744
814
  criticalWarnings.push(text);
745
815
  }
746
816
  }
747
817
  });
748
- }
749
- /**
750
- * Sets up error handling for the page
751
- */
752
- setupErrorHandling(page, errors) {
753
818
  page.on('pageerror', (error) => {
754
- errors.push(error.message);
819
+ consoleLogs.push({ type: 'error', text: error.message });
755
820
  });
756
821
  }
757
822
  /**
758
- * Injects render tracking code into the page
759
- */
760
- async injectRenderTracking(page) {
761
- // Instead of using evaluateOnNewDocument, we'll inject the script directly into the HTML
762
- // This avoids the Playwright-specific API issue
763
- // The actual injection will happen in createHTMLTemplate
764
- }
765
- /**
766
- * Waits for component to render and checks for timeouts
823
+ * Expose MJ utilities to the browser context
767
824
  */
768
- async waitForRender(page, options, errors) {
769
- const timeout = options.timeout || 10000; // 10 seconds default
770
- const renderWaitTime = options.renderWaitTime || 500; // Default 500ms for render completion
825
+ async exposeMJUtilities(page, contextUser, dataErrors, debug = false) {
826
+ // Don't check if already exposed - we always start fresh after goto('about:blank')
827
+ // The page.exposeFunction calls need to be made for each new page instance
828
+ // Serialize contextUser to pass to the browser context
829
+ // UserInfo is a simple object that can be serialized
830
+ const serializedContextUser = JSON.parse(JSON.stringify(contextUser));
831
+ // Create instances in Node.js context
832
+ const metadata = new core_1.Metadata();
833
+ const runView = new core_1.RunView();
834
+ const runQuery = new core_1.RunQuery();
835
+ // Create a lightweight mock metadata object with serializable data
836
+ // This avoids authentication/provider issues in the browser context
837
+ let entitiesData = [];
771
838
  try {
772
- if (options.waitForSelector) {
773
- await page.waitForSelector(options.waitForSelector, { timeout });
774
- }
775
- if (options.waitForLoadState) {
776
- await page.waitForLoadState(options.waitForLoadState);
839
+ // Try to get entities if available, otherwise use empty array
840
+ if (metadata.Entities) {
841
+ // Serialize the entities data (remove functions, keep data)
842
+ entitiesData = JSON.parse(JSON.stringify(metadata.Entities));
843
+ // Serialized entities for browser context
777
844
  }
778
845
  else {
779
- // Wait for React to finish rendering with configurable time
780
- await page.waitForTimeout(renderWaitTime);
781
- // Force React to flush all updates
782
- await page.evaluate(() => {
783
- if (window.React && window.React.flushSync) {
784
- try {
785
- window.React.flushSync(() => { });
786
- }
787
- catch (e) {
788
- console.error('flushSync error:', e);
789
- }
790
- }
791
- });
792
- // Additional small wait after flush to ensure DOM updates
793
- await page.waitForTimeout(50);
846
+ // Metadata.Entities not available, using empty array
794
847
  }
795
- return true;
796
848
  }
797
- catch (timeoutError) {
798
- errors.push(`Component rendering timeout after ${timeout}ms - possible infinite render loop`);
799
- return false;
849
+ catch (error) {
850
+ // Could not serialize entities
851
+ entitiesData = [];
800
852
  }
801
- }
802
- /**
803
- * Gets the render count from the page
804
- */
805
- async getRenderCount(page) {
806
- return await page.evaluate(() => window.__testHarnessRenderCount || 0);
807
- }
808
- /**
809
- * Collects runtime errors that were caught during component execution
810
- */
811
- async collectRuntimeErrors(page) {
812
- const runtimeErrors = await page.evaluate(() => {
813
- return window.__testHarnessRuntimeErrors || [];
853
+ // Create the mock metadata structure with entities and user
854
+ // Note: Don't include functions here as they can't be serialized
855
+ // Include common properties that Metadata.Provider might need
856
+ const mockMetadata = {
857
+ Entities: entitiesData,
858
+ CurrentUser: serializedContextUser,
859
+ Applications: [],
860
+ Queries: [],
861
+ QueryFields: [],
862
+ QueryCategories: [],
863
+ QueryPermissions: [],
864
+ Roles: [],
865
+ Libraries: [],
866
+ AuditLogTypes: [],
867
+ Authorizations: [],
868
+ VisibleExplorerNavigationItems: [],
869
+ AllExplorerNavigationItems: []
870
+ };
871
+ // Inject both the contextUser and mock metadata into the page
872
+ // Playwright only accepts a single argument, so wrap in an object
873
+ await page.evaluate((data) => {
874
+ const { ctxUser, mockMd } = data;
875
+ window.__mjContextUser = ctxUser;
876
+ // Add the EntityByName function directly in the browser context
877
+ mockMd.EntityByName = (name) => {
878
+ return mockMd.Entities.find((e) => e.Name === name) || null;
879
+ };
880
+ window.__mjMockMetadata = mockMd;
881
+ // IMPORTANT: Create global Metadata mock immediately to prevent undefined errors
882
+ // This must be available before any component code runs
883
+ if (!window.Metadata) {
884
+ window.Metadata = {
885
+ Provider: mockMd
886
+ };
887
+ // Created global Metadata mock with Provider (early)
888
+ }
889
+ // Received contextUser and mock metadata in browser
890
+ }, { ctxUser: serializedContextUser, mockMd: mockMetadata });
891
+ // Expose functions
892
+ await page.exposeFunction('__mjGetEntityObject', async (entityName) => {
893
+ try {
894
+ const entity = await metadata.GetEntityObject(entityName, contextUser);
895
+ return entity;
896
+ }
897
+ catch (error) {
898
+ console.error('Error in __mjGetEntityObject:', error);
899
+ return null;
900
+ }
814
901
  });
815
- const errors = [];
816
- runtimeErrors.forEach((error) => {
817
- errors.push(`${error.type} error: ${error.message}`);
818
- if (error.componentStack) {
819
- errors.push(`Component stack: ${error.componentStack}`);
902
+ await page.exposeFunction('__mjGetEntities', () => {
903
+ try {
904
+ // Return the entities array or empty array if not available
905
+ return entitiesData;
906
+ }
907
+ catch (error) {
908
+ console.error('Error in __mjGetEntities:', error);
909
+ return [];
820
910
  }
821
911
  });
822
- return errors;
823
- }
824
- /**
825
- * Captures async errors by waiting for asynchronous operations to complete
826
- * This catches errors from setTimeout, setInterval, Promises, and async effects
827
- */
828
- async captureAsyncErrors(page, waitTime) {
829
- const errors = [];
830
- try {
831
- // Clear any existing errors to track only new ones
832
- const initialErrorCount = await page.evaluate(() => {
833
- return window.__testHarnessRuntimeErrors?.length || 0;
834
- });
835
- // Wait for async operations to potentially fail
836
- await page.waitForTimeout(waitTime);
837
- // Collect any new errors that occurred during the wait
838
- const allErrors = await page.evaluate(() => {
839
- return window.__testHarnessRuntimeErrors || [];
840
- });
841
- // Process only new errors
842
- const newErrors = allErrors.slice(initialErrorCount);
843
- newErrors.forEach((error) => {
844
- if (error.type === 'promise-rejection') {
845
- errors.push(`Async operation failed: ${error.message}`);
846
- }
847
- else if (error.message && !errors.includes(`${error.type} error: ${error.message}`)) {
848
- errors.push(`Delayed ${error.type} error: ${error.message}`);
849
- }
850
- });
851
- // Also check if any console errors appeared
852
- const consoleErrors = await page.evaluate(() => {
853
- const logs = window.__testHarnessConsoleLogs || [];
854
- return logs.filter((log) => log.type === 'error').map((log) => log.text);
855
- });
856
- // Add unique console errors
857
- consoleErrors.forEach((error) => {
858
- if (!errors.some(e => e.includes(error))) {
859
- errors.push(`Console error during async wait: ${error}`);
860
- }
861
- });
862
- }
863
- catch (e) {
864
- errors.push(`Failed to capture async errors: ${e}`);
865
- }
866
- return errors;
867
- }
868
- /**
869
- * Performs deep render validation to catch errors that might be in the DOM
870
- */
871
- async validateDeepRender(page) {
872
- const errors = [];
873
- try {
874
- // Execute a full render cycle by forcing a state update
875
- await page.evaluate(() => {
876
- // Force React to complete all pending updates
877
- if (window.React && window.React.flushSync) {
878
- window.React.flushSync(() => { });
879
- }
880
- });
881
- // Check for render errors in the component tree
882
- const renderErrors = await page.evaluate(() => {
883
- const errors = [];
884
- // Walk the DOM and check for error boundaries or error text
885
- const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
886
- let node;
887
- while (node = walker.nextNode()) {
888
- const text = node.textContent || '';
889
- // Look for common error patterns
890
- if (text.includes('TypeError:') ||
891
- text.includes('ReferenceError:') ||
892
- text.includes('Cannot read properties of undefined') ||
893
- text.includes('Cannot access property') ||
894
- text.includes('is not a function') ||
895
- text.includes('Component Error:')) {
896
- // Only add if it's not already in our error list
897
- const errorMsg = text.trim();
898
- if (errorMsg.length < 500) { // Avoid huge text blocks
899
- errors.push(`Potential error in rendered content: ${errorMsg}`);
900
- }
912
+ await page.exposeFunction('__mjRunView', async (params) => {
913
+ try {
914
+ const result = await runView.RunView(params, contextUser);
915
+ // Debug logging for successful calls
916
+ if (debug) {
917
+ const rowCount = result.Results?.length || 0;
918
+ console.log(`āœ… RunView SUCCESS: Entity="${params.EntityName}" Rows=${rowCount}`);
919
+ if (params.ExtraFilter) {
920
+ console.log(` Filter: ${params.ExtraFilter}`);
901
921
  }
902
922
  }
903
- return errors;
904
- });
905
- errors.push(...renderErrors);
906
- }
907
- catch (e) {
908
- errors.push(`Deep render validation failed: ${e}`);
909
- }
910
- return errors;
911
- }
912
- /**
913
- * Determines if the component execution was successful
914
- */
915
- determineSuccess(errors, criticalWarnings, renderCount, hasTimeout) {
916
- const additionalErrors = [];
917
- if (renderCount > ComponentRunner.MAX_RENDER_COUNT) {
918
- additionalErrors.push(`Excessive render count: ${renderCount} renders detected`);
919
- }
920
- const success = errors.length === 0 &&
921
- criticalWarnings.length === 0 &&
922
- !hasTimeout &&
923
- renderCount <= ComponentRunner.MAX_RENDER_COUNT;
924
- return { success, additionalErrors };
925
- }
926
- /**
927
- * Expose MemberJunction utilities to the browser context
928
- */
929
- async exposeMJUtilities(page, contextUser) {
930
- // Check if utilities are already exposed (they persist across navigations)
931
- const alreadyExposed = await page.evaluate(() => {
932
- return typeof window.__mjGetEntityObject === 'function';
933
- });
934
- if (alreadyExposed) {
935
- return; // Already exposed, skip
936
- }
937
- try {
938
- // Create instances in Node.js context
939
- const metadata = new core_1.Metadata();
940
- const runView = new core_1.RunView();
941
- const runQuery = new core_1.RunQuery();
942
- // Expose individual functions since we can't pass complex objects
943
- await page.exposeFunction('__mjGetEntityObject', async (entityName) => {
944
- try {
945
- const entity = await metadata.GetEntityObject(entityName, contextUser);
946
- return entity;
947
- }
948
- catch (error) {
949
- console.error('Error in __mjGetEntityObject:', error);
950
- return null;
923
+ return result;
924
+ }
925
+ catch (error) {
926
+ const errorMessage = error instanceof Error ? error.message : String(error);
927
+ // Debug logging for errors
928
+ if (debug) {
929
+ console.log(`āŒ RunView FAILED: Entity="${params.EntityName || 'unknown'}"`);
930
+ console.log(` Error: ${errorMessage}`);
951
931
  }
952
- });
953
- await page.exposeFunction('__mjGetEntities', () => {
954
- try {
955
- return metadata.Entities;
932
+ else {
933
+ console.error('Error in __mjRunView:', errorMessage);
956
934
  }
957
- catch (error) {
958
- console.error('Error in __mjGetEntities:', error);
959
- return null;
935
+ // Collect this error for the test report
936
+ dataErrors.push(`RunView error: ${errorMessage} (Entity: ${params.EntityName || 'unknown'})`);
937
+ // Return error result that won't crash the component
938
+ return { Success: false, ErrorMessage: errorMessage, Results: [] };
939
+ }
940
+ });
941
+ await page.exposeFunction('__mjRunViews', async (params) => {
942
+ try {
943
+ const results = await runView.RunViews(params, contextUser);
944
+ // Debug logging for successful calls
945
+ if (debug) {
946
+ console.log(`āœ… RunViews SUCCESS: ${params.length} queries executed`);
947
+ params.forEach((p, i) => {
948
+ const rowCount = results[i]?.Results?.length || 0;
949
+ console.log(` [${i + 1}] Entity="${p.EntityName}" Rows=${rowCount}`);
950
+ });
960
951
  }
961
- });
962
- await page.exposeFunction('__mjRunView', async (params) => {
963
- try {
964
- return await runView.RunView(params, contextUser);
952
+ return results;
953
+ }
954
+ catch (error) {
955
+ const errorMessage = error instanceof Error ? error.message : String(error);
956
+ const entities = params.map(p => p.EntityName || 'unknown').join(', ');
957
+ // Debug logging for errors
958
+ if (debug) {
959
+ console.log(`āŒ RunViews FAILED: Entities=[${entities}]`);
960
+ console.log(` Error: ${errorMessage}`);
965
961
  }
966
- catch (error) {
967
- console.error('Error in __mjRunView:', error);
968
- const errorMessage = error instanceof Error ? error.message : String(error);
969
- return { Success: false, ErrorMessage: errorMessage, Results: [] };
962
+ else {
963
+ console.error('Error in __mjRunViews:', errorMessage);
970
964
  }
971
- });
972
- await page.exposeFunction('__mjRunViews', async (params) => {
973
- try {
974
- return await runView.RunViews(params, contextUser);
965
+ // Collect this error for the test report
966
+ dataErrors.push(`RunViews error: ${errorMessage} (Entities: ${entities})`);
967
+ // Return error results that won't crash the component
968
+ return params.map(() => ({ Success: false, ErrorMessage: errorMessage, Results: [] }));
969
+ }
970
+ });
971
+ await page.exposeFunction('__mjRunQuery', async (params) => {
972
+ try {
973
+ const result = await runQuery.RunQuery(params, contextUser);
974
+ // Debug logging for successful calls
975
+ if (debug) {
976
+ const queryIdentifier = params.QueryName || params.QueryID || 'unknown';
977
+ const rowCount = result.Results?.length || 0;
978
+ console.log(`āœ… RunQuery SUCCESS: Query="${queryIdentifier}" Rows=${rowCount}`);
979
+ if (params.Parameters && Object.keys(params.Parameters).length > 0) {
980
+ console.log(` Parameters:`, params.Parameters);
981
+ }
975
982
  }
976
- catch (error) {
977
- console.error('Error in __mjRunViews:', error);
978
- const errorMessage = error instanceof Error ? error.message : String(error);
979
- return params.map(() => ({ Success: false, ErrorMessage: errorMessage, Results: [] }));
983
+ return result;
984
+ }
985
+ catch (error) {
986
+ const errorMessage = error instanceof Error ? error.message : String(error);
987
+ const queryIdentifier = params.QueryName || params.QueryID || 'unknown';
988
+ // Debug logging for errors
989
+ if (debug) {
990
+ console.log(`āŒ RunQuery FAILED: Query="${queryIdentifier}"`);
991
+ console.log(` Error: ${errorMessage}`);
980
992
  }
981
- });
982
- await page.exposeFunction('__mjRunQuery', async (params) => {
983
- try {
984
- return await runQuery.RunQuery(params, contextUser);
993
+ else {
994
+ console.error('Error in __mjRunQuery:', errorMessage);
985
995
  }
986
- catch (error) {
987
- console.error('Error in __mjRunQuery:', error);
988
- const errorMessage = error instanceof Error ? error.message : String(error);
989
- return { Success: false, ErrorMessage: errorMessage, Results: [] };
996
+ // Collect this error for the test report
997
+ dataErrors.push(`RunQuery error: ${errorMessage} (Query: ${queryIdentifier})`);
998
+ // Return error result that won't crash the component
999
+ return { Success: false, ErrorMessage: errorMessage, Results: [] };
1000
+ }
1001
+ });
1002
+ // Make them available in utilities with the mock metadata
1003
+ await page.evaluate(() => {
1004
+ // Use the mock metadata for synchronous access
1005
+ const mockMd = window.__mjMockMetadata || { Entities: [], CurrentUser: null };
1006
+ window.__mjUtilities = {
1007
+ md: {
1008
+ // Use the mock metadata's Entities directly (synchronous)
1009
+ Entities: mockMd.Entities,
1010
+ entities: mockMd.Entities, // Support both cases
1011
+ CurrentUser: mockMd.CurrentUser,
1012
+ EntityByName: (name) => {
1013
+ return mockMd.Entities.find((e) => e.Name === name) || null;
1014
+ },
1015
+ // Keep async function for GetEntityObject for compatibility
1016
+ GetEntityObject: async (entityName) => await window.__mjGetEntityObject(entityName)
1017
+ },
1018
+ rv: {
1019
+ RunView: async (params) => await window.__mjRunView(params),
1020
+ RunViews: async (params) => await window.__mjRunViews(params)
1021
+ },
1022
+ rq: {
1023
+ RunQuery: async (params) => await window.__mjRunQuery(params)
990
1024
  }
991
- });
992
- }
993
- catch (error) {
994
- console.error('Failed to expose MJ utilities to page:', error);
995
- // Log more details about the error
996
- if (error instanceof Error && error.message.includes('addBinding')) {
997
- console.error('addBinding error - this usually means the page context is invalid');
1025
+ };
1026
+ // Update or create global Metadata mock (in case it wasn't created earlier)
1027
+ if (!window.Metadata) {
1028
+ window.Metadata = {
1029
+ Provider: mockMd
1030
+ };
1031
+ // Created global Metadata mock with Provider (late)
998
1032
  }
999
- throw error; // Re-throw to be caught by the main error handler
1000
- }
1001
- }
1002
- /**
1003
- * Analyze component errors to identify failed components
1004
- * @param errors Array of error messages
1005
- * @returns Array of component names that failed
1006
- */
1007
- static analyzeComponentErrors(errors) {
1008
- return react_runtime_1.ComponentErrorAnalyzer.identifyFailedComponents(errors);
1009
- }
1010
- /**
1011
- * Get detailed error analysis
1012
- * @param errors Array of error messages
1013
- * @returns Detailed failure information
1014
- */
1015
- static getDetailedErrorAnalysis(errors) {
1016
- return react_runtime_1.ComponentErrorAnalyzer.analyzeComponentErrors(errors);
1033
+ else {
1034
+ // Update the existing one to ensure it has the latest mock data
1035
+ window.Metadata.Provider = mockMd;
1036
+ // Updated existing Metadata.Provider with mock data
1037
+ }
1038
+ });
1017
1039
  }
1018
1040
  /**
1019
- * Gets the ComponentCompiler code to inject into the browser
1020
- * This is a simplified version that works in the browser context
1041
+ * Dump debug information
1021
1042
  */
1022
- getComponentCompilerCode() {
1023
- // Return a browser-compatible version of ComponentCompiler
1024
- return `
1025
- class ComponentCompiler {
1026
- constructor() {
1027
- this.cache = new Map();
1028
- }
1029
-
1030
- setBabelInstance(babel) {
1031
- this.babelInstance = babel;
1032
- }
1033
-
1034
- async compile(options) {
1035
- const { componentName, componentCode } = options;
1036
-
1037
- try {
1038
- // Validate inputs
1039
- if (!componentName || !componentCode) {
1040
- throw new Error('componentName and componentCode are required');
1041
- }
1042
-
1043
- // Wrap component code
1044
- const wrappedCode = this.wrapComponentCode(componentCode, componentName);
1045
-
1046
- // Transform using Babel
1047
- const result = this.babelInstance.transform(wrappedCode, {
1048
- presets: ['react'],
1049
- filename: componentName + '.jsx'
1050
- });
1051
-
1052
- // Create factory
1053
- const componentFactory = this.createComponentFactory(result.code, componentName);
1054
-
1055
- return {
1056
- success: true,
1057
- component: {
1058
- component: componentFactory,
1059
- id: componentName + '_' + Date.now(),
1060
- name: componentName,
1061
- compiledAt: new Date(),
1062
- warnings: []
1063
- },
1064
- duration: 0
1065
- };
1066
- } catch (error) {
1067
- return {
1068
- success: false,
1069
- error: {
1070
- message: error.message,
1071
- componentName: componentName,
1072
- phase: 'compilation'
1073
- },
1074
- duration: 0
1075
- };
1043
+ dumpDebugInfo(result) {
1044
+ console.log('\nšŸ“Š === Component Execution Results ===');
1045
+ console.log('Success:', result.success ? 'āœ…' : 'āŒ');
1046
+ console.log('Execution time:', result.executionTime + 'ms');
1047
+ console.log('Render count:', result.renderCount);
1048
+ if (result.errors && result.errors.length > 0) {
1049
+ console.log('\nāŒ Errors:', result.errors.length);
1050
+ result.errors.forEach((err, i) => {
1051
+ console.log(` ${i + 1}. ${err.message}`);
1052
+ });
1076
1053
  }
1077
- }
1078
-
1079
- wrapComponentCode(componentCode, componentName) {
1080
- // Make component libraries available in scope
1081
- const libraryDeclarations = componentLibraries
1082
- .map(lib => \`const \${lib.globalVariable} = libraries['\${lib.globalVariable}'];\`)
1083
- .join('\\n ');
1084
-
1085
- return \`
1086
- function createComponent(
1087
- React, ReactDOM,
1088
- useState, useEffect, useCallback, useMemo, useRef, useContext, useReducer, useLayoutEffect,
1089
- libraries, styles, console
1090
- ) {
1091
- \${libraryDeclarations}
1092
-
1093
- \${componentCode}
1094
-
1095
- if (typeof \${componentName} === 'undefined') {
1096
- throw new Error('Component "\${componentName}" is not defined in the provided code');
1097
- }
1098
-
1099
- return {
1100
- component: \${componentName},
1101
- print: function() { window.print(); },
1102
- refresh: function(data) { }
1103
- };
1104
- }
1105
- \`;
1106
- }
1107
-
1108
- createComponentFactory(transpiledCode, componentName) {
1109
- const factoryCreator = new Function(
1110
- 'React', 'ReactDOM',
1111
- 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext', 'useReducer', 'useLayoutEffect',
1112
- 'libraries', 'styles', 'console',
1113
- transpiledCode + '; return createComponent;'
1114
- );
1115
-
1116
- return (context, styles = {}) => {
1117
- const { React, ReactDOM, libraries = {} } = context;
1118
-
1119
- const createComponentFn = factoryCreator(
1120
- React, ReactDOM,
1121
- React.useState, React.useEffect, React.useCallback, React.useMemo,
1122
- React.useRef, React.useContext, React.useReducer, React.useLayoutEffect,
1123
- libraries, styles, console
1124
- );
1125
-
1126
- return createComponentFn(
1127
- React, ReactDOM,
1128
- React.useState, React.useEffect, React.useCallback, React.useMemo,
1129
- React.useRef, React.useContext, React.useReducer, React.useLayoutEffect,
1130
- libraries, styles, console
1131
- );
1132
- };
1133
- }
1134
- }
1135
- `;
1054
+ if (result.warnings && result.warnings.length > 0) {
1055
+ console.log('\nāš ļø Warnings:', result.warnings.length);
1056
+ result.warnings.forEach((warn, i) => {
1057
+ console.log(` ${i + 1}. ${warn.message}`);
1058
+ });
1059
+ }
1060
+ if (result.criticalWarnings && result.criticalWarnings.length > 0) {
1061
+ console.log('\nšŸ”“ Critical Warnings:', result.criticalWarnings.length);
1062
+ result.criticalWarnings.forEach((warn, i) => {
1063
+ console.log(` ${i + 1}. ${warn}`);
1064
+ });
1065
+ }
1066
+ console.log('\n========================================\n');
1136
1067
  }
1137
1068
  }
1138
1069
  exports.ComponentRunner = ComponentRunner;
@@ -1147,6 +1078,5 @@ ComponentRunner.CRITICAL_WARNING_PATTERNS = [
1147
1078
  /Error: Minified React error/i,
1148
1079
  /too many re-renders/i,
1149
1080
  ];
1150
- // Maximum allowed renders before considering it excessive
1151
1081
  ComponentRunner.MAX_RENDER_COUNT = 1000;
1152
1082
  //# sourceMappingURL=component-runner.js.map