Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 7336002

Browse files
authored
Convert remaining hook tests to RTL (#10166)
* convert useProfileInfo to RTL * convert useLatestResult to RTL
1 parent 8feed1a commit 7336002

File tree

2 files changed

+152
-154
lines changed

2 files changed

+152
-154
lines changed

test/hooks/useLatestResult-test.tsx

Lines changed: 111 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -14,77 +14,123 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
// eslint-disable-next-line deprecate/import
18-
import { mount } from "enzyme";
19-
import { sleep } from "matrix-js-sdk/src/utils";
20-
import React, { useEffect, useState } from "react";
21-
import { act } from "react-dom/test-utils";
17+
import { renderHook, RenderHookResult } from "@testing-library/react-hooks/dom";
2218

2319
import { useLatestResult } from "../../src/hooks/useLatestResult";
2420

25-
function LatestResultsComponent({ query, doRequest }: { query: number; doRequest(query: number): Promise<number> }) {
26-
const [value, setValueInternal] = useState<number>(0);
27-
const [updateQuery, updateResult] = useLatestResult(setValueInternal);
28-
useEffect(() => {
29-
updateQuery(query);
30-
doRequest(query).then((it: number) => {
31-
updateResult(query, it);
32-
});
33-
}, [doRequest, query, updateQuery, updateResult]);
34-
35-
return <div>{value}</div>;
21+
// All tests use fake timers throughout, comments will show the elapsed time in ms
22+
jest.useFakeTimers();
23+
24+
const mockSetter = jest.fn();
25+
26+
beforeEach(() => {
27+
mockSetter.mockClear();
28+
});
29+
30+
function simulateRequest(
31+
hookResult: RenderHookResult<typeof useLatestResult, ReturnType<typeof useLatestResult>>["result"],
32+
{ id, delayInMs, result }: { id: string; delayInMs: number; result: string },
33+
) {
34+
const [setQuery, setResult] = hookResult.current;
35+
setQuery(id);
36+
setTimeout(() => setResult(id, result), delayInMs);
3637
}
3738

38-
describe("useLatestResult", () => {
39-
it("should return results", async () => {
40-
const doRequest = async (query: number) => {
41-
await sleep(180);
42-
return query;
43-
};
44-
45-
const wrapper = mount(<LatestResultsComponent query={0} doRequest={doRequest} />);
46-
await act(async () => {
47-
await sleep(100);
48-
});
49-
expect(wrapper.text()).toEqual("0");
50-
wrapper.setProps({ doRequest, query: 1 });
51-
await act(async () => {
52-
await sleep(70);
53-
});
54-
wrapper.setProps({ doRequest, query: 2 });
55-
await act(async () => {
56-
await sleep(70);
57-
});
58-
expect(wrapper.text()).toEqual("0");
59-
await act(async () => {
60-
await sleep(120);
61-
});
62-
expect(wrapper.text()).toEqual("2");
39+
describe("renderhook tests", () => {
40+
it("should return a result", () => {
41+
const { result: hookResult } = renderHook(() => useLatestResult(mockSetter));
42+
43+
const query = { id: "query1", delayInMs: 100, result: "result1" };
44+
simulateRequest(hookResult, query);
45+
46+
// check we have made no calls to the setter
47+
expect(mockSetter).not.toHaveBeenCalled();
48+
49+
// advance timer until the timeout elapses, check we have called the setter
50+
jest.advanceTimersToNextTimer();
51+
expect(mockSetter).toHaveBeenCalledTimes(1);
52+
expect(mockSetter).toHaveBeenLastCalledWith(query.result);
6353
});
6454

65-
it("should prevent out-of-order results", async () => {
66-
const doRequest = async (query: number) => {
67-
await sleep(query);
68-
return query;
69-
};
70-
71-
const wrapper = mount(<LatestResultsComponent query={0} doRequest={doRequest} />);
72-
await act(async () => {
73-
await sleep(5);
74-
});
75-
expect(wrapper.text()).toEqual("0");
76-
wrapper.setProps({ doRequest, query: 50 });
77-
await act(async () => {
78-
await sleep(5);
79-
});
80-
wrapper.setProps({ doRequest, query: 1 });
81-
await act(async () => {
82-
await sleep(5);
83-
});
84-
expect(wrapper.text()).toEqual("1");
85-
await act(async () => {
86-
await sleep(50);
87-
});
88-
expect(wrapper.text()).toEqual("1");
55+
it("should not let a slower response to an earlier query overwrite the result of a later query", () => {
56+
const { result: hookResult } = renderHook(() => useLatestResult(mockSetter));
57+
58+
const slowQuery = { id: "slowQuery", delayInMs: 500, result: "slowResult" };
59+
const fastQuery = { id: "fastQuery", delayInMs: 100, result: "fastResult" };
60+
61+
simulateRequest(hookResult, slowQuery);
62+
simulateRequest(hookResult, fastQuery);
63+
64+
// advance to fastQuery response, check the setter call
65+
jest.advanceTimersToNextTimer();
66+
expect(mockSetter).toHaveBeenCalledTimes(1);
67+
expect(mockSetter).toHaveBeenLastCalledWith(fastQuery.result);
68+
69+
// advance time to slowQuery response, check the setter has _not_ been
70+
// called again and that the result is still from the fast query
71+
jest.advanceTimersToNextTimer();
72+
expect(mockSetter).toHaveBeenCalledTimes(1);
73+
expect(mockSetter).toHaveBeenLastCalledWith(fastQuery.result);
74+
});
75+
76+
it("should return expected results when all response times similar", () => {
77+
const { result: hookResult } = renderHook(() => useLatestResult(mockSetter));
78+
79+
const commonDelayInMs = 180;
80+
const query1 = { id: "q1", delayInMs: commonDelayInMs, result: "r1" };
81+
const query2 = { id: "q2", delayInMs: commonDelayInMs, result: "r2" };
82+
const query3 = { id: "q3", delayInMs: commonDelayInMs, result: "r3" };
83+
84+
// ELAPSED: 0ms, no queries sent
85+
simulateRequest(hookResult, query1);
86+
jest.advanceTimersByTime(100);
87+
88+
// ELAPSED: 100ms, query1 sent, no responses
89+
expect(mockSetter).not.toHaveBeenCalled();
90+
simulateRequest(hookResult, query2);
91+
jest.advanceTimersByTime(70);
92+
93+
// ELAPSED: 170ms, query1 and query2 sent, no responses
94+
expect(mockSetter).not.toHaveBeenCalled();
95+
simulateRequest(hookResult, query3);
96+
jest.advanceTimersByTime(70);
97+
98+
// ELAPSED: 240ms, all queries sent, responses for query1 and query2
99+
expect(mockSetter).not.toHaveBeenCalled();
100+
101+
// ELAPSED: 360ms, all queries sent, all queries have responses
102+
jest.advanceTimersByTime(120);
103+
expect(mockSetter).toHaveBeenLastCalledWith(query3.result);
104+
});
105+
106+
it("should prevent out of order results", () => {
107+
const { result: hookResult } = renderHook(() => useLatestResult(mockSetter));
108+
109+
const query1 = { id: "q1", delayInMs: 0, result: "r1" };
110+
const query2 = { id: "q2", delayInMs: 50, result: "r2" };
111+
const query3 = { id: "q3", delayInMs: 1, result: "r3" };
112+
113+
// ELAPSED: 0ms, no queries sent
114+
simulateRequest(hookResult, query1);
115+
jest.advanceTimersByTime(5);
116+
117+
// ELAPSED: 5ms, query1 sent, response from query1
118+
expect(mockSetter).toHaveBeenCalledTimes(1);
119+
expect(mockSetter).toHaveBeenLastCalledWith(query1.result);
120+
simulateRequest(hookResult, query2);
121+
jest.advanceTimersByTime(5);
122+
123+
// ELAPSED: 10ms, query1 and query2 sent, response from query1
124+
simulateRequest(hookResult, query3);
125+
jest.advanceTimersByTime(5);
126+
127+
// ELAPSED: 15ms, all queries sent, responses from query1 and query3
128+
expect(mockSetter).toHaveBeenCalledTimes(2);
129+
expect(mockSetter).toHaveBeenLastCalledWith(query3.result);
130+
131+
// ELAPSED: 65ms, all queries sent, all queries have responses
132+
// so check that the result is still from query3, not query2
133+
jest.advanceTimersByTime(50);
134+
expect(mockSetter).toHaveBeenLastCalledWith(query3.result);
89135
});
90136
});

test/hooks/useProfileInfo-test.tsx

Lines changed: 41 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,16 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
// eslint-disable-next-line deprecate/import
18-
import { mount } from "enzyme";
17+
import { waitFor } from "@testing-library/react";
18+
import { renderHook, act } from "@testing-library/react-hooks/dom";
1919
import { MatrixClient } from "matrix-js-sdk/src/matrix";
20-
import { sleep } from "matrix-js-sdk/src/utils";
21-
import React from "react";
22-
import { act } from "react-dom/test-utils";
2320

2421
import { useProfileInfo } from "../../src/hooks/useProfileInfo";
2522
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
2623
import { stubClient } from "../test-utils/test-utils";
2724

28-
function ProfileInfoComponent({ onClick }: { onClick(hook: ReturnType<typeof useProfileInfo>): void }) {
29-
const profileInfo = useProfileInfo();
30-
31-
const { ready, loading, profile } = profileInfo;
32-
33-
return (
34-
<div onClick={() => onClick(profileInfo)}>
35-
{(!ready || loading) && `ready: ${ready}, loading: ${loading}`}
36-
{profile && `Name: ${profile.display_name}`}
37-
</div>
38-
);
25+
function render() {
26+
return renderHook(() => useProfileInfo());
3927
}
4028

4129
describe("useProfileInfo", () => {
@@ -55,66 +43,44 @@ describe("useProfileInfo", () => {
5543
it("should display user profile when searching", async () => {
5644
const query = "@user:home.server";
5745

58-
const wrapper = mount(
59-
<ProfileInfoComponent
60-
onClick={(hook) => {
61-
hook.search({
62-
query,
63-
});
64-
}}
65-
/>,
66-
);
67-
68-
await act(async () => {
69-
await sleep(1);
70-
wrapper.simulate("click");
71-
return act(() => sleep(1));
46+
const { result } = render();
47+
48+
act(() => {
49+
result.current.search({ query });
7250
});
7351

74-
expect(wrapper.text()).toContain(query);
52+
await waitFor(() => expect(result.current.ready).toBe(true));
53+
54+
expect(result.current.profile?.display_name).toBe(query);
7555
});
7656

7757
it("should work with empty queries", async () => {
78-
const wrapper = mount(
79-
<ProfileInfoComponent
80-
onClick={(hook) => {
81-
hook.search({
82-
query: "",
83-
});
84-
}}
85-
/>,
86-
);
87-
88-
await act(async () => {
89-
await sleep(1);
90-
wrapper.simulate("click");
91-
return act(() => sleep(1));
58+
const query = "";
59+
60+
const { result } = render();
61+
62+
act(() => {
63+
result.current.search({ query });
9264
});
9365

94-
expect(wrapper.text()).toBe("");
66+
await waitFor(() => expect(result.current.ready).toBe(true));
67+
68+
expect(result.current.profile).toBeNull();
9569
});
9670

9771
it("should treat invalid mxids as empty queries", async () => {
9872
const queries = ["@user", "[email protected]"];
9973

10074
for (const query of queries) {
101-
const wrapper = mount(
102-
<ProfileInfoComponent
103-
onClick={(hook) => {
104-
hook.search({
105-
query,
106-
});
107-
}}
108-
/>,
109-
);
110-
111-
await act(async () => {
112-
await sleep(1);
113-
wrapper.simulate("click");
114-
return act(() => sleep(1));
75+
const { result } = render();
76+
77+
act(() => {
78+
result.current.search({ query });
11579
});
11680

117-
expect(wrapper.text()).toBe("");
81+
await waitFor(() => expect(result.current.ready).toBe(true));
82+
83+
expect(result.current.profile).toBeNull();
11884
}
11985
});
12086

@@ -124,43 +90,29 @@ describe("useProfileInfo", () => {
12490
};
12591
const query = "@user:home.server";
12692

127-
const wrapper = mount(
128-
<ProfileInfoComponent
129-
onClick={(hook) => {
130-
hook.search({
131-
query,
132-
});
133-
}}
134-
/>,
135-
);
136-
await act(async () => {
137-
await sleep(1);
138-
wrapper.simulate("click");
139-
return act(() => sleep(1));
93+
const { result } = render();
94+
95+
act(() => {
96+
result.current.search({ query });
14097
});
14198

142-
expect(wrapper.text()).toBe("");
99+
await waitFor(() => expect(result.current.ready).toBe(true));
100+
101+
expect(result.current.profile).toBeNull();
143102
});
144103

145104
it("should be able to handle an empty result", async () => {
146105
cli.getProfileInfo = () => null as unknown as Promise<{}>;
147106
const query = "@user:home.server";
148107

149-
const wrapper = mount(
150-
<ProfileInfoComponent
151-
onClick={(hook) => {
152-
hook.search({
153-
query,
154-
});
155-
}}
156-
/>,
157-
);
158-
await act(async () => {
159-
await sleep(1);
160-
wrapper.simulate("click");
161-
return act(() => sleep(1));
108+
const { result } = render();
109+
110+
act(() => {
111+
result.current.search({ query });
162112
});
163113

164-
expect(wrapper.text()).toBe("");
114+
await waitFor(() => expect(result.current.ready).toBe(true));
115+
116+
expect(result.current.profile?.display_name).toBeUndefined();
165117
});
166118
});

0 commit comments

Comments
 (0)