83
' builds to avoid leaking sensitive details. A digest property is included on this error instance which' +
84
' may provide additional details about the nature of the error.',
85
);
86
- expect(this.state.error.digest).toContain(this.props.expectedMessage);
86
+ let expectedDigest = this.props.expectedMessage;
87
+ if (
88
+ expectedDigest.startsWith('Error: {') ||
89
+ expectedDigest.startsWith('Error: <')
90
+ ) {
91
+ expectedDigest = '{}';
92
+ } else if (expectedDigest.startsWith('Error: [')) {
93
+ expectedDigest = '[]';
94
+ }
95
+ expect(this.state.error.digest).toContain(expectedDigest);
96
expect(this.state.error.stack).toBe(
97
'Error: ' + this.state.error.message,
98
);
785
});
786
});
787
788
+ it('should emit descriptions of errors in dev', async () => {
789
+ const ClientErrorBoundary = clientReference(ErrorBoundary);
790
+
791
+ function Throw({value}) {
792
+ throw value;
793
+ }
794
+
795
+ const testCases = (
796
+ <>
797
+ <ClientErrorBoundary expectedMessage="This is a real Error.">
798
+ <div>
799
+ <Throw value={new TypeError('This is a real Error.')} />
800
+ </div>
801
+ </ClientErrorBoundary>
802
+ <ClientErrorBoundary expectedMessage="Error: This is a string error.">
803
+ <div>
804
+ <Throw value="This is a string error." />
805
+ </div>
806
+ </ClientErrorBoundary>
807
+ <ClientErrorBoundary expectedMessage="Error: {message: ..., extra: ..., nested: ...}">
808
+ <div>
809
+ <Throw
810
+ value={{
811
+ message: 'This is a long message',
812
+ extra: 'properties',
813
+ nested: {more: 'prop'},
814
+ }}
815
+ />
816
+ </div>
817
+ </ClientErrorBoundary>
818
+ <ClientErrorBoundary
819
+ expectedMessage={
820
+ 'Error: {message: "Short", extra: ..., nested: ...}'
821
+ }>
822
+ <div>
823
+ <Throw
824
+ value={{
825
+ message: 'Short',
826
+ extra: 'properties',
827
+ nested: {more: 'prop'},
828
+ }}
829
+ />
830
+ </div>
831
+ </ClientErrorBoundary>
832
+ <ClientErrorBoundary expectedMessage="Error: Symbol(hello)">
833
+ <div>
834
+ <Throw value={Symbol('hello')} />
835
+ </div>
836
+ </ClientErrorBoundary>
837
+ <ClientErrorBoundary expectedMessage="Error: 123">
838
+ <div>
839
+ <Throw value={123} />
840
+ </div>
841
+ </ClientErrorBoundary>
842
+ <ClientErrorBoundary expectedMessage="Error: undefined">
843
+ <div>
844
+ <Throw value={undefined} />
845
+ </div>
846
+ </ClientErrorBoundary>
847
+ <ClientErrorBoundary expectedMessage="Error: <div/>">
848
+ <div>
849
+ <Throw value={<div />} />
850
+ </div>
851
+ </ClientErrorBoundary>
852
+ <ClientErrorBoundary expectedMessage="Error: function Foo() {}">
853
+ <div>
854
+ <Throw value={function Foo() {}} />
855
+ </div>
856
+ </ClientErrorBoundary>
857
+ <ClientErrorBoundary expectedMessage={'Error: ["array"]'}>
858
+ <div>
859
+ <Throw value={['array']} />
860
+ </div>
861
+ </ClientErrorBoundary>
862
+ </>
863
+ );
864
+
865
+ const transport = ReactNoopFlightServer.render(testCases, {
866
+ onError(x) {
867
+ if (__DEV__) {
868
+ return 'a dev digest';
869
+ }
870
+ if (x instanceof Error) {
871
+ return `digest("${x.message}")`;
872
+ } else if (Array.isArray(x)) {
873
+ return `digest([])`;
874
+ } else if (typeof x === 'object' && x !== null) {
875
+ return `digest({})`;
876
+ }
877
+ return `digest(Error: ${String(x)})`;
878
+ },
879
+ });
880
+
881
+ await act(() => {
882
+ startTransition(() => {
883
+ ReactNoop.render(ReactNoopFlightClient.read(transport));
884
+ });
885
+ });
886
+ });
887
+
888
it('should trigger the inner most error boundary inside a Client Component', async () => {
889
function ServerComponent() {
890
throw new Error('This was thrown in the Server Component.');