Files
supabase/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.levels.nav-check.test.ts
Miranda Limonczenko 4b7af187a4 feat(docs): add Option 1 navigation behind the flag (#52191)
Closes DOCS-1469

## Problem

Option 1 in the navigation proposal groups the docs into Start, Build,
Develop and deploy, Run and manage, Self-Hosting, Reference, and
Resources. The `docs-ia-mvp-v1` flag from #52049 switches to an Option 1
config. That config is still a copy of today's navigation.

## Solution

- **Top nav:** reads Start, Build, Develop and deploy, Run and manage,
Self-Hosting, Reference, and Resources. Menus still open sidebars by URL
prefix, as they do today.
- **Run and manage:** pages under `/guides/platform`,
`/guides/security`, and `/guides/observability` share one sidebar with
the outline's seven groups.
- **Other sidebars:** follow the outline. REST, GraphQL and Realtime
share one sidebar, and Cron and Queues share one sidebar.
- **Seven new topics:** keep their outline labels and link to the
section on their source page, for example
`getting-started/api-keys#leaked-key`.
- **No flash of today's nav:** flags load in the browser, so the nav
stays invisible until the query param or the flags resolve, with a
1-second fallback. The links stay in the HTML for agents.
- **Tests:** each page appears once across the sidebars, and every link
goes to a page in today's navigation. With the flag off, `useNavItems`
returns today's navigation.

**Not changed:** no URLs move. Breadcrumbs still show today's trail,
which DOCS-1470 covers.

## Manual testing

1. Open [Database Backups with the flag
on](https://docs-git-docs-ia-nav-option-1-supabase.vercel.app/docs/guides/platform/backups?docs-ia-mvp-v1=option-1).
The top nav reads Start, Build, Develop and deploy, Run and manage,
Self-Hosting, Reference, and Resources. The sidebar shows the seven Run
and manage groups.
2. Open [the same page with the flag
off](https://docs-git-docs-ia-nav-option-1-supabase.vercel.app/docs/guides/platform/backups?docs-ia-mvp-v1=control).
Today's navigation shows.
3. Open [Database with the flag
on](https://docs-git-docs-ia-nav-option-1-supabase.vercel.app/docs/guides/database/overview?docs-ia-mvp-v1=option-1).
The sidebar starts with Connect to your database.
4. In the sidebar, open Row Level Security, roles and Vault, and select
Test RLS policies with pgTAP. The testing overview opens at Database
unit testing with pgTAP.
2026-10-28 16:03:11 +00:00

96 lines
3.2 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { GlobalMenuItems } from '../Navigation.types'
import * as currentNavItems from './NavigationMenu.constants'
import * as option1NavItems from '@/features/IaNav/option1.constants'
const MAX_ITEMS = 9
type Level = { path: string; size: number }
type NavNode = { name?: string; enabled?: boolean; items?: NavNode[] }
// count only what renders: disabled items and their subtrees are hidden
const isRendered = (item: { enabled?: boolean }) => item.enabled !== false
function sidebarLevels(path: string, allItems: NavNode[]): Level[] {
const items = allItems.filter((item) => isRendered(item))
return [
{ path, size: items.length },
...items.flatMap((item) =>
item.items?.length ? sidebarLevels(`${path} > ${item.name}`, item.items) : []
),
]
}
function topNavLevels(allSections: GlobalMenuItems): Level[] {
const menu = allSections.filter(([item]) => isRendered(item))
return [
{ path: 'Top nav', size: menu.length },
...menu.flatMap(([item]) =>
(item.menuItems ?? []).map((column, index) => ({
path: `Top nav > ${item.label} > column ${index + 1}`,
size: column.filter((columnItem) => isRendered(columnItem)).length,
}))
),
]
}
function oversizedLevels(navItems: Record<string, unknown>): string[] {
// A sidebar shared by several URL prefixes is exported under each name.
const sidebars = new Set(
Object.values(navItems).filter(
(value): value is { title: string; items: NavNode[] } =>
typeof value === 'object' &&
value !== null &&
'title' in value &&
Array.isArray((value as { items?: unknown }).items)
)
)
return [
...topNavLevels(navItems.GLOBAL_MENU_ITEMS as GlobalMenuItems),
...[...sidebars].flatMap((sidebar) => sidebarLevels(sidebar.title, sidebar.items)),
]
.filter((level) => level.size > MAX_ITEMS)
.map((level) => level.path)
}
// Levels allowed 10 or more items, such as a list of peers like quickstarts or providers.
const ALLOWLIST = [
'Auth',
'Auth > Flows (How-tos)',
'Auth > Flows (How-tos) > Social Sign-in (OAuth)',
'Database',
'Database > Extensions',
'Database > Foreign Data Wrappers > Sources',
'Database > Working with your database (intermediate)',
'Edge Functions',
'Edge Functions > Examples',
'Edge Functions > Third-Party Tools',
'Platform > Billing > Manage your usage',
'Platform > Platform Configuration',
'Self-Hosting',
'Self-Hosting > How-to Guides',
'Start with Supabase > Framework Quickstarts',
'Start with Supabase > Web app demos',
'Storage > File Buckets',
]
const OPTION_1_ALLOWLIST = [
'Auth > Social Sign-in (OAuth)',
'Database > Foreign Data Wrappers > Apps and APIs',
'Database > Foreign Data Wrappers > Databases and warehouses',
'Start > Framework Quickstarts > JavaScript and TypeScript',
'Start > Web app demos',
]
describe('navigation level size', () => {
it.each([
["today's navigation", currentNavItems, ALLOWLIST],
['Option 1', option1NavItems, OPTION_1_ALLOWLIST],
])('%s has no level with 10 or more items outside the allowlist', (_, navItems, allowlist) => {
expect(oversizedLevels(navItems).sort()).toEqual([...allowlist].sort())
})
})