mirror of
https://github.com/vernu/textbee.git
synced 2026-08-01 09:59:02 -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>
46 lines
1.5 KiB
TypeScript
46 lines
1.5 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import { render, screen } from '@testing-library/react'
|
|
import RouteTabs from './route-tabs'
|
|
|
|
// Capture Link props: prefetch is router behavior, invisible in the DOM.
|
|
vi.mock('next/link', () => ({
|
|
default: ({ children, href, prefetch, ...rest }: any) => (
|
|
<a href={href} data-prefetch={String(prefetch)} {...rest}>
|
|
{children}
|
|
</a>
|
|
),
|
|
}))
|
|
|
|
vi.mock('next/navigation', () => ({
|
|
usePathname: () => '/dashboard/messaging',
|
|
}))
|
|
|
|
const tabs = [
|
|
{ href: '/dashboard/messaging', label: 'Send', exact: true },
|
|
{ href: '/dashboard/messaging/bulk', label: 'Bulk Send' },
|
|
{ href: '/dashboard/messaging/history', label: 'History' },
|
|
]
|
|
|
|
describe('RouteTabs', () => {
|
|
it('opts every tab link into full prefetch', () => {
|
|
render(<RouteTabs tabs={tabs} />)
|
|
// These routes are dynamic (session cookie in the app layout); without an
|
|
// explicit prefetch every tab click pays a full server round trip.
|
|
for (const link of screen.getAllByRole('link')) {
|
|
expect(link).toHaveAttribute('data-prefetch', 'true')
|
|
}
|
|
expect(screen.getAllByRole('link')).toHaveLength(tabs.length)
|
|
})
|
|
|
|
it('marks only the active tab with aria-current', () => {
|
|
render(<RouteTabs tabs={tabs} />)
|
|
expect(screen.getByRole('link', { name: 'Send' })).toHaveAttribute(
|
|
'aria-current',
|
|
'page'
|
|
)
|
|
expect(
|
|
screen.getByRole('link', { name: 'Bulk Send' })
|
|
).not.toHaveAttribute('aria-current')
|
|
})
|
|
})
|