mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-26 23:04:57 +00:00
test: reorganize hook tests
This commit is contained in:
100
frontends/nextjs/src/hooks/__tests__/useAuth.roles.test.ts
Normal file
100
frontends/nextjs/src/hooks/__tests__/useAuth.roles.test.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook, act, waitFor } from '@testing-library/react'
|
||||
import type { User } from '@/lib/level-types'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { fetchSession } from '@/lib/auth/api/fetch-session'
|
||||
import { login as loginRequest } from '@/lib/auth/api/login'
|
||||
import { logout as logoutRequest } from '@/lib/auth/api/logout'
|
||||
|
||||
vi.mock('@/lib/auth/api/fetch-session', () => ({
|
||||
fetchSession: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/auth/api/login', () => ({
|
||||
login: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/auth/api/logout', () => ({
|
||||
logout: vi.fn(),
|
||||
}))
|
||||
|
||||
const mockFetchSession = vi.mocked(fetchSession)
|
||||
const mockLogin = vi.mocked(loginRequest)
|
||||
const mockLogout = vi.mocked(logoutRequest)
|
||||
|
||||
const createUser = (overrides?: Partial<User>): User => ({
|
||||
id: 'user_1',
|
||||
username: 'alice',
|
||||
email: 'alice@example.com',
|
||||
role: 'user',
|
||||
createdAt: 1000,
|
||||
tenantId: undefined,
|
||||
profilePicture: undefined,
|
||||
bio: undefined,
|
||||
isInstanceOwner: false,
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const waitForIdle = async (result: { current: { isLoading: boolean } }) => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false)
|
||||
})
|
||||
}
|
||||
|
||||
const resetAuthStore = async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
await result.current.logout()
|
||||
})
|
||||
await waitForIdle(result)
|
||||
unmount()
|
||||
}
|
||||
|
||||
describe('useAuth role mapping', () => {
|
||||
beforeEach(async () => {
|
||||
mockFetchSession.mockReset()
|
||||
mockLogin.mockReset()
|
||||
mockLogout.mockReset()
|
||||
mockFetchSession.mockResolvedValue(null)
|
||||
mockLogout.mockResolvedValue(undefined)
|
||||
|
||||
await resetAuthStore()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ role: 'public', expectedLevel: 1 },
|
||||
{ role: 'user', expectedLevel: 2 },
|
||||
{ role: 'admin', expectedLevel: 4 },
|
||||
{ role: 'supergod', expectedLevel: 6 },
|
||||
{ role: 'unknown', expectedLevel: 0 },
|
||||
])('applies level for role "$role"', async ({ role, expectedLevel }) => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
mockLogin.mockResolvedValue(createUser({ role }))
|
||||
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
await result.current.login('alice@example.com', 'password')
|
||||
})
|
||||
|
||||
expect(result.current.user?.level).toBe(expectedLevel)
|
||||
|
||||
unmount()
|
||||
})
|
||||
|
||||
it('maps refreshed session roles to levels', async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
mockFetchSession.mockResolvedValue(createUser({ role: 'moderator' }))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.refresh()
|
||||
})
|
||||
await waitForIdle(result)
|
||||
|
||||
expect(result.current.user?.level).toBe(3)
|
||||
|
||||
unmount()
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook, act, waitFor } from '@testing-library/react'
|
||||
import type { User } from '@/lib/level-types'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { fetchSession } from '@/lib/auth/api/fetch-session'
|
||||
import { login as loginRequest } from '@/lib/auth/api/login'
|
||||
import { register as registerRequest } from '@/lib/auth/api/register'
|
||||
import { logout as logoutRequest } from '@/lib/auth/api/logout'
|
||||
|
||||
vi.mock('@/lib/auth/api/fetch-session', () => ({
|
||||
fetchSession: vi.fn(),
|
||||
@@ -18,12 +23,6 @@ vi.mock('@/lib/auth/api/logout', () => ({
|
||||
logout: vi.fn(),
|
||||
}))
|
||||
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { fetchSession } from '@/lib/auth/api/fetch-session'
|
||||
import { login as loginRequest } from '@/lib/auth/api/login'
|
||||
import { register as registerRequest } from '@/lib/auth/api/register'
|
||||
import { logout as logoutRequest } from '@/lib/auth/api/logout'
|
||||
|
||||
const mockFetchSession = vi.mocked(fetchSession)
|
||||
const mockLogin = vi.mocked(loginRequest)
|
||||
const mockRegister = vi.mocked(registerRequest)
|
||||
@@ -48,7 +47,17 @@ const waitForIdle = async (result: { current: { isLoading: boolean } }) => {
|
||||
})
|
||||
}
|
||||
|
||||
describe('useAuth', () => {
|
||||
const resetAuthStore = async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
await result.current.logout()
|
||||
})
|
||||
await waitForIdle(result)
|
||||
unmount()
|
||||
}
|
||||
|
||||
describe('useAuth session flows', () => {
|
||||
beforeEach(async () => {
|
||||
mockFetchSession.mockReset()
|
||||
mockLogin.mockReset()
|
||||
@@ -57,16 +66,10 @@ describe('useAuth', () => {
|
||||
mockFetchSession.mockResolvedValue(null)
|
||||
mockLogout.mockResolvedValue(undefined)
|
||||
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
await result.current.logout()
|
||||
})
|
||||
await waitForIdle(result)
|
||||
unmount()
|
||||
await resetAuthStore()
|
||||
})
|
||||
|
||||
it('should start unauthenticated after session check', async () => {
|
||||
it('starts unauthenticated after session check', async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
await waitForIdle(result)
|
||||
@@ -77,28 +80,20 @@ describe('useAuth', () => {
|
||||
unmount()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ email: 'alice@example.com', expectedName: 'alice' },
|
||||
{ email: 'bob.smith@corp.io', expectedName: 'bob.smith' },
|
||||
])('should authenticate $email', async ({ email, expectedName }) => {
|
||||
it('authenticates on login', async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
mockLogin.mockResolvedValue(createUser({
|
||||
id: 'user_1',
|
||||
username: expectedName,
|
||||
email,
|
||||
}))
|
||||
mockLogin.mockResolvedValue(createUser())
|
||||
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
await result.current.login(email, 'password')
|
||||
await result.current.login('alice@example.com', 'password')
|
||||
})
|
||||
|
||||
expect(result.current.user).toMatchObject({
|
||||
id: 'user_1',
|
||||
email,
|
||||
name: expectedName,
|
||||
username: expectedName,
|
||||
email: 'alice@example.com',
|
||||
username: 'alice',
|
||||
level: 2,
|
||||
})
|
||||
expect(result.current.isAuthenticated).toBe(true)
|
||||
@@ -106,7 +101,7 @@ describe('useAuth', () => {
|
||||
unmount()
|
||||
})
|
||||
|
||||
it('should clear user on logout', async () => {
|
||||
it('clears user on logout', async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
mockLogin.mockResolvedValue(createUser())
|
||||
@@ -126,14 +121,16 @@ describe('useAuth', () => {
|
||||
unmount()
|
||||
})
|
||||
|
||||
it('should register and authenticate', async () => {
|
||||
it('registers and authenticates', async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
mockRegister.mockResolvedValue(createUser({
|
||||
id: 'user_2',
|
||||
username: 'newbie',
|
||||
email: 'newbie@example.com',
|
||||
}))
|
||||
mockRegister.mockResolvedValue(
|
||||
createUser({
|
||||
id: 'user_2',
|
||||
username: 'newbie',
|
||||
email: 'newbie@example.com',
|
||||
})
|
||||
)
|
||||
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
@@ -143,7 +140,6 @@ describe('useAuth', () => {
|
||||
expect(result.current.user).toMatchObject({
|
||||
id: 'user_2',
|
||||
email: 'newbie@example.com',
|
||||
name: 'newbie',
|
||||
username: 'newbie',
|
||||
level: 2,
|
||||
})
|
||||
@@ -152,7 +148,7 @@ describe('useAuth', () => {
|
||||
unmount()
|
||||
})
|
||||
|
||||
it('should sync state across hooks', async () => {
|
||||
it('syncs state across hooks', async () => {
|
||||
const first = renderHook(() => useAuth())
|
||||
const second = renderHook(() => useAuth())
|
||||
|
||||
@@ -1,54 +1,34 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook, act, waitFor } from '@testing-library/react'
|
||||
import { useKV } from '@/hooks/useKV'
|
||||
import { useKV } from '@/hooks/data/useKV'
|
||||
|
||||
describe('useKV', () => {
|
||||
const STORAGE_PREFIX = 'mb_kv:'
|
||||
let store: Record<string, string>
|
||||
const STORAGE_PREFIX = 'mb_kv:'
|
||||
let store: Record<string, string>
|
||||
|
||||
const setupLocalStorage = (): void => {
|
||||
store = {}
|
||||
vi.stubGlobal('localStorage', {
|
||||
getItem: vi.fn((key: string) => store[key] ?? null),
|
||||
setItem: vi.fn((key: string, value: string) => {
|
||||
store[key] = value
|
||||
}),
|
||||
removeItem: vi.fn((key: string) => {
|
||||
delete store[key]
|
||||
}),
|
||||
clear: vi.fn(() => {
|
||||
Object.keys(store).forEach(k => delete store[k])
|
||||
}),
|
||||
length: 0,
|
||||
key: vi.fn(() => null),
|
||||
})
|
||||
}
|
||||
|
||||
describe('useKV storage', () => {
|
||||
beforeEach(() => {
|
||||
// Mock localStorage
|
||||
store = {}
|
||||
vi.stubGlobal('localStorage', {
|
||||
getItem: vi.fn((key: string) => store[key] ?? null),
|
||||
setItem: vi.fn((key: string, value: string) => { store[key] = value }),
|
||||
removeItem: vi.fn((key: string) => { delete store[key] }),
|
||||
clear: vi.fn(() => { Object.keys(store).forEach(k => delete store[k]) }),
|
||||
length: 0,
|
||||
key: vi.fn(() => null),
|
||||
})
|
||||
setupLocalStorage()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ key: 'user_name', defaultValue: 'John', description: 'string value' },
|
||||
{ key: 'user_count', defaultValue: 0, description: 'number value' },
|
||||
{ key: 'is_active', defaultValue: true, description: 'boolean value' },
|
||||
{ key: 'user_data', defaultValue: { id: 1, name: 'John' }, description: 'object value' },
|
||||
])('should initialize hook with $description', ({ key, defaultValue }) => {
|
||||
const { result } = renderHook(() => useKV(key, defaultValue))
|
||||
const [value] = result.current
|
||||
|
||||
expect(value).toBe(defaultValue)
|
||||
})
|
||||
|
||||
it('should initialize with undefined when no default value provided', () => {
|
||||
const { result } = renderHook(() => useKV('empty_key'))
|
||||
const [value] = result.current
|
||||
|
||||
expect(value).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should load value from localStorage when available', async () => {
|
||||
localStorage.setItem(`${STORAGE_PREFIX}stored_key`, JSON.stringify('stored'))
|
||||
|
||||
const { result } = renderHook(() => useKV('stored_key', 'default'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current[0]).toBe('stored')
|
||||
})
|
||||
})
|
||||
|
||||
it('should migrate legacy localStorage entries to namespaced keys', () => {
|
||||
it('migrates legacy localStorage entries to namespaced keys', () => {
|
||||
localStorage.setItem('legacy_key', JSON.stringify('legacy'))
|
||||
|
||||
const { result } = renderHook(() => useKV('legacy_key', 'default'))
|
||||
@@ -58,7 +38,7 @@ describe('useKV', () => {
|
||||
expect(localStorage.getItem('legacy_key')).toBeNull()
|
||||
})
|
||||
|
||||
it('should update value when using updater function', async () => {
|
||||
it('updates value when using updater function', async () => {
|
||||
const { result } = renderHook(() => useKV('counter', 0))
|
||||
|
||||
const [, updateValue] = result.current
|
||||
@@ -71,9 +51,8 @@ describe('useKV', () => {
|
||||
expect(newValue).toBe(1)
|
||||
})
|
||||
|
||||
it('should update value when providing direct value', async () => {
|
||||
it('updates value when providing direct value', async () => {
|
||||
const { result } = renderHook(() => useKV('name', 'John'))
|
||||
|
||||
const [, updateValue] = result.current
|
||||
|
||||
await act(async () => {
|
||||
@@ -84,7 +63,7 @@ describe('useKV', () => {
|
||||
expect(newValue).toBe('Jane')
|
||||
})
|
||||
|
||||
it('should handle complex object updates', async () => {
|
||||
it('handles complex object updates', async () => {
|
||||
const initialObject = { id: 1, name: 'John', email: 'john@example.com' }
|
||||
const { result } = renderHook(() => useKV('user', initialObject))
|
||||
|
||||
@@ -101,7 +80,7 @@ describe('useKV', () => {
|
||||
expect(newValue).toEqual({ id: 1, name: 'Jane', email: 'john@example.com' })
|
||||
})
|
||||
|
||||
it('should handle array updates', async () => {
|
||||
it('handles array updates', async () => {
|
||||
const initialArray = [1, 2, 3]
|
||||
const { result } = renderHook(() => useKV('items', initialArray))
|
||||
|
||||
@@ -115,7 +94,7 @@ describe('useKV', () => {
|
||||
expect(newValue).toEqual([1, 2, 3, 4])
|
||||
})
|
||||
|
||||
it('should maintain separate state for different keys', async () => {
|
||||
it('maintains separate state for different keys', () => {
|
||||
const { result: result1 } = renderHook(() => useKV('key1', 'value1'))
|
||||
const { result: result2 } = renderHook(() => useKV('key2', 'value2'))
|
||||
|
||||
@@ -126,7 +105,7 @@ describe('useKV', () => {
|
||||
expect(value2).toBe('value2')
|
||||
})
|
||||
|
||||
it('should persist updates across multiple hooks with same key', async () => {
|
||||
it('persists updates across multiple hooks with same key', async () => {
|
||||
const { result: firstHook } = renderHook(() => useKV('shared_key', 'initial'))
|
||||
const [, updateValue] = firstHook.current
|
||||
|
||||
@@ -134,14 +113,13 @@ describe('useKV', () => {
|
||||
await updateValue('updated')
|
||||
})
|
||||
|
||||
// Create a new hook with the same key
|
||||
const { result: secondHook } = renderHook(() => useKV('shared_key', 'initial'))
|
||||
const [value] = secondHook.current
|
||||
|
||||
expect(value).toBe('updated')
|
||||
})
|
||||
|
||||
it('should sync updates across mounted hooks with same key', async () => {
|
||||
it('syncs updates across mounted hooks with same key', async () => {
|
||||
const { result: firstHook } = renderHook(() => useKV('sync_key', 'initial'))
|
||||
const { result: secondHook } = renderHook(() => useKV('sync_key', 'initial'))
|
||||
|
||||
@@ -154,19 +132,7 @@ describe('useKV', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ initialValue: null, key: 'falsy_key_null', description: 'null value' },
|
||||
{ initialValue: false, key: 'falsy_key_false', description: 'false boolean' },
|
||||
{ initialValue: 0, key: 'falsy_key_zero', description: 'zero number' },
|
||||
{ initialValue: '', key: 'falsy_key_empty', description: 'empty string' },
|
||||
])('should handle falsy $description correctly', ({ initialValue, key }) => {
|
||||
const { result } = renderHook(() => useKV(key, initialValue))
|
||||
const [value] = result.current
|
||||
|
||||
expect(value).toBe(initialValue)
|
||||
})
|
||||
|
||||
it('should handle rapid updates correctly', async () => {
|
||||
it('handles rapid updates correctly', async () => {
|
||||
const { result } = renderHook(() => useKV('rapid_key', 0))
|
||||
const [, updateValue] = result.current
|
||||
|
||||
@@ -183,7 +149,7 @@ describe('useKV', () => {
|
||||
expect(finalValue).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
it('should persist updates to localStorage', async () => {
|
||||
it('persists updates to localStorage', async () => {
|
||||
const { result } = renderHook(() => useKV('persist_key', 'initial'))
|
||||
const [, updateValue] = result.current
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { useKV } from '@/hooks/data/useKV'
|
||||
|
||||
const STORAGE_PREFIX = 'mb_kv:'
|
||||
let store: Record<string, string>
|
||||
|
||||
const setupLocalStorage = (): void => {
|
||||
store = {}
|
||||
vi.stubGlobal('localStorage', {
|
||||
getItem: vi.fn((key: string) => store[key] ?? null),
|
||||
setItem: vi.fn((key: string, value: string) => {
|
||||
store[key] = value
|
||||
}),
|
||||
removeItem: vi.fn((key: string) => {
|
||||
delete store[key]
|
||||
}),
|
||||
clear: vi.fn(() => {
|
||||
Object.keys(store).forEach(k => delete store[k])
|
||||
}),
|
||||
length: 0,
|
||||
key: vi.fn(() => null),
|
||||
})
|
||||
}
|
||||
|
||||
describe('useKV validation', () => {
|
||||
beforeEach(() => {
|
||||
setupLocalStorage()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ key: 'user_name', defaultValue: 'John', description: 'string value' },
|
||||
{ key: 'user_count', defaultValue: 0, description: 'number value' },
|
||||
{ key: 'is_active', defaultValue: true, description: 'boolean value' },
|
||||
{ key: 'user_data', defaultValue: { id: 1, name: 'John' }, description: 'object value' },
|
||||
])('initializes with $description', ({ key, defaultValue }) => {
|
||||
const { result } = renderHook(() => useKV(key, defaultValue))
|
||||
const [value] = result.current
|
||||
|
||||
expect(value).toBe(defaultValue)
|
||||
})
|
||||
|
||||
it('initializes with undefined when no default value provided', () => {
|
||||
const { result } = renderHook(() => useKV('empty_key'))
|
||||
const [value] = result.current
|
||||
|
||||
expect(value).toBeUndefined()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ initialValue: null, key: 'falsy_key_null', description: 'null value' },
|
||||
{ initialValue: false, key: 'falsy_key_false', description: 'false boolean' },
|
||||
{ initialValue: 0, key: 'falsy_key_zero', description: 'zero number' },
|
||||
{ initialValue: '', key: 'falsy_key_empty', description: 'empty string' },
|
||||
])('handles $description correctly', ({ initialValue, key }) => {
|
||||
const { result } = renderHook(() => useKV(key, initialValue))
|
||||
const [value] = result.current
|
||||
|
||||
expect(value).toBe(initialValue)
|
||||
})
|
||||
|
||||
it('loads value from localStorage when available', () => {
|
||||
localStorage.setItem(`${STORAGE_PREFIX}stored_key`, JSON.stringify('stored'))
|
||||
|
||||
const { result } = renderHook(() => useKV('stored_key', 'default'))
|
||||
const [value] = result.current
|
||||
|
||||
expect(value).toBe('stored')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Auto-refresh error-handling tests
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { useAutoRefresh } from '../useAutoRefresh'
|
||||
|
||||
describe('useAutoRefresh error handling', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('stops refresh when disabled after being enabled', () => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useAutoRefresh({
|
||||
intervalMs: 5000,
|
||||
onRefresh,
|
||||
enabled: true,
|
||||
})
|
||||
)
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(2500)
|
||||
})
|
||||
|
||||
act(() => {
|
||||
result.current.setEnabled(false)
|
||||
})
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(10000)
|
||||
})
|
||||
|
||||
expect(onRefresh).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('continues scheduling refreshes after onRefresh errors', () => {
|
||||
const erroringRefresh = vi.fn().mockImplementation(() =>
|
||||
Promise.reject(new Error('refresh failed')).catch(() => {})
|
||||
)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useAutoRefresh({
|
||||
intervalMs: 2000,
|
||||
onRefresh: erroringRefresh,
|
||||
enabled: true,
|
||||
})
|
||||
)
|
||||
|
||||
expect(result.current.secondsUntilNextRefresh).toBe(2)
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(2000)
|
||||
})
|
||||
expect(erroringRefresh).toHaveBeenCalledTimes(1)
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(1000)
|
||||
})
|
||||
expect(result.current.secondsUntilNextRefresh).toBe(1)
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(1000)
|
||||
})
|
||||
expect(result.current.secondsUntilNextRefresh).toBe(2)
|
||||
expect(erroringRefresh).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,11 @@
|
||||
/**
|
||||
* Tests for useAutoRefresh hook - Auto-refresh polling management
|
||||
* Following parameterized test pattern per project conventions
|
||||
* Auto-refresh polling tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { renderHook, act, waitFor } from '@testing-library/react'
|
||||
import { useAutoRefresh } from './useAutoRefresh'
|
||||
import { useAutoRefresh } from '../useAutoRefresh'
|
||||
|
||||
describe('useAutoRefresh', () => {
|
||||
describe('useAutoRefresh polling', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
@@ -21,7 +19,7 @@ describe('useAutoRefresh', () => {
|
||||
{ enabled: false, expectAutoRefreshing: false },
|
||||
{ enabled: true, expectAutoRefreshing: true },
|
||||
{ enabled: undefined, expectAutoRefreshing: false },
|
||||
])('should initialize with enabled=$enabled -> isAutoRefreshing=$expectAutoRefreshing', ({ enabled, expectAutoRefreshing }) => {
|
||||
])('initializes with enabled=$enabled -> isAutoRefreshing=$expectAutoRefreshing', ({ enabled, expectAutoRefreshing }) => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
@@ -39,7 +37,7 @@ describe('useAutoRefresh', () => {
|
||||
{ intervalMs: 30000, expectedSeconds: 30 },
|
||||
{ intervalMs: 60000, expectedSeconds: 60 },
|
||||
{ intervalMs: 5000, expectedSeconds: 5 },
|
||||
])('should set secondsUntilNextRefresh from intervalMs=$intervalMs', ({ intervalMs, expectedSeconds }) => {
|
||||
])('sets secondsUntilNextRefresh from intervalMs=$intervalMs', ({ intervalMs, expectedSeconds }) => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
@@ -55,7 +53,7 @@ describe('useAutoRefresh', () => {
|
||||
})
|
||||
|
||||
describe('toggleAutoRefresh', () => {
|
||||
it('should toggle from disabled to enabled', () => {
|
||||
it('toggles from disabled to enabled', () => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
@@ -75,7 +73,7 @@ describe('useAutoRefresh', () => {
|
||||
expect(result.current.isAutoRefreshing).toBe(true)
|
||||
})
|
||||
|
||||
it('should toggle from enabled to disabled', () => {
|
||||
it('toggles from enabled to disabled', () => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
@@ -102,7 +100,7 @@ describe('useAutoRefresh', () => {
|
||||
{ initial: true, setTo: false, expected: false },
|
||||
{ initial: false, setTo: false, expected: false },
|
||||
{ initial: true, setTo: true, expected: true },
|
||||
])('should set from $initial to $setTo', ({ initial, setTo, expected }) => {
|
||||
])('sets from $initial to $setTo', ({ initial, setTo, expected }) => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
@@ -122,7 +120,7 @@ describe('useAutoRefresh', () => {
|
||||
})
|
||||
|
||||
describe('refresh timing', () => {
|
||||
it('should call onRefresh after intervalMs when enabled', async () => {
|
||||
it('calls onRefresh after intervalMs when enabled', async () => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
renderHook(() =>
|
||||
@@ -133,23 +131,20 @@ describe('useAutoRefresh', () => {
|
||||
})
|
||||
)
|
||||
|
||||
// Not called initially
|
||||
expect(onRefresh).not.toHaveBeenCalled()
|
||||
|
||||
// Advance to just before interval
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(4999)
|
||||
})
|
||||
expect(onRefresh).not.toHaveBeenCalled()
|
||||
|
||||
// Advance past interval
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(1)
|
||||
})
|
||||
expect(onRefresh).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should not call onRefresh when disabled', () => {
|
||||
it('does not call onRefresh when disabled', () => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
renderHook(() =>
|
||||
@@ -167,7 +162,7 @@ describe('useAutoRefresh', () => {
|
||||
expect(onRefresh).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call onRefresh multiple times at interval', () => {
|
||||
it('calls onRefresh multiple times at interval', () => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
renderHook(() =>
|
||||
@@ -179,7 +174,7 @@ describe('useAutoRefresh', () => {
|
||||
)
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(15000) // 3 intervals
|
||||
vi.advanceTimersByTime(15000)
|
||||
})
|
||||
|
||||
expect(onRefresh).toHaveBeenCalledTimes(3)
|
||||
@@ -187,7 +182,7 @@ describe('useAutoRefresh', () => {
|
||||
})
|
||||
|
||||
describe('countdown', () => {
|
||||
it('should decrement countdown every second when enabled', () => {
|
||||
it('decrements countdown every second when enabled', () => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
@@ -211,7 +206,7 @@ describe('useAutoRefresh', () => {
|
||||
expect(result.current.secondsUntilNextRefresh).toBe(3)
|
||||
})
|
||||
|
||||
it('should reset countdown after reaching zero', () => {
|
||||
it('resets countdown after reaching zero', () => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
@@ -224,45 +219,11 @@ describe('useAutoRefresh', () => {
|
||||
|
||||
expect(result.current.secondsUntilNextRefresh).toBe(3)
|
||||
|
||||
// Advance 3 seconds to reach zero
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(3000)
|
||||
})
|
||||
|
||||
// Should reset to initial value
|
||||
expect(result.current.secondsUntilNextRefresh).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cleanup', () => {
|
||||
it('should stop refresh when disabled after being enabled', () => {
|
||||
const onRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useAutoRefresh({
|
||||
intervalMs: 5000,
|
||||
onRefresh,
|
||||
enabled: true,
|
||||
})
|
||||
)
|
||||
|
||||
// Advance half interval
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(2500)
|
||||
})
|
||||
|
||||
// Disable
|
||||
act(() => {
|
||||
result.current.setEnabled(false)
|
||||
})
|
||||
|
||||
// Advance another full interval
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(10000)
|
||||
})
|
||||
|
||||
// Should not have been called (disabled before first interval completed)
|
||||
expect(onRefresh).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user