mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
The edge answers every path under /apps with its one page (lib/edge.ts `APP`, `ROOTS`, `DATA`), the page signs a stranger in to the address and draws the app for a member, and hosted.spec.ts holds who sees what: a member holding beta.crm alone has CRM and its live host in the pane, staff and a SuperAdmin have all five, a reader the switch is off for has the request path, a stranger is sent to sign in and back, a phone has the apps as rows. Needs @hanzo/app >= 0.2.38 (lib/hosted). Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
101 lines
5.4 KiB
TypeScript
101 lines
5.4 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test'
|
|
import { serveExport } from './export'
|
|
import { answer, signIn, type Who } from '../session'
|
|
|
|
/**
|
|
* THE APPS THAT DRAW INSIDE THE APP, at /apps/<id> (@hanzo/app lib/hosted).
|
|
*
|
|
* CRM, Commerce, CMS, Analytics and Insights stay standalone apps on hosts of
|
|
* their own. The app gives each reader who `opens` one a mark on the rail and a
|
|
* pane that frames the app's live host (`?embed=1`), and everyone else the Betas
|
|
* list's request path. The export is served under hanzo.ai, as host.spec.ts does,
|
|
* because a page on a developer's own machine opens every app and a flag would
|
|
* measure nothing; nothing here reaches the apps' hosts, a frame's page included.
|
|
*/
|
|
|
|
const MAPPED = ['hanzo.ai']
|
|
test.use({ launchOptions: { args: [`--host-resolver-rules=${MAPPED.map((host) => `MAP ${host} 127.0.0.1`).join(', ')}`] } })
|
|
|
|
let port: string
|
|
let stop: () => Promise<void>
|
|
test.beforeAll(async () => {
|
|
const served = await serveExport()
|
|
port = new URL(served.url).port
|
|
stop = served.close
|
|
})
|
|
test.afterAll(async () => stop())
|
|
|
|
const at = (path: string) => `http://hanzo.ai:${port}${path}`
|
|
const ALL = ['crm', 'commerce', 'cms', 'analytics', 'insights']
|
|
|
|
/** The reader, the cloud holding exactly these switches for them, and nothing off this machine answering. */
|
|
async function reader(page: Page, who: Who, ...held: string[]) {
|
|
await page.route('**/*', (route) => (new URL(route.request().url()).port === port ? route.continue() : route.abort()))
|
|
await signIn(page, who)
|
|
await answer(page, '/v1/flag/platform', { flags: Object.fromEntries(held.map((id) => [`beta.${id}`, true])) })
|
|
await answer(page, '/v1/marketing/beta', { betas: [] })
|
|
await answer(page, '/v1/flag/optin', { on: false, blocked: false, public: [] })
|
|
}
|
|
const ada: Who = { name: 'ada', email: '[email protected]' }
|
|
|
|
test('a member with beta.crm alone has CRM on the rail and its live host in the pane, and nothing else', async ({ page }) => {
|
|
await reader(page, ada, 'crm')
|
|
await page.goto(at('/apps/crm'))
|
|
await expect(page.locator('[data-slot="rail-app-crm"]')).toBeVisible({ timeout: 30_000 })
|
|
for (const id of ALL.slice(1)) await expect(page.locator(`[data-slot="rail-app-${id}"]`)).toHaveCount(0)
|
|
await expect(page.locator('[data-slot="rail-app-crm"]')).toHaveAttribute('aria-current', 'page')
|
|
const frames = page.locator('iframe')
|
|
await expect(frames).toHaveCount(1)
|
|
await expect(frames.first()).toHaveAttribute('src', 'https://crm.hanzo.ai/?embed=1')
|
|
// No sidebar beside it, as Tabs and Betas.
|
|
await expect(page.getByText('New chat')).toHaveCount(0)
|
|
})
|
|
|
|
test('staff, for whom every beta opens, have all five on the rail, each going to its own address', async ({ page }) => {
|
|
await reader(page, { name: 'zach', email: '[email protected]', orgs: [{ org: 'admin', role: 'admin' }] }, ...ALL)
|
|
await page.goto(at('/apps/cms'))
|
|
for (const id of ALL) await expect(page.locator(`[data-slot="rail-app-${id}"]`)).toBeVisible({ timeout: 30_000 })
|
|
await expect(page.locator('iframe')).toHaveAttribute('src', 'https://cms.hanzo.ai/admin?embed=1')
|
|
await page.locator('[data-slot="rail-app-insights"]').click()
|
|
await expect(page).toHaveURL(at('/apps/insights'))
|
|
await expect(page.locator('iframe')).toHaveAttribute('src', 'https://insights.hanzo.ai/?embed=1')
|
|
})
|
|
|
|
test('a SuperAdmin has all five with no switch held', async ({ page }) => {
|
|
await reader(page, { sub: 'z', name: 'z', email: '[email protected]', org: 'admin' })
|
|
await page.goto(at('/apps/commerce'))
|
|
for (const id of ALL) await expect(page.locator(`[data-slot="rail-app-${id}"]`)).toBeVisible({ timeout: 30_000 })
|
|
await expect(page.locator('iframe')).toHaveAttribute('src', 'https://commerce-admin.hanzo.ai/admin/?embed=1')
|
|
})
|
|
|
|
test('a reader the switch is off for is shown the request path at the address, and no frame', async ({ page }) => {
|
|
await reader(page, ada)
|
|
await page.goto(at('/apps/crm'))
|
|
await expect(page.locator('[data-slot="hosted-request"]')).toBeVisible({ timeout: 30_000 })
|
|
await expect(page.locator('iframe')).toHaveCount(0)
|
|
await expect(page.locator('[data-slot="rail-app-crm"]')).toHaveCount(0)
|
|
await expect(page.locator('[data-slot="beta-request"]')).toBeVisible()
|
|
})
|
|
|
|
test('a stranger at an address under /apps is sent to sign in and back to it, and nothing is framed', async ({ page }) => {
|
|
await page.route('**/*', (route) => (new URL(route.request().url()).port === port ? route.continue() : route.abort()))
|
|
await page.goto(at('/apps/crm'))
|
|
await expect(page).toHaveURL(/\/(login|signup)\?next=%2Fapps%2Fcrm$/, { timeout: 30_000 })
|
|
await expect(page.locator('iframe')).toHaveCount(0)
|
|
})
|
|
|
|
test('on a phone the pane is full-bleed under the bar, and the menu lists the apps as rows', async ({ page }) => {
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await reader(page, ada, 'crm', 'analytics')
|
|
await page.goto(at('/apps/analytics'))
|
|
const frame = page.locator('iframe')
|
|
await expect(frame).toBeVisible({ timeout: 30_000 })
|
|
const box = (await frame.boundingBox())!
|
|
expect(box.width).toBeGreaterThanOrEqual(388)
|
|
expect(await page.evaluate(() => document.scrollingElement!.scrollWidth <= window.innerWidth)).toBe(true)
|
|
await page.getByRole('button', { name: 'Open sidebar' }).click()
|
|
await expect(page.locator('[data-slot="place-app-crm"]')).toBeVisible()
|
|
await expect(page.locator('[data-slot="place-app-analytics"]')).toHaveAttribute('aria-current', 'page')
|
|
await expect(page.locator('[data-slot="place-app-cms"]')).toHaveCount(0)
|
|
})
|