Skip to content

Commit 28abc13

Browse files
committed
FI-1534 feat: add mockWebSocketRoute/unmockWebSocketRoute actions
1 parent c41b348 commit 28abc13

28 files changed

+470
-53
lines changed

src/README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Modules in the dependency graph should only import the modules above them:
99
2. `constants`
1010
3. `configurator`
1111
4. `generators`
12-
5. `utils/browser`
12+
5. `utils/parse`
1313
6. `utils/getDurationWithUnits`
1414
7. `utils/setReadonlyProperty`
1515
8. `utils/selectors`
@@ -40,11 +40,12 @@ Modules in the dependency graph should only import the modules above them:
4040
33. `Route`
4141
34. `ApiRoute`
4242
35. `PageRoute`
43-
36. `testController`
44-
37. `useContext`
45-
38. `context`
46-
39. `utils/log`
47-
40. `utils/waitForEvents`
48-
41. `utils/expect`
49-
42. `expect`
50-
43. ...
43+
36. `WebSocketRoute`
44+
37. `testController`
45+
38. `useContext`
46+
39. `context`
47+
40. `utils/log`
48+
41. `utils/waitForEvents`
49+
42. `utils/expect`
50+
43. `expect`
51+
44. ...

src/Route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {SLASHES_AT_THE_END_REGEXP, SLASHES_AT_THE_START_REGEXP} from './constants/internal';
22

3-
import type {Method, Url, ZeroOrOneArg} from './types/internal';
3+
import type {Url, ZeroOrOneArg} from './types/internal';
44

55
/**
66
* Abstract route with base methods.
@@ -25,7 +25,7 @@ export abstract class Route<RouteParams> {
2525
* Returns route params from the passed url.
2626
* @throws {Error} If the route does not match on the url.
2727
*/
28-
static getParamsFromUrlOrThrow?(url: Url, method?: Method): unknown;
28+
static getParamsFromUrlOrThrow?(url: Url): unknown;
2929

3030
/**
3131
* Returns the url of the route.

src/WebSocketRoute.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {Route} from './Route';
2+
3+
/**
4+
* Abstract route for WebSocket "requests".
5+
*/
6+
export abstract class WebSocketRoute<
7+
Params = undefined,
8+
SomeRequest = unknown,
9+
SomeResponse = unknown,
10+
> extends Route<Params> {
11+
/**
12+
* Request type of WebSocket route.
13+
*/
14+
// eslint-disable-next-line @typescript-eslint/naming-convention
15+
declare readonly __REQUEST_KEY: SomeRequest;
16+
17+
/**
18+
* Response type of WebSocket route.
19+
*/
20+
// eslint-disable-next-line @typescript-eslint/naming-convention
21+
declare readonly __RESPONSE_KEY: SomeResponse;
22+
23+
/**
24+
* Returns `true`, if the request body is in JSON format.
25+
*/
26+
getIsRequestBodyInJsonFormat(): boolean {
27+
return true;
28+
}
29+
30+
/**
31+
* Returns `true`, if the response body is in JSON format.
32+
*/
33+
getIsResponseBodyInJsonFormat(): boolean {
34+
return true;
35+
}
36+
}

