@khanacademy/perseus 17.3.1 → 17.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/es/index.js +14 -5
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +14 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/error-boundary.test.tsx +76 -0
- package/src/error-boundary.tsx +22 -7
- package/tsconfig-build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {screen, render} from "@testing-library/react";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import {testDependencies} from "../../../../testing/test-dependencies";
|
|
5
|
+
import * as Dependencies from "../dependencies";
|
|
6
|
+
import ErrorBoundary from "../error-boundary";
|
|
7
|
+
|
|
8
|
+
import "@testing-library/jest-dom";
|
|
9
|
+
|
|
10
|
+
const ProblematicComponent = () => {
|
|
11
|
+
throw new Error("I can haz error");
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe("error boundary", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
|
|
17
|
+
testDependencies,
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should render children", () => {
|
|
22
|
+
render(
|
|
23
|
+
<ErrorBoundary>
|
|
24
|
+
<span>Hello world</span>
|
|
25
|
+
</ErrorBoundary>,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(screen.getByText("Hello world")).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should render error icon when children error", () => {
|
|
32
|
+
render(
|
|
33
|
+
<ErrorBoundary>
|
|
34
|
+
<ProblematicComponent />
|
|
35
|
+
</ErrorBoundary>,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(screen.getByRole("img")).toBeInTheDocument();
|
|
39
|
+
expect(screen.getByTitle("Rendering Error!")).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should call the onError callback when children error", () => {
|
|
43
|
+
const onErrorCallback = jest.fn();
|
|
44
|
+
|
|
45
|
+
render(
|
|
46
|
+
<ErrorBoundary onError={onErrorCallback}>
|
|
47
|
+
<ProblematicComponent />
|
|
48
|
+
</ErrorBoundary>,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(onErrorCallback).toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should log the error when children error", () => {
|
|
55
|
+
const errorSpy = jest.spyOn(testDependencies.Log, "error");
|
|
56
|
+
|
|
57
|
+
render(
|
|
58
|
+
<ErrorBoundary>
|
|
59
|
+
<ProblematicComponent />
|
|
60
|
+
</ErrorBoundary>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
64
|
+
"Unhandled Perseus error: I can haz error",
|
|
65
|
+
"Internal",
|
|
66
|
+
{
|
|
67
|
+
cause: expect.anything(),
|
|
68
|
+
loggedMetadata: {
|
|
69
|
+
componentStack: `
|
|
70
|
+
in ProblematicComponent
|
|
71
|
+
in ErrorBoundary`,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
});
|
package/src/error-boundary.tsx
CHANGED
|
@@ -22,13 +22,27 @@ class ErrorBoundary extends React.Component<Props, State> {
|
|
|
22
22
|
componentDidCatch(error: Error, info: any) {
|
|
23
23
|
this.setState({error: error.toString()});
|
|
24
24
|
this.props.onError?.(error, info);
|
|
25
|
-
Log.error(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
Log.error(
|
|
26
|
+
// NOTE(jeremy): We concatenate the error messsage here. Typical
|
|
27
|
+
// Khan Academy error handling guidance says that you should never
|
|
28
|
+
// "build" the error message that might be sent to our error
|
|
29
|
+
// reporting tool (currently Sentry). However, if we don't
|
|
30
|
+
// differentiate between the different errors that are thrown, they
|
|
31
|
+
// all end up being grouped as a single Sentry event, which is very
|
|
32
|
+
// unhelpful.
|
|
33
|
+
"Unhandled Perseus error: " + error.message,
|
|
34
|
+
Errors.Internal,
|
|
35
|
+
{
|
|
36
|
+
cause: error,
|
|
37
|
+
loggedMetadata: {
|
|
38
|
+
componentStack:
|
|
39
|
+
!!info && "componentStack" in info
|
|
40
|
+
? info.componentStack
|
|
41
|
+
: "componentStack not provided",
|
|
42
|
+
...this.props.metadata,
|
|
43
|
+
},
|
|
30
44
|
},
|
|
31
|
-
|
|
45
|
+
);
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
render(): React.ReactNode {
|
|
@@ -39,7 +53,8 @@ class ErrorBoundary extends React.Component<Props, State> {
|
|
|
39
53
|
// errors into inline elements.
|
|
40
54
|
// TODO(michaelpolyak): Link error icon to "Report a problem".
|
|
41
55
|
return (
|
|
42
|
-
<svg height="16" width="16" viewBox="0 0 16 16">
|
|
56
|
+
<svg height="16" width="16" viewBox="0 0 16 16" role={"img"}>
|
|
57
|
+
<title>Rendering Error!</title>
|
|
43
58
|
<path
|
|
44
59
|
d="m8 16c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-3c0.55 0 1-0.45 1-1s-0.45-1-1-1-1 0.45-1 1 0.45 1 1 1zm0-9c-0.55 0-1 0.45-1 1v4c0 0.55.45 1 1 1s1-0.45 1-1v-4c0-0.55-0.45-1-1-1z"
|
|
45
60
|
fill="#d92916"
|