refactor: unify task and habit list rows
ci / gitleaks (push) Successful in 10s
ci / docker (push) Successful in 3m45s

This commit is contained in:
2026-09-18 06:25:13 +08:00
parent e4e1d96b30
commit a4c1ac5978
8 changed files with 219 additions and 108 deletions
+34 -11
View File
@@ -1,5 +1,5 @@
import type { APIRequestContext, Locator, Page } from '@playwright/test'
import { expect, test } from './fixtures'
import { allowExpectedError, expect, test } from './fixtures'
async function csrf(request: APIRequestContext) {
const state = await request.storageState()
@@ -61,16 +61,19 @@ async function box(locator: Locator) {
}
test('task rows use the body for detail and Trash keeps distinct actions', async ({ page, request, baseURL }, testInfo) => {
const suffix = testInfo.project.name
const suffix = `${testInfo.project.name}-${testInfo.workerIndex}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
const bootstrap = await request.get('/api/v1/bootstrap')
const inbox = (await bootstrap.json()).lists.find((item: { is_inbox: boolean }) => item.is_inbox)
expect(inbox).toBeTruthy()
const todayTitle = `验收今天超长任务标题-${suffix}-用于确认正文获得更多实际可用宽度`
const inboxTitle = `验收清单任务-${suffix}`
const trashTitle = `验收回收站任务-${suffix}`
const restoreTitle = `验收恢复任务-${suffix}`
const purgeTitle = `验收永久删除任务-${suffix}`
await createTask(request, baseURL!, inboxTitle, inbox.id)
const trash = await createTask(request, baseURL!, trashTitle, inbox.id)
expect((await mutate(request, baseURL!, `/api/v1/tasks/${trash.id}`, { method: 'DELETE' })).ok()).toBeTruthy()
const restoreCandidate = await createTask(request, baseURL!, restoreTitle, inbox.id)
const purgeCandidate = await createTask(request, baseURL!, purgeTitle, inbox.id)
expect((await mutate(request, baseURL!, `/api/v1/tasks/${restoreCandidate.id}`, { method: 'DELETE' })).ok()).toBeTruthy()
expect((await mutate(request, baseURL!, `/api/v1/tasks/${purgeCandidate.id}`, { method: 'DELETE' })).ok()).toBeTruthy()
await page.goto('/')
await page.getByRole('button', { name: '添加任务' }).click()
@@ -98,10 +101,30 @@ test('task rows use the body for detail and Trash keeps distinct actions', async
await page.getByRole('button', { name: '关闭详情' }).click()
await openSidebarView(page, '回收站')
const deletedRow = await taskRow(page, trashTitle)
await expect(deletedRow.getByRole('button', { name: '恢复' })).toBeVisible()
await expect(deletedRow.getByRole('button', { name: '永久删除' })).toBeVisible()
expect(await deletedRow.locator('.task-detail-trigger').count()).toBe(0)
const restoreRow = await taskRow(page, restoreTitle)
const purgeRow = await taskRow(page, purgeTitle)
for (const deletedRow of [restoreRow, purgeRow]) {
await expect(deletedRow.getByRole('button', { name: '恢复' })).toBeVisible()
await expect(deletedRow.getByRole('button', { name: '永久删除' })).toBeVisible()
await expect(deletedRow.locator('.task-main')).not.toHaveAttribute('role')
await expect(deletedRow.locator('.task-main')).not.toHaveAttribute('tabindex')
expect(await deletedRow.locator('.task-detail-trigger, .task-check').count()).toBe(0)
}
await restoreRow.getByRole('button', { name: '恢复' }).click()
await expect(restoreRow).toHaveCount(0)
await purgeRow.getByRole('button', { name: '永久删除' }).click()
const purgeDialog = page.getByRole('dialog', { name: `永久删除“${purgeTitle}”?` })
await expect(purgeDialog).toBeVisible()
// The UI can abort the completed 204 request while the confirmation overlay closes.
allowExpectedError(page, `requestfailed: DELETE ${baseURL}/api/v1/trash/`)
await purgeDialog.getByRole('button', { name: '确认', exact: true }).click()
await expect(purgeRow).toHaveCount(0)
await page.reload()
await expect(page.locator('.task-row').filter({ hasText: restoreTitle })).toHaveCount(0)
await expect(page.locator('.task-row').filter({ hasText: purgeTitle })).toHaveCount(0)
await openSidebarView(page, '收集箱')
await expect(await taskRow(page, restoreTitle)).toHaveCount(1)
await expect(page.locator('.task-row').filter({ hasText: purgeTitle })).toHaveCount(0)
await expectNoHorizontalOverflow(page, 'tasks-trash', testInfo)
})
@@ -131,8 +154,8 @@ test('Settings removes intro/empty danger and places mode-specific restore risk
})
test('Habits and Countdowns use reduced headers, compact rows, and continuous archive styling', async ({ page, request, baseURL }, testInfo) => {
const suffix = testInfo.project.name
const day = new Date().toLocaleDateString('sv-SE')
const suffix = `${testInfo.project.name}-${testInfo.workerIndex}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
const day = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Shanghai' }).format(new Date())
const focus = await createCountdown(request, baseURL!, `置顶倒数-${suffix}`, day, true)
await createCountdown(request, baseURL!, `普通倒数-${suffix}`, day)
const archived = await createCountdown(request, baseURL!, `归档倒数-${suffix}`, day)