Skip to content

Commit

Permalink
WIP: Basic plumbing for record/replay modes
Browse files Browse the repository at this point in the history
  • Loading branch information
msutkowski committed Sep 25, 2021
1 parent 7dc00d1 commit d81b46d
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 13 deletions.
18 changes: 10 additions & 8 deletions example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const App = () => {
<ChakraProvider>
<MSWToolbar
worker={worker}
apiUrl="http://www.example.com"
apiUrl="https://pokeapi.co"
isEnabled={true}
actions={
<HStack spacing={2}>
Expand All @@ -37,14 +37,16 @@ const App = () => {
>
<Button
onClick={() =>
fetch('http://www.example.com').then(async (res) => {
if (res.ok) {
const content = await res.json();
alert(
`Here is the mocked response!: ${JSON.stringify(content)}`
);
fetch('https://pokeapi.co/api/v2/pokemon/ditto').then(
async (res) => {
if (res.ok) {
const content = await res.json();
alert(
`Here is the mocked response!: ${JSON.stringify(content)}`
);
}
}
})
)
}
mt={50}
>
Expand Down
65 changes: 63 additions & 2 deletions src/component/MSWToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { rest } from 'msw';
import { RequestHandler, rest } from 'msw';
import {
SetupWorkerApi,
MockedRequest,
Expand All @@ -10,7 +10,35 @@ import { usePrevious } from './hooks';
import styles from './styles.module.css';
import { WorkerMode } from '../types';
import { get, modes, set } from '../helpers';
import { MSWToolbarProps } from '..';
import { MSWToolbarProps, TrackedRequest } from '..';

async function buildHandlersFromRecordedRequests(
trackedRequests: TrackedRequest
): Promise<RequestHandler[]> {
let handlers = [];
for (const [_reqId, { request, response }] of trackedRequests.entries()) {
if (!response) continue;

// TODO: this should probably happen before we add it to the map,
// and we'd most likely want to set the response type there (text, json)
// along with the data
const data = await response.json();

const handler = (rest as any)[request.method.toLocaleLowerCase()](
request.url.href,
(
_req: MockedRequest<any>,
res: ResponseComposition<any>,
ctx: RestContext
) => {
return res(ctx.json(data));
}
);
handlers.push(handler);
}

return handlers;
}

/**
* This is a simple toolbar that allows you to toggle MSW handlers in local development as well as easily invalidate all of the caches.
Expand All @@ -35,6 +63,7 @@ export const MSWToolbar = ({
}

const workerRef = React.useRef<SetupWorkerApi>();
const trackedRequestsRef = React.useRef<TrackedRequest>(new Map());

const [isReady, setIsReady] = React.useState(isEnabled ? false : true);

Expand Down Expand Up @@ -83,7 +112,39 @@ export const MSWToolbar = ({
set(prefix, 'mode', mode);

switch (mode) {
case 'record':
// Passing empty handlers will cause everything to run as a bypass
workerRef.current?.use(...[]);
workerRef.current?.events.on('request:start', request => {
trackedRequestsRef.current.set(request.id, {
request,
response: null as any,
});
});

workerRef.current?.events.on('response:bypass', async (res, reqId) => {
const clone = res.clone();
const existingRequestEntry = trackedRequestsRef.current.get(reqId);
if (existingRequestEntry) {
trackedRequestsRef.current.set(reqId, {
...existingRequestEntry,
response: clone,
});
}
});

return;
case 'replay':
workerRef.current?.events.removeAllListeners();
buildHandlersFromRecordedRequests(trackedRequestsRef.current).then(
handlers => {
workerRef.current?.resetHandlers(...handlers);
}
);

return;
case 'normal':
workerRef.current?.events.removeAllListeners();
workerRef.current?.resetHandlers();
return;
case 'error':
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/settings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Setting } from '..';
import { WorkerMode, WorkerStatus } from '../types';

export const modes: WorkerMode[] = ['normal', 'error'];
export const modes: WorkerMode[] = ['normal', 'error', 'record', 'replay'];

type SettingValueMap = {
mode: WorkerMode;
Expand Down
11 changes: 9 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SetupWorkerApi } from 'msw';
import { MockedRequest, SetupWorkerApi } from 'msw';

export interface MSWToolbarProps {
/**
Expand Down Expand Up @@ -32,4 +32,11 @@ export interface MSWToolbarProps {

export type Setting = 'mode' | 'delay' | 'status';
export type WorkerStatus = 'enabled' | 'disabled';
export type WorkerMode = 'normal' | 'error';
export type WorkerMode = 'normal' | 'error' | 'record' | 'replay';
export type TrackedRequest = Map<
string,
{
request: MockedRequest;
response: Response | null;
}
>;
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@
"forceConsistentCasingInFileNames": true,
// `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
"noEmit": true,
"target": "es2015"
}
}

0 comments on commit d81b46d

Please sign in to comment.