mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
_web.tsx draws rooms' App inside rooms' `Orgs`, as every room is: a reader with no organization makes one, one with several picks one, and a team on Free is asked for the Team plan before the app is drawn; a personal organization on Free opens it. The app reads its place from the path: /chat and /chat/<id> are Chat, a path under /dev is Dev. /?chat=<id> and /chat?thread=<id> move to /chat/<id>, /?at= to /dev?at=, whose place the door replaces with its path, as it does the builder's grammar rooms 0.1.35-0.1.38 wrote under /dev. The gates read the app at its paths and the rooms' shell at /home, with new Linux baselines for it; the limit banner, the model chip and the side panel are held to what rooms 0.1.55 draws; the landing's budget is 445 KB, since rooms' lib/host carries @hanzo/build's route grammar. app/gui.css is what the build writes. Co-authored-by: Hanzo Dev <[email protected]>
136 lines
6.0 KiB
TypeScript
136 lines
6.0 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`)
|
|
await expect(page.locator('input[value="https://hanzo.ai/?ref=K7Q2ZP"]')).toBeVisible()
|
|
await expect(page.getByText('Using Hanzo').first()).toBeVisible()
|
|
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 is said in words, never as a 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.getByText('Your referral link could not be loaded right now. Try again in a moment.')).toBeVisible()
|
|
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)
|
|
})
|