style: refine dense task views
ci / gitleaks (push) Successful in 8s
ci / docker (push) Successful in 7m5s

This commit is contained in:
2026-09-17 12:20:59 +08:00
parent 1571c3946f
commit cb7d18e192
13 changed files with 381 additions and 42 deletions
+157
View File
@@ -0,0 +1,157 @@
import type { APIRequestContext, Locator, Page } from '@playwright/test'
import { expect, test } from './fixtures'
async function csrf(request: APIRequestContext) {
const state = await request.storageState()
return state.cookies.find(cookie => cookie.name === 'dodo_csrf')?.value ?? ''
}
async function mutate(request: APIRequestContext, baseURL: string, path: string, options: Parameters<APIRequestContext['fetch']>[1]) {
return request.fetch(path, { ...options, headers: { ...options?.headers, 'x-csrf-token': await csrf(request), origin: baseURL } })
}
function bottomTab(page: Page, name: string) {
return page.getByRole('navigation', { name: '主要导航' }).getByRole('button', { name, exact: true })
}
async function openSidebarView(page: Page, name: string) {
await page.locator('.topbar').getByRole('button', { name: /展开菜单|收起菜单/ }).click()
const target = page.locator('.sidebar').getByRole('button', { name, exact: true })
await expect(target).toBeInViewport()
await target.click()
}
async function expectHeightInRange(locator: Locator, minimum: number, maximum: number) {
const box = await locator.boundingBox()
expect(box).not.toBeNull()
expect(box!.height).toBeGreaterThanOrEqual(minimum)
expect(box!.height).toBeLessThanOrEqual(maximum)
}
async function expectNotClipped(locator: Locator) {
const metrics = await locator.evaluate((element: HTMLElement) => ({
clientWidth: element.clientWidth,
scrollWidth: element.scrollWidth,
clientHeight: element.clientHeight,
scrollHeight: element.scrollHeight,
}))
expect(metrics.scrollWidth).toBeLessThanOrEqual(metrics.clientWidth)
expect(metrics.scrollHeight).toBeLessThanOrEqual(metrics.clientHeight)
}
test('approved polish keeps search state, dense rows, title-only memos, and unique detail titles', async ({ page, request, baseURL }, testInfo) => {
const suffix = testInfo.project.name
const bootstrapResponse = await request.get('/api/v1/bootstrap')
expect(bootstrapResponse.ok()).toBeTruthy()
const inbox = (await bootstrapResponse.json()).lists.find((item: { is_inbox: boolean }) => item.is_inbox)
expect(inbox).toBeTruthy()
const taskTitle = `搜索保留-${suffix}-这是用于检查长标题和右侧控件不会裁切的任务标题`
const taskResponse = await mutate(request, baseURL!, '/api/v1/tasks', {
method: 'POST',
data: { title: taskTitle, list_id: inbox.id },
})
expect(taskResponse.ok(), await taskResponse.text()).toBeTruthy()
const memoTitle = `Markdown 摘要-${suffix}`
const memoResponse = await mutate(request, baseURL!, '/api/v1/memos', {
method: 'POST',
data: { title: memoTitle, content: '# 标题\n\n**重点** 与 [链接文字](https://example.com)\n\n- 列表项' },
})
expect(memoResponse.ok(), await memoResponse.text()).toBeTruthy()
const day = new Date().toLocaleDateString('sv-SE')
const countdownTitle = `紧凑倒数-${suffix}`
const countdownResponse = await mutate(request, baseURL!, '/api/v1/countdowns', {
method: 'POST',
data: { title: countdownTitle, event_date: day, kind: 'countdown', repeat_rule: 'none', calendar_mode: 'solar', ignore_year: false, pinned: true },
})
expect(countdownResponse.ok(), await countdownResponse.text()).toBeTruthy()
await page.goto('/')
await openSidebarView(page, '收集箱')
const searchToggle = page.getByRole('button', { name: '展开搜索任务' })
await expect(searchToggle).toHaveAttribute('aria-expanded', 'false')
await searchToggle.click()
const searchInput = page.getByRole('textbox', { name: '搜索任务' })
await expect(searchInput).toBeFocused()
await searchInput.fill(`搜索保留-${suffix}`)
const taskRow = page.locator('.task-row').filter({ hasText: taskTitle })
await expect(taskRow).toHaveCount(1)
await expectHeightInRange(taskRow, 64, 68)
await expectNotClipped(taskRow)
await searchInput.press('Escape')
const collapsedToggle = page.getByRole('button', { name: '展开搜索任务' })
await expect(collapsedToggle).toBeFocused()
await expect(collapsedToggle).toHaveAttribute('aria-expanded', 'false')
await collapsedToggle.click()
await expect(searchInput).toHaveValue(`搜索保留-${suffix}`)
await bottomTab(page, '习惯').click()
const habitName = `长标题习惯-${suffix}-检查进度与按钮不被裁切`
await page.getByRole('button', { name: '添加习惯' }).click()
await page.getByLabel('新习惯名称').fill(habitName)
await page.getByLabel('习惯类型').selectOption('numeric')
await page.getByLabel('目标值').fill('8')
await page.getByRole('button', { name: '添加习惯', exact: true }).click()
const habitRow = page.locator('.habit-row').filter({ hasText: habitName })
await expect(habitRow).toHaveCount(1)
await expectHeightInRange(habitRow, 84, 88)
await expectNotClipped(habitRow)
await habitRow.getByRole('button', { name: `查看习惯详情:${habitName}` }).click()
const habitDetail = page.getByRole('dialog', { name: habitName })
await expect(habitDetail.getByRole('heading', { name: habitName, exact: true })).toHaveCount(1)
await expect(habitDetail).not.toContainText('习惯详情')
await habitDetail.getByRole('button', { name: '关闭习惯详情' }).click()
await bottomTab(page, '倒数日').click()
const focus = page.locator('.countdown-focus').filter({ hasText: countdownTitle })
await expect(focus).toHaveCount(1)
await expectHeightInRange(focus, 136, 148)
await expect(focus).not.toContainText(/置顶的重要日子|下一个重要日子|还有|已经过去/)
await expectNotClipped(focus)
await focus.click()
const countdownDetail = page.getByRole('dialog', { name: countdownTitle })
await expect(countdownDetail.getByRole('heading', { name: countdownTitle, exact: true })).toHaveCount(1)
await expect(countdownDetail).not.toContainText('重要日子详情')
await countdownDetail.getByRole('button', { name: '关闭详情' }).click()
await openSidebarView(page, '备忘录')
const memoRow = page.locator('.memo-row').filter({ hasText: memoTitle })
await expect(memoRow).toHaveCount(1)
await expectHeightInRange(memoRow, 72, 76)
await expect(memoRow.locator('.memo-row__excerpt')).toHaveCount(0)
await expect(memoRow).not.toContainText('标题 重点 与 链接文字 列表项')
await expect(memoRow.getByText(memoTitle, { exact: true })).toHaveCount(1)
await expect(memoRow.locator('time')).toHaveCount(1)
await expectNotClipped(memoRow)
const shortMemoTitle = `短正文-${suffix}`
const shortMemoResponse = await mutate(request, baseURL!, '/api/v1/memos', {
method: 'POST',
data: { title: shortMemoTitle, content: 'BODY_ONLY_SHORT' },
})
expect(shortMemoResponse.ok(), await shortMemoResponse.text()).toBeTruthy()
await page.reload()
const shortMemoRow = page.locator('.memo-row').filter({ hasText: shortMemoTitle })
await expect(shortMemoRow).toHaveCount(1)
await expectHeightInRange(shortMemoRow, 72, 76)
await expect(shortMemoRow.locator('.memo-row__excerpt')).toHaveCount(0)
await expect(shortMemoRow).not.toContainText('BODY_ONLY_SHORT')
await page.setViewportSize({ width: 1440, height: 900 })
const desktopInbox = page.locator('.sidebar').getByRole('button', { name: '收集箱', exact: true })
await expect(desktopInbox).toBeInViewport()
await desktopInbox.click()
await expect(page.getByRole('button', { name: /展开搜索任务|收起搜索任务/ })).toBeHidden()
const desktopSearch = page.getByRole('textbox', { name: '搜索任务' })
await expect(desktopSearch).toBeVisible()
const desktopPanel = page.locator('.search-reveal')
const desktopBox = await desktopPanel.boundingBox()
expect(desktopBox).not.toBeNull()
expect(desktopBox!.width).toBeGreaterThanOrEqual(320)
await desktopSearch.fill(`搜索保留-${suffix}`)
const desktopTaskRow = page.locator('.task-row').filter({ hasText: taskTitle })
await expect(desktopTaskRow).toHaveCount(1)
await expectNotClipped(desktopTaskRow)
})