@@ -14,77 +14,123 @@ See the License for the specific language governing permissions and
14
14
limitations under the License.
15
15
*/
16
16
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" ;
22
18
23
19
import { useLatestResult } from "../../src/hooks/useLatestResult" ;
24
20
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 ) ;
36
37
}
37
38
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 ) ;
63
53
} ) ;
64
54
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 ) ;
89
135
} ) ;
90
136
} ) ;
0 commit comments