src/actions/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export {getBrowserConsoleMessages} from './getBrowserConsoleMessages';
1717
export {getBrowserJsErrors} from './getBrowserJsErrors';
1818
export {getCookies} from './getCookies';
1919
export {hover} from './hover';
20-
export {mockApiRoute, unmockApiRoute} from './mock';
20+
export {mockApiRoute, mockWebSocketRoute, unmockApiRoute, unmockWebSocketRoute} from './mock';
2121
export {navigateToUrl} from './navigateToUrl';
2222
export {
2323
assertPage,

src/actions/mock/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export {mockApiRoute} from './mockApiRoute';
2+
export {mockWebSocketRoute} from './mockWebSocketRoute';
23
export {unmockApiRoute} from './unmockApiRoute';
4+
export {unmockWebSocketRoute} from './unmockWebSocketRoute';

src/actions/mock/mockApiRoute.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type {
1919
* Mock API for some API route.
2020
* Applicable only for routes with the `getParamsFromUrlOrThrow` method.
2121
* The mock is applied to a request that matches the route by url
22-
* (by methods `getParamsFromUrlOrThrow` and `isMatchUrl`) and by HTTP method (by `getMethod`).
22+
* (by methods `getParamsFromUrlOrThrow` and `isMatchUrl`).
2323
*/
2424
export const mockApiRoute = async <
2525
RouteParams,
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {LogEventType} from '../../constants/internal';
2+
import {getFullMocksState} from '../../context/fullMocks';
3+
import {getWebSocketMockState} from '../../context/webSocketMockState';
4+
import {getPlaywrightPage} from '../../useContext';
5+
import {assertValueIsDefined} from '../../utils/asserts';
6+
import {setCustomInspectOnFunction} from '../../utils/fn';
7+
import {log} from '../../utils/log';
8+
import {getRequestsFilter, getSetResponse} from '../../utils/mockWebSocketRoute';
9+
import {setReadonlyProperty} from '../../utils/setReadonlyProperty';
10+
11+
import type {
12+
WebSocketMockFunction,
13+
WebSocketRouteClassTypeWithGetParamsFromUrl,
14+
} from '../../types/internal';
15+
16+
/**
17+
* Mock WebSocket for some API route.
18+
* Applicable only for routes with the `getParamsFromUrlOrThrow` method.
19+
* The mock is applied to a WebSocket that matches the route by url
20+
* (by methods `getParamsFromUrlOrThrow` and `isMatchUrl`).
21+
*/
22+
export const mockWebSocketRoute = async <RouteParams, SomeRequest, SomeResponse>(
23+
Route: WebSocketRouteClassTypeWithGetParamsFromUrl<RouteParams>,
24+
webSocketMockFunction: WebSocketMockFunction<RouteParams, SomeRequest, SomeResponse>,
25+
{skipLogs = false}: {skipLogs?: boolean} = {},
26+
): Promise<void> => {
27+
setCustomInspectOnFunction(webSocketMockFunction);
28+
29+
const webSocketMockState = getWebSocketMockState();
30+
31+
if (!webSocketMockState.isMocksEnabled) {
32+
return;
33+
}
34+
35+
const fullMocksState = getFullMocksState();
36+
37+
if (fullMocksState?.appliedMocks !== undefined) {
38+
setReadonlyProperty(webSocketMockState, 'isMocksEnabled', false);
39+
}
40+
41+
let {optionsByRoute} = webSocketMockState;
42+
43+
if (optionsByRoute === undefined) {
44+
optionsByRoute = new Map();
45+
46+
setReadonlyProperty(webSocketMockState, 'optionsByRoute', optionsByRoute);
47+
48+
const requestsFilter = getRequestsFilter(webSocketMockState);
49+
50+
setReadonlyProperty(webSocketMockState, 'requestsFilter', requestsFilter);
51+
}
52+
53+
if (optionsByRoute.size === 0) {
54+
const {requestsFilter} = webSocketMockState;
55+
56+
assertValueIsDefined(requestsFilter, 'requestsFilter is defined', {
57+
routeName: Route.name,
58+
webSocketMockState,
59+
});
60+
61+
const page = getPlaywrightPage();
62+
63+
const setResponse = getSetResponse(webSocketMockState);
64+
65+
await page.routeWebSocket(requestsFilter, setResponse);
66+
}
67+
68+
optionsByRoute.set(Route, {
69+
skipLogs,
70+
webSocketMockFunction: webSocketMockFunction as WebSocketMockFunction,
71+
});
72+
73+
if (skipLogs !== true) {
74+
log(
75+
`Mock WebSocket for route "${Route.name}"`,
76+
{webSocketMockFunction},
77+
LogEventType.InternalAction,
78+
);
79+
}
80+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {LogEventType} from '../../constants/internal';
2+
import {getWebSocketMockState} from '../../context/webSocketMockState';
3+
import {getPlaywrightPage} from '../../useContext';
4+
import {assertValueIsDefined} from '../../utils/asserts';
5+
import {setCustomInspectOnFunction} from '../../utils/fn';
6+
import {log} from '../../utils/log';
7+
8+
import type {
9+
WebSocketMockFunction,
10+
WebSocketRouteClassTypeWithGetParamsFromUrl,
11+
} from '../../types/internal';
12+
13+
/**
14+
* Unmock WebSocket (remove mock, if any) for some WebSocket route.
15+
*/
16+
export const unmockWebSocketRoute = async <RouteParams, SomeRequest, SomeResponse>(
17+
Route: WebSocketRouteClassTypeWithGetParamsFromUrl<RouteParams, SomeRequest, SomeResponse>,
18+
): Promise<void> => {
19+
const webSocketMockState = getWebSocketMockState();
20+
const {optionsByRoute, requestsFilter} = webSocketMockState;
21+
let webSocketMockFunction: WebSocketMockFunction | undefined;
22+
let routeWasMocked = false;
23+
let skipLogs: boolean | undefined;
24+
25+
if (optionsByRoute?.has(Route)) {
26+
const options = optionsByRoute.get(Route);
27+
28+
webSocketMockFunction = options?.webSocketMockFunction;
29+
skipLogs = options?.skipLogs;
30+
31+
routeWasMocked = true;
32+
optionsByRoute.delete(Route);
33+
}
34+
35+
if (optionsByRoute?.size === 0) {
36+
assertValueIsDefined(requestsFilter, 'requestsFilter is defined', {
37+
routeName: Route.name,
38+
routeWasMocked,
39+
});
40+
41+
const page = getPlaywrightPage();
42+
43+
await page.unroute(requestsFilter);
44+
}
45+
46+
if (webSocketMockFunction) {
47+
setCustomInspectOnFunction(webSocketMockFunction);
48+
}
49+
50+
if (skipLogs !== true) {
51+
log(
52+
`Unmock WebSocket for route "${Route.name}"`,
53+
{routeWasMocked, webSocketMockFunction},
54+
LogEventType.InternalAction,
55+
);
56+
}
57+
};

src/context/webSocketMockState.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {useContext} from '../useContext';
2+
3+
import type {WebSocketMockState} from '../types/internal';
4+
5+
/**
6+
* Raw get and set internal (maybe `undefined`) WebSocket mock state.
7+
* @internal
8+
*/
9+
const [getRawWebSocketMockState, setRawWebSocketMockState] = useContext<WebSocketMockState>();
10+
11+
/**
12+
* Get internal always defined WebSocket mock state (for `mockWebSocketRoute`).
13+
* @internal
14+
*/
15+
export const getWebSocketMockState = (): WebSocketMockState => {
16+
const maybeWebSocketMockState = getRawWebSocketMockState();
17+
18+
if (maybeWebSocketMockState !== undefined) {
19+
return maybeWebSocketMockState;
20+
}
21+
22+
const webSocketMockState: WebSocketMockState = {
23+
isMocksEnabled: true,
24+
optionsByRoute: undefined,
25+
optionsWithRouteByUrl: Object.create(null) as {},
26+
requestsFilter: undefined,
27+
};
28+
29+
setRawWebSocketMockState(webSocketMockState);
30+
31+
return webSocketMockState;
32+
};

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {PageRoute} from './PageRoute';
77
export {devices} from './playwright';
88
export {Route} from './Route';
99
export {getPlaywrightPage, useContext} from './useContext';
10+
export {WebSocketRoute} from './WebSocketRoute';
1011

1112
/**
1213
* Public modules, dependent on internal utils.

0 commit comments

Comments
 (0)