Skip to content

Commit

Permalink
feat: msw http redefine to proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
minsgy committed Jun 7, 2024
1 parent bb4d01f commit d817a36
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 56 deletions.
25 changes: 19 additions & 6 deletions example/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import { http, HttpResponse } from "msw"
import { HttpResponse } from "msw"
import { http } from "msw-devtools"

export const handlers = [
http.get("https://api.example.com/user", () => {
return HttpResponse.json({
firstName: "John",
lastName: "Maverick",
http
.get("https://api.example.com/user", () => {
return HttpResponse.json({
firstName: "John",
lastName: "Maverick",
})
})
}),
.presets([
{
status: 200,
description: "Success",
response: {
firstName: "John",
lastName: "Maverick",
},
},
]),
http.post("https://api.example.com/user", () => {
return HttpResponse.json({ success: true })
}),
http.put("https://api.example.com/user/:id", () => {
return HttpResponse.json({ success: true })
}),

http.delete("https://api.example.com/user/:id", () => {
return HttpResponse.json({ success: true })
}),
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"./styles": "./dist/index.css"
},
"scripts": {
"dev": "tsup src/index.ts --format cjs,esm --dts --watch ",
"build": "tsup src/index.ts --format cjs,esm --dts --minify ",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
"build": "tsup src/index.ts --format cjs,esm --dts --minify",
"example": "pnpm build && cd example && pnpm i && pnpm dev"
},
"dependencies": {
Expand Down Expand Up @@ -70,7 +70,7 @@
"tailwind-merge": "2.3.0",
"tailwindcss-animate": "1.0.7",
"ts-pattern": "^5.1.2",
"typescript": "5.1.6"
"typescript": "5.4.5"
},
"devDependencies": {
"autoprefixer": "^10.4.19",
Expand Down
27 changes: 18 additions & 9 deletions src/features/DevtoolsHandlerList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { match } from "ts-pattern"

import { cn } from "@/shared/lib/cn"
import { Checkbox } from "@/shared/ui/checkbox"
import { InlineCode, P } from "@/shared/ui/typography"
import { HttpMethods } from "msw"
import {
Expand All @@ -12,32 +11,42 @@ import {
} from "@/providers/useMswDevtoolsContext"
import { HandlerSelect } from "./HandlerSelect"
import { Button } from "@/shared/ui/button"
import { InputContainer } from "@/shared/ui/input-container"
import { Label } from "@/shared/ui/label"
import { Switch } from "@/shared/ui/switch"

export const DevtoolsHandlerList = () => {
const { routes, onToggleHandler } = useRoute()
const { onOpenEditPanel } = useEditorRouteState()

return (
<ul className=" list-none overflow-y-auto h-[250px] scrollbar-hide bg-secondary rounded-[4px]">
<ul className="list-none overflow-y-auto h-[250px] scrollbar-hide rounded-[4px] bg-transparent">
<li className="p-[12px] flex items-center">
<P className="font-semibold">Skip</P>
<P className="font-semibold">Actions</P>
<P className="font-semibold">Options</P>
</li>
{routes.map((route) => (
<li key={route.id} className="p-[12px] flex items-center">
<Checkbox
id={route.id}
<Switch
checked={route.isSkip}
onCheckedChange={(checked) => {
onToggleHandler(route.id)
onToggleHandler(route.id, checked)
}}
/>
<label
<Label
htmlFor={route.id}
className="pl-[12px] flex items-center w-full"
>
<MethodTag method={route.method} />
<span className="font-semibold">{route.url}</span>
<div className="ml-auto flex items-center">
<InputContainer label="delay" />
<HandlerSelect
options={route.handlers}
defaultValue={route.handlers[0].id}
options={route.handlers ?? []}
defaultValue={
route.handlers?.[route.selectedHandlerIndex]?.id ?? undefined
}
/>
<Button
className="ml-[12px]"
Expand All @@ -49,7 +58,7 @@ export const DevtoolsHandlerList = () => {
Edit
</Button>
</div>
</label>
</Label>
</li>
))}
</ul>
Expand Down
6 changes: 3 additions & 3 deletions src/features/HandlerSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ export const HandlerSelect = ({ options, ...rest }: HandlerSelectProps) => {
return (
<Select {...rest}>
<SelectTrigger className="w-[280px]">
<SelectValue placeholder="Select a timezone" />
<SelectValue placeholder="test" />
</SelectTrigger>
<SelectContent>
{options.map(({ id, description }) => (
{options?.map(({ id, description, status }) => (
<SelectItem key={id} value={id}>
{description}
{status} - {description}
</SelectItem>
))}
</SelectContent>
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useMswDevtoolsState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ export const useMswDevtoolsState = (initialState: MswDevtoolsContextType) => {
setRoutes((prev) => [...prev, route])
}

const onToggleHandler = (id: string) => {
const onToggleHandler = (id: string, isSkip: boolean) => {
const findIndex = routes.findIndex((route) => route.id === id)
const newRoutes = [...routes]
newRoutes[findIndex].isSkip = !newRoutes[findIndex].isSkip
newRoutes[findIndex].isSkip = isSkip
setRoutes(newRoutes)
}

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ import { MSWDevtools } from "./app/MSWDevtools"
export { MSWDevtools }
export * from "./types"
export * from "./constants"
export * from "./shared/lib/http"
84 changes: 84 additions & 0 deletions src/shared/lib/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ROUTE_METHODS } from "@/constants"
import {
DefaultBodyType,
HttpHandler,
HttpResponseResolver,
Path,
PathParams,
http as originHttp,
} from "msw"

// 1. constant definition
const HTTP_METHODS = ROUTE_METHODS.map((method) =>
method.toLowerCase()
) as (keyof typeof originHttp)[]

// 2. proxy method creation
const createProxyMethod = <
Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
ResponseBodyType extends DefaultBodyType = undefined,
RequestPath extends Path = Path,
>(
method: keyof typeof originHttp
): HttpRequestHandler<
Params,
RequestBodyType,
ResponseBodyType,
RequestPath
> => {
return new Proxy(originHttp[method], {
apply: (target, thisArg, argArray) => {
const [url, responseHandlers] = argArray
const responseHandlersWithPresets = (...rest: any) =>
responseHandlers(...rest)
const result = Reflect.apply(target, thisArg, [
url,
responseHandlersWithPresets,
])
result.presets = (presets: HttpPreset[]) =>
Reflect.apply(target, thisArg, [
url,
responseHandlersWithPresets,
presets,
])
return result
},
}) as HttpRequestHandler<
Params,
RequestBodyType,
ResponseBodyType,
RequestPath
>
}

// 3. type definition
type HttpPreset = {
status: number
description: string
response: any
}

type HttpRequestHandler<
Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
ResponseBodyType extends DefaultBodyType = undefined,
RequestPath extends Path = Path,
> = (
path: RequestPath,
resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>
) => HttpHandler & {
presets: (presets: HttpPreset[]) => HttpHandler
}

// 4. custom http object creation
let http = originHttp as {
[method in keyof typeof originHttp]: HttpRequestHandler
}

// 5. method assignment
HTTP_METHODS.forEach((method: keyof typeof originHttp) => {
http[method] = createProxyMethod(method)
})

export { http }
2 changes: 1 addition & 1 deletion src/shared/ui/FixedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Card } from "./card"

export const FixedLayout = ({ children }: { children: ReactNode }) => {
return (
<Card className="bottom-0 right-0 left-0 h-[400px] fixed z-[9999]">
<Card className="bottom-0 right-0 left-0 h-[400px] fixed z-[10]">
{children}
</Card>
)
Expand Down
26 changes: 26 additions & 0 deletions src/shared/ui/input-container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { twMerge } from "tailwind-merge"
import { Input, InputProps } from "./input"
import { Label } from "./label"

type InputContainerProps = InputProps & {
label: string
containerClassName?: string
}

export const InputContainer = ({
containerClassName,
label,
...rest
}: InputContainerProps) => {
return (
<div className={twMerge("flex items-center", containerClassName)}>
<Label
htmlFor={rest.id}
className="text-primary flex-shrink-0 font-semibold"
>
{label}
</Label>
<Input className={twMerge("ml-[12px]", rest.className)} {...rest} />
</div>
)
}
62 changes: 55 additions & 7 deletions src/shared/utils/createHandler.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
import { DevtoolsRoute } from "@/types"
import { faker } from "@faker-js/faker"
import {
HttpHandler,
HttpMethods,
HttpRequestHandler,
HttpResponse,
RequestHandler,
delay,
} from "msw"

export const createHandler = (
handler: DevtoolsRoute
): RequestHandler | null => {
const { method, url, handlers } = handler
export const createHandler = (route: DevtoolsRoute): RequestHandler | null => {
const { method, url, handlers, selectedHandlerIndex } = route

const httpHandler = createHttpHandler(method)
return httpHandler(url, (info) => {
const handler = handlers[0]
return HttpResponse.json(handler.response ?? { message: "No Response" })
return httpHandler(url, async (info) => {
const handler = handlers[selectedHandlerIndex]
const jsonResponse = JSON.parse(handler.response)

if (jsonResponse) {
recursiveTransform(jsonResponse, (key, value) => {
if (typeof value === "number") {
return value
}

if (value.startsWith("faker.")) {
return resolveFakerValue(key, value)
}

return value
})
}

if (route.delay) {
await delay(route.delay)
}

return HttpResponse.json(jsonResponse ?? { message: "No Response" })
})
}

Expand All @@ -26,3 +46,31 @@ export const createHttpHandler = <Method extends HttpMethods | RegExp>(
return new HttpHandler(method, path, resolver, options)
}
}

const resolveFakerValue = (key: string, value: string) => {
const fnSandbox = Function("faker", `return ${value}`)
const result = fnSandbox.call(undefined, faker)
return typeof result === "function" ? result.call() : result
}

const recursiveTransform = (
json: Record<string, any>,
walkFn: (key: string, value: string | number) => string | number
) => {
const keys = Object.keys(json)
for (const key of keys) {
const value = json[key]

if (Array.isArray(value)) {
value.forEach((item) => recursiveTransform(item, walkFn))
return
}

if (typeof value === "object") {
recursiveTransform(value, walkFn)
return
}

json[key] = walkFn(key, value)
}
}
Loading

0 comments on commit d817a36

Please sign in to comment.