test(web): share one sweep and settle the control's resize

Review follow-ups on the column-overflow scenario:

- Memoize the sweep so the golden and the assertions consume the same
  readings, which is what its contract already claimed; two runs could
  disagree if a resize settled differently between them.
- Settle the column width before the mutation control measures. The test
  arrives from 1680 alone and from the sweep's 600 in a full run, and the
  frame eases its column tracks, so an immediate read can report the
  previous viewport's bleed.
- Name the wheel delta, assert the bleed stays inside it, and compare the
  travelled distance rounded: a clamp or a sub-pixel would otherwise read
  as a broken fix.
This commit is contained in:
creatixchu
2026-08-05 18:32:14 +08:00
parent 21148232f5
commit 94e2c8129d
@@ -49,6 +49,8 @@ const MODE = webSnapshotMode()
const WIDTHS = [1680, 1200, 1000, 800, 600]
/** Element id of the mutation control's injected sheet, so the test can take it back out. */
const CONTROL_STYLE_ID = 'dsh-column-overflow-control'
/** Horizontal wheel delta per gesture; must exceed the widest bleed the sweep can produce. */
const WHEEL_DELTA = 300
/** One viewport stop: whether the glow bleeds past the column, and whether that bleed scrolls. */
interface ColumnMetrics {
@@ -124,7 +126,7 @@ async function wheelHorizontally(page: Page): Promise<number> {
return { x: box.left + box.width / 2, y: box.top + 60 }
})
await page.mouse.move(origin.x, origin.y)
await page.mouse.wheel(300, 0)
await page.mouse.wheel(WHEEL_DELTA, 0)
// A fixed settle, then two frames. Polling for a settled value cannot be
// used here — the value under test is 0, which a poll starting at 0 accepts
// before the gesture has had any chance to move it — so the wait is
@@ -190,28 +192,45 @@ describe('web e2e: the conversation column scrolls on one axis', () => {
})
/**
* Sweep the stops once and hand the readings to every assertion below, so
* the golden and the assertions describe the same measurement rather than
* two runs that could disagree.
* Resize to a viewport and read the column once its width stops moving.
*
* The glow rides the hero box, which rides the column, and the frame eases
* its column tracks over `--ds-transition-duration-slow`: reading straight
* after a resize can report the previous viewport's relation, or a width
* caught mid-transition.
* @param width - viewport width to settle at.
* @returns the column's readings at that width.
*/
const settleAt = async (width: number): Promise<ColumnMetrics> => {
await page.setViewportSize({ width, height: 900 })
let previous = -1
await expect.poll(async () => {
const current = (await measureColumn(page, width)).columnWidth
const settled = current === previous
previous = current
return settled
}, { timeout: 10_000 }).toBe(true)
return measureColumn(page, width)
}
/**
* Sweep the stops once per run and hand the SAME readings to every assertion
* below, so the golden and the assertions describe one measurement instead of
* two runs that could disagree. Memoized rather than re-run per test: the
* gestures below move the viewport, and a second sweep would be a second
* chance for a resize to settle differently.
* @returns the stops in {@link WIDTHS} order.
*/
const sweep = async (): Promise<ColumnStop[]> => {
const stops: ColumnStop[] = []
for (const width of WIDTHS) {
await page.setViewportSize({ width, height: 900 })
// The glow rides the hero box, which rides the column, and the column's
// track animates: settle on a column width that stops moving, or a stop
// gets read mid-transition and reports the previous viewport's relation.
let previous = -1
await expect.poll(async () => {
const current = (await measureColumn(page, width)).columnWidth
const settled = current === previous
previous = current
return settled
}, { timeout: 10_000 }).toBe(true)
stops.push({ ...await measureColumn(page, width), scrollLeftAfterWheel: await wheelHorizontally(page) })
}
return stops
let swept: Promise<ColumnStop[]> | undefined
const sweep = (): Promise<ColumnStop[]> => {
swept ??= (async () => {
const stops: ColumnStop[] = []
for (const width of WIDTHS) {
stops.push({ ...await settleAt(width), scrollLeftAfterWheel: await wheelHorizontally(page) })
}
return stops
})()
return swept
}
it('never scrolls horizontally, at any width the glow bleeds past', async () => {
@@ -242,7 +261,10 @@ describe('web e2e: the conversation column scrolls on one axis', () => {
// that a one-axis scroller computes to `auto` — and shows the same gesture,
// at the same timing, carrying the column to the full bleed. Without it a
// `scrollLeft` of 0 could equally mean the wheel never arrived.
await page.setViewportSize({ width: 1200, height: 900 })
// Settle the resize first: this test runs at 1680 on its own and after the
// sweep's 600 in a full run, and an unsettled column reports the previous
// viewport's bleed.
await settleAt(1200)
// Injected with an id rather than through `addStyleTag`, so the teardown
// below can take the sheet out again by selector: it must not outlive this
// test, or the golden ends up reading the control.
@@ -255,8 +277,16 @@ describe('web e2e: the conversation column scrolls on one axis', () => {
try {
const before = await measureColumn(page, 1200)
expect(before.overflowX).toBe('auto')
expect(await wheelHorizontally(page)).toBe(before.bleedRange)
expect(before.bleedRange).toBeGreaterThan(0)
// The gesture has to be able to reach the far edge, or the equality below
// would fail on the clamp and read as a broken fix. Stated as its own
// assertion so that failure names itself.
expect(before.bleedRange).toBeLessThan(WHEEL_DELTA)
// Rounded: `scrollLeft` is fractional under a fractional layout while
// `scrollWidth - clientWidth` is integral, and the claim is that the
// column travelled the whole bleed — not that two engines agree on a
// sub-pixel.
expect(Math.round(await wheelHorizontally(page))).toBe(before.bleedRange)
} finally {
await page.evaluate((id: string) => {
document.getElementById(id)?.remove()