175 lines
7.1 KiB
JavaScript
175 lines
7.1 KiB
JavaScript
import { expect, test } from '../fixtures/auth.js'
|
|
import { API_URL } from '../global-setup.js'
|
|
|
|
// Fetch the full chore list for the authenticated user via the API — used to
|
|
// verify a task's persisted fields without relying on list-page badge markup.
|
|
async function fetchChores(page) {
|
|
const token = await page.evaluate(() => localStorage.getItem('token'))
|
|
const apiRes = await fetch(`${API_URL}/api/v1/chores/`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
expect(apiRes.ok).toBe(true)
|
|
const { res: chores } = await apiRes.json()
|
|
return chores
|
|
}
|
|
|
|
test.describe('AddTaskModal (quick-add)', () => {
|
|
// All tests in this suite run as the pre-authenticated E2E user
|
|
test.use({
|
|
storageState: '.auth/state.json',
|
|
})
|
|
|
|
test('creates a task from plain text title only and it appears in the list', async ({
|
|
page,
|
|
}) => {
|
|
const taskName = `E2E Quick Task ${Date.now()}`
|
|
|
|
// ── Open the quick-add modal from the chores list ───────────────────────
|
|
await page.goto('/chores')
|
|
await page.getByTestId('open-add-task-modal').click()
|
|
|
|
const dialog = page.getByRole('dialog', { name: 'Create new task' })
|
|
await expect(dialog).toBeVisible()
|
|
|
|
// ── Type a plain title (no parseable tokens) and submit with Enter ──────
|
|
const input = dialog.locator('textarea')
|
|
await input.fill(taskName)
|
|
await input.press('Enter')
|
|
|
|
// ── Modal closes and the task shows up in the list ──────────────────────
|
|
await expect(dialog).toBeHidden()
|
|
await expect(page.getByText(taskName)).toBeVisible({ timeout: 10_000 })
|
|
|
|
// ── Verify it exists via the API too ─────────────────────────────────────
|
|
const chores = await fetchChores(page)
|
|
const created = chores.find(c => c.name === taskName)
|
|
expect(created).toBeDefined()
|
|
})
|
|
|
|
test('smart-parses a due date and priority from typed text', async ({
|
|
page,
|
|
}) => {
|
|
// Kept lowercase: parsePriority's cleanup step lowercases the whole
|
|
// sentence when a priority token matches, so an already-lowercase prefix
|
|
// sidesteps that quirk and round-trips unchanged.
|
|
const taskName = `e2e parse task ${Date.now()}`
|
|
|
|
await page.goto('/chores')
|
|
await page.getByTestId('open-add-task-modal').click()
|
|
|
|
const dialog = page.getByRole('dialog', { name: 'Create new task' })
|
|
await expect(dialog).toBeVisible()
|
|
|
|
const input = dialog.locator('textarea')
|
|
await input.fill(`${taskName} !p1 tomorrow`)
|
|
|
|
// ── Confirm the smart parse is reflected in the pickers before saving ───
|
|
await expect(
|
|
dialog.getByRole('button', { name: 'P1', exact: true }),
|
|
).toBeVisible()
|
|
// The due-date trigger only ever reads "Due" in its empty state, so once
|
|
// parsing lands a date the button relabels itself away from that text.
|
|
await expect(
|
|
dialog.getByRole('button', { name: 'Due', exact: true }),
|
|
).toHaveCount(0)
|
|
|
|
await input.press('Enter')
|
|
|
|
await expect(dialog).toBeHidden()
|
|
await expect(page.getByText(taskName)).toBeVisible({ timeout: 10_000 })
|
|
|
|
// ── Verify the parsed priority + due date persisted via the API ─────────
|
|
const chores = await fetchChores(page)
|
|
const created = chores.find(c => c.name === taskName)
|
|
expect(created).toBeDefined()
|
|
expect(created.priority).toBe(1)
|
|
expect(created.nextDueDate).toBeTruthy()
|
|
})
|
|
|
|
test('creates a task using the picker UI directly, skipping smart-parse tokens', async ({
|
|
page,
|
|
}) => {
|
|
const taskName = `E2E Picker Task ${Date.now()}`
|
|
|
|
await page.goto('/chores')
|
|
await page.getByTestId('open-add-task-modal').click()
|
|
|
|
const dialog = page.getByRole('dialog', { name: 'Create new task' })
|
|
await expect(dialog).toBeVisible()
|
|
|
|
// Plain title only — none of "due date"/"priority"/"repeat" tokens.
|
|
await dialog.locator('textarea').fill(taskName)
|
|
|
|
// ── Set the due date via the picker (Due → Tomorrow → Apply) ────────────
|
|
await dialog.getByRole('button', { name: 'Due', exact: true }).click()
|
|
const dueDateDialog = page.getByRole('dialog', { name: 'Due Date' })
|
|
await dueDateDialog
|
|
.getByRole('checkbox', { name: 'Tomorrow', exact: true })
|
|
.click()
|
|
await dueDateDialog
|
|
.getByRole('button', { name: 'Apply', exact: true })
|
|
.click()
|
|
|
|
// ── Set the priority via the picker (Priority → P2) ──────────────────────
|
|
await dialog.getByRole('button', { name: 'Priority', exact: true }).click()
|
|
await page.getByRole('button', { name: 'P2', exact: true }).click()
|
|
await expect(
|
|
dialog.getByRole('button', { name: 'P2', exact: true }),
|
|
).toBeVisible()
|
|
|
|
// ── Set the repeat schedule via the picker (Repeat → Daily → Apply) ─────
|
|
await dialog.getByRole('button', { name: 'Repeat', exact: true }).click()
|
|
const repeatDialog = page.getByRole('dialog', { name: 'Repeat Schedule' })
|
|
await repeatDialog
|
|
.getByRole('checkbox', { name: 'Daily', exact: true })
|
|
.click()
|
|
await repeatDialog
|
|
.getByRole('button', { name: 'Apply', exact: true })
|
|
.click()
|
|
|
|
await dialog.getByRole('button', { name: 'Create', exact: true }).click()
|
|
|
|
await expect(dialog).toBeHidden()
|
|
await expect(page.getByText(taskName)).toBeVisible({ timeout: 10_000 })
|
|
|
|
// ── Verify the picker-set values persisted via the API ───────────────────
|
|
const chores = await fetchChores(page)
|
|
const created = chores.find(c => c.name === taskName)
|
|
expect(created).toBeDefined()
|
|
expect(created.priority).toBe(2)
|
|
expect(created.frequencyType).toBe('daily')
|
|
expect(created.nextDueDate).toBeTruthy()
|
|
})
|
|
|
|
test('Create button is disabled until the title is non-empty', async ({
|
|
page,
|
|
}) => {
|
|
await page.goto('/chores')
|
|
await page.getByTestId('open-add-task-modal').click()
|
|
|
|
const dialog = page.getByRole('dialog', { name: 'Create new task' })
|
|
await expect(dialog).toBeVisible()
|
|
|
|
const createButton = dialog.getByRole('button', {
|
|
name: 'Create',
|
|
exact: true,
|
|
})
|
|
const input = dialog.locator('textarea')
|
|
|
|
// ── Empty title ───────────────────────────────────────────────────────
|
|
await expect(createButton).toBeDisabled()
|
|
|
|
// ── Whitespace-only title still counts as empty ─────────────────────────
|
|
await input.fill(' ')
|
|
await expect(createButton).toBeDisabled()
|
|
|
|
// ── Real text enables the button ─────────────────────────────────────────
|
|
await input.fill('E2E Enable Check')
|
|
await expect(createButton).toBeEnabled()
|
|
|
|
// No task should be created by this test — back out via Cancel.
|
|
await dialog.getByRole('button', { name: 'Cancel', exact: true }).click()
|
|
await expect(dialog).toBeHidden()
|
|
})
|
|
})
|