refactor: remove manual refresh and search controls
This commit is contained in:
@@ -92,7 +92,7 @@ test('complete ZIP backup preflights and replace-restores task, habit history, c
|
||||
await confirm.getByRole('button', { name: '确认', exact: true }).click()
|
||||
await expect(page.getByRole('status')).toContainText('数据已恢复')
|
||||
|
||||
const restoredTasksResponse = await request.get(`/api/v1/tasks?q=${encodeURIComponent(taskTitle)}&limit=100`)
|
||||
const restoredTasksResponse = await request.get('/api/v1/tasks?limit=100')
|
||||
expect(restoredTasksResponse.ok()).toBeTruthy()
|
||||
const restoredTasks = (await restoredTasksResponse.json()).items as Array<{ id: string; title: string }>
|
||||
expect(restoredTasks.filter(item => item.title === taskTitle)).toHaveLength(1)
|
||||
|
||||
@@ -161,7 +161,6 @@ test('settings match the approved paper-ledger geometry and action hierarchy', a
|
||||
const sectionHeaders = [...page.querySelectorAll<HTMLElement>('.settings-group > header')]
|
||||
const rows = [...page.querySelectorAll<HTMLElement>('.settings-row')]
|
||||
const menu = main.querySelector<HTMLElement>('.topbar > .icon')!.getBoundingClientRect()
|
||||
const refresh = main.querySelector<HTMLElement>('.settings-refresh')!.getBoundingClientRect()
|
||||
const rect = page.getBoundingClientRect()
|
||||
const mainRect = main.getBoundingClientRect()
|
||||
return {
|
||||
@@ -171,7 +170,7 @@ test('settings match the approved paper-ledger geometry and action hierarchy', a
|
||||
headingSize: getComputedStyle(heading).fontSize,
|
||||
sectionHeights: sectionHeaders.map(header => header.getBoundingClientRect().height),
|
||||
rowHeights: rows.map(row => row.getBoundingClientRect().height),
|
||||
controlsOverlap: !(menu.right <= refresh.left || refresh.right <= menu.left || menu.bottom <= refresh.top || refresh.bottom <= menu.top),
|
||||
menuWidth: menu.width,
|
||||
}
|
||||
})
|
||||
expect(layout.bodyOverflow).toBe(0)
|
||||
@@ -188,7 +187,8 @@ test('settings match the approved paper-ledger geometry and action hierarchy', a
|
||||
}
|
||||
expect(layout.sectionHeights.every(height => height === 44)).toBeTruthy()
|
||||
expect(layout.rowHeights.every(height => height >= (isMobileContract ? 64 : 62))).toBeTruthy()
|
||||
expect(layout.controlsOverlap).toBe(false)
|
||||
expect(layout.menuWidth).toBeGreaterThanOrEqual(44)
|
||||
await expect(page.getByRole('button', { name: /刷新|搜索/ })).toHaveCount(0)
|
||||
|
||||
const exportButton = page.getByRole('button', { name: '导出 ZIP' })
|
||||
const fileInput = page.locator('input[type=file]')
|
||||
|
||||
@@ -32,6 +32,15 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
const habitResponse = await mutate(request, baseURL!, '/api/v1/habits', { name: habitName, kind: 'numeric', target: 8, max_value: 8, schedule_type: 'daily' })
|
||||
expect(habitResponse.ok(), await habitResponse.text()).toBeTruthy()
|
||||
const habitId = (await habitResponse.json()).id as string
|
||||
await page.route('**/api/v1/today/environment', route => route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
date: { solar_date: '2026-09-19', weekday: '星期六', lunar: '八月初九' },
|
||||
weather: { status: 'fresh', temperature_c: 26.4, text: '多云', observed_at: '2026-09-19T20:45:00+08:00' },
|
||||
gold: { status: 'fresh', contract: 'Au99.99', price_cny_per_gram: 835.62, market_date: '2026-09-18' },
|
||||
}),
|
||||
}))
|
||||
await page.goto('/')
|
||||
const taskRowLocator = page.locator('#today-tasks .task-row').filter({ hasText: taskTitle })
|
||||
const habitRowLocator = page.locator('.today-habit-row').filter({ hasText: habitName })
|
||||
@@ -59,7 +68,7 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
await expect(taskRowLocator).toHaveCount(1)
|
||||
await expect(habitRowLocator).toHaveCount(1)
|
||||
await expect(page.locator('.today-environment')).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: '刷新当前页面' })).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: /刷新|搜索/ })).toHaveCount(0)
|
||||
await expect(page.getByRole('switch', { name: '显示已完成' })).toHaveCount(1)
|
||||
await expect(page.locator('.today-environment__gold')).toContainText('Au99.99')
|
||||
const metrics = await page.evaluate(({ taskId, habitId }) => {
|
||||
@@ -136,7 +145,7 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
insideContent,
|
||||
weatherText: weather.innerText,
|
||||
goldText: gold.innerText,
|
||||
goldLabel: gold.querySelector('.today-environment__gold-date')?.getAttribute('aria-label'),
|
||||
goldLabel: gold.querySelector('.today-environment__gold-date')?.getAttribute('aria-label') ?? '',
|
||||
summaries,
|
||||
styles: {
|
||||
mainPaddingLeft: getComputedStyle(main).paddingLeft,
|
||||
|
||||
@@ -200,56 +200,3 @@ test('Habits and Countdowns use reduced headers, compact rows, and continuous ar
|
||||
expect(archiveMetrics.buttons.every(item => item.width >= 44 && item.height >= 44)).toBeTruthy()
|
||||
await expectNoHorizontalOverflow(page, 'countdowns', testInfo)
|
||||
})
|
||||
|
||||
test('Memo mobile search is one-row, focus-safe, persistent when collapsed, and desktop-wide', async ({ page }, testInfo) => {
|
||||
await page.goto('/')
|
||||
await openSidebarView(page, '备忘录')
|
||||
const toolbar = page.locator('.memo-toolbar')
|
||||
const scope = page.locator('.memo-scope')
|
||||
const toggle = page.getByRole('button', { name: '展开搜索备忘录' })
|
||||
const collapsedGeometry = { toolbar: await box(toolbar), scope: await box(scope), toggle: await box(toggle) }
|
||||
console.log(`[qa-metrics][${testInfo.project.name}][memo-mobile-collapsed] ${JSON.stringify(collapsedGeometry)}`)
|
||||
expect(collapsedGeometry.toggle.width).toBeGreaterThanOrEqual(44)
|
||||
expect(collapsedGeometry.toggle.height).toBeGreaterThanOrEqual(44)
|
||||
expect(Math.abs(collapsedGeometry.scope.y - collapsedGeometry.toggle.y)).toBeLessThanOrEqual(4)
|
||||
expect(collapsedGeometry.toolbar.height).toBeLessThanOrEqual(52)
|
||||
await expect(toggle).toHaveAttribute('aria-expanded', 'false')
|
||||
|
||||
await toggle.click()
|
||||
const closeToggle = page.getByRole('button', { name: '收起搜索备忘录' })
|
||||
const input = page.getByRole('textbox', { name: '搜索备忘录' })
|
||||
await expect(closeToggle).toHaveAttribute('aria-expanded', 'true')
|
||||
await expect(input).toBeFocused()
|
||||
await input.fill(`保留查询-${testInfo.project.name}`)
|
||||
const openPanel = page.locator('#memo-search-panel')
|
||||
const openMetrics = await openPanel.evaluate(element => {
|
||||
const rect = element.getBoundingClientRect()
|
||||
return { x: rect.x, right: rect.right, width: rect.width, scrollWidth: (element as HTMLElement).scrollWidth, clientWidth: (element as HTMLElement).clientWidth }
|
||||
})
|
||||
console.log(`[qa-metrics][${testInfo.project.name}][memo-mobile-open] ${JSON.stringify(openMetrics)}`)
|
||||
expect(openMetrics.x).toBeGreaterThanOrEqual(0)
|
||||
expect(openMetrics.right).toBeLessThanOrEqual(page.viewportSize()!.width + 1)
|
||||
expect(openMetrics.scrollWidth).toBeLessThanOrEqual(openMetrics.clientWidth)
|
||||
|
||||
await closeToggle.click()
|
||||
await expect(openPanel).toBeHidden()
|
||||
await expect(toggle).toBeFocused()
|
||||
await expect(toggle).toHaveAttribute('aria-expanded', 'false')
|
||||
await toggle.click()
|
||||
await expect(input).toHaveValue(`保留查询-${testInfo.project.name}`)
|
||||
await expect(input).toBeFocused()
|
||||
await expectNoHorizontalOverflow(page, 'memo-mobile', testInfo)
|
||||
|
||||
await page.setViewportSize({ width: 1440, height: 900 })
|
||||
await expect(openPanel).toBeVisible()
|
||||
const desktopMetrics = await openPanel.evaluate(element => {
|
||||
const rect = element.getBoundingClientRect()
|
||||
const style = getComputedStyle(element)
|
||||
return { viewport: { width: innerWidth, height: innerHeight }, x: rect.x, y: rect.y, width: rect.width, display: style.display, hidden: (element as HTMLElement).hidden, documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth }
|
||||
})
|
||||
console.log(`[qa-metrics][${testInfo.project.name}][memo-desktop] ${JSON.stringify(desktopMetrics)}`)
|
||||
expect(desktopMetrics.hidden).toBeFalsy()
|
||||
expect(desktopMetrics.width).toBeGreaterThanOrEqual(320)
|
||||
expect(desktopMetrics.documentOverflow).toBe(0)
|
||||
await expect(toggle).toBeHidden()
|
||||
})
|
||||
|
||||
@@ -36,7 +36,8 @@ test('Upcoming shares the task-list hierarchy while keeping its date scope and n
|
||||
const inbox = (await bootstrapResponse.json()).lists.find((item: { is_inbox: boolean }) => item.is_inbox)
|
||||
expect(inbox).toBeTruthy()
|
||||
|
||||
for (const [title, days] of [['未来一天任务', 1], ['未来三天任务', 3]] as const) {
|
||||
const createdTitles = ['未来一天任务', '未来三天任务'] as const
|
||||
for (const [title, days] of [[createdTitles[0], 1], [createdTitles[1], 3]] as const) {
|
||||
const response = await mutate(request, baseURL!, '/api/v1/tasks', {
|
||||
method: 'POST',
|
||||
data: { title, list_id: inbox.id, due_at: futureDueAt(days), due_has_time: false },
|
||||
@@ -55,8 +56,9 @@ test('Upcoming shares the task-list hierarchy while keeping its date scope and n
|
||||
await expect(header.getByRole('switch', { name: '显示已完成' })).toBeVisible()
|
||||
await expect(page.locator('.list-section-heading')).toContainText('任务')
|
||||
await expect(page.getByRole('button', { name: '调整顺序' })).toHaveCount(0)
|
||||
await expect(page.locator('.task-row')).toHaveCount(2)
|
||||
await expect(page.locator('.task-row')).toContainText(['未来一天任务', '未来三天任务'])
|
||||
const createdRows = page.locator('.task-row').filter({ hasText: /未来一天任务|未来三天任务/ })
|
||||
await expect(createdRows).toHaveCount(2)
|
||||
await expect(createdRows).toContainText([...createdTitles])
|
||||
|
||||
const geometry = await page.evaluate(() => {
|
||||
const rect = (selector: string) => {
|
||||
@@ -67,14 +69,12 @@ test('Upcoming shares the task-list hierarchy while keeping its date scope and n
|
||||
viewportWidth: innerWidth,
|
||||
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
||||
header: rect('.list-page-context'),
|
||||
search: rect('.list-search-reveal'),
|
||||
section: rect('.list-section-heading'),
|
||||
list: rect('.task-list'),
|
||||
}
|
||||
})
|
||||
expect(geometry.overflow).toBe(0)
|
||||
expect(geometry.header.left).toBeCloseTo(geometry.search.left, 0)
|
||||
expect(geometry.search.left).toBeCloseTo(geometry.section.left, 0)
|
||||
expect(geometry.header.left).toBeCloseTo(geometry.section.left, 0)
|
||||
expect(geometry.section.left).toBeCloseTo(geometry.list.left, 0)
|
||||
expect(geometry.header.width).toBeCloseTo(geometry.list.width, 0)
|
||||
if (geometry.viewportWidth <= 720) {
|
||||
|
||||
@@ -114,14 +114,14 @@ test('boolean habits complete in place while paused and unscheduled rows explain
|
||||
await expect(unscheduledRow.getByRole('button', { name: '今天未安排' })).toBeDisabled()
|
||||
})
|
||||
|
||||
test('approved polish keeps search state, dense rows, title-only memos, and unique detail titles', async ({ page, request, baseURL }, testInfo) => {
|
||||
test('approved polish keeps dense rows, title-only memos, and unique detail titles', async ({ page, request, baseURL }, testInfo) => {
|
||||
const suffix = `${testInfo.project.name}-${testInfo.workerIndex}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||
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 taskTitle = `长标题任务-${suffix}-这是用于检查长标题和右侧控件不会裁切的任务标题`
|
||||
const taskResponse = await mutate(request, baseURL!, '/api/v1/tasks', {
|
||||
method: 'POST',
|
||||
data: { title: taskTitle, list_id: inbox.id },
|
||||
@@ -145,26 +145,7 @@ test('approved polish keeps search state, dense rows, title-only memos, and uniq
|
||||
|
||||
await page.goto('/')
|
||||
await openSidebarView(page, '收集箱')
|
||||
const mobileGeometry = await page.evaluate(() => {
|
||||
const main = document.querySelector('main.list-main')!.getBoundingClientRect()
|
||||
const header = document.querySelector('.list-page-context')!.getBoundingClientRect()
|
||||
const filter = document.querySelector('.list-inline-filter')!.getBoundingClientRect()
|
||||
const search = document.querySelector('.list-search-reveal')!.getBoundingClientRect()
|
||||
const toggle = document.querySelector('.list-search-reveal .task-search-toggle')!.getBoundingClientRect()
|
||||
return { main: { left: main.left, right: main.right }, header: { left: header.left, right: header.right, bottom: header.bottom }, filter: { left: filter.left, bottom: filter.bottom }, search: { left: search.left, right: search.right, top: search.top }, toggle: { left: toggle.left, top: toggle.top } }
|
||||
})
|
||||
expect(mobileGeometry.header.left - mobileGeometry.main.left).toBeCloseTo(29, 0)
|
||||
expect(mobileGeometry.main.right - mobileGeometry.header.right).toBeCloseTo(29, 0)
|
||||
expect(mobileGeometry.search.left).toBeGreaterThanOrEqual(mobileGeometry.header.left)
|
||||
expect(mobileGeometry.search.right).toBeLessThanOrEqual(mobileGeometry.header.right)
|
||||
expect(mobileGeometry.toggle.top).toBeGreaterThanOrEqual(mobileGeometry.header.bottom - 1)
|
||||
expect(mobileGeometry.toggle.left).toBeGreaterThanOrEqual(mobileGeometry.filter.left)
|
||||
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}`)
|
||||
await expect(page.getByRole('button', { name: /刷新|搜索/ })).toHaveCount(0)
|
||||
const taskRow = page.locator('.task-row').filter({ hasText: taskTitle })
|
||||
await expect(taskRow).toHaveCount(1)
|
||||
await expectHeightInRange(taskRow, 57, 59)
|
||||
@@ -180,12 +161,6 @@ test('approved polish keeps search state, dense rows, title-only memos, and uniq
|
||||
await expect(taskDetail.getByRole('button', { name: '保存更改' })).toBeEnabled()
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(taskDetail).toBeHidden()
|
||||
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 mobileHabitGeometry = await page.evaluate(() => {
|
||||
@@ -261,22 +236,5 @@ test('approved polish keeps search state, dense rows, title-only memos, and uniq
|
||||
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('.list-search-reveal')
|
||||
const desktopBox = await desktopPanel.boundingBox()
|
||||
expect(desktopBox).not.toBeNull()
|
||||
expect(desktopBox!.width).toBeGreaterThanOrEqual(320)
|
||||
const desktopHeader = page.locator('.list-page-context')
|
||||
const desktopList = page.locator('.task-list')
|
||||
const desktopGeometry = await Promise.all([desktopHeader, desktopPanel, desktopList].map(async locator => locator.boundingBox()))
|
||||
for (const box of desktopGeometry) expect(box).not.toBeNull()
|
||||
expect(Math.abs(desktopGeometry[0]!.x - desktopGeometry[1]!.x)).toBeLessThanOrEqual(1)
|
||||
expect(Math.abs(desktopGeometry[1]!.x - desktopGeometry[2]!.x)).toBeLessThanOrEqual(1)
|
||||
expect(desktopGeometry[0]!.width).toBeLessThanOrEqual(900)
|
||||
await desktopSearch.fill(`搜索保留-${suffix}`)
|
||||
const desktopTaskRow = page.locator('.task-row').filter({ hasText: taskTitle })
|
||||
await expect(desktopTaskRow).toHaveCount(1)
|
||||
await expectNotClipped(desktopTaskRow)
|
||||
await expect(page.getByRole('button', { name: /刷新|搜索/ })).toHaveCount(0)
|
||||
})
|
||||
|
||||
@@ -30,13 +30,13 @@ test('inbox and task composer match approved visuals', async ({ page, request, b
|
||||
const inbox = (await bootstrap.json()).lists.find((item: { is_inbox: boolean }) => item.is_inbox)
|
||||
expect(inbox).toBeTruthy()
|
||||
|
||||
await createTask(request, baseURL!, '整理本周工作记录', inbox.id)
|
||||
await createTask(request, baseURL!, '检查 Dodo 备份', inbox.id)
|
||||
await createTask(request, baseURL!, '周末买水果和牛奶', inbox.id)
|
||||
const seededTitles = ['整理本周工作记录', '检查 Dodo 备份', '周末买水果和牛奶'] as const
|
||||
for (const title of seededTitles) await createTask(request, baseURL!, title, inbox.id)
|
||||
|
||||
await page.goto('/')
|
||||
await openInbox(page)
|
||||
await expect(page.locator('.task-row')).toHaveCount(3)
|
||||
const seededRows = page.locator('.task-row').filter({ hasText: /整理本周工作记录|检查 Dodo 备份|周末买水果和牛奶/ })
|
||||
await expect(seededRows).toHaveCount(3)
|
||||
await expect(page).toHaveScreenshot('inbox.png', { fullPage: true })
|
||||
|
||||
await page.getByRole('button', { name: '添加任务' }).click()
|
||||
|
||||
Reference in New Issue
Block a user