Skip to content

Commit

Permalink
chore: vitest unit test integration for web
Browse files Browse the repository at this point in the history
  • Loading branch information
aseerkt committed Jul 3, 2024
1 parent daadaa3 commit b8fc619
Show file tree
Hide file tree
Showing 17 changed files with 1,161 additions and 6 deletions.
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
"e2e:test": "pnpm --filter e2e test"
},
"keywords": [
"chat", "realtime", "socket.io", "react", "node.js", "react-query"
"chat",
"realtime",
"socket.io",
"react",
"node.js",
"react-query"
],
"author": "Aseer KT",
"license": "ISC",
Expand Down
812 changes: 811 additions & 1 deletion pnpm-lock.yaml

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc && vite build",
"lint": "tsc && eslint --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"@tanstack/react-query": "^5.48.0",
Expand All @@ -23,6 +24,9 @@
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "^5.47.0",
"@testing-library/dom": "^10.3.0",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
"@types/node": "^20.14.5",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
Expand All @@ -33,12 +37,14 @@
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"jsdom": "^24.1.0",
"postcss": "^8.4.38",
"prettier": "^3.3.2",
"prettier-plugin-tailwindcss": "^0.6.5",
"rollup-plugin-visualizer": "^5.12.0",
"tailwindcss": "^3.4.4",
"typescript": "^5.2.2",
"vite": "^5.2.0"
"vite": "^5.2.0",
"vitest": "^1.6.0"
}
}
9 changes: 9 additions & 0 deletions web/src/components/tests/Button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { render } from '@testing-library/react'
import { Button } from '../Button'

describe('<Button /> tests', () => {
it('should match snapshot', () => {
const { baseElement } = render(<Button>button</Button>)
expect(baseElement).toMatchSnapshot()
})
})
22 changes: 22 additions & 0 deletions web/src/components/tests/Dialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { render } from '@testing-library/react'
import { Dialog } from '../Dialog'

describe('<Dialog /> tests', () => {
it('should match snapshot for isOpen false', () => {
const { baseElement } = render(
<Dialog isOpen={false} onClose={() => {}}>
hello world
</Dialog>,
)
expect(baseElement).toMatchSnapshot()
})

it('should match snapshot for isOpen true', () => {
const { baseElement } = render(
<Dialog isOpen={true} onClose={() => {}}>
hello world
</Dialog>,
)
expect(baseElement).toMatchSnapshot()
})
})
17 changes: 17 additions & 0 deletions web/src/components/tests/Toast.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { timeout } from '@/utils/testUtils'
import { render } from '@testing-library/react'
import { Toast } from '../Toast'

describe('<Toast / > tests', () => {
it('should show toast only for 3 sec', async () => {
const onOnOpenSpy = vi.fn()
const { baseElement } = render(
<Toast open={true} onOpenChange={onOnOpenSpy}>
This is a toast!
</Toast>,
)
expect(baseElement).toMatchSnapshot('toast visible')
await timeout(3500)
expect(onOnOpenSpy).toBeCalledWith(false)
})
})
13 changes: 13 additions & 0 deletions web/src/components/tests/__snapshots__/Button.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`<Button /> tests > should match snapshot 1`] = `
<body>
<div>
<button
class="rounded-lg border-2 border-black disabled:bg-gray-300 bg-black text-white h-10 px-3"
>
button
</button>
</div>
</body>
`;
43 changes: 43 additions & 0 deletions web/src/components/tests/__snapshots__/Dialog.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`<Dialog /> tests > should match snapshot for isOpen false 1`] = `
<body>
<div />
</body>
`;

exports[`<Dialog /> tests > should match snapshot for isOpen false 2`] = `
<body>
<div />
<div
aria-label="dialog_overlay"
class="fixed top-0 flex h-screen w-screen items-center justify-center bg-black bg-opacity-80"
>
<div
aria-modal="true"
class="border-3 rounded bg-white p-6 shadow-md"
role="dialog"
>
hello world
</div>
</div>
</body>
`;

