Point and shoot. SeancePage no longer redirects a stranger to /enter; it silently provisions a wanderer (POST /auth/guest, a real account) and drops them straight into the séance. The landing CTA goes to /seance unconditionally. Registering is now what it should always have been: how you *keep* a codex and unlock device pairing — not the toll to get in. Details that matter: - One auto-attempt only, guarded by a ref: survives StrictMode's double effect, and a failure (rate limit, offline) doesn't retry forever. - The loading veil covers provisioning, so there's no flash of an empty séance while the account is created. - If provisioning genuinely fails, it falls back to /enter rather than a blank screen — the manual door still exists. - A returning visitor already holds a cookie, so this never fires for them. App.test's "sends anonymous visitors to /enter" assertion was inverted to assert the open door instead — that test encoded the wall we just removed. Verified live: POST /auth/guest returns wanderer-a6f1 and that cookie authenticates on /auth/me. 366 frontend tests pass; i18n parity holds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
import { render, screen, waitFor } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
import './i18n'
|
|
import App from './App'
|
|
import * as api from './api'
|
|
|
|
vi.mock('./api')
|
|
|
|
const statsBody = { entities: 7, sessions: 42, utterances: 133, anomalies: 1024 }
|
|
const codexBody = { entities: [] }
|
|
|
|
function mockFetch() {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async (input: RequestInfo | URL) => {
|
|
const url = String(input)
|
|
const body = url.includes('/api/stats') ? statsBody : codexBody
|
|
return new Response(JSON.stringify(body), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
describe('App', () => {
|
|
beforeEach(() => {
|
|
mockFetch()
|
|
vi.mocked(api.me).mockRejectedValue(new Error('not authenticated'))
|
|
})
|
|
|
|
it('renders the landing page with the Quantumancy title', async () => {
|
|
render(<App />)
|
|
await waitFor(() =>
|
|
expect(screen.getAllByText('QUANTUMANCY').length).toBeGreaterThan(0),
|
|
)
|
|
})
|
|
|
|
it('shows live veil activity from /api/stats', async () => {
|
|
render(<App />)
|
|
// counters animate up from 0 over ~900ms — wait for the final values
|
|
await waitFor(
|
|
() => {
|
|
const values = Array.from(document.querySelectorAll('.stat-value')).map(
|
|
(el) => el.textContent,
|
|
)
|
|
expect(values).toContain('42')
|
|
expect(values).toContain('7')
|
|
},
|
|
{ timeout: 4000 },
|
|
)
|
|
})
|
|
|
|
it('takes anonymous visitors straight to the séance, with no sign-in wall', async () => {
|
|
// The open door: a stranger must reach the séance itself. SeancePage
|
|
// provisions a wanderer for them on arrival; /enter is an upgrade path
|
|
// (keep your codex, pair a device), never a gate.
|
|
render(<App />)
|
|
await waitFor(() =>
|
|
expect(screen.getAllByText('QUANTUMANCY').length).toBeGreaterThan(0),
|
|
)
|
|
const cta = screen.getByRole('button', { name: /begin the séance/i })
|
|
await userEvent.click(cta)
|
|
await waitFor(() => expect(window.location.pathname).toBe('/seance'))
|
|
})
|
|
})
|