143
this.props.expectedMessage,
144
);
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
+ }
154
} else {
155
expect(this.state.error.message).toBe(
156
'An error occurred in the Server Components render. The specific message is omitted in production' +
2610
);
2611
});
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
+
2711
it('can change the environment name inside a component', async () => {
2712
let env = 'A';
2713
function Component(props) {