import '../styles/app.css';
import '@luke-ui/theme-tactile/stylesheet.css';
import { Provider } from '@luke-ui/react/provider';
import spriteSheetHref from '@luke-ui/react/spritesheet.svg?url&no-inline';
import {
createMemoryHistory,
createRootRoute,
createRouter,
RouterProvider,
} from '@tanstack/react-router';
import type { Root as PageTree } from 'fumadocs-core/page-tree';
import { RootProvider } from 'fumadocs-ui/provider/tanstack';
import type { ReactNode } from 'react';
import { act } from 'react';
import type { Root } from 'react-dom/client';
import { createRoot } from 'react-dom/client';
import { afterEach, expect, test } from 'vite-plus/test';
import { page, userEvent } from 'vite-plus/test/context';
import { DocsShell } from './docs-shell.js';
import { DocsSearchProvider } from './search.js';
import { DocsThemeRoot } from './theme-controls.js';
const tree: PageTree = {
name: 'Docs',
children: [
{
type: 'folder',
name: Documentation,
root: true,
children: [
{ type: 'separator', name: 'Overview' },
{ type: 'page', name: 'Installation', url: '/docs/installation' },
{ type: 'separator', name: 'Foundations' },
{ type: 'page', name: 'Composition', url: '/docs/composition' },
],
},
],
fallback: {
name: 'Components',
children: [
{
type: 'folder',
name: 'Components',
root: true,
index: { type: 'page', name: 'All components', url: '/components' },
children: [
{ type: 'separator', name: 'Actions' },
{ type: 'page', name: 'Button', url: '/components/actions/button' },
{ type: 'page', name: 'Text', url: '/components/typography/text' },
],
},
],
},
};
let container: HTMLElement | undefined;
let root: Root | undefined;
afterEach(async () => {
if (root) act(() => root?.unmount());
container?.remove();
localStorage.clear();
document.documentElement.dir = '';
container = undefined;
root = undefined;
await page.viewport(1024, 800);
});
// Docs size queries are `@container` queries against `:root`. Without that containment they never
// match, and every layout silently falls back to its base style.
test('contains the root so docs container queries have a container to match', () => {
expect(getComputedStyle(document.documentElement).containerType).toBe('inline-size');
});
test('keeps every section visible in RTL with plain link semantics', async () => {
await page.viewport(1280, 800);
document.documentElement.dir = 'rtl';
await renderAt(
'/docs/installation',
Article
,
);
const navigation = page.getByRole('navigation', { name: 'Docs' });
await expect.element(navigation.getByText('Foundations')).toBeVisible();
await expect.element(navigation.getByText('Actions')).not.toBeInTheDocument();
await expect
.element(navigation.getByRole('link', { name: 'Installation' }))
.toHaveAttribute('aria-current', 'page');
await expect
.element(navigation.getByRole('link', { name: 'Composition' }))
.not.toHaveAttribute('aria-current');
});
test('groups separator sections into nested lists and moves keyboard focus through the desktop nav with Tab', async () => {
await page.viewport(1280, 800);
await renderAt(
'/docs/installation',
Article
,
);
const navigation = page.getByRole('navigation', { name: 'Docs' });
const installation = navigation.getByRole('link', { name: 'Installation' });
const overview = navigation.getByRole('heading', { name: 'Overview', level: 2 });
const foundations = navigation.getByRole('heading', { name: 'Foundations', level: 2 });
await expect.element(overview).toBeVisible();
await expect.element(foundations).toBeVisible();
await expect.element(navigation.getByText('Documentation')).not.toBeInTheDocument();
await expect.element(navigation.getByText('Actions')).not.toBeInTheDocument();
await expect.element(navigation.getByRole('link', { name: 'Button' })).not.toBeInTheDocument();
await expect.element(navigation.getByRole('button')).not.toBeInTheDocument();
const overviewList = navigation
.element()
.querySelector(`ul[aria-labelledby="${overview.element().id}"]`);
expect(overviewList).not.toBeNull();
expect(
Array.from(overviewList!.querySelectorAll(':scope > li > a'), (link) => link.textContent),
).toEqual(['Installation']);
const foundationsList = navigation
.element()
.querySelector(`ul[aria-labelledby="${foundations.element().id}"]`);
expect(foundationsList).not.toBeNull();
expect(
Array.from(foundationsList!.querySelectorAll(':scope > li > a'), (link) => link.textContent),
).toEqual(['Composition']);
// Nested section lists must not pick up UA markers or indent without Preflight.
for (const list of navigation.element().querySelectorAll('ul')) {
const listStyle = getComputedStyle(list);
expect(listStyle.listStyleType).toBe('none');
expect(listStyle.marginTop).toBe('0px');
expect(listStyle.marginBottom).toBe('0px');
expect(listStyle.paddingInlineStart).toBe('0px');
}
expect(
Array.from(navigation.element().querySelectorAll('a'), (link) => link.textContent),
).toEqual(['Installation', 'Composition']);
await expect.element(installation).toHaveAttribute('aria-current', 'page');
await act(async () => {
installation.element().focus();
});
await act(async () => {
await userEvent.tab();
});
await expect.element(navigation.getByRole('link', { name: 'Composition' })).toHaveFocus();
});
test('shows the Documentation section at the docs root', async () => {
await page.viewport(1280, 800);
await renderAt(
'/docs',
Article
,
);
const navigation = page.getByRole('navigation', { name: 'Docs' });
await expect.element(navigation.getByRole('link', { name: 'Installation' })).toBeVisible();
await expect
.element(navigation.getByRole('link', { name: 'All components' }))
.not.toBeInTheDocument();
});
for (const pathname of ['/components', '/components/actions/button']) {
test(`renders only the Components section at ${pathname}`, async () => {
await page.viewport(1280, 800);
await renderAt(
pathname,
Article
,
);
const navigation = page.getByRole('navigation', { name: 'Docs' });
const actions = navigation.getByRole('heading', { name: 'Actions', level: 2 });
await expect.element(actions).toBeVisible();
await expect
.element(navigation.getByRole('link', { name: 'Installation' }))
.not.toBeInTheDocument();
const actionsList = navigation
.element()
.querySelector(`ul[aria-labelledby="${actions.element().id}"]`);
expect(actionsList).not.toBeNull();
expect(
Array.from(actionsList!.querySelectorAll(':scope > li > a'), (link) => link.textContent),
).toEqual(['Button', 'Text']);
expect(
Array.from(navigation.element().querySelectorAll('a'), (link) => link.textContent),
).toEqual(['All components', 'Button', 'Text']);
await expect
.element(
navigation.getByRole('link', {
name: pathname === '/components' ? 'All components' : 'Button',
}),
)
.toHaveAttribute('aria-current', 'page');
expect(
navigation
.getByRole('link', { name: 'All components' })
.element()
.getAttribute('aria-current'),
).toBe(pathname === '/components' ? 'page' : null);
});
}
test('opens the mobile drawer, exposes site and docs navigation, and restores focus on Escape', async () => {
await page.viewport(390, 800);
await renderAt(
'/docs/installation',
Article
,
);
const trigger = page.getByRole('button', { name: 'Menu' });
await act(async () => {
await userEvent.click(trigger);
});
await expect.element(page.getByRole('dialog', { name: 'Docs navigation' })).toBeVisible();
await expect
.element(
page.getByRole('navigation', { name: 'Site' }).getByRole('link', { name: 'Playground' }),
)
.toBeVisible();
await expect
.element(page.getByRole('dialog').getByRole('link', { name: 'Installation' }))
.toBeVisible();
await expect
.element(page.getByRole('dialog').getByRole('link', { name: 'Button' }))
.not.toBeInTheDocument();
await act(async () => {
await userEvent.keyboard('{Escape}');
});
await expect.element(page.getByRole('dialog')).not.toBeInTheDocument();
await expect.element(trigger).toHaveFocus();
});
test('closes the mobile drawer from its close button and restores focus', async () => {
await page.viewport(390, 800);
await renderAt(
'/docs/installation',
Article
,
);
const trigger = page.getByRole('button', { name: 'Menu' });
await act(async () => {
await userEvent.click(trigger);
});
await expect.element(page.getByRole('dialog', { name: 'Docs navigation' })).toBeVisible();
await act(async () => {
await userEvent.click(page.getByRole('button', { name: 'Close docs navigation' }));
});
await expect.element(page.getByRole('dialog')).not.toBeInTheDocument();
await expect.element(trigger).toHaveFocus();
});
test('closes the mobile drawer when a docs nav link is activated', async () => {
await page.viewport(390, 800);
await renderAt(
'/docs/installation',
Article
,
);
await act(async () => {
await userEvent.click(page.getByRole('button', { name: 'Menu' }));
});
const dialog = page.getByRole('dialog', { name: 'Docs navigation' });
await expect.element(dialog).toBeVisible();
await act(async () => {
await userEvent.click(dialog.getByRole('link', { name: 'Composition' }));
});
await expect.element(page.getByRole('dialog')).not.toBeInTheDocument();
});
test('shows the Components section in the mobile drawer', async () => {
await page.viewport(390, 800);
await renderAt(
'/components/actions/button',
Article
,
);
await act(async () => {
await userEvent.click(page.getByRole('button', { name: 'Menu' }));
});
const dialog = page.getByRole('dialog', { name: 'Docs navigation' });
await expect.element(dialog.getByRole('link', { name: 'All components' })).toBeVisible();
await expect
.element(dialog.getByRole('link', { name: 'Button' }))
.toHaveAttribute('aria-current', 'page');
await expect.element(dialog.getByRole('link', { name: 'Installation' })).not.toBeInTheDocument();
});
test('closes the mobile drawer when the viewport crosses into the desktop layout', async () => {
await page.viewport(390, 800);
await renderAt(
'/docs/installation',
Article
,
);
await act(async () => {
await userEvent.click(page.getByRole('button', { name: 'Menu' }));
});
await expect.element(page.getByRole('dialog', { name: 'Docs navigation' })).toBeVisible();
await act(async () => {
await page.viewport(1280, 800);
});
await expect
.element(page.getByRole('dialog', { name: 'Docs navigation' }))
.not.toBeInTheDocument();
const navigation = page.getByRole('navigation', { name: 'Docs' });
await expect.element(navigation.getByRole('link', { name: 'Installation' })).toBeVisible();
await expect
.element(navigation.getByRole('link', { name: 'Installation' }))
.toHaveAttribute('aria-current', 'page');
await act(async () => {
navigation.getByRole('link', { name: 'Installation' }).element().focus();
});
await act(async () => {
await userEvent.tab();
});
await expect.element(navigation.getByRole('link', { name: 'Composition' })).toHaveFocus();
});
async function renderAt(pathname: string, children: ReactNode) {
const rootRoute = createRootRoute({
component: () => (
{children}
),
});
const router = createRouter({
history: createMemoryHistory({ initialEntries: [pathname] }),
routeTree: rootRoute,
});
container = document.body.appendChild(document.createElement('div'));
root = createRoot(container);
await act(async () => {
root?.render();
await router.load();
});
}