Rename all djarea references to mizan

- djarea_clients.py → clients.py (both example apps)
- export_djarea_schema → export_mizan_schema (management command)
- djarea.spec.ts → mizan.spec.ts (playwright test)
- fetch.mjs: command name updated
- apps.py/asgi.py: import paths updated
- Removed stale generated.djarea.* artifacts
- Fixed desktop app: asgi.py import, vite config aliases, package.json dep path

373 Django tests pass. Both example apps verified running.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 11:26:19 -04:00
parent 1c6d9075ad
commit 6108845d99
11 changed files with 18 additions and 4783 deletions

View File

@@ -0,0 +1,186 @@
/**
* 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')
})
})