@@ -108,6 +108,31 @@ 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 (
125+ line . indexOf ( __filename ) === - 1 &&
126+ line . indexOf ( '<anonymous>' ) === - 1
127+ ) {
128+ } else {
129+ ignoreListedStack += '\n' + line . replace ( __dirname , '.' ) ;
130+ }
131+ }
132+
133+ return ignoreListedStack ;
134+ }
135+
111136 function readResult ( stream ) {
112137 return new Promise ( ( resolve , reject ) => {
113138 let buffer = '' ;
@@ -762,6 +787,156 @@ describe('ReactFlightDOMNode', () => {
762787 }
763788 } ) ;
764789
790+ // @gate enableHalt
791+ it ( 'includes source locations in component and owner stacks for halted Client components' , async ( ) => {
792+ function SharedComponent ( { p1, p2, p3} ) {
793+ use ( p1 ) ;
794+ use ( p2 ) ;
795+ use ( p3 ) ;
796+ return < div > Hello, Dave!</ div > ;
797+ }
798+ const ClientComponentOnTheServer = clientExports ( SharedComponent ) ;
799+ const ClientComponentOnTheClient = clientExports (
800+ SharedComponent ,
801+ 123 ,
802+ 'path/to/chunk.js' ,
803+ ) ;
804+
805+ let resolvePendingPromise ;
806+ function ServerComponent ( ) {
807+ const p1 = Promise . resolve ( ) ;
808+ const p2 = new Promise ( resolve => {
809+ resolvePendingPromise = value => {
810+ p2 . status = 'fulfilled' ;
811+ p2 . value = value ;
812+ } ;
813+ } ) ;
814+ const p3 = new Promise ( ( ) => { } ) ;
815+ return ReactServer . createElement ( ClientComponentOnTheClient , {
816+ p1 : p1 ,
817+ p2 : p2 ,
818+ p3 : p3 ,
819+ } ) ;
820+ }
821+
822+ function App ( ) {
823+ return ReactServer . createElement (
824+ 'html' ,
825+ null ,
826+ ReactServer . createElement (
827+ 'body' ,
828+ null ,
829+ ReactServer . createElement (
830+ ReactServer . Suspense ,
831+ { fallback : 'Loading...' } ,
832+ ReactServer . createElement ( ServerComponent , null ) ,
833+ ) ,
834+ ) ,
835+ ) ;
836+ }
837+
838+ const errors = [ ] ;
839+ const rscStream = await serverAct ( ( ) =>
840+ ReactServerDOMServer . renderToPipeableStream (
841+ ReactServer . createElement ( App , null ) ,
842+ webpackMap ,
843+ ) ,
844+ ) ;
845+
846+ const readable = new Stream . PassThrough ( streamOptions ) ;
847+ rscStream . pipe ( readable ) ;
848+
849+ function ClientRoot ( { response} ) {
850+ return use ( response ) ;
851+ }
852+
853+ const serverConsumerManifest = {
854+ moduleMap : {
855+ [ webpackMap [ ClientComponentOnTheClient . $$id ] . id ] : {
856+ '*' : webpackMap [ ClientComponentOnTheServer . $$id ] ,
857+ } ,
858+ } ,
859+ moduleLoading : webpackModuleLoading ,
860+ } ;
861+
862+ expect ( errors ) . toEqual ( [ ] ) ;
863+
864+ function ClientRoot ( { response} ) {
865+ return use ( response ) ;
866+ }
867+
868+ const response = ReactServerDOMClient . createFromNodeStream (
869+ readable ,
870+ serverConsumerManifest ,
871+ ) ;
872+
873+ let componentStack ;
874+ let ownerStack ;
875+
876+ const clientAbortController = new AbortController ( ) ;
877+
878+ const fizzPrerenderStreamResult = ReactDOMFizzStatic . prerender (
879+ React . createElement ( ClientRoot , { response} ) ,
880+ {
881+ signal : clientAbortController . signal ,
882+ onError ( error , errorInfo ) {
883+ componentStack = errorInfo . componentStack ;
884+ ownerStack = React . captureOwnerStack
885+ ? React . captureOwnerStack ( )
886+ : null ;
887+ } ,
888+ } ,
889+ ) ;
890+
891+ await serverAct (
892+ async ( ) =>
893+ new Promise ( resolve => {
894+ setImmediate ( ( ) => {
895+ resolvePendingPromise ( ) ;
896+ clientAbortController . abort ( ) ;
897+ resolve ( ) ;
898+ } ) ;
899+ } ) ,
900+ ) ;
901+
902+ const fizzPrerenderStream = await fizzPrerenderStreamResult ;
903+ const prerenderHTML = await readWebResult ( fizzPrerenderStream . prelude ) ;
904+
905+ expect ( prerenderHTML ) . toContain ( 'Loading...' ) ;
906+
907+ if ( __DEV__ ) {
908+ expect ( normalizeCodeLocInfo ( componentStack ) ) . toBe (
909+ '\n' +
910+ ' in SharedComponent (at **)\n' +
911+ ' in ServerComponent\n' +
912+ ' in Suspense\n' +
913+ ' in body\n' +
914+ ' in html\n' +
915+ ' in App (at **)\n' +
916+ ' in ClientRoot (at **)' ,
917+ ) ;
918+ } else {
919+ expect ( normalizeCodeLocInfo ( componentStack ) ) . toBe (
920+ '\n' +
921+ ' in SharedComponent (at **)\n' +
922+ ' in Suspense\n' +
923+ ' in body\n' +
924+ ' in html\n' +
925+ ' in ClientRoot (at **)' ,
926+ ) ;
927+ }
928+
929+ if ( __DEV__ ) {
930+ expect ( ignoreListStack ( ownerStack ) ) . toBe (
931+ '' +
932+ '\n at ServerComponent (file://./ReactFlightDOMNode-test.js:815:26)' +
933+ '\n at App (file://./ReactFlightDOMNode-test.js:832:25)' ,
934+ ) ;
935+ } else {
936+ expect ( ownerStack ) . toBeNull ( ) ;
937+ }
938+ } ) ;
939+
765940 // @gate enableHalt
766941 it ( 'includes deeper location for aborted stacks' , async ( ) => {
767942 async function getData ( ) {
@@ -1346,12 +1521,12 @@ describe('ReactFlightDOMNode', () => {
13461521 '\n' +
13471522 ' in Dynamic' +
13481523 ( gate ( flags => flags . enableAsyncDebugInfo )
1349- ? ' (file://ReactFlightDOMNode-test.js:1216 :27)\n'
1524+ ? ' (file://ReactFlightDOMNode-test.js:1391 :27)\n'
13501525 : '\n' ) +
13511526 ' in body\n' +
13521527 ' in html\n' +
1353- ' in App (file://ReactFlightDOMNode-test.js:1233 :25)\n' +
1354- ' in ClientRoot (ReactFlightDOMNode-test.js:1308 :16)' ,
1528+ ' in App (file://ReactFlightDOMNode-test.js:1408 :25)\n' +
1529+ ' in ClientRoot (ReactFlightDOMNode-test.js:1483 :16)' ,
13551530 ) ;
13561531 } else {
13571532 expect (
@@ -1360,7 +1535,7 @@ describe('ReactFlightDOMNode', () => {
13601535 '\n' +
13611536 ' in body\n' +
13621537 ' in html\n' +
1363- ' in ClientRoot (ReactFlightDOMNode-test.js:1308 :16)' ,
1538+ ' in ClientRoot (ReactFlightDOMNode-test.js:1483 :16)' ,
13641539 ) ;
13651540 }
13661541
@@ -1370,16 +1545,16 @@ describe('ReactFlightDOMNode', () => {
13701545 normalizeCodeLocInfo ( ownerStack , { preserveLocation : true } ) ,
13711546 ) . toBe (
13721547 '\n' +
1373- ' in Dynamic (file://ReactFlightDOMNode-test.js:1216 :27)\n' +
1374- ' in App (file://ReactFlightDOMNode-test.js:1233 :25)' ,
1548+ ' in Dynamic (file://ReactFlightDOMNode-test.js:1391 :27)\n' +
1549+ ' in App (file://ReactFlightDOMNode-test.js:1408 :25)' ,
13751550 ) ;
13761551 } else {
13771552 expect (
13781553 normalizeCodeLocInfo ( ownerStack , { preserveLocation : true } ) ,
13791554 ) . toBe (
13801555 '' +
13811556 '\n' +
1382- ' in App (file://ReactFlightDOMNode-test.js:1233 :25)' ,
1557+ ' in App (file://ReactFlightDOMNode-test.js:1408 :25)' ,
13831558 ) ;
13841559 }
13851560 } else {
0 commit comments