mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
77dddd5e8a
Release branch for 4.0.0.
99 lines
4.4 KiB
TypeScript
99 lines
4.4 KiB
TypeScript
import { test, expect, devices } from '@playwright/test'
|
|
import type { Page } from '@playwright/test'
|
|
import { dismissSystemNotices } from './helpers'
|
|
|
|
// Phone coverage for #2051: the day details area steps days under a horizontal
|
|
// swipe, so the trip is navigable one-handed instead of only from the chip rail
|
|
// pinned to the top of the screen.
|
|
//
|
|
// jsdom can drive the handlers but not the browser around them: whether the
|
|
// gesture survives real touch dispatch, whether the browser hands the vertical
|
|
// axis to its own scroller first, and whether a swipe at the first day stays put
|
|
// are all engine behaviour. This drives CDP's Input.dispatchTouchEvent, so the
|
|
// events are the ones a finger produces — not synthesised TouchEvent objects.
|
|
//
|
|
// Chromium, because CDP is where real touch injection lives. The file-level
|
|
// device sets a phone viewport with a coarse pointer, which is what puts the
|
|
// mobile shell on screen at all.
|
|
test.use({ ...devices['Pixel 7'] })
|
|
|
|
/** One finger, dispatched through the browser's own input pipeline. */
|
|
async function swipe(page: Page, from: { x: number; y: number }, to: { x: number; y: number }, steps = 8) {
|
|
const cdp = await page.context().newCDPSession(page)
|
|
await cdp.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [{ x: from.x, y: from.y }] })
|
|
for (let i = 1; i <= steps; i++) {
|
|
await cdp.send('Input.dispatchTouchEvent', {
|
|
type: 'touchMove',
|
|
touchPoints: [{
|
|
x: from.x + ((to.x - from.x) * i) / steps,
|
|
y: from.y + ((to.y - from.y) * i) / steps,
|
|
}],
|
|
})
|
|
}
|
|
await cdp.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] })
|
|
await cdp.detach()
|
|
// The out leg is 150ms and the in leg 220ms.
|
|
await page.waitForTimeout(500)
|
|
}
|
|
|
|
const activeChip = (page: Page) => page.locator('button[aria-current="true"]')
|
|
|
|
test('#2051 phone: the day details area swipes between days', async ({ page }) => {
|
|
await page.goto('/dashboard')
|
|
await dismissSystemNotices(page)
|
|
|
|
// Without a phone viewport and a coarse pointer the mobile shell never mounts,
|
|
// and everything below would be proving nothing.
|
|
const env = await page.evaluate(() => ({
|
|
width: window.innerWidth,
|
|
coarse: window.matchMedia('(pointer: coarse)').matches,
|
|
}))
|
|
expect(env.width, 'Pixel sits below the 768px phone breakpoint').toBeLessThan(768)
|
|
expect(env.coarse, 'Pixel reports a coarse primary pointer').toBe(true)
|
|
|
|
const stamp = Date.now()
|
|
const created = await page.request.post('/api/trips', {
|
|
data: { title: `Day swipe ${stamp}`, start_date: '2099-06-01', end_date: '2099-06-05' },
|
|
})
|
|
expect(created.ok(), 'seed trip').toBeTruthy()
|
|
const tripId = (await created.json()).trip?.id ?? (await created.json()).id
|
|
|
|
await page.goto(`/trips/${tripId}`)
|
|
await expect(activeChip(page)).toBeVisible({ timeout: 30_000 })
|
|
|
|
const chips = page.locator('button[aria-current], button[aria-current="true"]')
|
|
const rail = activeChip(page).locator('xpath=..')
|
|
expect(await rail.locator('button').count(), 'the seeded trip has five days').toBe(5)
|
|
expect(await chips.count()).toBeGreaterThan(0)
|
|
|
|
// The swipe zone: below the chip rail, above the dock, across the middle.
|
|
const mid = Math.round(env.width / 2)
|
|
const y = 460
|
|
|
|
const day1 = (await activeChip(page).textContent())!.trim()
|
|
|
|
// 1. A swipe left steps forward.
|
|
await swipe(page, { x: mid + 90, y }, { x: mid - 90, y })
|
|
const day2 = (await activeChip(page).textContent())!.trim()
|
|
expect(day2, 'swiping left moved to the next day').not.toBe(day1)
|
|
|
|
// 2. A swipe right steps back to where it started.
|
|
await swipe(page, { x: mid - 90, y }, { x: mid + 90, y })
|
|
expect((await activeChip(page).textContent())!.trim()).toBe(day1)
|
|
|
|
// 3. The first day has nowhere to go back to — it must stay put, and the
|
|
// browser must not have been handed a page-back either.
|
|
await swipe(page, { x: mid - 90, y }, { x: mid + 90, y })
|
|
expect((await activeChip(page).textContent())!.trim()).toBe(day1)
|
|
expect(page.url()).toContain(`/trips/${tripId}`)
|
|
|
|
// 4. A vertical drag belongs to the card's own scroller, never to the day.
|
|
await swipe(page, { x: mid, y: y + 120 }, { x: mid, y: y - 120 })
|
|
expect((await activeChip(page).textContent())!.trim()).toBe(day1)
|
|
|
|
// 5. And the rail itself still works, so the gesture added a path rather than
|
|
// replacing one.
|
|
await rail.locator('button').nth(2).click()
|
|
await expect(activeChip(page)).not.toHaveText(day1)
|
|
})
|