diff --git a/frontend/src/app/routes.tsx b/frontend/src/app/routes.tsx index 25e88c8..6b820c7 100644 --- a/frontend/src/app/routes.tsx +++ b/frontend/src/app/routes.tsx @@ -1,38 +1,36 @@ +import type { ComponentType } from 'react' import type { RouteObject } from 'react-router' import { RequireAuth } from '@features/auth/RequireAuth' import { AppShell } from '@shared/layout/AppShell' import HomePage from '@pages/public/HomePage' -import LoginPage from '@pages/public/auth/LoginPage' -import RegisterPage from '@pages/public/auth/RegisterPage' -import ProfilePage from '@pages/public/ProfilePage' import NotFoundPage from '@pages/public/NotFoundPage' -// Code-split: the download page pulls in react-markdown, so it loads on demand. -import DownloadRoute from '@pages/download/DownloadRoute' -import AccountPage from '@pages/app/AccountPage' -import LinkPage from '@pages/app/LinkPage' -import ThemesRoute from '@pages/themes/ThemesRoute' -import ConfigsPage from '@pages/app/ConfigsPage' -import ShowcaseRoute from '@pages/showcase/ShowcaseRoute' -import ListingRoute from '@pages/showcase/ListingRoute' +// Adapts a page module's default export to the shape React Router's `lazy` expects. +function toRoute(m: { default: ComponentType }) { + return { Component: m.default } +} + +// Everything below is route-level code-split via React Router's `lazy` field: +// each page's JS only downloads when a visitor actually navigates to it, so the +// initial bundle only carries the app shell + the home page. export const routes: RouteObject[] = [ { element: , children: [ { path: '/', element: }, - { path: '/login', element: }, - { path: '/register', element: }, - { path: '/download', element: }, - { path: '/themes', element: }, - { path: '/u/:id', element: }, - { path: '/showcase', element: }, - { path: '/showcase/:id', element: }, + { path: '/login', lazy: () => import('@pages/public/auth/LoginPage').then(toRoute) }, + { path: '/register', lazy: () => import('@pages/public/auth/RegisterPage').then(toRoute) }, + { path: '/download', lazy: () => import('@pages/download/DownloadRoute').then(toRoute) }, + { path: '/themes', lazy: () => import('@pages/themes/ThemesRoute').then(toRoute) }, + { path: '/u/:id', lazy: () => import('@pages/public/ProfilePage').then(toRoute) }, + { path: '/showcase', lazy: () => import('@pages/showcase/ShowcaseRoute').then(toRoute) }, + { path: '/showcase/:id', lazy: () => import('@pages/showcase/ListingRoute').then(toRoute) }, { element: , children: [ - { path: '/link', element: }, - { path: '/account', element: }, - { path: '/configs', element: }, + { path: '/link', lazy: () => import('@pages/app/LinkPage').then(toRoute) }, + { path: '/account', lazy: () => import('@pages/app/AccountPage').then(toRoute) }, + { path: '/configs', lazy: () => import('@pages/app/ConfigsPage').then(toRoute) }, ], }, { path: '*', element: }, diff --git a/frontend/src/features/auth/tests/pages.test.tsx b/frontend/src/features/auth/tests/pages.test.tsx index 13c18a5..04d7f87 100644 --- a/frontend/src/features/auth/tests/pages.test.tsx +++ b/frontend/src/features/auth/tests/pages.test.tsx @@ -42,7 +42,12 @@ test('registering after a redirect lands back where the visitor was going', asyn }) const { router } = renderApp(routes, '/link') await user.click(await screen.findByRole('link', { name: 'Регистрация' })) - await user.type(await screen.findByLabelText('Почта'), 'a@b.c') + // /login and /register are lazy-loaded and share the "Почта" email label, so the + // previous (login) page can still be mounted for a tick while the register chunk + // resolves. Wait for a register-only marker before querying its email field, or a + // stale reference to the login field (about to unmount) can end up typed into instead. + await screen.findByLabelText('Ник на сайте') + await user.type(screen.getByLabelText('Почта'), 'a@b.c') await user.type(screen.getByLabelText('Ник на сайте'), 'Rider') await user.type(screen.getByLabelText('Пароль'), 'correct-horse') await user.click(screen.getByRole('button', { name: 'Создать аккаунт' })) diff --git a/frontend/src/test/setup.ts b/frontend/src/test/setup.ts index 41d921d..5e9f4fa 100644 --- a/frontend/src/test/setup.ts +++ b/frontend/src/test/setup.ts @@ -1,11 +1,16 @@ import '@testing-library/jest-dom/vitest' import 'fake-indexeddb/auto' -import { cleanup } from '@testing-library/react' +import { cleanup, configure } from '@testing-library/react' import { afterEach, vi } from 'vitest' import { initI18n } from '@app/i18n' initI18n('ru') +// Route pages load via dynamic import() (see src/app/routes.tsx), and under a loaded +// test run (many jsdom workers in parallel) that first chunk resolution can take longer +// than the default 1000ms findBy/waitFor timeout. Give async queries more headroom. +configure({ asyncUtilTimeout: 5000 }) + afterEach(() => cleanup()) afterEach(() => vi.unstubAllGlobals()) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 8459305..e004031 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -35,5 +35,8 @@ export default defineConfig({ environment: 'jsdom', setupFiles: ['./src/test/setup.ts'], css: false, + // Route pages now load via dynamic import() (src/app/routes.tsx); the default 5000ms + // per-test budget can be tight for a route-transition test under a loaded parallel run. + testTimeout: 10000, }, })