@@ -10,7 +10,7 @@ jest.mock(
1010 return {
1111 loadScript ( ) {
1212 return new Promise ( resolve => {
13- global . Prism = require ( '../../../staticresources/LoggerResources/prism.js' ) ;
13+ global . Prism = require ( '../../../staticresources/LoggerResources/Prism/ prism.min .js' ) ;
1414 resolve ( ) ;
1515 } ) ;
1616 } ,
@@ -31,6 +31,40 @@ jest.mock(
3131 { virtual : true }
3232) ;
3333
34+ // Number of microtask hops to drain across getRecord, c-logger-code-viewer creation,
35+ // and the Promise.all(map(async)) chain inside _loadPrismResources.
36+ const flushPromises = async ( ) => {
37+ for ( let i = 0 ; i < 8 ; i ++ ) {
38+ /* eslint-disable-next-line no-await-in-loop */
39+ await Promise . resolve ( ) ;
40+ }
41+ } ;
42+
43+ const buildSnippet = overrides => ( {
44+ Code : 'some-code-block' ,
45+ ApiVersion : '65.0' ,
46+ TotalLinesOfCode : 123 ,
47+ StartingLineNumber : 55 ,
48+ TargetLineNumber : 65 ,
49+ EndingLineNumber : 68 ,
50+ ...overrides
51+ } ) ;
52+
53+ const buildLogEntryRecord = ( { source, metadataType, snippet } ) => {
54+ const apiNameField = source === 'Exception' ? 'ExceptionSourceApiName__c' : 'OriginSourceApiName__c' ;
55+ const apiVersionField = source === 'Exception' ? 'ExceptionSourceApiVersion__c' : 'OriginSourceApiVersion__c' ;
56+ const metadataTypeField = source === 'Exception' ? 'ExceptionSourceMetadataType__c' : 'OriginSourceMetadataType__c' ;
57+ const snippetField = source === 'Exception' ? 'ExceptionSourceSnippet__c' : 'OriginSourceSnippet__c' ;
58+ return {
59+ fields : {
60+ [ apiNameField ] : { value : 'SomeApexClass' } ,
61+ [ apiVersionField ] : { value : '65.0' } ,
62+ [ metadataTypeField ] : { value : metadataType } ,
63+ [ snippetField ] : { value : snippet === null ? null : JSON . stringify ( snippet ) }
64+ }
65+ } ;
66+ } ;
67+
3468describe ( 'LogEntryMetadataViewer LWC Tests' , ( ) => {
3569 afterEach ( ( ) => {
3670 while ( document . body . firstChild ) {
@@ -65,9 +99,7 @@ describe('LogEntryMetadataViewer LWC Tests', () => {
6599
66100 document . body . appendChild ( element ) ;
67101 getRecord . emit ( mockLogEntryRecord ) ;
68- await Promise . resolve ( 'resolves getRecord() call' ) ;
69- await Promise . resolve ( 'resolves creating an instance of c-logger-code-viewer' ) ;
70- await Promise . resolve ( 'resolves loading & running PrismJS inside of c-logger-code-viewer' ) ;
102+ await flushPromises ( ) ;
71103
72104 const sectionTitle = element . shadowRoot . querySelector ( 'c-logger-page-section span[slot="title"]' ) ;
73105 expect ( sectionTitle ) . toBeTruthy ( ) ;
@@ -116,9 +148,7 @@ describe('LogEntryMetadataViewer LWC Tests', () => {
116148
117149 document . body . appendChild ( element ) ;
118150 getRecord . emit ( mockLogEntryRecord ) ;
119- await Promise . resolve ( 'resolves getRecord() call' ) ;
120- await Promise . resolve ( 'resolves creating an instance of c-logger-code-viewer' ) ;
121- await Promise . resolve ( 'resolves loading & running PrismJS inside of c-logger-code-viewer' ) ;
151+ await flushPromises ( ) ;
122152
123153 const sectionTitle = element . shadowRoot . querySelector ( 'c-logger-page-section span[slot="title"]' ) ;
124154 expect ( sectionTitle ) . toBeTruthy ( ) ;
@@ -140,4 +170,184 @@ describe('LogEntryMetadataViewer LWC Tests', () => {
140170 expect ( viewFullSourceButton . label ) . toBe ( 'View Full Source' ) ;
141171 expect ( viewFullSourceButton . variant ) . toBe ( 'inverse' ) ;
142172 } ) ;
173+
174+ it ( 'should show a spinner before the wired log entry record loads' , async ( ) => {
175+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
176+ element . sourceMetadata = 'Exception' ;
177+ element . recordId = 'test-log-entry-id' ;
178+
179+ document . body . appendChild ( element ) ;
180+ await flushPromises ( ) ;
181+
182+ expect ( element . shadowRoot . querySelector ( 'lightning-spinner' ) ) . toBeTruthy ( ) ;
183+ expect ( element . shadowRoot . querySelector ( 'c-logger-page-section' ) ) . toBeNull ( ) ;
184+ expect ( element . shadowRoot . querySelector ( 'c-logger-code-viewer' ) ) . toBeNull ( ) ;
185+ } ) ;
186+
187+ it ( 'should render "No source snippet available" when the snippet field is empty' , async ( ) => {
188+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
189+ element . sourceMetadata = 'Exception' ;
190+ element . recordId = 'test-log-entry-id' ;
191+
192+ document . body . appendChild ( element ) ;
193+ getRecord . emit ( buildLogEntryRecord ( { source : 'Exception' , metadataType : 'ApexClass' , snippet : null } ) ) ;
194+ await flushPromises ( ) ;
195+
196+ expect ( element . shadowRoot . querySelector ( 'lightning-spinner' ) ) . toBeNull ( ) ;
197+ expect ( element . shadowRoot . querySelector ( 'c-logger-page-section' ) ) . toBeNull ( ) ;
198+ expect ( element . shadowRoot . querySelector ( 'c-logger-code-viewer' ) ) . toBeNull ( ) ;
199+ expect ( element . shadowRoot . textContent ) . toContain ( 'No source snippet available' ) ;
200+ } ) ;
201+
202+ it ( 'should produce a .trigger title for ApexTrigger metadata' , async ( ) => {
203+ getMetadata . mockResolvedValue ( { Code : 'full code here' , HasCodeBeenModified : false } ) ;
204+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
205+ element . sourceMetadata = 'Exception' ;
206+ element . recordId = 'test-log-entry-id' ;
207+
208+ document . body . appendChild ( element ) ;
209+ getRecord . emit ( buildLogEntryRecord ( { source : 'Exception' , metadataType : 'ApexTrigger' , snippet : buildSnippet ( ) } ) ) ;
210+ await flushPromises ( ) ;
211+
212+ const codeViewerTitle = element . shadowRoot . querySelector ( 'c-logger-code-viewer span[slot="title"]' ) ;
213+ expect ( codeViewerTitle ) . toBeTruthy ( ) ;
214+ expect ( codeViewerTitle . textContent ) . toBe ( 'SomeApexClass.trigger - 65.0' ) ;
215+ } ) ;
216+
217+ it ( 'should hide the "View Full Source" button when there is no full source metadata' , async ( ) => {
218+ // getMetadata resolves with an empty object → hasFullSourceMetadata is false.
219+ getMetadata . mockResolvedValue ( { } ) ;
220+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
221+ element . sourceMetadata = 'Exception' ;
222+ element . recordId = 'test-log-entry-id' ;
223+
224+ document . body . appendChild ( element ) ;
225+ getRecord . emit ( buildLogEntryRecord ( { source : 'Exception' , metadataType : 'ApexClass' , snippet : buildSnippet ( ) } ) ) ;
226+ await flushPromises ( ) ;
227+
228+ const actionsSlotButtons = element . shadowRoot . querySelectorAll ( 'c-logger-code-viewer span[slot="actions"] lightning-button' ) ;
229+ expect ( actionsSlotButtons . length ) . toBe ( 0 ) ;
230+ } ) ;
231+
232+ it ( 'should open the full-source modal with success notification when code is unmodified' , async ( ) => {
233+ getMetadata . mockResolvedValue ( { Code : 'full code here' , HasCodeBeenModified : false } ) ;
234+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
235+ element . sourceMetadata = 'Exception' ;
236+ element . recordId = 'test-log-entry-id' ;
237+ document . body . appendChild ( element ) ;
238+ getRecord . emit ( buildLogEntryRecord ( { source : 'Exception' , metadataType : 'ApexClass' , snippet : buildSnippet ( ) } ) ) ;
239+ await flushPromises ( ) ;
240+
241+ const viewFullSourceButton = element . shadowRoot . querySelector ( 'c-logger-code-viewer span[slot="actions"] lightning-button' ) ;
242+ viewFullSourceButton . click ( ) ;
243+ await flushPromises ( ) ;
244+
245+ const modalSection = element . shadowRoot . querySelector ( 'section.slds-modal' ) ;
246+ expect ( modalSection ) . toBeTruthy ( ) ;
247+ const modalTitle = element . shadowRoot . querySelector ( 'section.slds-modal h2.slds-text-heading_medium' ) ;
248+ expect ( modalTitle . textContent ) . toBe ( 'Full Source: SomeApexClass.cls - 65.0' ) ;
249+ const notification = element . shadowRoot . querySelector ( 'section.slds-modal div[role="alert"]' ) ;
250+ expect ( notification . classList . contains ( 'slds-theme_success' ) ) . toBe ( true ) ;
251+ expect ( notification . classList . contains ( 'slds-theme_warning' ) ) . toBe ( false ) ;
252+ const notificationIcon = notification . querySelector ( 'lightning-icon' ) ;
253+ expect ( notificationIcon . iconName ) . toBe ( 'utility:success' ) ;
254+ const notificationMessage = notification . querySelector ( 'h2' ) ;
255+ expect ( notificationMessage . textContent ) . toBe ( 'This Apex code has not been modified since this log entry was generated.' ) ;
256+ const modalCodeViewer = element . shadowRoot . querySelector ( 'section.slds-modal c-logger-code-viewer' ) ;
257+ expect ( modalCodeViewer ) . toBeTruthy ( ) ;
258+ expect ( modalCodeViewer . code ) . toBe ( 'full code here' ) ;
259+ } ) ;
260+
261+ it ( 'should open the full-source modal with warning notification when code has been modified' , async ( ) => {
262+ getMetadata . mockResolvedValue ( { Code : 'modified code' , HasCodeBeenModified : true } ) ;
263+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
264+ element . sourceMetadata = 'Exception' ;
265+ element . recordId = 'test-log-entry-id' ;
266+ document . body . appendChild ( element ) ;
267+ getRecord . emit ( buildLogEntryRecord ( { source : 'Exception' , metadataType : 'ApexClass' , snippet : buildSnippet ( ) } ) ) ;
268+ await flushPromises ( ) ;
269+
270+ element . shadowRoot . querySelector ( 'c-logger-code-viewer span[slot="actions"] lightning-button' ) . click ( ) ;
271+ await flushPromises ( ) ;
272+
273+ const notification = element . shadowRoot . querySelector ( 'section.slds-modal div[role="alert"]' ) ;
274+ expect ( notification . classList . contains ( 'slds-theme_success' ) ) . toBe ( false ) ;
275+ expect ( notification . classList . contains ( 'slds-theme_warning' ) ) . toBe ( true ) ;
276+ const notificationIcon = notification . querySelector ( 'lightning-icon' ) ;
277+ expect ( notificationIcon . iconName ) . toBe ( 'utility:warning' ) ;
278+ const notificationMessage = notification . querySelector ( 'h2' ) ;
279+ expect ( notificationMessage . textContent ) . toBe ( 'This Apex code has been modified since this log entry was generated.' ) ;
280+ } ) ;
281+
282+ it ( 'should close the modal when the header close icon is clicked' , async ( ) => {
283+ getMetadata . mockResolvedValue ( { Code : 'full code here' , HasCodeBeenModified : false } ) ;
284+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
285+ element . sourceMetadata = 'Exception' ;
286+ element . recordId = 'test-log-entry-id' ;
287+ document . body . appendChild ( element ) ;
288+ getRecord . emit ( buildLogEntryRecord ( { source : 'Exception' , metadataType : 'ApexClass' , snippet : buildSnippet ( ) } ) ) ;
289+ await flushPromises ( ) ;
290+
291+ element . shadowRoot . querySelector ( 'c-logger-code-viewer span[slot="actions"] lightning-button' ) . click ( ) ;
292+ await flushPromises ( ) ;
293+ expect ( element . shadowRoot . querySelector ( 'section.slds-modal' ) ) . toBeTruthy ( ) ;
294+
295+ element . shadowRoot . querySelector ( 'section.slds-modal button.slds-modal__close' ) . click ( ) ;
296+ await flushPromises ( ) ;
297+ expect ( element . shadowRoot . querySelector ( 'section.slds-modal' ) ) . toBeNull ( ) ;
298+ } ) ;
299+
300+ it ( 'should close the modal when the footer Close button is clicked' , async ( ) => {
301+ getMetadata . mockResolvedValue ( { Code : 'full code here' , HasCodeBeenModified : false } ) ;
302+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
303+ element . sourceMetadata = 'Exception' ;
304+ element . recordId = 'test-log-entry-id' ;
305+ document . body . appendChild ( element ) ;
306+ getRecord . emit ( buildLogEntryRecord ( { source : 'Exception' , metadataType : 'ApexClass' , snippet : buildSnippet ( ) } ) ) ;
307+ await flushPromises ( ) ;
308+
309+ element . shadowRoot . querySelector ( 'c-logger-code-viewer span[slot="actions"] lightning-button' ) . click ( ) ;
310+ await flushPromises ( ) ;
311+
312+ element . shadowRoot . querySelector ( 'lightning-button[data-id="close-btn"]' ) . click ( ) ;
313+ await flushPromises ( ) ;
314+ expect ( element . shadowRoot . querySelector ( 'section.slds-modal' ) ) . toBeNull ( ) ;
315+ } ) ;
316+
317+ it ( 'should close the modal when the Escape key is pressed' , async ( ) => {
318+ getMetadata . mockResolvedValue ( { Code : 'full code here' , HasCodeBeenModified : false } ) ;
319+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
320+ element . sourceMetadata = 'Exception' ;
321+ element . recordId = 'test-log-entry-id' ;
322+ document . body . appendChild ( element ) ;
323+ getRecord . emit ( buildLogEntryRecord ( { source : 'Exception' , metadataType : 'ApexClass' , snippet : buildSnippet ( ) } ) ) ;
324+ await flushPromises ( ) ;
325+
326+ element . shadowRoot . querySelector ( 'c-logger-code-viewer span[slot="actions"] lightning-button' ) . click ( ) ;
327+ await flushPromises ( ) ;
328+
329+ const modalSection = element . shadowRoot . querySelector ( 'section.slds-modal' ) ;
330+ modalSection . dispatchEvent ( new KeyboardEvent ( 'keydown' , { code : 'Escape' , bubbles : true } ) ) ;
331+ await flushPromises ( ) ;
332+
333+ expect ( element . shadowRoot . querySelector ( 'section.slds-modal' ) ) . toBeNull ( ) ;
334+ } ) ;
335+
336+ it ( 'should leave the modal open when a non-Escape key is pressed' , async ( ) => {
337+ getMetadata . mockResolvedValue ( { Code : 'full code here' , HasCodeBeenModified : false } ) ;
338+ const element = createElement ( 'c-log-entry-metadata-viewer' , { is : LogEntryMetadataViewer } ) ;
339+ element . sourceMetadata = 'Exception' ;
340+ element . recordId = 'test-log-entry-id' ;
341+ document . body . appendChild ( element ) ;
342+ getRecord . emit ( buildLogEntryRecord ( { source : 'Exception' , metadataType : 'ApexClass' , snippet : buildSnippet ( ) } ) ) ;
343+ await flushPromises ( ) ;
344+ element . shadowRoot . querySelector ( 'c-logger-code-viewer span[slot="actions"] lightning-button' ) . click ( ) ;
345+ await flushPromises ( ) ;
346+
347+ const modalSection = element . shadowRoot . querySelector ( 'section.slds-modal' ) ;
348+ modalSection . dispatchEvent ( new KeyboardEvent ( 'keydown' , { code : 'Enter' , bubbles : true } ) ) ;
349+ await flushPromises ( ) ;
350+
351+ expect ( element . shadowRoot . querySelector ( 'section.slds-modal' ) ) . toBeTruthy ( ) ;
352+ } ) ;
143353} ) ;
0 commit comments