158 lines
7.8 KiB
TypeScript
158 lines
7.8 KiB
TypeScript
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)
|
|
})
|