messages implemented
This commit is contained in:
112
tests/e2e/messaging.spec.ts
Normal file
112
tests/e2e/messaging.spec.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
|
||||
type Fixture = {
|
||||
email: string
|
||||
password: string
|
||||
conversation_id: number
|
||||
}
|
||||
|
||||
function seedMessagingFixture(): Fixture {
|
||||
const token = `${Date.now().toString().slice(-6)}${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`
|
||||
const ownerEmail = `e2e-messages-owner-${token}@example.test`
|
||||
const peerEmail = `e2e-messages-peer-${token}@example.test`
|
||||
const ownerUsername = `e2eo${token}`.slice(0, 20)
|
||||
const peerUsername = `e2ep${token}`.slice(0, 20)
|
||||
|
||||
const script = [
|
||||
"use App\\Models\\User;",
|
||||
"use App\\Models\\Conversation;",
|
||||
"use App\\Models\\ConversationParticipant;",
|
||||
"use App\\Models\\Message;",
|
||||
"use Illuminate\\Support\\Facades\\Hash;",
|
||||
"use Illuminate\\Support\\Carbon;",
|
||||
`$owner = User::updateOrCreate(['email' => '${ownerEmail}'], [`,
|
||||
" 'name' => 'E2E Owner',",
|
||||
` 'username' => '${ownerUsername}',`,
|
||||
" 'onboarding_step' => 'complete',",
|
||||
" 'email_verified_at' => now(),",
|
||||
" 'is_active' => 1,",
|
||||
" 'password' => Hash::make('password'),",
|
||||
"]);",
|
||||
`$peer = User::updateOrCreate(['email' => '${peerEmail}'], [`,
|
||||
" 'name' => 'E2E Peer',",
|
||||
` 'username' => '${peerUsername}',`,
|
||||
" 'onboarding_step' => 'complete',",
|
||||
" 'email_verified_at' => now(),",
|
||||
" 'is_active' => 1,",
|
||||
" 'password' => Hash::make('password'),",
|
||||
"]);",
|
||||
"$conversation = Conversation::create(['type' => 'direct', 'created_by' => $owner->id]);",
|
||||
"ConversationParticipant::insert([",
|
||||
" ['conversation_id' => $conversation->id, 'user_id' => $owner->id, 'role' => 'admin', 'joined_at' => now(), 'last_read_at' => null],",
|
||||
" ['conversation_id' => $conversation->id, 'user_id' => $peer->id, 'role' => 'member', 'joined_at' => now(), 'last_read_at' => now()],",
|
||||
"]);",
|
||||
"$first = Message::create(['conversation_id' => $conversation->id, 'sender_id' => $peer->id, 'body' => 'Seed hello']);",
|
||||
"$last = Message::create(['conversation_id' => $conversation->id, 'sender_id' => $owner->id, 'body' => 'Seed latest from owner']);",
|
||||
"$conversation->update(['last_message_at' => $last->created_at]);",
|
||||
"ConversationParticipant::where('conversation_id', $conversation->id)->where('user_id', $peer->id)->update(['last_read_at' => Carbon::parse($last->created_at)->addSeconds(15)]);",
|
||||
"echo json_encode(['email' => $owner->email, 'password' => 'password', 'conversation_id' => $conversation->id]);",
|
||||
].join(' ')
|
||||
|
||||
const raw = execFileSync('php', ['artisan', 'tinker', `--execute=${script}`], {
|
||||
cwd: process.cwd(),
|
||||
encoding: 'utf8',
|
||||
})
|
||||
|
||||
const lines = raw
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
const jsonLine = [...lines].reverse().find((line) => line.startsWith('{') && line.endsWith('}'))
|
||||
if (!jsonLine) {
|
||||
throw new Error(`Unable to parse fixture JSON from tinker output: ${raw}`)
|
||||
}
|
||||
|
||||
return JSON.parse(jsonLine) as Fixture
|
||||
}
|
||||
|
||||
async function login(page: Parameters<typeof test>[0]['page'], fixture: Fixture) {
|
||||
await page.goto('/login')
|
||||
await page.locator('input[name="email"]').fill(fixture.email)
|
||||
await page.locator('input[name="password"]').fill(fixture.password)
|
||||
await page.getByRole('button', { name: 'Sign In' }).click()
|
||||
await page.waitForURL(/\/dashboard/)
|
||||
}
|
||||
|
||||
test.describe('Messaging UI', () => {
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
let fixture: Fixture
|
||||
|
||||
test.beforeAll(() => {
|
||||
fixture = seedMessagingFixture()
|
||||
})
|
||||
|
||||
test('restores draft from localStorage on reload', async ({ page }) => {
|
||||
await login(page, fixture)
|
||||
await page.goto(`/messages/${fixture.conversation_id}`)
|
||||
|
||||
const textarea = page.locator('textarea[placeholder^="Write a message"]')
|
||||
const draft = 'E2E draft should survive reload'
|
||||
|
||||
await textarea.fill(draft)
|
||||
await expect(textarea).toHaveValue(draft)
|
||||
|
||||
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||
|
||||
await expect(textarea).toHaveValue(draft)
|
||||
|
||||
const stored = await page.evaluate((key) => window.localStorage.getItem(key), `nova_draft_${fixture.conversation_id}`)
|
||||
expect(stored).toBe(draft)
|
||||
})
|
||||
|
||||
test('shows seen indicator on latest direct message from current user', async ({ page }) => {
|
||||
await login(page, fixture)
|
||||
await page.goto(`/messages/${fixture.conversation_id}`)
|
||||
|
||||
await expect(page.locator('text=Seed latest from owner')).toBeVisible()
|
||||
await expect(page.locator('text=/^Seen\\s.+\\sago$/')).toBeVisible()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user