Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
NEXT_PUBLIC_BACKEND_API_URL="http://localhost:8000"
NEXT_PUBLIC_API_MOCKING=true
4 changes: 4 additions & 0 deletions __mocks__/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers/index'

export const worker = setupWorker(...handlers)
1 change: 1 addition & 0 deletions __mocks__/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const handlers = []
33 changes: 33 additions & 0 deletions __mocks__/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { appConfig } from '@/config/app-config'

export async function enableMocking() {
if (appConfig.isDev && !appConfig.isMockingEnabled) {
console.log(`MSW mocking disabled: ${appConfig.isDev}, ${appConfig.isMockingEnabled} `)
return
}
try {
if (typeof window === 'undefined') {
const { server } = await import('./server')
server.listen({
onUnhandledRequest: (req, print) => {
if (req.url.includes('_next')) {
return
}
print.warning()
},
})
} else {
const { worker } = await import('./browser')
await worker.start({
onUnhandledRequest: (req, print) => {
if (req.url.includes('_next')) {
return
}
print.warning()
},
})
}
} catch (error) {
console.log('Failed to start MSW server: ', error)
}
}
4 changes: 4 additions & 0 deletions __mocks__/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { setupServer } from 'msw/node'
import { handlers } from './handlers/index'

export const server = setupServer(...handlers)
3 changes: 3 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { appConfig, validateAppConfig } from '@/config/app-config'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { enableMocking } from '@/__mocks__/init'

enableMocking()

const inter = Inter({
subsets: ['latin'],
Expand Down
13 changes: 13 additions & 0 deletions components/msw-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use client'

import { useEffect } from 'react'

export function MockServiceWorkerProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
import('@/__mocks__/init').then(({ enableMocking }) => {
enableMocking()
})
}, [])

return <>{children}</>
}
7 changes: 6 additions & 1 deletion components/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

// Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top
import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { MockServiceWorkerProvider } from './msw-provider'

function makeQueryClient() {
return new QueryClient({
Expand Down Expand Up @@ -39,5 +40,9 @@ export default function Providers({ children }: { children: React.ReactNode }) {
// render if it suspends and there is no boundary
const queryClient = getQueryClient()

return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
return (
<MockServiceWorkerProvider>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</MockServiceWorkerProvider>
)
}
4 changes: 4 additions & 0 deletions config/app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const appConfigSchema = z.object({
appName: z.string().min(1, { error: 'App name is required.' }),
appDescription: z.string().min(1, { error: 'App description is required' }),
backendBaseUrl: z.string().min(1, { error: 'Backend base URL is required' }),
isDev: z.boolean(),
isMockingEnabled: z.boolean(),
})

type TAppConfig = z.infer<typeof appConfigSchema>
Expand All @@ -12,6 +14,8 @@ export const appConfig: TAppConfig = {
appName: 'Stride',
appDescription: 'An effective todo management systems for your teams.',
backendBaseUrl: process.env.NEXT_PUBLIC_BACKEND_API_URL || '',
isDev: process.env.NODE_ENV === 'development',
isMockingEnabled: process.env.NEXT_PUBLIC_API_MOCKING === 'true',
}

export const validateAppConfig = (config: TAppConfig) => {
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"eslint-plugin-storybook": "9.0.16",
"jsdom": "26.1.0",
"local-ssl-proxy": "2.0.5",
"msw": "2.10.4",
"npm-run-all": "4.1.5",
"postcss": "8.5.6",
"postcss-loader": "8.1.1",
Expand All @@ -93,5 +94,10 @@
"volta": {
"node": "22.13.0",
"pnpm": "9.15.0"
},
"msw": {
"workerDirectory": [
"public"
]
}
}
Loading