exports[`<Dialog /> tests > should match snapshot for isOpen true 1`] = `
<body>
<div />
<div
aria-label="dialog_overlay"
class="fixed top-0 flex h-screen w-screen items-center justify-center bg-black bg-opacity-80"
>
<div
aria-modal="true"
class="border-3 rounded bg-white p-6 shadow-md"
role="dialog"
>
hello world
</div>
</div>
</body>
`;
52 changes: 52 additions & 0 deletions web/src/components/tests/__snapshots__/Toast.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`<Toast / > tests > should show toast only for 3 sec > toast hidden 1`] = `
<body>
<div />
<div
class="fixed flex text-white justify-between gap-4 md:min-w-[250px] shadow-lg border rounded p-3 bottom-6 z-10 transition-all bg-blue-800 right-6"
>
This is a toast!
<button
aria-label="close toast"
class="inline-flex h-5 w-5 items-center justify-center rounded-full text-white hover:bg-gray-100"
>
x
</button>
</div>
</body>
`;
exports[`<Toast / > tests > should show toast only for 3 sec > toast visible 1`] = `
<body>
<div />
<div
class="fixed flex text-white justify-between gap-4 md:min-w-[250px] shadow-lg border rounded p-3 bottom-6 z-10 transition-all bg-blue-800 right-6"
>
This is a toast!
<button
aria-label="close toast"
class="inline-flex h-5 w-5 items-center justify-center rounded-full text-white hover:bg-gray-100"
>
x
</button>
</div>
</body>
`;
exports[`<Toast / > tests > should show toast only for 3 sec 1`] = `
<body>
<div />
<div
class="fixed flex text-white justify-between gap-4 md:min-w-[250px] shadow-lg border rounded p-3 bottom-6 z-10 transition-all bg-blue-800 right-6"
>
This is a toast!
<button
aria-label="close toast"
class="inline-flex h-5 w-5 items-center justify-center rounded-full text-white hover:bg-gray-100"
>
x
</button>
</div>
</body>
`;
71 changes: 71 additions & 0 deletions web/src/hooks/tests/useAuthRedirect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { renderHook } from '@testing-library/react'
import { useLocation, useNavigate } from 'react-router-dom'
import { Mock } from 'vitest'
import { useAuthState } from '../useAuth'
import { useAuthRedirect } from '../useAuthRedirect'

vi.mock('react-router-dom', () => ({
useLocation: vi.fn(),
useNavigate: vi.fn(),
}))

vi.mock('../useAuth', () => ({
useAuthState: vi.fn(),
}))

describe('useAuthRedirect', () => {
it('should navigate to /chat if authenticated and on the root path', () => {
const mockNavigate = vi.fn()
;(useLocation as Mock).mockReturnValue({ pathname: '/' })
;(useNavigate as Mock).mockReturnValue(mockNavigate)
;(useAuthState as Mock).mockReturnValue(true)

renderHook(() => useAuthRedirect())

expect(mockNavigate).toHaveBeenCalledWith('/chat', { replace: true })
})

it('should navigate to /login if not authenticated and on the root path', () => {
const mockNavigate = vi.fn()
;(useLocation as Mock).mockReturnValue({ pathname: '/' })
;(useNavigate as Mock).mockReturnValue(mockNavigate)
;(useAuthState as Mock).mockReturnValue(false)

renderHook(() => useAuthRedirect())

expect(mockNavigate).toHaveBeenCalledWith('/login', { replace: true })
})

it('should navigate to / if authenticated and on a guest route', () => {
const mockNavigate = vi.fn()
;(useLocation as Mock).mockReturnValue({ pathname: '/login' })
;(useNavigate as Mock).mockReturnValue(mockNavigate)
;(useAuthState as Mock).mockReturnValue(true)

renderHook(() => useAuthRedirect())

expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true })
})

