Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5225c56

Browse files
authored
fix(site): add tests forcreateMockWebSocket (#19172)
Needed for#19126 and#18679## Changes made- Moved `createWebSocket` to dedicated file and addressed edge cases formaking it a reliable mock- Added test cases to validate mock functionality
1 parent0a3afed commit5225c56

File tree

3 files changed

+388
-174
lines changed

3 files changed

+388
-174
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import{createMockWebSocket}from"./websockets";
2+
3+
describe(createMockWebSocket.name,()=>{
4+
it("Throws if URL does not have ws:// or wss:// protocols",()=>{
5+
consturls:readonlystring[]=[
6+
"http://www.dog.ceo/roll-over",
7+
"https://www.dog.ceo/roll-over",
8+
];
9+
for(consturlofurls){
10+
expect(()=>{
11+
voidcreateMockWebSocket(url);
12+
}).toThrow("URL must start with ws:// or wss://");
13+
}
14+
});
15+
16+
it("Sends events from server to socket",()=>{
17+
const[socket,server]=createMockWebSocket("wss://www.dog.ceo/shake");
18+
19+
constonOpen=jest.fn();
20+
constonError=jest.fn();
21+
constonMessage=jest.fn();
22+
constonClose=jest.fn();
23+
24+
socket.addEventListener("open",onOpen);
25+
socket.addEventListener("error",onError);
26+
socket.addEventListener("message",onMessage);
27+
socket.addEventListener("close",onClose);
28+
29+
constopenEvent=newEvent("open");
30+
consterrorEvent=newEvent("error");
31+
constmessageEvent=newMessageEvent<string>("message");
32+
constcloseEvent=newCloseEvent("close");
33+
34+
server.publishOpen(openEvent);
35+
server.publishError(errorEvent);
36+
server.publishMessage(messageEvent);
37+
server.publishClose(closeEvent);
38+
39+
expect(onOpen).toHaveBeenCalledTimes(1);
40+
expect(onOpen).toHaveBeenCalledWith(openEvent);
41+
42+
expect(onError).toHaveBeenCalledTimes(1);
43+
expect(onError).toHaveBeenCalledWith(errorEvent);
44+
45+
expect(onMessage).toHaveBeenCalledTimes(1);
46+
expect(onMessage).toHaveBeenCalledWith(messageEvent);
47+
48+
expect(onClose).toHaveBeenCalledTimes(1);
49+
expect(onClose).toHaveBeenCalledWith(closeEvent);
50+
});
51+
52+
it("Sends JSON data to the socket for message events",()=>{
53+
const[socket,server]=createMockWebSocket("wss://www.dog.ceo/wag");
54+
constonMessage=jest.fn();
55+
56+
// Could type this as a special JSON type, but unknown is good enough,
57+
// since any invalid values will throw in the test case
58+
constjsonData:readonlyunknown[]=[
59+
"blah",
60+
42,
61+
true,
62+
false,
63+
null,
64+
{},
65+
[],
66+
[{value:"blah"},{value:"guh"},{value:"huh"}],
67+
{
68+
name:"Hershel Layton",
69+
age:40,
70+
profession:"Puzzle Solver",
71+
sadBackstory:true,
72+
greatVideoGames:true,
73+
},
74+
];
75+
for(constjdofjsonData){
76+
socket.addEventListener("message",onMessage);
77+
server.publishMessage(
78+
newMessageEvent("message",{data:JSON.stringify(jd)}),
79+
);
80+
81+
expect(onMessage).toHaveBeenCalledTimes(1);
82+
expect(onMessage).toHaveBeenCalledWith(
83+
newMessageEvent("message",{data:JSON.stringify(jd)}),
84+
);
85+
86+
socket.removeEventListener("message",onMessage);
87+
onMessage.mockClear();
88+
}
89+
});
90+
91+
it("Only registers each socket event handler once",()=>{
92+
const[socket,server]=createMockWebSocket("wss://www.dog.ceo/borf");
93+
94+
constonOpen=jest.fn();
95+
constonError=jest.fn();
96+
constonMessage=jest.fn();
97+
constonClose=jest.fn();
98+
99+
// Do it once
100+
socket.addEventListener("open",onOpen);
101+
socket.addEventListener("error",onError);
102+
socket.addEventListener("message",onMessage);
103+
socket.addEventListener("close",onClose);
104+
105+
// Do it again with the exact same functions
106+
socket.addEventListener("open",onOpen);
107+
socket.addEventListener("error",onError);
108+
socket.addEventListener("message",onMessage);
109+
socket.addEventListener("close",onClose);
110+
111+
server.publishOpen(newEvent("open"));
112+
server.publishError(newEvent("error"));
113+
server.publishMessage(newMessageEvent<string>("message"));
114+
server.publishClose(newCloseEvent("close"));
115+
116+
expect(onOpen).toHaveBeenCalledTimes(1);
117+
expect(onError).toHaveBeenCalledTimes(1);
118+
expect(onMessage).toHaveBeenCalledTimes(1);
119+
expect(onClose).toHaveBeenCalledTimes(1);
120+
});
121+
122+
it("Lets a socket unsubscribe to event types",()=>{
123+
const[socket,server]=createMockWebSocket("wss://www.dog.ceo/zoomies");
124+
125+
constonOpen=jest.fn();
126+
constonError=jest.fn();
127+
constonMessage=jest.fn();
128+
constonClose=jest.fn();
129+
130+
socket.addEventListener("open",onOpen);
131+
socket.addEventListener("error",onError);
132+
socket.addEventListener("message",onMessage);
133+
socket.addEventListener("close",onClose);
134+
135+
socket.removeEventListener("open",onOpen);
136+
socket.removeEventListener("error",onError);
137+
socket.removeEventListener("message",onMessage);
138+
socket.removeEventListener("close",onClose);
139+
140+
server.publishOpen(newEvent("open"));
141+
server.publishError(newEvent("error"));
142+
server.publishMessage(newMessageEvent<string>("message"));
143+
server.publishClose(newCloseEvent("close"));
144+
145+
expect(onOpen).not.toHaveBeenCalled();
146+
expect(onError).not.toHaveBeenCalled();
147+
expect(onMessage).not.toHaveBeenCalled();
148+
expect(onClose).not.toHaveBeenCalled();
149+
});
150+
151+
it("Renders socket inert after being closed",()=>{
152+
const[socket,server]=createMockWebSocket("wss://www.dog.ceo/woof");
153+
expect(server.isConnectionOpen).toBe(true);
154+
155+
constonMessage=jest.fn();
156+
socket.addEventListener("message",onMessage);
157+
158+
socket.close();
159+
expect(server.isConnectionOpen).toBe(false);
160+
161+
server.publishMessage(newMessageEvent<string>("message"));
162+
expect(onMessage).not.toHaveBeenCalled();
163+
});
164+
165+
it("Tracks arguments sent by the mock socket",()=>{
166+
const[socket,server]=createMockWebSocket("wss://www.dog.ceo/wan-wan");
167+
constdata=JSON.stringify({
168+
famousDogs:[
169+
"snoopy",
170+
"clifford",
171+
"lassie",
172+
"beethoven",
173+
"courage the cowardly dog",
174+
],
175+
});
176+
177+
socket.send(data);
178+
expect(server.clientSentData).toHaveLength(1);
179+
expect(server.clientSentData).toEqual([data]);
180+
181+
socket.close();
182+
socket.send(data);
183+
expect(server.clientSentData).toHaveLength(1);
184+
expect(server.clientSentData).toEqual([data]);
185+
});
186+
});

‎site/src/testHelpers/websockets.ts‎

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
importtype{WebSocketEventType}from"utils/OneWayWebSocket";
2+
3+
typeSocketSendData=Parameters<WebSocket["send"]>[0];
4+
5+
exporttypeMockWebSocketServer=Readonly<{
6+
publishMessage:(event:MessageEvent<string>)=>void;
7+
publishError:(event:Event)=>void;
8+
publishClose:(event:CloseEvent)=>void;
9+
publishOpen:(event:Event)=>void;
10+
11+
readonlyisConnectionOpen:boolean;
12+
readonlyclientSentData:readonlySocketSendData[];
13+
}>;
14+
15+
typeCallbackStore={
16+
[KinkeyofWebSocketEventMap]:Set<(event:WebSocketEventMap[K])=>void>;
17+
};
18+
19+
typeMockWebSocket=Omit<WebSocket,"send">&{
20+
/**
21+
* A version of the WebSocket `send` method that has been pre-wrapped inside
22+
* a Jest mock.
23+
*
24+
* The Jest mock functionality should be used at a minimum. Basically:
25+
* 1. If you want to check that the mock socket sent something to the mock
26+
* server: call the `send` method as a function, and then check the
27+
* `clientSentData` on `MockWebSocketServer` to see what data got
28+
* received.
29+
* 2. If you need to make sure that the client-side `send` method got called
30+
* at all: you can use the Jest mock functionality, but you should
31+
* probably also be checking `clientSentData` still and making additional
32+
* assertions with it.
33+
*
34+
* Generally, tests should center around whether socket-to-server
35+
* communication was successful, not whether the client-side method was
36+
* called.
37+
*/
38+
send:jest.Mock<void,[SocketSendData],unknown>;
39+
};
40+
41+
exportfunctioncreateMockWebSocket(
42+
url:string,
43+
protocol?:string|string[]|undefined,
44+
):readonly[MockWebSocket,MockWebSocketServer]{
45+
if(!url.startsWith("ws://")&&!url.startsWith("wss://")){
46+
thrownewError("URL must start with ws:// or wss://");
47+
}
48+
49+
constactiveProtocol=Array.isArray(protocol)
50+
?protocol.join(" ")
51+
:(protocol??"");
52+
53+
letisOpen=true;
54+
conststore:CallbackStore={
55+
message:newSet(),
56+
error:newSet(),
57+
close:newSet(),
58+
open:newSet(),
59+
};
60+
61+
constsentData:SocketSendData[]=[];
62+
63+
constmockSocket:MockWebSocket={
64+
CONNECTING:0,
65+
OPEN:1,
66+
CLOSING:2,
67+
CLOSED:3,
68+
69+
url,
70+
protocol:activeProtocol,
71+
readyState:1,
72+
binaryType:"blob",
73+
bufferedAmount:0,
74+
extensions:"",
75+
onclose:null,
76+
onerror:null,
77+
onmessage:null,
78+
onopen:null,
79+
dispatchEvent:jest.fn(),
80+
81+
send:jest.fn((data)=>{
82+
if(!isOpen){
83+
return;
84+
}
85+
sentData.push(data);
86+
}),
87+
88+
addEventListener:<EextendsWebSocketEventType>(
89+
eventType:E,
90+
callback:(event:WebSocketEventMap[E])=>void,
91+
)=>{
92+
if(!isOpen){
93+
return;
94+
}
95+
constsubscribers=store[eventType];
96+
subscribers.add(callback);
97+
},
98+
99+
removeEventListener:<EextendsWebSocketEventType>(
100+
eventType:E,
101+
callback:(event:WebSocketEventMap[E])=>void,
102+
)=>{
103+
if(!isOpen){
104+
return;
105+
}
106+
constsubscribers=store[eventType];
107+
subscribers.delete(callback);
108+
},
109+
110+
close:()=>{
111+
isOpen=false;
112+
},
113+
};
114+
115+
constpublisher:MockWebSocketServer={
116+
getisConnectionOpen(){
117+
returnisOpen;
118+
},
119+
120+
getclientSentData(){
121+
return[...sentData];
122+
},
123+
124+
publishOpen:(event)=>{
125+
if(!isOpen){
126+
return;
127+
}
128+
for(constsubofstore.open){
129+
sub(event);
130+
}
131+
},
132+
133+
publishError:(event)=>{
134+
if(!isOpen){
135+
return;
136+
}
137+
for(constsubofstore.error){
138+
sub(event);
139+
}
140+
},
141+
142+
publishMessage:(event)=>{
143+
if(!isOpen){
144+
return;
145+
}
146+
for(constsubofstore.message){
147+
sub(event);
148+
}
149+
},
150+
151+
publishClose:(event)=>{
152+
if(!isOpen){
153+
return;
154+
}
155+
for(constsubofstore.close){
156+
sub(event);
157+
}
158+
},
159+
};
160+
161+
return[mockSocket,publisher]asconst;
162+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp