mirror of
https://github.com/vernu/textbee.git
synced 2026-07-31 09:28:14 -04:00
Search previously listed only the 5 sidebar routes plus Log out, so subroutes like bulk send, message history, webhook deliveries and every account section were unreachable by search. Typing "csv" or "invoice" or "password" returned nothing. New search-registry.ts is the single source of truth: every user-reachable destination with a description and the words people actually type, rather than the labels we happened to choose for the nav. cmdk matches those keywords in addition to the label, so "csv" finds Bulk send and "invoice" finds Billing. External resources (Android app, quick start, status, contribute) are included and open in a new tab. Mobile had no search at all: the trigger and the dialog were one component living inside the `hidden md:flex` sidebar. Split them. The dialog now mounts in the dashboard layout with open state lifted, the sidebar keeps its trigger, and mobile gets a labelled sticky search bar. That matters most on mobile, where the tab bar is capped at 4 items and search is the only path to Webhooks and every subroute. The palette also gains theme actions, since the theme control now lives in the desktop-only sidebar. search-registry.test.ts walks the App Router tree on disk and fails if any page.tsx has no registry entry, naming the offending route. Redirect-only routes are allowlisted. Verified it actually fails by removing an entry, rather than assuming a green test means coverage. Plus e2e proving keyword search, the mobile path to webhooks, and the keyboard shortcut. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
import { expect, test } from '@playwright/test'
|
|
import { authenticate } from './session'
|
|
import { mockApi } from './mock-api'
|
|
|
|
// Search must find pages by the words a user thinks in, not by our nav labels,
|
|
// and it must be reachable on mobile (the trigger used to live only in the
|
|
// desktop-only sidebar).
|
|
|
|
test.describe('command palette search (mocked API, no real backend)', () => {
|
|
test('desktop: keyword search reaches a subroute the sidebar does not list', async ({
|
|
page,
|
|
context,
|
|
}) => {
|
|
await page.setViewportSize({ width: 1280, height: 900 })
|
|
await authenticate(context)
|
|
await mockApi(page)
|
|
await page.goto('/dashboard')
|
|
|
|
await page.getByRole('button', { name: /search/i }).first().click()
|
|
|
|
// "csv" appears nowhere in the label "Bulk send"; it matches via keywords.
|
|
await page.getByPlaceholder(/search pages/i).fill('csv')
|
|
await page.getByRole('option', { name: /bulk send/i }).click()
|
|
|
|
await expect(page).toHaveURL(/\/dashboard\/messaging\/bulk/)
|
|
})
|
|
|
|
test('desktop: business wording finds billing', async ({ page, context }) => {
|
|
await page.setViewportSize({ width: 1280, height: 900 })
|
|
await authenticate(context)
|
|
await mockApi(page)
|
|
await page.goto('/dashboard')
|
|
|
|
await page.getByRole('button', { name: /search/i }).first().click()
|
|
await page.getByPlaceholder(/search pages/i).fill('invoice')
|
|
await page.getByRole('option', { name: /billing/i }).click()
|
|
|
|
await expect(page).toHaveURL(/\/dashboard\/account\/billing/)
|
|
})
|
|
|
|
test('mobile: search is reachable and reaches webhooks', async ({
|
|
page,
|
|
context,
|
|
}) => {
|
|
await page.setViewportSize({ width: 375, height: 800 })
|
|
await authenticate(context)
|
|
await mockApi(page)
|
|
await page.goto('/dashboard')
|
|
|
|
// Webhooks is deliberately absent from the 4-item mobile tab bar, so
|
|
// search is the mobile path to it.
|
|
await page.getByRole('button', { name: /search/i }).first().click()
|
|
await page.getByPlaceholder(/search pages/i).fill('callback')
|
|
await page.getByRole('option', { name: /^webhooks/i }).first().click()
|
|
|
|
await expect(page).toHaveURL(/\/dashboard\/webhooks/)
|
|
})
|
|
|
|
test('keyboard shortcut opens the palette', async ({ page, context }) => {
|
|
await page.setViewportSize({ width: 1280, height: 900 })
|
|
await authenticate(context)
|
|
await mockApi(page)
|
|
await page.goto('/dashboard')
|
|
|
|
await page.keyboard.press('ControlOrMeta+k')
|
|
await expect(page.getByPlaceholder(/search pages/i)).toBeVisible()
|
|
})
|
|
|
|
test('theme can be changed from the palette (the mobile path)', async ({
|
|
page,
|
|
context,
|
|
}) => {
|
|
await page.setViewportSize({ width: 375, height: 800 })
|
|
await authenticate(context)
|
|
await mockApi(page)
|
|
await page.goto('/dashboard')
|
|
|
|
await page.getByRole('button', { name: /search/i }).first().click()
|
|
await page.getByPlaceholder(/search pages/i).fill('dark')
|
|
await page.getByRole('option', { name: /dark theme/i }).click()
|
|
|
|
await expect(page.locator('html')).toHaveClass(/dark/)
|
|
})
|
|
})
|