mirror of
https://github.com/vernu/textbee.git
synced 2026-07-31 09:28:14 -04:00
- 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>
61 lines
1.9 KiB
TypeScript
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()
|
|
}
|