Files
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

71 lines
3.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from '@playwright/test'
import { serveExport } from './export'
import { answer, signIn } from '../session'
/**
* A STARTER DOES WHAT IT SAYS.
*
* Three suggestions sit under an empty chat's composer (@hanzo/build
* `Suggestions`, since @hanzo/app 0.1.83): the person's activity first, then
* what they told us at signup, then Hanzo's starters to fill the three. The
* whole of their value is that pressing one does it, and nothing checked that:
* a row that stopped working would still draw, still take a press, and read as
* decoration nobody had noticed was inert.
*
* IT SENDS NOW, where it used to seed. `065a987e` made a press put the question
* in the field to finish; the suggestions that replaced the four fixed openings
* are actions, each one done on the press ("Pressing a row DOES it", build
* suggestions.tsx): a `send` row is asked in this chat at once. So the
* assertion is the turn on the wire and in the transcript, not the field.
*
* A person with no activity and nothing kept from signup is the case held here:
* the account's kept rows are fresh and empty (`GET /v1/pref`), so the three are
* Hanzo's starters and no model is asked to write any.
*/
const STARTER = 'Ask what Hanzo can do for you'
const PROMPT = 'What can you do for me? Give me three concrete things to try today.'
const ROWS = ['Ask what Hanzo can do for you', 'Build a landing page with Dev', 'Get a cited brief on AI agents']
test('a starter does what it says', async ({ page }) => {
await signIn(page)
await answer(page, '/v1/pref', { prefs: { suggest: { at: new Date().toISOString(), sig: '', rows: [], dismissed: [] } } })
const sent: { messages?: { role: string; content: unknown }[] }[] = []
await page.route(
(url) => url.pathname === '/v1/chat/completions',
async (route) => {
sent.push(JSON.parse(route.request().postData() || '{}'))
await route.fulfill({
status: 200,
contentType: 'text/event-stream',
body: `data: ${JSON.stringify({ id: 'x', choices: [{ index: 0, delta: { role: 'assistant', content: 'Three things.' } }] })}\n\ndata: [DONE]\n\n`,
})
},
)
const site = await serveExport()
try {
await page.setViewportSize({ width: 1440, height: 900 })
await page.goto(`${site.url}/chat`)
const field = page.locator('textarea').first()
await expect(field).toBeVisible({ timeout: 20_000 })
await expect(field).toHaveValue('')
const rows = page.getByRole('list', { name: 'Suggestions' })
await expect(rows).toBeVisible({ timeout: 20_000 })
await expect(rows.getByRole('listitem')).toHaveCount(ROWS.length)
for (const title of ROWS) await expect(rows.getByRole('button', { name: title, exact: true })).toBeVisible()
expect(sent, 'no model was asked to write the rows').toEqual([])
await rows.getByRole('button', { name: STARTER, exact: true }).click()
// The press asked its question here, at once: on the wire and in the transcript.
await expect.poll(() => sent.length, { timeout: 20_000 }).toBeGreaterThan(0)
expect(JSON.stringify(sent[0].messages), 'the starter’s own question').toContain(PROMPT)
await expect(page.locator('main').getByText(PROMPT)).toBeVisible()
await expect(page.locator('main').getByText('Three things.')).toBeVisible({ timeout: 20_000 })
await expect(rows, 'the suggestions go once something is said').toHaveCount(0)
} finally {
await site.close()
}
})