@@ -143,7 +143,14 @@ describe('ReactFlight', () => {
143
143
this . props . expectedMessage ,
144
144
) ;
145
145
expect ( this . state . error . digest ) . toBe ( 'a dev digest' ) ;
146
- expect ( this . state . error . environmentName ) . toBe ( 'Server' ) ;
146
+ expect ( this . state . error . environmentName ) . toBe (
147
+ this . props . expectedEnviromentName || 'Server' ,
148
+ ) ;
149
+ if ( this . props . expectedErrorStack !== undefined ) {
150
+ expect ( this . state . error . stack ) . toContain (
151
+ this . props . expectedErrorStack ,
152
+ ) ;
153
+ }
147
154
} else {
148
155
expect ( this . state . error . message ) . toBe (
149
156
'An error occurred in the Server Components render. The specific message is omitted in production' +
@@ -2603,6 +2610,104 @@ describe('ReactFlight', () => {
2603
2610
) ;
2604
2611
} ) ;
2605
2612
2613
+ it ( 'preserves error stacks passed through server-to-server with source maps' , async ( ) => {
2614
+ async function ServerComponent ( { transport} ) {
2615
+ // This is a Server Component that receives other Server Components from a third party.
2616
+ const thirdParty = ReactServer . use (
2617
+ ReactNoopFlightClient . read ( transport , {
2618
+ findSourceMapURL ( url ) {
2619
+ // By giving a source map url we're saying that we can't use the original
2620
+ // file as the sourceURL, which gives stack traces a rsc://React/ prefix.
2621
+ return 'source-map://' + url ;
2622
+ } ,
2623
+ } ) ,
2624
+ ) ;
2625
+ // This will throw a third-party error inside the first-party server component.
2626
+ await thirdParty . model ;
2627
+ return 'Should never render' ;
2628
+ }
2629
+
2630
+ async function bar ( ) {
2631
+ throw new Error ( 'third-party-error' ) ;
2632
+ }
2633
+
2634
+ async function foo ( ) {
2635
+ await bar ( ) ;
2636
+ }
2637
+
2638
+ const rejectedPromise = foo ( ) ;
2639
+
2640
+ const thirdPartyTransport = ReactNoopFlightServer . render (
2641
+ { model : rejectedPromise } ,
2642
+ {
2643
+ environmentName : 'third-party' ,
2644
+ onError ( x ) {
2645
+ if ( __DEV__ ) {
2646
+ return 'a dev digest' ;
2647
+ }
2648
+ return `digest("${ x . message } ")` ;
2649
+ } ,
2650
+ } ,
2651
+ ) ;
2652
+
2653
+ let originalError ;
2654
+ try {
2655
+ await rejectedPromise ;
2656
+ } catch ( x ) {
2657
+ originalError = x ;
2658
+ }
2659
+ expect ( originalError . message ) . toBe ( 'third-party-error' ) ;
2660
+
2661
+ const transport = ReactNoopFlightServer . render (
2662
+ < ServerComponent transport = { thirdPartyTransport } /> ,
2663
+ {
2664
+ onError ( x ) {
2665
+ if ( __DEV__ ) {
2666
+ return 'a dev digest' ;
2667
+ }
2668
+ return x . digest ; // passthrough
2669
+ } ,
2670
+ } ,
2671
+ ) ;
2672
+
2673
+ await 0 ;
2674
+ await 0 ;
2675
+ await 0 ;
2676
+
2677
+ const expectedErrorStack = originalError . stack
2678
+ // Test only the first rows since there's a lot of noise after that is eliminated.
2679
+ . split ( '\n' )
2680
+ . slice ( 0 , 4 )
2681
+ . join ( '\n' )
2682
+ . replaceAll (
2683
+ ' (/' ,
2684
+ gate ( flags => flags . enableOwnerStacks ) ? ' (file:///' : ' (/' ,
2685
+ ) ; // The eval will end up normalizing these
2686
+
2687
+ let sawReactPrefix = false ;
2688
+ await act ( async ( ) => {
2689
+ ReactNoop . render (
2690
+ < ErrorBoundary
2691
+ expectedMessage = "third-party-error"
2692
+ expectedEnviromentName = "third-party"
2693
+ expectedErrorStack = { expectedErrorStack } >
2694
+ { ReactNoopFlightClient . read ( transport , {
2695
+ findSourceMapURL ( url ) {
2696
+ if ( url . startsWith ( 'rsc://React/' ) ) {
2697
+ // We don't expect to see any React prefixed URLs here.
2698
+ sawReactPrefix = true ;
2699
+ }
2700
+ // My not giving a source map, we should leave it intact.
2701
+ return null ;
2702
+ } ,
2703
+ } ) }
2704
+ </ ErrorBoundary > ,
2705
+ ) ;
2706
+ } ) ;
2707
+
2708
+ expect ( sawReactPrefix ) . toBe ( false ) ;
2709
+ } ) ;
2710
+
2606
2711
it ( 'can change the environment name inside a component' , async ( ) => {
2607
2712
let env = 'A' ;
2608
2713
function Component ( props ) {
0 commit comments