Files
mizan/e2e/djarea.spec.ts
Ryth Azhur c866142770 Rename djarea to mizan and fix React casing conventions
Rename the package from djarea to mizan across the entire codebase —
Python package, React library, generators, tests, and examples. Fix
JSX/hook casing (MizanProvider, useMizan, etc.) that broke when the
original PascalCase names were lowercased during the rename.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:41:31 -04:00

187 lines
7.5 KiB
TypeScript

/**
* mizan E2E Integration Tests
*
* Real Chromium → Real React app (generated hooks) → Real Django backend
*
* Every test uses the generated mizan API, not raw call() or fetch().
*/
import { test, expect } from '@playwright/test'
const BASE = process.env.HARNESS_URL || 'http://localhost:5174'
async function fixture(page: any, name: string) {
await page.goto(`${BASE}#${name}`)
await page.waitForSelector('[data-testid="result"], [data-testid="error-type"]', { timeout: 10000 })
}
async function getResult(page: any): Promise<any> {
const el = page.locator('[data-testid="result"]')
if (await el.count() > 0) return JSON.parse(await el.textContent())
return null
}
async function getError(page: any) {
const typeEl = page.locator('[data-testid="error-type"]')
if (await typeEl.count() === 0) return null
return {
type: await typeEl.textContent(),
code: await page.locator('[data-testid="error-code"]').textContent(),
message: await page.locator('[data-testid="error-message"]').textContent(),
}
}
// ─── useEcho, useAdd, useMultiply ───────────────────────────────────────────
test.describe('generated function hooks', () => {
test('useEcho returns echoed text', async ({ page }) => {
await fixture(page, 'echo')
const result = await getResult(page)
expect(result.message).toContain('e2e-test')
})
test('useAdd returns correct sum', async ({ page }) => {
await fixture(page, 'add')
const result = await getResult(page)
expect(result.result).toBe(42)
})
test('useMultiply (class-based ServerFunction) returns product', async ({ page }) => {
await fixture(page, 'multiply')
const result = await getResult(page)
expect(result.product).toBe(42)
})
test('usePermissionCheckFn succeeds with correct secret', async ({ page }) => {
await fixture(page, 'permission-success')
const result = await getResult(page)
expect(result.message).toBe('access granted')
})
})
// ─── Error handling ─────────────────────────────────────────────────────────
test.describe('error codes from generated hooks', () => {
test('non-existent function → DjangoError NOT_FOUND', async ({ page }) => {
await fixture(page, 'not-found')
const error = await getError(page)
expect(error!.type).toBe('DjangoError')
expect(error!.code).toBe('NOT_FOUND')
})
test('wrong input types → DjangoError VALIDATION_ERROR', async ({ page }) => {
await fixture(page, 'validation-error')
const error = await getError(page)
expect(error!.type).toBe('DjangoError')
expect(error!.code).toBe('VALIDATION_ERROR')
})
test('useWhoami anonymous → auth error', async ({ page }) => {
await fixture(page, 'auth-required')
const error = await getError(page)
expect(error!.type).toBe('DjangoError')
expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code)
})
test('useStaffOnly anonymous → UNAUTHORIZED', async ({ page }) => {
await fixture(page, 'staff-only')
const error = await getError(page)
expect(error!.type).toBe('DjangoError')
expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code)
})
test('useSuperuserOnly anonymous → UNAUTHORIZED', async ({ page }) => {
await fixture(page, 'superuser-only')
const error = await getError(page)
expect(error!.type).toBe('DjangoError')
expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code)
})
test('useVerifiedOnly anonymous → FORBIDDEN', async ({ page }) => {
await fixture(page, 'verified-only')
const error = await getError(page)
expect(error!.type).toBe('DjangoError')
expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code)
})
test('useNotImplementedFn → NOT_IMPLEMENTED', async ({ page }) => {
await fixture(page, 'not-implemented')
const error = await getError(page)
expect(error!.type).toBe('DjangoError')
expect(error!.code).toBe('NOT_IMPLEMENTED')
})
test('useBuggyFn → INTERNAL_ERROR', async ({ page }) => {
await fixture(page, 'internal-error')
const error = await getError(page)
expect(error!.type).toBe('DjangoError')
expect(error!.code).toBe('INTERNAL_ERROR')
})
test('usePermissionCheckFn wrong secret → FORBIDDEN', async ({ page }) => {
await fixture(page, 'permission-error')
const error = await getError(page)
expect(error!.type).toBe('DjangoError')
expect(error!.code).toBe('FORBIDDEN')
})
})
// ─── Context hooks ──────────────────────────────────────────────────────────
test.describe('generated context hooks', () => {
test('useCurrentUser returns anonymous data', async ({ page }) => {
await page.goto(`${BASE}#context-current-user`)
// Context loads async, wait for result
await page.waitForSelector('[data-testid="result"]', { timeout: 10000 })
const result = await getResult(page)
expect(result.authenticated).toBe(false)
expect(result.email).toBe('')
})
})
// ─── Form hooks ─────────────────────────────────────────────────────────────
test.describe('generated form hooks', () => {
test('useLoginForm loads schema with field definitions', async ({ page }) => {
await fixture(page, 'form-login-schema')
const result = await getResult(page)
expect(result.fields).toBeDefined()
expect(result.fields.login).toBeDefined()
expect(result.fields.password).toBeDefined()
})
test('useContactForm loads schema with mizanFormMeta', async ({ page }) => {
await fixture(page, 'form-contact-schema')
const result = await getResult(page)
expect(result.title).toBe('Contact Us')
expect(result.subtitle).toBe("We'd love to hear from you")
expect(result.submit_label).toBe('Send Message')
expect(result.meta.live_validation).toBe(true)
})
test('useContactForm submit returns on_submit_success data', async ({ page }) => {
await fixture(page, 'form-contact-submit')
const result = await getResult(page)
expect(result.success).toBe(true)
expect(result.data.received).toBe(true)
expect(result.data.from).toBe('test@example.com')
})
})
// ─── Channel hooks ──────────────────────────────────────────────────────────
test.describe('generated channel hooks', () => {
test('useChatChannel receives echoed message', async ({ page }) => {
await page.goto(`${BASE}#channel-chat`)
await page.waitForFunction(
() => {
const el = document.querySelector('[data-testid="channel-message-count"]')
return el && parseInt(el.textContent || '0') > 0
},
{ timeout: 15000 }
)
const msg = JSON.parse(await page.locator('[data-testid="channel-last-message"]').textContent())
expect(msg.text).toBe('hello from e2e')
})
})