diff --git a/.gitignore b/.gitignore
index c8c9c86..a4728d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,10 @@ logs
/prisma/generated
/dev.db
+
+# Local test output
+/coverage/
+/playwright-report/
+/test-results/
+/.vitest/
+/.nuxtrc
diff --git a/app/pages/auth.vue b/app/pages/auth.vue
index f9ad540..d941699 100644
--- a/app/pages/auth.vue
+++ b/app/pages/auth.vue
@@ -1,23 +1,12 @@
+
+
+
+
diff --git a/tests/fixtures/e2e-app/app/plugins/ready.client.ts b/tests/fixtures/e2e-app/app/plugins/ready.client.ts
new file mode 100644
index 0000000..f41d26b
--- /dev/null
+++ b/tests/fixtures/e2e-app/app/plugins/ready.client.ts
@@ -0,0 +1,7 @@
+export default defineNuxtPlugin({
+ hooks: {
+ 'app:mounted'() {
+ document.documentElement.dataset.testReady = 'true'
+ },
+ },
+})
diff --git a/tests/fixtures/e2e-app/nuxt.config.ts b/tests/fixtures/e2e-app/nuxt.config.ts
new file mode 100644
index 0000000..7d9894f
--- /dev/null
+++ b/tests/fixtures/e2e-app/nuxt.config.ts
@@ -0,0 +1,5 @@
+export default defineNuxtConfig({
+ compatibilityDate: '2025-07-15',
+ devtools: { enabled: false },
+ modules: ['@nuxt/ui'],
+})
diff --git a/tests/unit/auth-form.test.ts b/tests/unit/auth-form.test.ts
new file mode 100644
index 0000000..28eee86
--- /dev/null
+++ b/tests/unit/auth-form.test.ts
@@ -0,0 +1,40 @@
+import { describe, expect, it } from 'vitest'
+import { createAuthFormSchema } from '../../app/utils/auth-form'
+
+describe('auth form schema', () => {
+ it('accepts a valid generated address before OTP is requested', () => {
+ const result = createAuthFormSchema(false).safeParse({
+ email: 'member@example.test',
+ })
+
+ expect(result.success).toBe(true)
+ })
+
+ it('rejects a malformed address with the screen validation message', () => {
+ const result = createAuthFormSchema(false).safeParse({
+ email: 'not-an-email',
+ })
+
+ expect(result.success).toBe(false)
+ expect(result.error?.issues[0]?.message).toBe('Invalid email')
+ })
+
+ it('requires all six OTP positions after the email step', () => {
+ const result = createAuthFormSchema(true).safeParse({
+ email: 'member@example.test',
+ otp: ['1', '2', '3', '4', '5'],
+ })
+
+ expect(result.success).toBe(false)
+ expect(result.error?.issues[0]?.message).toBe('Must be 6 digits')
+ })
+
+ it('accepts a complete six-position OTP for a valid address', () => {
+ const result = createAuthFormSchema(true).safeParse({
+ email: 'member@example.test',
+ otp: ['1', '2', '3', '4', '5', '6'],
+ })
+
+ expect(result.success).toBe(true)
+ })
+})
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..ab02f2f
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineVitestConfig } from '@nuxt/test-utils/config'
+
+export default defineVitestConfig({
+ test: {
+ environment: 'nuxt',
+ include: ['tests/unit/**/*.test.ts'],
+ },
+})