@@ -108,6 +108,28 @@ describe('ReactFlightDOMNode', () => {
108108 ) ;
109109 }
110110
111+ /**
112+ * Removes all stackframes not pointing into this file
113+ */
114+ function ignoreListStack ( str ) {
115+ if ( ! str ) {
116+ return str ;
117+ }
118+
119+ let ignoreListedStack = '' ;
120+ const lines = str . split ( '\n' ) ;
121+
122+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
123+ for ( const line of lines ) {
124+ if ( line . indexOf ( __filename ) === - 1 ) {
125+ } else {
126+ ignoreListedStack += '\n' + line . replace ( __dirname , '.' ) ;
127+ }
128+ }
129+
130+ return ignoreListedStack ;
131+ }
132+
111133 function readResult ( stream ) {
112134 return new Promise ( ( resolve , reject ) => {
113135 let buffer = '' ;
@@ -784,6 +806,165 @@ describe('ReactFlightDOMNode', () => {
784806 }
785807 } ) ;
786808
809+ // @gate enableHalt
810+ it ( 'includes source locations in component and owner stacks for halted Client components' , async ( ) => {
811+ function SharedComponent ( { p1, p2, p3} ) {
812+ use ( p1 ) ;
813+ use ( p2 ) ;
814+ use ( p3 ) ;
815+ return < div > Hello, Dave!</ div > ;
816+ }
817+ const ClientComponentOnTheServer = clientExports ( SharedComponent ) ;
818+ const ClientComponentOnTheClient = clientExports (
819+ SharedComponent ,
820+ 123 ,
821+ 'path/to/chunk.js' ,
822+ ) ;
823+
824+ let resolvePendingPromise ;
825+ function ServerComponent ( ) {
826+ const p1 = Promise . resolve ( ) ;
827+ const p2 = new Promise ( resolve => {
828+ resolvePendingPromise = value => {
829+ p2 . status = 'fulfilled' ;
830+ p2 . value = value ;
831+ resolve ( value ) ;
832+ } ;
833+ } ) ;
834+ const p3 = new Promise ( ( ) => { } ) ;
835+ return ReactServer . createElement ( ClientComponentOnTheClient , {
836+ p1 : p1 ,
837+ p2 : p2 ,
838+ p3 : p3 ,
839+ } ) ;
840+ }
841+
842+ function App ( ) {
843+ return ReactServer . createElement (
844+ 'html' ,
845+ null ,
846+ ReactServer . createElement (
847+ 'body' ,
848+ null ,
849+ ReactServer . createElement (
850+ ReactServer . Suspense ,
851+ { fallback : 'Loading...' } ,
852+ ReactServer . createElement ( ServerComponent , null ) ,
853+ ) ,
854+ ) ,
855+ ) ;
856+ }
857+
858+ const errors = [ ] ;
859+ const rscStream = await serverAct ( ( ) =>
860+ ReactServerDOMServer . renderToPipeableStream (
861+ ReactServer . createElement ( App , null ) ,
862+ webpackMap ,
863+ ) ,
864+ ) ;
865+
866+ const readable = new Stream . PassThrough ( streamOptions ) ;
867+ rscStream . pipe ( readable ) ;
868+
869+ function ClientRoot ( { response} ) {
870+ return use ( response ) ;
871+ }
872+
873+ const serverConsumerManifest = {
874+ moduleMap : {
875+ [ webpackMap [ ClientComponentOnTheClient . $$id ] . id ] : {
876+ '*' : webpackMap [ ClientComponentOnTheServer . $$id ] ,
877+ } ,
878+ } ,
879+ moduleLoading : webpackModuleLoading ,
880+ } ;
881+
882+ expect ( errors ) . toEqual ( [ ] ) ;
883+
884+ function ClientRoot ( { response} ) {
885+ return use ( response ) ;
886+ }
887+
888+ const response = ReactServerDOMClient . createFromNodeStream (
889+ readable ,
890+ serverConsumerManifest ,
891+ ) ;
892+
893+ let componentStack ;
894+ let ownerStack ;
895+
896+ const clientAbortController = new AbortController ( ) ;
897+
898+ const fizzPrerenderStreamResult = ReactDOMFizzStatic . prerender (
899+ React . createElement ( ClientRoot , { response} ) ,
900+ {
901+ signal : clientAbortController . signal ,
902+ onError ( error , errorInfo ) {
903+ componentStack = errorInfo . componentStack ;
904+ ownerStack = React . captureOwnerStack
905+ ? React . captureOwnerStack ( )
906+ : null ;
907+ } ,
908+ } ,
909+ ) ;
910+
911+ resolvePendingPromise ( 'custom-instrum-resolve' ) ;
912+ await serverAct (
913+ async ( ) =>
914+ new Promise ( resolve => {
915+ setImmediate ( ( ) => {
916+ clientAbortController . abort ( ) ;
917+ resolve ( ) ;
918+ } ) ;
919+ } ) ,
920+ ) ;
921+
922+ const fizzPrerenderStream = await fizzPrerenderStreamResult ;
923+ const prerenderHTML = await readWebResult ( fizzPrerenderStream . prelude ) ;
924+
925+ expect ( prerenderHTML ) . toContain ( 'Loading...' ) ;
926+
927+ if ( __DEV__ ) {
928+ expect ( normalizeCodeLocInfo ( componentStack ) ) . toBe (
929+ '\n' +
930+ ' in SharedComponent (at **)\n' +
931+ ' in ServerComponent' +
932+ ( gate ( flags => flags . enableAsyncDebugInfo ) ? ' (at **)' : '' ) +
933+ '\n' +
934+ ' in Suspense\n' +
935+ ' in body\n' +
936+ ' in html\n' +
937+ ' in App (at **)\n' +
938+ ' in ClientRoot (at **)' ,
939+ ) ;
940+ } else {
941+ expect ( normalizeCodeLocInfo ( componentStack ) ) . toBe (
942+ '\n' +
943+ ' in SharedComponent (at **)\n' +
944+ ' in Suspense\n' +
945+ ' in body\n' +
946+ ' in html\n' +
947+ ' in ClientRoot (at **)' ,
948+ ) ;
949+ }
950+
951+ if ( __DEV__ ) {
952+ expect ( ignoreListStack ( ownerStack ) ) . toBe (
953+ // eslint-disable-next-line react-internal/safe-string-coercion
954+ '' +
955+ // The concrete location may change as this test is updated.
956+ // Just make sure they still point at React.use(p2)
957+ ( gate ( flags => flags . enableAsyncDebugInfo )
958+ ? '\n at SharedComponent (./ReactFlightDOMNode-test.js:813:7)'
959+ : '' ) +
960+ '\n at ServerComponent (file://./ReactFlightDOMNode-test.js:835:26)' +
961+ '\n at App (file://./ReactFlightDOMNode-test.js:852:25)' ,
962+ ) ;
963+ } else {
964+ expect ( ownerStack ) . toBeNull ( ) ;
965+ }
966+ } ) ;
967+
787968 // @gate enableHalt
788969 it ( 'includes deeper location for aborted stacks' , async ( ) => {
789970 async function getData ( ) {
@@ -1364,12 +1545,12 @@ describe('ReactFlightDOMNode', () => {
13641545 '\n' +
13651546 ' in Dynamic' +
13661547 ( gate ( flags => flags . enableAsyncDebugInfo )
1367- ? ' (file://ReactFlightDOMNode-test.js:1238 :27)\n'
1548+ ? ' (file://ReactFlightDOMNode-test.js:1419 :27)\n'
13681549 : '\n' ) +
13691550 ' in body\n' +
13701551 ' in html\n' +
1371- ' in App (file://ReactFlightDOMNode-test.js:1251 :25)\n' +
1372- ' in ClientRoot (ReactFlightDOMNode-test.js:1326 :16)' ,
1552+ ' in App (file://ReactFlightDOMNode-test.js:1432 :25)\n' +
1553+ ' in ClientRoot (ReactFlightDOMNode-test.js:1507 :16)' ,
13731554 ) ;
13741555 } else {
13751556 expect (
@@ -1378,7 +1559,7 @@ describe('ReactFlightDOMNode', () => {
13781559 '\n' +
13791560 ' in body\n' +
13801561 ' in html\n' +
1381- ' in ClientRoot (ReactFlightDOMNode-test.js:1326 :16)' ,
1562+ ' in ClientRoot (ReactFlightDOMNode-test.js:1507 :16)' ,
13821563 ) ;
13831564 }
13841565
@@ -1388,16 +1569,16 @@ describe('ReactFlightDOMNode', () => {
13881569 normalizeCodeLocInfo ( ownerStack , { preserveLocation : true } ) ,
13891570 ) . toBe (
13901571 '\n' +
1391- ' in Dynamic (file://ReactFlightDOMNode-test.js:1238 :27)\n' +
1392- ' in App (file://ReactFlightDOMNode-test.js:1251 :25)' ,
1572+ ' in Dynamic (file://ReactFlightDOMNode-test.js:1419 :27)\n' +
1573+ ' in App (file://ReactFlightDOMNode-test.js:1432 :25)' ,
13931574 ) ;
13941575 } else {
13951576 expect (
13961577 normalizeCodeLocInfo ( ownerStack , { preserveLocation : true } ) ,
13971578 ) . toBe (
13981579 '' +
13991580 '\n' +
1400- ' in App (file://ReactFlightDOMNode-test.js:1251 :25)' ,
1581+ ' in App (file://ReactFlightDOMNode-test.js:1432 :25)' ,
14011582 ) ;
14021583 }
14031584 } else {
0 commit comments