Files
hanzo.ai/e2e/gates/sidebar.spec.ts
zeekay 70349b34c7 beta: a post about one topic, with that topic's page and card
The share card offers Hanzo, the products and the betas the member asked
for, the rest under More by group. A topic picks the posts Shuffle offers,
the link (/beta/for/<topic>, tagged utm_content) and the card it unfurls
to: that page carries the topic's title, line and card, a canonical naming
itself and the one noindex (lib/publish SHARES), and sends a person on to
/beta with the query kept, so ref and the campaign land as before.

lib/hanzo/topics is the list, its words the site's and BETAS's own.
generate-og draws a card per topic from it, palette-quantized with sharp:
49 cards, 2.9 MB. The cards gate reads every card's rendered words.

Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
2026-10-26 12:03:39 -07:00

138 lines
6.2 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test'
import { serveExport } from './export'
import { rail } from './rail'
import { answer, signIn } from '../session'
/**
* THE COLUMN'S FILTER NARROWS THE COLUMN, AND A SHARED LINK SHOWS NO API ERROR.
*
* The filter used to reach the chats and the rooms and nothing else: with no
* chats, typing changed nothing on screen, and the two folders under it listed
* every row whatever was typed. It now narrows the places, the lists, and the
* folders — a folder with matches opens on them, one with none is not drawn —
* and Escape clears it.
*
* /referral read `/v1/user/<id>/referrers`, a route the API never served, and
* printed "API error 404" to everybody who pressed Share Hanzo. It reads cloud's
* `/v1/referral` now, and the `?ref=` a friend arrives with is claimed quietly
* once they are signed in, whatever the answer.
*/
let url: string
let stop: () => Promise<void>
test.beforeAll(async () => {
const served = await serveExport()
url = served.url
stop = served.close
})
test.afterAll(async () => stop())
/** A signed-in reader with no chats, projects or rooms. */
async function reader(page: Page): Promise<void> {
await signIn(page)
await answer(page, '/v1/billing/balance', { available: 0 })
await answer(page, '/v1/agents', { agents: [] })
await answer(page, '/v1/agents/chat/conversations', { conversations: [] })
await answer(page, '/v1/agents/sessions', { sessions: [] })
await answer(page, '/v1/projects', [])
await answer(page, '/v1/team/rooms', { rooms: [] })
await answer(page, '/v1/channels/inbox', { items: [] })
}
test('the filter narrows the places, the lists and the folders, and Escape clears it', async ({ page }) => {
await reader(page)
await page.setViewportSize({ width: 1440, height: 900 })
await page.goto(`${url}/home`)
await rail(page, '[data-slot="list"]')
const box = page.locator('input[aria-label="Filter sidebar"]')
const places = page.locator('[data-slot="places-list"]')
const row = (name: string) => page.getByRole('button', { name, exact: true })
const folder = (name: string) => page.locator('[role="button"][aria-expanded]', { hasText: name })
await box.fill('stor')
await expect(places.getByRole('button')).toHaveCount(0)
await expect(folder('Cloud & Platform')).toHaveAttribute('aria-expanded', 'true')
await expect(row('S3 Object Storage')).toBeVisible()
await expect(row('Datastore')).toBeVisible()
await expect(row('Compute & VMs')).toHaveCount(0)
await expect(row('Settings')).toHaveCount(0)
await expect(page.getByText('No chat matches “stor”.')).toBeVisible()
await box.fill('settings')
await expect(folder('Cloud & Platform')).toHaveCount(0)
await expect(row('Settings')).toBeVisible()
await box.fill('dev')
await expect(places.getByRole('button', { name: 'Dev', exact: true })).toBeVisible()
await expect(places.getByRole('button', { name: 'Chat', exact: true })).toHaveCount(0)
await box.press('Escape')
await expect(box).toHaveValue('')
await expect(places.getByRole('button', { name: 'Chat', exact: true })).toBeVisible()
await expect(folder('Cloud & Platform')).toHaveAttribute('aria-expanded', 'false')
await expect(row('Settings')).toBeVisible()
})
test('the filter over settings says when nothing matches', async ({ page }) => {
await reader(page)
await page.setViewportSize({ width: 1440, height: 900 })
await page.goto(`${url}/home`)
await rail(page, '[data-slot="list"]')
await page.getByRole('button', { name: 'Settings', exact: true }).first().click()
const box = page.locator('input[aria-label="Filter sidebar"]')
await box.fill('bill')
await expect(page.getByRole('button', { name: 'Billing', exact: true })).toBeVisible()
await box.fill('zzzz')
await expect(page.getByText('No matches for “zzzz”.')).toBeVisible()
})
test('Share Hanzo reads the referral service, and no API error reaches the page', async ({ page }) => {
await reader(page)
const asked: string[] = []
page.on('request', (r) => asked.push(new URL(r.url()).pathname))
await answer(page, '/v1/referral', {
code: 'K7Q2ZP',
link: 'https://hanzo.ai/?ref=K7Q2ZP',
counts: { total: 3, signup: 2, qualified: 1 },
referrals: [{ id: 'r1', referee: 'acme', status: 'qualified', createdAt: 1790000000, qualifiedAt: 1790100000 }],
})
await page.setViewportSize({ width: 1440, height: 900 })
await page.goto(`${url}/referral`)
// The link is about Hanzo until a topic is picked (lib/hanzo/topics): its page, with the code.
await expect(page.getByText('https://hanzo.ai/beta/for/hanzo?ref=K7Q2ZP', { exact: true })).toBeVisible()
await expect(page.getByRole('link', { name: 'Referral dashboard' })).toHaveCount(0)
await expect(page.getByText('acme')).toBeVisible()
await expect(page.getByText(/API error|404/)).toHaveCount(0)
expect(asked.filter((p) => p.startsWith('/v1/user/') || p.startsWith('/v1/referrer'))).toEqual([])
})
test('a referral service that cannot answer shows no link and no status code', async ({ page }) => {
await reader(page)
await page.route(
(u) => u.pathname === '/v1/referral',
(route) => route.fulfill({ status: 404, contentType: 'application/json', body: '{"error":"not found"}' }),
)
await page.goto(`${url}/referral`)
await expect(page.getByRole('heading', { name: 'Invite builders. Climb the board.' })).toBeVisible()
await expect(page.getByText('Your link', { exact: true })).toHaveCount(0)
await expect(page.getByText(/API error|404/)).toHaveCount(0)
})
test('a friend who follows the link is credited once signed in, and sees nothing of it', async ({ page }) => {
await reader(page)
const claims: unknown[] = []
await page.route(
(u) => u.pathname === '/v1/referral/claim',
(route) => {
claims.push(route.request().postDataJSON())
// An unknown code is a final answer too: dropped, and never shown.
return route.fulfill({ status: 404, contentType: 'application/json', body: '{"error":"unknown code"}' })
},
)
await page.goto(`${url}/chat?ref=K7Q2ZP`)
await expect.poll(() => claims).toEqual([{ code: 'K7Q2ZP' }])
await expect.poll(() => page.evaluate(() => localStorage.getItem('hz_ref'))).toBeNull()
await expect(page.getByText(/API error|404|unknown code/)).toHaveCount(0)
})