/** * E2E Test Fixtures * * Each fixture uses GENERATED mizan hooks (not raw call()). * Playwright reads the DOM to verify behavior. * * URL hash selects the fixture: #echo, #add, #multiply, etc. */ import { useState, useEffect, useRef } from 'react' // Generated typed hooks — the actual mizan API import { DjangoContext, useEcho, useAdd, useMultiply, useWhoami, useStaffOnly, useSuperuserOnly, useVerifiedOnly, useNotImplementedFn, useBuggyFn, usePermissionCheckFn, useCurrentUser, DjangoError, useMizan, } from './api/generated.django' import { useContactForm, useLoginForm } from './api/generated.forms' import { useChatChannel } from './api/generated.channels.hooks' // ─── Fixture router ───────────────────────────────────────────────────────── export function Fixtures() { const [hash, setHash] = useState(window.location.hash.slice(1)) useEffect(() => { const onHash = () => setHash(window.location.hash.slice(1)) window.addEventListener('hashchange', onHash) return () => window.removeEventListener('hashchange', onHash) }, []) switch (hash) { case 'echo': return case 'add': return case 'multiply': return case 'not-found': return case 'validation-error': return case 'auth-required': return case 'staff-only': return case 'superuser-only': return case 'verified-only': return case 'not-implemented': return case 'internal-error': return case 'permission-error': return case 'permission-success': return case 'context-current-user': return case 'form-login-schema': return case 'form-contact-schema': return case 'form-contact-submit': return case 'channel-chat': return default: return
Harness ready. Set #hash.
} } // ─── Result helper ────────────────────────────────────────────────────────── function Result({ data, error }: { data?: unknown; error?: unknown }) { return ( <> {data !== undefined && (
{JSON.stringify(data)}
)} {error !== undefined && error !== null && ( <>
{error instanceof DjangoError ? 'DjangoError' : 'Error'}
{error instanceof DjangoError ? error.code : ''}
                        {error instanceof Error ? error.message : String(error)}
                    
)} ) } // ─── Hook runner: calls a generated hook and renders result ───────────────── function useRun(hook: () => (input?: any) => Promise, input?: any) { const call = hook() const [data, setData] = useState() const [error, setError] = useState() useEffect(() => { call(input).then(setData).catch(setError) }, []) // eslint-disable-line react-hooks/exhaustive-deps return { data, error } } // ─── Server function fixtures ─────────────────────────────────────────────── function Echo() { const { data, error } = useRun(useEcho, { text: 'e2e-test' }) return } function Add() { const { data, error } = useRun(useAdd, { a: 17, b: 25 }) return } function Multiply() { const { data, error } = useRun(useMultiply, { x: 6, y: 7 }) return } function NotFound() { // Deliberately call a non-existent function via the raw primitive const { call } = useMizan() const [error, setError] = useState() useEffect(() => { call('does_not_exist').catch(setError) }, [call]) return } function ValidationError() { // Send wrong types to add (strings instead of numbers) const call = useAdd() const [error, setError] = useState() useEffect(() => { (call as any)({ a: 'not_a_number', b: 'also_not' }).catch(setError) }, [call]) return } function AuthRequired() { const { data, error } = useRun(useWhoami) return } function StaffOnly() { const { data, error } = useRun(useStaffOnly) return } function SuperuserOnly() { const { data, error } = useRun(useSuperuserOnly) return } function VerifiedOnly() { const { data, error } = useRun(useVerifiedOnly) return } function NotImplemented() { const { data, error } = useRun(useNotImplementedFn) return } function InternalError() { const { data, error } = useRun(useBuggyFn) return } function PermissionError_() { const { data, error } = useRun(usePermissionCheckFn, { secret: 'wrong' }) return } function PermissionSuccess() { const { data, error } = useRun(usePermissionCheckFn, { secret: 'open-sesame' }) return } // ─── Context fixtures ─────────────────────────────────────────────────────── function ContextCurrentUser() { // useCurrentUser throws if context not loaded yet, so catch that try { const user = useCurrentUser() return
{JSON.stringify(user)}
} catch { return
loading context...
} } // ─── Form fixtures (using generated form hooks) ───────────────────────────── function FormLoginSchema() { const form = useLoginForm() if (form.loading) return
loading...
return
{JSON.stringify(form.schema)}
} function FormContactSchema() { const form = useContactForm() if (form.loading) return
loading...
return
{JSON.stringify(form.schema)}
} function FormContactSubmit() { const form = useContactForm() const [result, setResult] = useState() const [submitted, setSubmitted] = useState(false) useEffect(() => { if (!form.loading && !submitted) { form.set('name', 'Test User') form.set('email', 'test@example.com') form.set('message', 'Hello from e2e') setSubmitted(true) } }, [form.loading, submitted, form]) useEffect(() => { if (submitted && !result) { form.submit().then(setResult) } }, [submitted, result, form]) if (!result) return
loading...
return
{JSON.stringify(result)}
} // ─── Channel fixtures ─────────────────────────────────────────────────────── function ChannelChatFixture() { // DjangoContext already includes ChannelProvider return } function ChannelChat() { const chat = useChatChannel({ room: 'e2e' }) const [sent, setSent] = useState(false) const prevStatus = useRef(chat.status) useEffect(() => { // Send once when status transitions to 'connected' (meaning subscribed) // The hook maps subscribed → 'connected', but we need to wait for it // to go through 'connecting' first (before subscription is confirmed) const wasConnecting = prevStatus.current === 'connecting' prevStatus.current = chat.status if (wasConnecting && chat.status === 'connected' && !sent) { chat.send({ text: 'hello from e2e' }) setSent(true) } }, [chat.status, sent, chat]) return (
{chat.status}
{chat.messages.length}
{chat.messages.length > 0 && (
                    {JSON.stringify(chat.messages[chat.messages.length - 1])}
                
)}
) }