@samitouri / QOS-React / commits / f7c8e3a05c

Experimenting with profiler tests

Brian Vaughn committed May 3, 2019 at 15:01 UTC f7c8e3a05c9dd4f32891f563efa4307dc1333f5a
6 files changed +217 -24
package.json
+2
@@ -23,6 +23,7 @@
23 "<rootDir>/src/__tests__/setupTests"
24 ],
25 "snapshotSerializers": [
26 + "<rootDir>/src/__tests__/profilingSummarySerializer",
27 "<rootDir>/src/__tests__/storeSerializer"
28 ],
29 "testMatch": [
@@ -134,6 +135,7 @@
135 "react-color": "^2.11.7",
136 "react-dom": "0.0.0-fb28e9048",
137 "react-is": "0.0.0-fb28e9048",
138 + "react-test-renderer": "0.0.0-fb28e9048",
139 "react-virtualized-auto-sizer": "^1.0.2",
140 "react-window": "^1.8.0",
141 "request-promise": "^4.2.4",
src/__tests__/__snapshots__/profiler-test.js.snap
+92 -13
@@ -1,31 +1,110 @@
1 // Jest Snapshot v1, https://goo.gl/fbAQLP
2
3 -exports[`Profiler should start and stop profiling, handle root unmounting: 1: mount 1`] = `
3 +exports[`Profiler should clean up after a root has been unmounted: 1: mount 1`] = `
4 [root]
5 - ▸ <Parent key="A">
5 + ▾ <Parent key="A">
6 + <Child key="0">
7 + <Child key="1">
8 + <Child key="2">
9 [root]
7 - ▸ <Parent key="B">
10 + ▾ <Parent key="B">
11 + <Child key="0">
12 + <Child key="1">
13 `;
14
10 -exports[`Profiler should start and stop profiling, handle root unmounting: 2: profiling started 1`] = `
15 +exports[`Profiler should clean up after a root has been unmounted: 2: profiling started 1`] = `
16 [root]
12 - ▸ <Parent key="A">
17 + ▾ <Parent key="A">
18 + <Child key="0">
19 + <Child key="1">
20 + <Child key="2">
21 [root]
14 - ▸ <Parent key="B">
22 + ▾ <Parent key="B">
23 + <Child key="0">
24 + <Child key="1">
25 `;
26
17 -exports[`Profiler should start and stop profiling, handle root unmounting: 3: update 1`] = `
27 +exports[`Profiler should clean up after a root has been unmounted: 3: update 1`] = `
28 [root]
19 - ▸ <Parent key="A">
29 + ▾ <Parent key="A">
30 + <Child key="0">
31 + <Child key="1">
32 + <Child key="2">
33 + <Child key="3">
34 [root]
21 - ▸ <Parent key="B">
35 + ▾ <Parent key="B">
36 + <Child key="0">
37 `;
38
24 -exports[`Profiler should start and stop profiling, handle root unmounting: 4: unmount B 1`] = `
39 +exports[`Profiler should clean up after a root has been unmounted: 4: unmount B 1`] = `
40 [root]
26 - ▸ <Parent key="A">
41 + ▾ <Parent key="A">
42 + <Child key="0">
43 + <Child key="1">
44 + <Child key="2">
45 + <Child key="3">
46 `;
47
29 -exports[`Profiler should start and stop profiling, handle root unmounting: 5: unmount A 1`] = ``;
48 +exports[`Profiler should clean up after a root has been unmounted: 5: unmount A 1`] = ``;
49
31 -exports[`Profiler should start and stop profiling, handle root unmounting: 6: profiling stopped 1`] = ``;
50 +exports[`Profiler should clean up after a root has been unmounted: 6: profiling stopped 1`] = ``;
51 +
52 +exports[`Profiler should collect basic profiling metrics: 1: mount 1`] = `
53 +[root]
54 + ▾ <Parent key="A">
55 + <Child key="0">
56 + <Child key="1">
57 +`;
58 +
59 +exports[`Profiler should collect basic profiling metrics: 2: add child 1`] = `
60 +[root]
61 + ▾ <Parent key="A">
62 + <Child key="0">
63 + <Child key="1">
64 + <Child key="2">
65 +`;
66 +
67 +exports[`Profiler should collect basic profiling metrics: 3: remove children 1`] = `
68 +[root]
69 + ▾ <Parent key="A">
70 + <Child key="0">
71 +`;
72 +
73 +exports[`Profiler should collect basic profiling metrics: 4: profiling stopped 1`] = `
74 +[root]
75 + ▾ <Parent key="A">
76 + <Child key="0">
77 +`;
78 +
79 +exports[`Profiler should collect basic profiling metrics: ProfilingSummary 1`] = `
80 +{
81 + "rootID": 1,
82 + "commitDurations": [
83 + 0,
84 + 1
85 + ],
86 + "commitTimes": [
87 + 0,
88 + 1
89 + ],
90 + "initialTreeBaseDurations": [
91 + [
92 + 1,
93 + 0
94 + ],
95 + [
96 + 2,
97 + 1
98 + ],
99 + [
100 + 3,
101 + 2
102 + ],
103 + [
104 + 4,
105 + 3
106 + ]
107 + ],
108 + "interactionCount": 0
109 +}
110 +`;
src/__tests__/profiler-test.js
+78 -7
@@ -3,7 +3,9 @@
3 describe('Profiler', () => {
4 let React;
5 let ReactDOM;
6 + let TestRenderer;
7 let TestUtils;
8 + let agent;
9 let store;
10
11 const act = (callback: Function) => {
@@ -13,15 +15,88 @@ describe('Profiler', () => {
15 jest.runAllTimers(); // Flush Bridge operations
16 };
17
18 + const renderAndResolve = async (root, element) => {
19 + // $FlowFixMe Flow doens't know about "await act()" yet
20 + await TestUtils.act(async () => {
21 + root.update(element);
22 +
23 + // Resolve pending suspense promises
24 + jest.runAllTimers();
25 + });
26 +
27 + // Re-render after resolved promises
28 + jest.runAllTimers();
29 + };
30 +
31 beforeEach(() => {
32 + agent = global.agent;
33 store = global.store;
34 + store.collapseNodesByDefault = false;
35
36 React = require('react');
37 ReactDOM = require('react-dom');
38 TestUtils = require('react-dom/test-utils');
39 +
40 + // Hide the hook before requiring TestRenderer, so we don't end up with a loop.
41 + const hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
42 + delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
43 + TestRenderer = require('react-test-renderer');
44 + global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;
45 + });
46 +
47 + it('should collect basic profiling metrics', async done => {
48 + const Parent = ({ count }) =>
49 + new Array(count).fill(true).map((_, index) => <Child key={index} />);
50 + const Child = () => {
51 + jest.advanceTimersByTime(1);
52 + return null;
53 + };
54 +
55 + const container = document.createElement('div');
56 +
57 + act(() => ReactDOM.render(<Parent key="A" count={2} />, container));
58 + expect(store).toMatchSnapshot('1: mount');
59 +
60 + act(() => store.startProfiling());
61 +
62 + act(() => ReactDOM.render(<Parent key="A" count={3} />, container));
63 + expect(store).toMatchSnapshot('2: add child');
64 +
65 + act(() => ReactDOM.render(<Parent key="A" count={1} />, container));
66 + expect(store).toMatchSnapshot('3: remove children');
67 +
68 + act(() => store.stopProfiling());
69 + expect(store).toMatchSnapshot('4: profiling stopped');
70 +
71 + let profilingSummary;
72 + function Suspender({ rendererID, rootID }) {
73 + profilingSummary = store.profilingCache.ProfilingSummary.read({
74 + rendererID,
75 + rootID,
76 + });
77 + return null;
78 + }
79 +
80 + // HACK There's only one renderer for this test
81 + const rendererID = Object.keys(agent._rendererInterfaces)[0];
82 + const rootID = store.roots[0];
83 +
84 + let root = TestRenderer.create();
85 + await renderAndResolve(
86 + root,
87 + <React.Suspense fallback={null}>
88 + <Suspender rendererID={rendererID} rootID={rootID} />
89 + </React.Suspense>
90 + );
91 +
92 + // HACK root.toTree() doesn't handle Suspense yet
93 + // but Jest serializer wouldn't work with a JSON string
94 + expect(profilingSummary).toMatchSnapshot('ProfilingSummary');
95 +
96 + done();
97 });
98
24 - it('should start and stop profiling, handle root unmounting', async () => {
99 + it('should clean up after a root has been unmounted', async () => {
100 const Parent = ({ count }) =>
101 new Array(count).fill(true).map((_, index) => <Child key={index} />);
102 const Child = () => <div>Hi!</div>;
@@ -35,9 +110,7 @@ describe('Profiler', () => {
110 });
111 expect(store).toMatchSnapshot('1: mount');
112
38 - act(() => {
39 - store.startProfiling();
40 - });
113 + act(() => store.startProfiling());
114 expect(store).toMatchSnapshot('2: profiling started');
115
116 act(() => {
@@ -52,9 +125,7 @@ describe('Profiler', () => {
125 act(() => ReactDOM.unmountComponentAtNode(containerA));
126 expect(store).toMatchSnapshot('5: unmount A');
127
55 - act(() => {
56 - store.stopProfiling();
57 - });
128 + act(() => store.stopProfiling());
129 expect(store).toMatchSnapshot('6: profiling stopped');
130 });
131 });
src/__tests__/profilingSummarySerializer.js new
+29
@@ -0,0 +1,29 @@
1 +// test() is part of Jest's serializer API
2 +export function test(maybeProfilingSummary) {
3 + return (
4 + typeof maybeProfilingSummary === 'object' &&
5 + maybeProfilingSummary !== null &&
6 + typeof maybeProfilingSummary.rootID === 'number' &&
7 + Array.isArray(maybeProfilingSummary.commitDurations) &&
8 + Array.isArray(maybeProfilingSummary.commitTimes) &&
9 + typeof maybeProfilingSummary.initialTreeBaseDurations === 'object' &&
10 + maybeProfilingSummary.initialTreeBaseDurations !== null &&
11 + typeof maybeProfilingSummary.interactionCount === 'number'
12 + );
13 +}
14 +
15 +// print() is part of Jest's serializer API
16 +export function print(profilingSummary, serialize, indent) {
17 + return JSON.stringify(
18 + {
19 + ...profilingSummary,
20 + commitDurations: profilingSummary.commitDurations.map((_, i) => i),
21 + commitTimes: profilingSummary.commitTimes.map((_, i) => i),
22 + initialTreeBaseDurations: [
23 + ...profilingSummary.initialTreeBaseDurations,
24 + ].map(([id, _], index) => [id, index]),
25 + },
26 + null,
27 + 2
28 + );
29 +}
src/__tests__/setupTests.js
+6 -4
@@ -8,10 +8,6 @@ import { installHook } from 'src/hook';
8
9 const env = jasmine.getEnv();
10 env.beforeEach(() => {
11 - // It's important to reset modules between test runs;
12 - // Without this, ReactDOM won't re-inject itself into the new hook.
13 - jest.resetModules();
14 -
11 // Fake timers let us flush Bridge operations between setup and assertions.
12 jest.useFakeTimers();
13
@@ -55,4 +51,10 @@ env.beforeEach(() => {
51 });
52 env.afterEach(() => {
53 delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
54 +
55 + // It's important to reset modules between test runs;
56 + // Without this, ReactDOM won't re-inject itself into the new hook.
57 + // It's also important to reset after tests, rather than before,
58 + // so that we don't disconnect the ReactCurrentDispatcher ref.
59 + jest.resetModules();
60 });
yarn.lock
+10
@@ -9837,6 +9837,16 @@ react-lifecycles-compat@^3.0.4:
9837 resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
9838 integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
9839
9840 +react-test-renderer@0.0.0-fb28e9048:
9841 + version "0.0.0-fb28e9048"
9842 + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-0.0.0-fb28e9048.tgz#1a94c8d19cbb1ac98ab37c66c0b294e5be280c52"
9843 + integrity sha512-WK/wQOh0v6+8Gbkurgb3he9hKoOKWueqQY+RFs2vM3u3vn7PMyYhzm/KkU75VvTG/GVciojNQBHBpekuvU5dYw==
9844 + dependencies:
9845 + object-assign "^4.1.1"
9846 + prop-types "^15.6.2"
9847 + react-is "0.0.0-fb28e9048"
9848 + scheduler "0.0.0-fb28e9048"
9849 +
9850 react-virtualized-auto-sizer@^1.0.2:
9851 version "1.0.2"
9852 resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"