Add aria-labels to form controls (selects, checkboxes, switches), set html lang attribute and page title, fix color contrast for --candle-dim and --text-faint tokens, underline inline links, remove opacity hack. Harden dev login endpoints with atomic findOneAndUpdate and tokenVersion in JWT. Update Playwright timeouts and E2E test helpers.
123 lines
4.4 KiB
JavaScript
123 lines
4.4 KiB
JavaScript
import { test, expect } from './helpers/fixtures.js'
|
|
|
|
test.describe('My Updates page', () => {
|
|
test('authenticated user sees the my-updates page', async ({ adminPage }) => {
|
|
await adminPage.goto('/member/my-updates')
|
|
|
|
await expect(adminPage.locator('h1', { hasText: 'My Updates' })).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
})
|
|
|
|
test('authenticated user sees the new update link', async ({ adminPage }) => {
|
|
await adminPage.goto('/member/my-updates')
|
|
|
|
// Wait for ClientOnly content to hydrate
|
|
await expect(adminPage.locator('h1', { hasText: 'My Updates' })).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
|
|
// The page shows either the "+ New Update" button (stats row) or
|
|
// the "+ Post Your First Update" link (empty state) — both go to /updates/new
|
|
const newUpdateLink = adminPage.locator('a[href="/updates/new"]')
|
|
await expect(newUpdateLink.first()).toBeVisible({ timeout: 10000 })
|
|
})
|
|
|
|
test('unauthenticated user sees sign-in prompt', async ({ browser }) => {
|
|
const context = await browser.newContext()
|
|
const page = await context.newPage()
|
|
|
|
await page.goto('/member/my-updates')
|
|
|
|
// Should show the page's "Sign in required" heading
|
|
await expect(page.locator('.state-heading')).toBeVisible({ timeout: 10000 })
|
|
|
|
await context.close()
|
|
})
|
|
})
|
|
|
|
test.describe('New Update page', () => {
|
|
test('loads the new update form', async ({ adminPage }) => {
|
|
await adminPage.goto('/updates/new')
|
|
|
|
await expect(adminPage.locator('h1', { hasText: 'New Update' })).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
|
|
// Form elements are present
|
|
await expect(adminPage.locator('textarea')).toBeVisible()
|
|
await expect(adminPage.locator('select')).toBeVisible()
|
|
|
|
// Submit button exists and starts disabled (empty textarea)
|
|
const submitBtn = adminPage.locator('button[type="submit"]', { hasText: 'Post Update' })
|
|
await expect(submitBtn).toBeVisible()
|
|
await expect(submitBtn).toBeDisabled()
|
|
})
|
|
|
|
test('submit button enables when content is entered', async ({ adminPage }) => {
|
|
await adminPage.goto('/updates/new')
|
|
await adminPage.waitForLoadState('networkidle')
|
|
|
|
await expect(adminPage.locator('h1', { hasText: 'New Update' })).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
|
|
const textarea = adminPage.locator('textarea')
|
|
const submitBtn = adminPage.locator('button[type="submit"]', { hasText: 'Post Update' })
|
|
|
|
await expect(submitBtn).toBeDisabled()
|
|
// Use click + type to ensure Vue hydration processes the input events
|
|
await textarea.click()
|
|
await textarea.pressSequentially('Test update content', { delay: 10 })
|
|
await expect(submitBtn).toBeEnabled({ timeout: 5000 })
|
|
})
|
|
|
|
test('privacy selector defaults to members and has all options', async ({ adminPage }) => {
|
|
await adminPage.goto('/updates/new')
|
|
|
|
await expect(adminPage.locator('h1', { hasText: 'New Update' })).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
|
|
const select = adminPage.locator('select')
|
|
await expect(select).toHaveValue('members')
|
|
|
|
// Verify all three privacy options exist
|
|
await expect(select.locator('option[value="members"]')).toBeAttached()
|
|
await expect(select.locator('option[value="public"]')).toBeAttached()
|
|
await expect(select.locator('option[value="private"]')).toBeAttached()
|
|
})
|
|
|
|
test('cancel link navigates back to my-updates', async ({ adminPage }) => {
|
|
await adminPage.goto('/updates/new')
|
|
|
|
await expect(adminPage.locator('h1', { hasText: 'New Update' })).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
|
|
const cancelLink = adminPage.locator('a', { hasText: 'Cancel' })
|
|
await expect(cancelLink).toHaveAttribute('href', '/member/my-updates')
|
|
})
|
|
|
|
test('back link points to my-updates', async ({ adminPage }) => {
|
|
await adminPage.goto('/updates/new')
|
|
|
|
const backLink = adminPage.locator('.back-link a')
|
|
await expect(backLink).toBeVisible({ timeout: 10000 })
|
|
await expect(backLink).toHaveAttribute('href', '/member/my-updates')
|
|
})
|
|
})
|
|
|
|
test.describe('Updates API (public access)', () => {
|
|
test('public updates endpoint returns data', async ({ page }) => {
|
|
const response = await page.request.get('/api/updates')
|
|
|
|
expect(response.ok()).toBe(true)
|
|
|
|
const data = await response.json()
|
|
expect(data).toHaveProperty('updates')
|
|
expect(data).toHaveProperty('total')
|
|
expect(data).toHaveProperty('hasMore')
|
|
expect(Array.isArray(data.updates)).toBe(true)
|
|
})
|
|
})
|