@@ -18,6 +18,7 @@ import {
1818 formatWithStyles ,
1919 gt ,
2020 gte ,
21+ parseSourceFromComponentStack ,
2122} from 'react-devtools-shared/src/backend/utils' ;
2223import {
2324 REACT_SUSPENSE_LIST_TYPE as SuspenseList ,
@@ -297,4 +298,93 @@ describe('utils', () => {
297298 expect ( isPlainObject ( Object . create ( null ) ) ) . toBe ( true ) ;
298299 } ) ;
299300 } ) ;
301+
302+ describe ( 'parseSourceFromComponentStack' , ( ) => {
303+ it ( 'should return null if passed empty string' , ( ) => {
304+ expect ( parseSourceFromComponentStack ( '' ) ) . toEqual ( null ) ;
305+ } ) ;
306+
307+ it ( 'should construct the source from the first frame if available' , ( ) => {
308+ expect (
309+ parseSourceFromComponentStack (
310+ 'at l (https://react.dev/_next/static/chunks/main-78a3b4c2aa4e4850.js:1:10389)\n' +
311+ 'at f (https://react.dev/_next/static/chunks/pages/%5B%5B...markdownPath%5D%5D-af2ed613aedf1d57.js:1:8519)\n' +
312+ 'at r (https://react.dev/_next/static/chunks/pages/_app-dd0b77ea7bd5b246.js:1:498)\n' ,
313+ ) ,
314+ ) . toEqual ( {
315+ sourceURL :
316+ 'https://react.dev/_next/static/chunks/main-78a3b4c2aa4e4850.js' ,
317+ line : 1 ,
318+ column : 10389 ,
319+ } ) ;
320+ } ) ;
321+
322+ it ( 'should construct the source from highest available frame' , ( ) => {
323+ expect (
324+ parseSourceFromComponentStack (
325+ ' at Q\n' +
326+ ' at a\n' +
327+ ' at m (https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js:5:9236)\n' +
328+ ' at div\n' +
329+ ' at div\n' +
330+ ' at div\n' +
331+ ' at nav\n' +
332+ ' at div\n' +
333+ ' at te (https://react.dev/_next/static/chunks/363-3c5f1b553b6be118.js:1:158857)\n' +
334+ ' at tt (https://react.dev/_next/static/chunks/363-3c5f1b553b6be118.js:1:165520)\n' +
335+ ' at f (https://react.dev/_next/static/chunks/pages/%5B%5B...markdownPath%5D%5D-af2ed613aedf1d57.js:1:8519)' ,
336+ ) ,
337+ ) . toEqual ( {
338+ sourceURL :
339+ 'https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js' ,
340+ line : 5 ,
341+ column : 9236 ,
342+ } ) ;
343+ } ) ;
344+
345+ it ( 'should construct the source from frame, which has only url specified' , ( ) => {
346+ expect (
347+ parseSourceFromComponentStack (
348+ ' at Q\n' +
349+ ' at a\n' +
350+ ' at https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js:5:9236\n' ,
351+ ) ,
352+ ) . toEqual ( {
353+ sourceURL :
354+ 'https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js' ,
355+ line : 5 ,
356+ column : 9236 ,
357+ } ) ;
358+ } ) ;
359+
360+ it ( 'should parse sourceURL correctly if it includes parentheses' , ( ) => {
361+ expect (
362+ parseSourceFromComponentStack (
363+ 'at HotReload (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:307:11)\n' +
364+ ' at Router (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:181:11)\n' +
365+ ' at ErrorBoundaryHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:114:9)' ,
366+ ) ,
367+ ) . toEqual ( {
368+ sourceURL :
369+ 'webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js' ,
370+ line : 307 ,
371+ column : 11 ,
372+ } ) ;
373+ } ) ;
374+
375+ it ( 'should support Firefox stack' , ( ) => {
376+ expect (
377+ parseSourceFromComponentStack (
378+ 'tt@https://react.dev/_next/static/chunks/363-3c5f1b553b6be118.js:1:165558\n' +
379+ 'f@https://react.dev/_next/static/chunks/pages/%5B%5B...markdownPath%5D%5D-af2ed613aedf1d57.js:1:8535\n' +
380+ 'r@https://react.dev/_next/static/chunks/pages/_app-dd0b77ea7bd5b246.js:1:513' ,
381+ ) ,
382+ ) . toEqual ( {
383+ sourceURL :
384+ 'https://react.dev/_next/static/chunks/363-3c5f1b553b6be118.js' ,
385+ line : 1 ,
386+ column : 165558 ,
387+ } ) ;
388+ } ) ;
389+ } ) ;
300390} ) ;
0 commit comments