7
7
createMockWebSocket ,
8
8
} from "testHelpers/websockets" ;
9
9
import { OneWayWebSocket } from "utils/OneWayWebSocket" ;
10
- import { createUseAgentLogs } from "./useAgentLogs" ;
10
+ import { type OnError , createUseAgentLogs } from "./useAgentLogs" ;
11
11
12
12
const millisecondsInOneMinute = 60_000 ;
13
13
@@ -30,9 +30,16 @@ function generateMockLogs(
30
30
} ) ;
31
31
}
32
32
33
- // A mutable object holding the most recent mock WebSocket publisher. The inner
34
- // value will change as the hook opens/closes new connections
35
- type PublisherResult = { current : MockWebSocketPublisher } ;
33
+ // A mutable object holding the most recent mock WebSocket publisher. Inner
34
+ // value will be undefined if the hook is disabled on mount, but will otherwise
35
+ // have some kind of value
36
+ type PublisherResult = { current : MockWebSocketPublisher | undefined } ;
37
+
38
+ type MountHookOptions = Readonly < {
39
+ initialAgentId : string ;
40
+ enabled ?: boolean ;
41
+ onError ?: OnError ;
42
+ } > ;
36
43
37
44
type MountHookResult = Readonly < {
38
45
rerender : ( props : { agentId : string ; enabled : boolean } ) => void ;
@@ -43,12 +50,10 @@ type MountHookResult = Readonly<{
43
50
hookResult : { current : readonly WorkspaceAgentLog [ ] } ;
44
51
} > ;
45
52
46
- function mountHook ( initialAgentId : string ) : MountHookResult {
47
- // Have to cheat the types a little bit to avoid a chicken-and-the-egg
48
- // scenario. publisherResult will be initialized with an undefined current
49
- // value, but it'll be guaranteed not to be undefined by the time mountHook
50
- // returns its value.
51
- const publisherResult : Partial < PublisherResult > = { current : undefined } ;
53
+ function mountHook ( options : MountHookOptions ) : MountHookResult {
54
+ const { initialAgentId, enabled = true , onError = jest . fn ( ) } = options ;
55
+
56
+ const publisherResult : PublisherResult = { current : undefined } ;
52
57
const useAgentLogs = createUseAgentLogs ( ( agentId , params ) => {
53
58
return new OneWayWebSocket ( {
54
59
apiRoute : `/api/v2/workspaceagents/${ agentId } /logs` ,
@@ -62,29 +67,32 @@ function mountHook(initialAgentId: string): MountHookResult {
62
67
return mockSocket ;
63
68
} ,
64
69
} ) ;
65
- } ) ;
70
+ } , onError ) ;
66
71
67
72
const { result, rerender } = renderHook (
68
73
( { agentId, enabled } ) => useAgentLogs ( agentId , enabled ) ,
69
- { initialProps : { agentId : initialAgentId , enabled : true } } ,
74
+ { initialProps : { agentId : initialAgentId , enabled : enabled } } ,
70
75
) ;
71
76
72
77
return {
73
78
rerender,
74
79
hookResult : result ,
75
- publisherResult : publisherResult as PublisherResult ,
80
+ publisherResult,
76
81
} ;
77
82
}
78
83
79
84
describe ( "useAgentLogs" , ( ) => {
80
85
it ( "Automatically sorts logs that are received out of order" , async ( ) => {
81
- const { hookResult, publisherResult } = mountHook ( MockWorkspaceAgent . id ) ;
86
+ const { hookResult, publisherResult } = mountHook ( {
87
+ initialAgentId : MockWorkspaceAgent . id ,
88
+ } ) ;
89
+
82
90
const logs = generateMockLogs ( 10 , new Date ( "september 9, 1999" ) ) ;
83
91
const reversed = logs . toReversed ( ) ;
84
92
85
93
for ( const log of reversed ) {
86
94
act ( ( ) => {
87
- publisherResult . current . publishMessage (
95
+ publisherResult . current ? .publishMessage (
88
96
new MessageEvent < string > ( "message" , {
89
97
data : JSON . stringify ( [ log ] ) ,
90
98
} ) ,
@@ -94,54 +102,86 @@ describe("useAgentLogs", () => {
94
102
await waitFor ( ( ) => expect ( hookResult . current ) . toEqual ( logs ) ) ;
95
103
} ) ;
96
104
105
+ it ( "Never creates a connection if hook is disabled on mount" , ( ) => {
106
+ const { publisherResult } = mountHook ( {
107
+ initialAgentId : MockWorkspaceAgent . id ,
108
+ enabled : false ,
109
+ } ) ;
110
+
111
+ expect ( publisherResult . current ) . toBe ( undefined ) ;
112
+ } ) ;
113
+
97
114
it ( "Automatically closes the socket connection when the hook is disabled" , async ( ) => {
98
- const { publisherResult, rerender } = mountHook ( MockWorkspaceAgent . id ) ;
99
- expect ( publisherResult . current . isConnectionOpen ( ) ) . toBe ( true ) ;
115
+ const { publisherResult, rerender } = mountHook ( {
116
+ initialAgentId : MockWorkspaceAgent . id ,
117
+ } ) ;
118
+
119
+ expect ( publisherResult . current ?. isConnectionOpen ( ) ) . toBe ( true ) ;
100
120
rerender ( { agentId : MockWorkspaceAgent . id , enabled : false } ) ;
101
121
await waitFor ( ( ) => {
102
- expect ( publisherResult . current . isConnectionOpen ( ) ) . toBe ( false ) ;
122
+ expect ( publisherResult . current ? .isConnectionOpen ( ) ) . toBe ( false ) ;
103
123
} ) ;
104
124
} ) ;
105
125
106
126
it ( "Automatically closes the old connection when the agent ID changes" , ( ) => {
107
- const { publisherResult, rerender } = mountHook ( MockWorkspaceAgent . id ) ;
127
+ const { publisherResult, rerender } = mountHook ( {
128
+ initialAgentId : MockWorkspaceAgent . id ,
129
+ } ) ;
130
+
108
131
const publisher1 = publisherResult . current ;
109
- expect ( publisher1 . isConnectionOpen ( ) ) . toBe ( true ) ;
132
+ expect ( publisher1 ? .isConnectionOpen ( ) ) . toBe ( true ) ;
110
133
111
134
const newAgentId = `${ MockWorkspaceAgent . id } -2` ;
112
135
rerender ( { agentId : newAgentId , enabled : true } ) ;
113
136
114
137
const publisher2 = publisherResult . current ;
115
- expect ( publisher1 . isConnectionOpen ( ) ) . toBe ( false ) ;
116
- expect ( publisher2 . isConnectionOpen ( ) ) . toBe ( true ) ;
138
+ expect ( publisher1 ?. isConnectionOpen ( ) ) . toBe ( false ) ;
139
+ expect ( publisher2 ?. isConnectionOpen ( ) ) . toBe ( true ) ;
140
+ } ) ;
141
+
142
+ it ( "Calls error callback when error is received (but only while hook is enabled)" , async ( ) => {
143
+ const onError = jest . fn ( ) ;
144
+ const { publisherResult, rerender } = mountHook ( {
145
+ initialAgentId : MockWorkspaceAgent . id ,
146
+ // Start off disabled so that we can check that the callback is
147
+ // never called when there is no connection
148
+ enabled : false ,
149
+ onError,
150
+ } ) ;
151
+
152
+ const errorEvent = new Event ( "error" ) ;
153
+ act ( ( ) => publisherResult . current ?. publishError ( errorEvent ) ) ;
154
+ expect ( onError ) . not . toHaveBeenCalled ( ) ;
155
+
156
+ rerender ( { agentId : MockWorkspaceAgent . id , enabled : true } ) ;
157
+ act ( ( ) => publisherResult . current ?. publishError ( errorEvent ) ) ;
158
+ expect ( onError ) . toHaveBeenCalledTimes ( 1 ) ;
117
159
} ) ;
118
160
119
161
it ( "Clears logs when hook becomes disabled (protection to avoid duplicate logs when hook goes back to being re-enabled)" , async ( ) => {
120
- const { hookResult, publisherResult, rerender } = mountHook (
121
- MockWorkspaceAgent . id ,
122
- ) ;
162
+ const { hookResult, publisherResult, rerender } = mountHook ( {
163
+ initialAgentId : MockWorkspaceAgent . id ,
164
+ } ) ;
123
165
124
166
// Send initial logs so that we have something to clear out later
125
167
const initialLogs = generateMockLogs ( 3 , new Date ( "april 5, 1997" ) ) ;
126
168
const initialEvent = new MessageEvent < string > ( "message" , {
127
169
data : JSON . stringify ( initialLogs ) ,
128
170
} ) ;
129
- act ( ( ) => publisherResult . current . publishMessage ( initialEvent ) ) ;
171
+ act ( ( ) => publisherResult . current ? .publishMessage ( initialEvent ) ) ;
130
172
await waitFor ( ( ) => expect ( hookResult . current ) . toEqual ( initialLogs ) ) ;
131
173
132
- // Disable the hook (and have the hook close the connection behind the
133
- // scenes)
174
+ // Disable the hook
134
175
rerender ( { agentId : MockWorkspaceAgent . id , enabled : false } ) ;
135
176
await waitFor ( ( ) => expect ( hookResult . current ) . toHaveLength ( 0 ) ) ;
136
177
137
- // Re-enable the hook (creating an entirely new connection), and send
138
- // new logs
178
+ // Re-enable the hook and send new logs
139
179
rerender ( { agentId : MockWorkspaceAgent . id , enabled : true } ) ;
140
180
const newLogs = generateMockLogs ( 3 , new Date ( "october 3, 2005" ) ) ;
141
181
const newEvent = new MessageEvent < string > ( "message" , {
142
182
data : JSON . stringify ( newLogs ) ,
143
183
} ) ;
144
- act ( ( ) => publisherResult . current . publishMessage ( newEvent ) ) ;
184
+ act ( ( ) => publisherResult . current ? .publishMessage ( newEvent ) ) ;
145
185
await waitFor ( ( ) => expect ( hookResult . current ) . toEqual ( newLogs ) ) ;
146
186
} ) ;
147
187
} ) ;
0 commit comments