feat: simplify today into plain list
This commit is contained in:
@@ -20,42 +20,47 @@ async function assertInsideViewport(locator: Locator, page: Page) {
|
||||
expect(box!.y + box!.height).toBeLessThanOrEqual(viewport!.height + 1)
|
||||
}
|
||||
|
||||
test('Today paper composition has no overlap or horizontal overflow', async ({ page }) => {
|
||||
test('Today plain-list composition has no overlap or horizontal overflow', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
await expect(await bottomTab(page, '今天')).toHaveAttribute('aria-current', 'page')
|
||||
const board = page.locator('.today-board')
|
||||
const environment = page.locator('.today-environment')
|
||||
const tracks = board.locator('.today-track')
|
||||
await expect(board).toBeVisible()
|
||||
await expect(environment).toBeVisible()
|
||||
await expect(tracks).toHaveCount(2)
|
||||
|
||||
const geometry = await board.evaluate((node) => {
|
||||
const boardRect = node.getBoundingClientRect()
|
||||
const environmentRect = node.querySelector('.today-environment')!.getBoundingClientRect()
|
||||
const trackRects = [...node.querySelectorAll('.today-track')].map(item => item.getBoundingClientRect())
|
||||
const context = page.locator('.today-context')
|
||||
const environment = page.locator('.today-environment')
|
||||
const title = page.locator('.today-page-title')
|
||||
const remaining = page.locator('.today-remaining')
|
||||
const filter = page.locator('.today-inline-filter')
|
||||
const firstSection = page.locator('.today-section-toggle').first()
|
||||
|
||||
for (const locator of [context, environment, title, remaining, filter, firstSection]) {
|
||||
await expect(locator).toBeVisible()
|
||||
}
|
||||
|
||||
const geometry = await page.locator('main').evaluate((main) => {
|
||||
const selectors = ['.today-environment', '.today-page-title', '.today-remaining', '.today-inline-filter', '.today-section-toggle']
|
||||
const rects = selectors.map(selector => main.querySelector(selector)!.getBoundingClientRect())
|
||||
const context = main.querySelector('.today-context')!
|
||||
const contextRect = context.getBoundingClientRect()
|
||||
return {
|
||||
boardRight: boardRect.right,
|
||||
boardBottom: boardRect.bottom,
|
||||
boardScrollWidth: node.scrollWidth,
|
||||
boardClientWidth: node.clientWidth,
|
||||
environmentBottom: environmentRect.bottom,
|
||||
trackTops: trackRects.map(rect => rect.top),
|
||||
trackRights: trackRects.map(rect => rect.right),
|
||||
trackBottoms: trackRects.map(rect => rect.bottom),
|
||||
mainScrollWidth: main.scrollWidth,
|
||||
mainClientWidth: main.clientWidth,
|
||||
contextScrollWidth: context.scrollWidth,
|
||||
contextClientWidth: context.clientWidth,
|
||||
contextLeft: contextRect.left,
|
||||
contextRight: contextRect.right,
|
||||
rects: rects.map(rect => ({ left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height })),
|
||||
}
|
||||
})
|
||||
expect(geometry.boardScrollWidth).toBeLessThanOrEqual(geometry.boardClientWidth)
|
||||
expect(Math.min(...geometry.trackTops)).toBeGreaterThanOrEqual(geometry.environmentBottom - 1)
|
||||
expect(Math.max(...geometry.trackRights)).toBeLessThanOrEqual(geometry.boardRight + 1)
|
||||
expect(Math.max(...geometry.trackBottoms)).toBeLessThanOrEqual(geometry.boardBottom + 1)
|
||||
|
||||
for (const track of await tracks.all()) {
|
||||
const head = track.locator('.today-track-head')
|
||||
const labels = head.locator('strong, span')
|
||||
await expect(labels).toHaveCount(2)
|
||||
const boxes = await labels.evaluateAll(nodes => nodes.map(node => node.getBoundingClientRect()))
|
||||
expect(Math.abs(boxes[0].top - boxes[1].top)).toBeLessThan(4)
|
||||
expect(geometry.mainScrollWidth).toBeLessThanOrEqual(geometry.mainClientWidth)
|
||||
expect(geometry.contextScrollWidth).toBeLessThanOrEqual(geometry.contextClientWidth)
|
||||
for (const rect of geometry.rects) {
|
||||
expect(rect.width).toBeGreaterThan(0)
|
||||
expect(rect.height).toBeGreaterThan(0)
|
||||
expect(rect.left).toBeGreaterThanOrEqual(geometry.contextLeft - 1)
|
||||
expect(rect.right).toBeLessThanOrEqual(geometry.contextRight + 1)
|
||||
}
|
||||
for (let index = 0; index < geometry.rects.length - 1; index += 1) {
|
||||
expect(geometry.rects[index].bottom).toBeLessThanOrEqual(geometry.rects[index + 1].top + 1)
|
||||
}
|
||||
})
|
||||
test('Today task persists through detail, completion and reopen', async ({ page }, testInfo) => {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { expect, test } from './fixtures'
|
||||
|
||||
const widths = [721, 720, 390, 375]
|
||||
|
||||
test('Today plain-list layout matches the approved responsive geometry', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
for (const width of widths) {
|
||||
await page.setViewportSize({ width, height: width <= 390 ? 844 : 900 })
|
||||
await expect(page.locator('.today-environment')).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: '刷新当前页面' })).toBeVisible()
|
||||
await expect(page.getByRole('switch', { name: '显示已完成' })).toHaveCount(1)
|
||||
await expect(page.locator('.today-environment__gold')).toContainText('Au99.99')
|
||||
const metrics = await page.evaluate(() => {
|
||||
const rect = (selector: string) => document.querySelector<HTMLElement>(selector)!.getBoundingClientRect()
|
||||
const environment = document.querySelector<HTMLElement>('.today-environment')!
|
||||
const calendar = document.querySelector<HTMLElement>('.today-environment__calendar')!
|
||||
const weather = document.querySelector<HTMLElement>('.today-environment__weather')!
|
||||
const gold = document.querySelector<HTMLElement>('.today-environment__gold')!
|
||||
const title = document.querySelector<HTMLElement>('.today-page-title')!
|
||||
const remaining = document.querySelector<HTMLElement>('.today-remaining')!
|
||||
const filter = document.querySelector<HTMLElement>('.today-inline-filter')!
|
||||
const firstSection = document.querySelector<HTMLElement>('.today-section-toggle')!
|
||||
const main = document.querySelector<HTMLElement>('main.today-main')!
|
||||
const content = rect('.today-context')
|
||||
const orderedElements = [environment, title, remaining, filter, firstSection]
|
||||
const ordered = orderedElements.map((element) => {
|
||||
const box = element.getBoundingClientRect()
|
||||
return { left: box.left, right: box.right, top: box.top, bottom: box.bottom, width: box.width, height: box.height }
|
||||
})
|
||||
const tolerance = 1
|
||||
const orderedPairs = ordered.slice(0, -1).map((current, index) => ({
|
||||
previousBottom: current.bottom,
|
||||
nextTop: ordered[index + 1].top,
|
||||
separated: current.bottom <= ordered[index + 1].top + tolerance,
|
||||
}))
|
||||
const positiveGeometry = ordered.every(box => box.width > 0 && box.height > 0)
|
||||
const insideContent = ordered.every(box => box.left >= content.left - tolerance && box.right <= content.right + tolerance)
|
||||
const visible = (element: HTMLElement) => {
|
||||
const style = getComputedStyle(element)
|
||||
return style.visibility !== 'hidden' && style.display !== 'none' && element.getBoundingClientRect().width > 0
|
||||
}
|
||||
const rows = [...environment.children]
|
||||
.filter((node): node is HTMLElement => node instanceof HTMLElement && visible(node) && !node.classList.contains('sr-only'))
|
||||
.map(node => Math.round(node.getBoundingClientRect().top))
|
||||
const uniqueRows = [...new Set(rows)]
|
||||
const collisions = [calendar, weather, gold].some((a, index, items) => items.slice(index + 1).some(b => {
|
||||
const ar = a.getBoundingClientRect(); const br = b.getBoundingClientRect()
|
||||
return ar.left < br.right && ar.right > br.left && ar.top < br.bottom && ar.bottom > br.top
|
||||
}))
|
||||
const secondary = [...environment.querySelectorAll<HTMLElement>('small')].map(node => ({ scrollWidth: node.scrollWidth, clientWidth: node.clientWidth, visible: visible(node) }))
|
||||
return {
|
||||
viewport: innerWidth,
|
||||
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
||||
mainOverflow: main.scrollWidth - main.clientWidth,
|
||||
content: { left: content.left, right: content.right, width: content.width },
|
||||
environment: { ...environment.getBoundingClientRect().toJSON(), scrollWidth: environment.scrollWidth, clientWidth: environment.clientWidth },
|
||||
directions: { calendar: getComputedStyle(calendar).flexDirection, weather: getComputedStyle(weather).flexDirection, gold: getComputedStyle(gold).flexDirection },
|
||||
uniqueRows,
|
||||
collisions,
|
||||
secondary,
|
||||
ordered,
|
||||
orderedPairs,
|
||||
positiveGeometry,
|
||||
insideContent,
|
||||
weatherText: weather.innerText,
|
||||
goldText: gold.innerText,
|
||||
goldLabel: gold.querySelector('.today-environment__gold-date')?.getAttribute('aria-label'),
|
||||
}
|
||||
})
|
||||
console.log(`[today-geometry][${width}] ${JSON.stringify(metrics)}`)
|
||||
expect(metrics.documentOverflow).toBe(0)
|
||||
expect(metrics.mainOverflow).toBeLessThanOrEqual(0)
|
||||
expect(metrics.environment.scrollWidth).toBeLessThanOrEqual(metrics.environment.clientWidth)
|
||||
expect(metrics.collisions).toBeFalsy()
|
||||
expect(metrics.positiveGeometry).toBeTruthy()
|
||||
expect(metrics.insideContent).toBeTruthy()
|
||||
expect(metrics.orderedPairs.every(pair => pair.separated)).toBeTruthy()
|
||||
expect(metrics.goldText).toContain('Au99.99')
|
||||
expect(metrics.goldLabel).toMatch(/市场日期 \d{4}-\d{2}-\d{2}/)
|
||||
expect(metrics.secondary.every(line => line.visible && line.scrollWidth <= line.clientWidth)).toBeTruthy()
|
||||
if (width === 721) {
|
||||
expect(metrics.uniqueRows).toHaveLength(1)
|
||||
expect(metrics.environment.height).toBeLessThan(80)
|
||||
} else {
|
||||
expect(metrics.uniqueRows).toHaveLength(2)
|
||||
expect(metrics.directions.calendar).toBe('row')
|
||||
expect(metrics.directions.weather).toBe('column')
|
||||
expect(metrics.directions.gold).toBe('column')
|
||||
}
|
||||
if (width >= 721) {
|
||||
expect(metrics.content.width).toBeLessThanOrEqual(721)
|
||||
expect(Math.abs(metrics.content.left - (width - metrics.content.width) / 2)).toBeLessThanOrEqual(2)
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user