Files
textbee/web/test/setup.ts
isra el 18e7d1b3d8 perf(web): make dashboard navigation fast and cut session fetches
- prefetch route-tab links so tab clicks reuse a cached payload instead
  of paying an uncached server round trip each time
- add loading.tsx boundaries (dashboard root and each section) so clicks
  paint within a frame; section-level files keep the header and tabs
  mounted while only the content area swaps
- link the sidebar Account item and the checkout page straight to
  /dashboard/account/billing. The /dashboard/account redirect stub stays
  for old links, but no internal link pays the extra hop anymore. The
  stub also dropped query params, which silently ate the plan-change
  success toast.
- set QueryClient defaults (staleTime 60s, no focus refetch, retry 1);
  mutations already invalidate their keys, so the user's own changes
  stay instant. Device messages and webhook deliveries get a 15s
  staleTime since they change from outside the tab.
- replace the axios getCachedSession TTL cache with a token seeded from
  the server session in Providers and kept in sync by a session bridge.
  Requests attach the token synchronously; /api/auth/session is only a
  deduped fallback, instead of a refetch every 2 minutes with a
  thundering herd on expiry.
- swap the billing card's 16px loading spinner for a card-shaped
  skeleton so the tab no longer looks blank while loading

Tests: interceptor seeding/dedupe/signed-out behavior, provider
defaults and token seeding, nav active-state matching, tab prefetch,
billing loading state, plus e2e coverage for direct-to-billing
navigation and an at-most-one-session-call budget guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 13:49:14 +03:00

61 lines
1.9 KiB
TypeScript

import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterAll, afterEach, beforeAll, vi } from 'vitest'
import { server } from './msw/server'
// The axios browser client's interceptor falls back to next-auth's
// getSession() when no token has been seeded (tests render components without
// the app's Providers), which would otherwise trigger a real /api/auth/session
// fetch. Mock it so the interceptor can attach the Bearer token without any
// network access.
const hoisted = vi.hoisted(() => ({ accessToken: 'test-access-token' }))
vi.mock('next-auth/react', async (importOriginal) => {
const actual = await importOriginal<typeof import('next-auth/react')>()
return {
...actual,
getSession: async () => ({
user: {
id: 'user_test_1',
name: 'Test User',
email: 'test.user@example.com',
accessToken: hoisted.accessToken,
},
expires: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
}),
}
})
// Fail loudly on any request that is not explicitly mocked. This guarantees a
// test can never silently fall through to a real backend.
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => {
cleanup()
server.resetHandlers()
})
afterAll(() => server.close())
// jsdom lacks these browser APIs that Radix UI / next-themes rely on.
if (!window.matchMedia) {
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
addListener: vi.fn(),
removeListener: vi.fn(),
dispatchEvent: vi.fn(),
}))
}
class ResizeObserverMock {
observe() {}
unobserve() {}
disconnect() {}
}
window.ResizeObserver = window.ResizeObserver || (ResizeObserverMock as any)
if (!window.HTMLElement.prototype.scrollIntoView) {
window.HTMLElement.prototype.scrollIntoView = vi.fn()
}