it('should navigate to /login if not authenticated and on a protected route', () => {
const mockNavigate = vi.fn()
;(useLocation as Mock).mockReturnValue({ pathname: '/chat' })
;(useNavigate as Mock).mockReturnValue(mockNavigate)
;(useAuthState as Mock).mockReturnValue(false)

renderHook(() => useAuthRedirect())

expect(mockNavigate).toHaveBeenCalledWith('/login', { replace: true })
})

it('should not navigate if the conditions are not met', () => {
const mockNavigate = vi.fn()
;(useLocation as Mock).mockReturnValue({ pathname: '/chat' })
;(useNavigate as Mock).mockReturnValue(mockNavigate)
;(useAuthState as Mock).mockReturnValue(true)

renderHook(() => useAuthRedirect())

expect(mockNavigate).not.toHaveBeenCalled()
})
})
35 changes: 35 additions & 0 deletions web/src/hooks/tests/useAutoFocus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { renderHook } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { useAutoFocus } from '../useAutoFocus'

describe('useAutoFocus', () => {
it('should focus the element on mount', () => {
const focusMock = vi.fn()

const ref = {
current: { focus: focusMock },
} as unknown as React.RefObject<HTMLInputElement>

renderHook(() => useAutoFocus(ref))

expect(focusMock).toHaveBeenCalledTimes(1)
})

it('should focus the element on dependency change', () => {
const focusMock = vi.fn()

const ref = {
current: { focus: focusMock },
} as unknown as React.RefObject<HTMLInputElement>

const { rerender } = renderHook(({ deps }) => useAutoFocus(ref, deps), {
initialProps: { deps: [1] },
})

expect(focusMock).toHaveBeenCalledTimes(1)

rerender({ deps: [2] })

expect(focusMock).toHaveBeenCalledTimes(2)
})
})
60 changes: 60 additions & 0 deletions web/src/hooks/tests/useClickOutside.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { renderHook } from '@testing-library/react'
import { Mock } from 'vitest'
import { useClickOutside } from '../useClickOutside'

describe('useClickOutside', () => {
let ref: React.RefObject<HTMLDivElement>
let callback: Mock

beforeEach(() => {
callback = vi.fn()
ref = {
current: document.createElement('div'),
} as React.RefObject<HTMLDivElement>
})

it('should call callback when clicking outside the element', () => {
document.body.appendChild(ref.current!)

renderHook(() => useClickOutside(ref, callback))

// Simulate clicking outside the element
document.body.click()

expect(callback).toHaveBeenCalledTimes(1)
})

it('should not call callback when clicking inside the element', () => {
document.body.appendChild(ref.current!)

renderHook(() => useClickOutside(ref, callback))

// Simulate clicking inside the element
ref.current!.click()

expect(callback).not.toHaveBeenCalled()
})

it('should clean up event listeners on unmount', () => {
const addEventListenerSpy = vi.spyOn(document, 'addEventListener')
const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener')

const { unmount } = renderHook(() => useClickOutside(ref, callback))

expect(addEventListenerSpy).toHaveBeenCalledWith(
'mousedown',
expect.any(Function),
)

unmount()

expect(removeEventListenerSpy).toHaveBeenCalledWith(
'mousedown',
expect.any(Function),
)

// Clean up
addEventListenerSpy.mockRestore()
removeEventListenerSpy.mockRestore()
})
})
3 changes: 2 additions & 1 deletion web/src/hooks/useClickOutside.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ export const useClickOutside = <TElement extends HTMLElement>(
return () => {
document.removeEventListener('mousedown', handleOutsideClick)
}
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref, callback])
}
1 change: 1 addition & 0 deletions web/src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom/vitest'
2 changes: 2 additions & 0 deletions web/src/utils/testUtils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const timeout = (timeout: number) =>
new Promise(res => setTimeout(res, timeout))
Loading

0 comments on commit b8fc619

Please sign in to comment.