Files
dodo/frontend/e2e/upcoming-list-parity.spec.ts
T
bboysoul 4973d56a64
ci / gitleaks (push) Successful in 8s
ci / docker (push) Successful in 7m17s
refactor: remove manual refresh and search controls
2026-09-19 21:15:23 +08:00

90 lines
4.1 KiB
TypeScript

import type { APIRequestContext, 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 } })
}
async function openSidebarView(page: Page, name: string) {
const sidebar = page.locator('.sidebar')
const target = sidebar.getByRole('button', { name, exact: true })
if (await page.locator('.shell').evaluate(element => element.classList.contains('sidebar-collapsed'))) {
await page.getByRole('button', { name: '展开菜单' }).click()
} else if (await page.evaluate(() => window.innerWidth <= 930)) {
await page.locator('main .topbar > button').first().click()
} else if (!await target.isVisible()) {
await page.getByRole('button', { name: /展开菜单|收起菜单/ }).click()
}
await target.click()
}
function futureDueAt(days: number) {
const date = new Date()
date.setDate(date.getDate() + days)
date.setHours(12, 0, 0, 0)
return date.toISOString()
}
test('Upcoming shares the task-list hierarchy while keeping its date scope and no manual reorder', async ({ page, request, baseURL }) => {
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 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 },
})
expect(response.ok(), await response.text()).toBeTruthy()
}
await page.goto('/')
await openSidebarView(page, '最近 7 天')
await expect(page.locator('main')).toHaveClass(/list-main/)
await expect(page.locator('.topbar-title')).toHaveCount(0)
const header = page.locator('.list-page-context')
await expect(header.getByRole('heading', { name: '最近 7 天' })).toBeVisible()
await expect(header).toContainText(/还有 \d+ 项待完成/)
await expect(header.getByRole('switch', { name: '显示已完成' })).toBeVisible()
await expect(page.locator('.list-section-heading')).toContainText('任务')
await expect(page.getByRole('button', { name: '调整顺序' })).toHaveCount(0)
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) => {
const value = document.querySelector<HTMLElement>(selector)!.getBoundingClientRect()
return { left: value.left, right: value.right, width: value.width, top: value.top, bottom: value.bottom }
}
return {
viewportWidth: innerWidth,
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
header: rect('.list-page-context'),
section: rect('.list-section-heading'),
list: rect('.task-list'),
}
})
expect(geometry.overflow).toBe(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) {
expect(geometry.header.left).toBeCloseTo(29, 0)
expect(geometry.list.right).toBeCloseTo(geometry.viewportWidth - 29, 0)
}
const fab = page.getByRole('button', { name: '添加任务' })
await expect(fab).toBeVisible()
await fab.click()
await expect(page.getByRole('dialog', { name: '添加任务' })).toBeVisible()
})