From 27d2684e6adc427a9f58d2206c172e46f88c1ea1 Mon Sep 17 00:00:00 2001 From: Ankit Singh Date: Fri, 24 Oct 2025 14:26:33 +0530 Subject: [PATCH] W-19978121:add runtimeEnv util and reusable ErrorDisplay LWC; show env in hmrTest --- .../default/lwc/errorDisplay/errorDisplay.css | 59 ++++++ .../lwc/errorDisplay/errorDisplay.html | 53 ++++++ .../default/lwc/errorDisplay/errorDisplay.js | 173 ++++++++++++++++++ .../lwc/errorDisplay/errorDisplay.js-meta.xml | 10 + .../main/default/lwc/hmrTest/hmrTest.css | 4 +- .../main/default/lwc/hmrTest/hmrTest.html | 6 +- .../main/default/lwc/hmrTest/hmrTest.js | 19 ++ .../default/lwc/runtimeEnv/runtimeEnv.html | 5 + .../main/default/lwc/runtimeEnv/runtimeEnv.js | 47 +++++ .../lwc/runtimeEnv/runtimeEnv.js-meta.xml | 5 + 10 files changed, 377 insertions(+), 4 deletions(-) create mode 100644 test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.css create mode 100644 test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.html create mode 100644 test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.js create mode 100644 test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.js-meta.xml create mode 100644 test/projects/sfdx-lwc-project/force-app/main/default/lwc/runtimeEnv/runtimeEnv.html create mode 100644 test/projects/sfdx-lwc-project/force-app/main/default/lwc/runtimeEnv/runtimeEnv.js create mode 100644 test/projects/sfdx-lwc-project/force-app/main/default/lwc/runtimeEnv/runtimeEnv.js-meta.xml diff --git a/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.css b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.css new file mode 100644 index 00000000..34594d2d --- /dev/null +++ b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.css @@ -0,0 +1,59 @@ +.title { + font-weight: 700; + font-size: 14px; + color: #c23934; + margin-bottom: 8px; +} +.kv { margin-bottom: 12px; } +.kv-row { display: flex; gap: 8px; margin: 2px 0; font-size: 12px; } +.kv-label { width: 90px; color: #6b6d70; font-weight: 600; } +.kv-value { color: #080707; } +.meta { + display: flex; + gap: 6px; + margin-bottom: 12px; +} +.pill { + background: #f3f2f2; + border-radius: 10px; + padding: 2px 8px; + font-size: 12px; +} +.stack { + background: #fffbe6; + border: 1px solid #ece0a1; + padding: 8px; + margin-bottom: 12px; + max-height: 180px; + overflow: auto; +} +.stack.empty { + color: #6b6d70; + background: #fafafa; + border: 1px dashed #d8dde6; +} +.section-title { + font-weight: 600; + font-size: 12px; + color: #3e3e3c; + margin: 6px 0 4px; +} +.stack-line { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 12px; + white-space: pre-wrap; +} +.props-title { + font-weight: 600; + margin-bottom: 4px; +} +.prop-row { + display: flex; + gap: 8px; + font-size: 12px; +} +.k { color: #6b6d70; } +.v { color: #080707; } +.actions { margin-top: 12px; } + + diff --git a/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.html b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.html new file mode 100644 index 00000000..b906b4ba --- /dev/null +++ b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.html @@ -0,0 +1,53 @@ + + + diff --git a/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.js b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.js new file mode 100644 index 00000000..88fe5a57 --- /dev/null +++ b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.js @@ -0,0 +1,173 @@ +import { LightningElement, api, track } from 'lwc'; + +export default class ErrorDisplay extends LightningElement { + _payload; + @track propEntriesInternal = []; + @track view = { title: 'No error payload provided.', errorId: 'LWC-Runtime-EID-DEMO', timestamp: '', componentName: 'c-unknown', stackLines: [] }; + + @api + get payload() { + return this._payload; + } + set payload(value) { + this._payload = value; + this.computeView(); + this.normalizeProps(); + } + + connectedCallback() { + // Provide a visible default so demos render even if parent forgets to pass a payload + if (!this._payload) { + // Default to the new wrapper format with one error sample + this._payload = { + success: true, + count: 1, + errors: [ + { + errorId: 'a1b2c3d4-e5f6-4a1b-8c9d-0e1f2a3b4c5d', + timestamp: '2025-10-21T10:30:45.123Z', + error: { + name: 'ReferenceError', + message: 'nonExistentMethod is not defined', + stack: 'ReferenceError: nonExistentMethod is not defined\n at ErrorTestComponent.connectedCallback...', + sanitizedStack: [ + { + functionName: 'connectedCallback', + fileName: 'errorTestComponent.js', + lineNumber: 5, + columnNumber: 10, + isLocalSource: true, + }, + ], + }, + component: { + name: 'c-error-test-component', + namespace: 'c', + tagName: 'c-error-test-component', + lifecycle: 'connectedCallback', + filePath: null, + }, + runtime: { + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...', + viewport: { width: 1920, height: 1080 }, + url: 'https://myorg.lightning.force.com/...', + lwcVersion: null, + isDevelopment: true, + }, + state: { props: { recordId: '001xx000003DHP0' }, publicProperties: ['recordId', 'objectApiName'], isConnected: true }, + source: { fileName: 'errorTestComponent.js', lineNumber: 5, columnNumber: 10 }, + metadata: { severity: 'error', wasHandled: false, occurrenceCount: 1, tags: ['runtime', 'browser', 'lwc'] }, + }, + ], + }; + } + this.computeView(); + this.normalizeProps(); + } + + computeView() { + try { + let title = 'Unknown error'; + let errorId = 'N/A'; + let timestamp = ''; + let componentName = 'c-unknown'; + let stackLines = []; + + if (this._payload && Array.isArray(this._payload.errors) && this._payload.errors.length > 0) { + const e = this._payload.errors[0]; + title = (e && e.error && (e.error.message || e.error.name)) || title; + errorId = e && e.errorId ? e.errorId : errorId; + timestamp = e && e.timestamp ? e.timestamp : ''; + componentName = (e && e.component && (e.component.name || e.component.tagName)) || componentName; + + if (e && e.error && Array.isArray(e.error.sanitizedStack) && e.error.sanitizedStack.length) { + stackLines = e.error.sanitizedStack.map((f) => { + const fn = f.functionName || ''; + const file = f.fileName || 'unknown'; + const line = f.lineNumber != null ? f.lineNumber : '?'; + const col = f.columnNumber != null ? f.columnNumber : '?'; + return `${fn} (${file}:${line}:${col})`; + }); + } else if (e && e.error && e.error.stack) { + stackLines = String(e.error.stack) + .split('\n') + .map((l) => l.trim()) + .filter(Boolean) + .slice(0, 30); + } + + // also set props for normalizeProps() + this._propsForView = (e && e.state && e.state.props) || {}; + } else if (this._payload) { + // Back-compat with older simple shape + title = this._payload.errorMessage || title; + errorId = this._payload.errorId || errorId; + timestamp = this._payload.timestamp || ''; + componentName = this._payload.componentName || componentName; + stackLines = Array.isArray(this._payload.stackTrace) ? this._payload.stackTrace : []; + this._propsForView = this._payload.props || {}; + } + + this.view = { title, errorId, timestamp, componentName, stackLines }; + } catch (_) { + // keep default view + } + } + + normalizeProps() { + const p = this._propsForView || {}; + if (!p) { + this.propEntriesInternal = []; + return; + } + const entries = []; + for (const k of Object.keys(p)) { + let v = p[k]; + try { + if (typeof v === 'object') v = JSON.stringify(v); + } catch (e) { + // ignore stringify errors + } + entries.push({ key: k, value: String(v) }); + } + // Only assign if changed to avoid unnecessary re-renders + const prev = this.propEntriesInternal || []; + const sameLength = prev.length === entries.length; + const samePairs = sameLength && prev.every((it, i) => it.key === entries[i].key && it.value === entries[i].value); + if (!samePairs) { + this.propEntriesInternal = entries; + } + } + + get hasProps() { + return this.propEntriesInternal && this.propEntriesInternal.length > 0; + } + + get propEntries() { + return this.propEntriesInternal; + } + + async handleCopy() { + const text = JSON.stringify(this._payload || {}, null, 2); + try { + await navigator.clipboard.writeText(text); + } catch (e) { + // fallback + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + try { + document.execCommand('copy'); + } catch (_) { + // ignore + } + document.body.removeChild(ta); + } + } +} + + diff --git a/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.js-meta.xml b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.js-meta.xml new file mode 100644 index 00000000..620831e8 --- /dev/null +++ b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/errorDisplay/errorDisplay.js-meta.xml @@ -0,0 +1,10 @@ + + + 66.0 + true + + lightning__AppPage + lightning__RecordPage + lightning__HomePage + + diff --git a/test/projects/sfdx-lwc-project/force-app/main/default/lwc/hmrTest/hmrTest.css b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/hmrTest/hmrTest.css index 5ca797e6..550b7eb9 100644 --- a/test/projects/sfdx-lwc-project/force-app/main/default/lwc/hmrTest/hmrTest.css +++ b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/hmrTest/hmrTest.css @@ -1,12 +1,12 @@ .important-text { font-weight: bold; - color: blue; + color: rgb(190, 30, 121); padding-bottom: 10px; } .footer-text { font-style: italic; - color: grey; + color: rgb(193, 12, 12); font-size: 0.8rem; } diff --git a/test/projects/sfdx-lwc-project/force-app/main/default/lwc/hmrTest/hmrTest.html b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/hmrTest/hmrTest.html index de8ab00c..431405d9 100644 --- a/test/projects/sfdx-lwc-project/force-app/main/default/lwc/hmrTest/hmrTest.html +++ b/test/projects/sfdx-lwc-project/force-app/main/default/lwc/hmrTest/hmrTest.html @@ -1,13 +1,15 @@