mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
960520c6d9
The iPad case could no longer fail on its own regression: a draggable row behind the long-press bridge is what tablets get by design since #1616, and nothing in the case swiped. What turned swipes into drags in #1432 was the drag-drop-touch polyfill, so the case now asserts that a tablet never loads it. Two help-media guides waited for "1 files" and "2 flight(s) imported", which now read "1 file" and "2 flights imported".
96 lines
5.2 KiB
TypeScript
96 lines
5.2 KiB
TypeScript
import { test, expect, devices } from '@playwright/test'
|
|
import { dismissSystemNotices } from './helpers'
|
|
|
|
// Tablet regression guard for #1432 — the places list must scroll under a touch swipe.
|
|
//
|
|
// A tablet is a coarse-pointer device at a *desktop* viewport width, so the width-based
|
|
// "is this mobile" check that 3.2.1 shipped left `draggable` armed on iPad: the swipe
|
|
// became an HTML5 drag and raised the drop-to-import overlay instead of scrolling. What
|
|
// turned the swipe into a drag was the drag-drop-touch polyfill, confined to hybrid
|
|
// laptops since. #1616 then gave tablets their drag back on purpose, through the
|
|
// long-press bridge ([data-touch-drag]): a row may be draggable, but only a press that
|
|
// is held becomes a drag, so a swipe still scrolls. Only a real device context proves
|
|
// that; a jsdom unit test cannot express "coarse pointer at 834px".
|
|
//
|
|
// Needs WebKit (`npx playwright install webkit`, plus libmanette-0.2-0 and libwoff1 on
|
|
// Debian/Ubuntu). WebKit is the right engine here, not a nicety: every browser on iPadOS
|
|
// is WebKit underneath, which is why the reporter saw this in all three they tried.
|
|
test.use({ ...devices['iPad Pro 11'] })
|
|
|
|
test('#1432 iPad: the places list scrolls under a swipe', async ({ page }) => {
|
|
await page.goto('/dashboard')
|
|
|
|
await dismissSystemNotices(page)
|
|
|
|
await page.locator('.add-trip-card').click()
|
|
const createBtn = page.getByRole('button', { name: 'Create New Trip' })
|
|
await expect(createBtn).toBeVisible()
|
|
const title = `iPad 1432 ${Date.now()}`
|
|
await page.getByPlaceholder('e.g. Summer in Japan').fill(title)
|
|
await createBtn.click()
|
|
|
|
await page.getByText(title).first().click()
|
|
await expect(page).toHaveURL(/\/trips\/\d+/)
|
|
await expect(page.locator('.leaflet-container')).toBeVisible({ timeout: 20_000 })
|
|
|
|
const tripId = page.url().match(/\/trips\/(\d+)/)![1]
|
|
|
|
// Seed enough places for the list to overflow and actually need scrolling.
|
|
for (let i = 1; i <= 25; i++) {
|
|
const res = await page.request.post(`/api/trips/${tripId}/places`, {
|
|
data: { name: `Place ${i}`, lat: 48.85 + i * 0.01, lng: 2.35 + i * 0.01 },
|
|
})
|
|
expect(res.ok(), `seed place ${i}`).toBeTruthy()
|
|
}
|
|
await page.reload()
|
|
await expect(page.locator('.leaflet-container')).toBeVisible({ timeout: 20_000 })
|
|
|
|
// An iPad is inside the 768-1023px band, where only one side panel is open at a
|
|
// time so the map keeps a usable width (#2247). The places list is one tap away.
|
|
// Located by the tab's own aria-label, not by role+name: the tab bar carries a
|
|
// "Plan" button of its own.
|
|
await page.locator('button[aria-label="Places"]').click()
|
|
await expect(page.getByText('Place 1').first()).toBeVisible({ timeout: 20_000 })
|
|
|
|
// The context must really be the one from the bug report: coarse pointer, desktop
|
|
// width. If either is wrong, everything below proves nothing.
|
|
const env = await page.evaluate(() => ({
|
|
coarse: window.matchMedia('(pointer: coarse)').matches,
|
|
width: window.innerWidth,
|
|
}))
|
|
expect(env.coarse, 'iPad reports a coarse primary pointer').toBe(true)
|
|
expect(env.width, 'iPad sits above the 768px "mobile" breakpoint').toBeGreaterThanOrEqual(768)
|
|
|
|
// 1. A swipe must not become a drag. What turned it into one in #1432 was the
|
|
// drag-drop-touch polyfill; it belongs to hybrid laptops only now
|
|
// (utils/touchDragPolyfill.ts), so a tablet must never load it. A row may be
|
|
// draggable, but only behind the long-press bridge (#1616).
|
|
const polyfillLoaded = await page.evaluate(() =>
|
|
performance.getEntriesByType('resource').some(e => /drag-?drop-?touch/i.test(e.name)))
|
|
expect(polyfillLoaded, 'the drag-drop-touch polyfill stays off a tablet').toBe(false)
|
|
const row = page.locator('div[draggable]').filter({ hasText: 'Place 1' }).first()
|
|
await expect(row).toBeVisible()
|
|
const swipeSafe = await row.evaluate(el => el.getAttribute('draggable') !== 'true' || !!el.closest('[data-touch-drag]'))
|
|
expect(swipeSafe, 'a draggable row sits behind the long-press bridge').toBe(true)
|
|
|
|
// 2. The list must scroll, and no drop-to-import overlay may appear.
|
|
// The list's scroll container carries trek-stagger among its other classes. Taken
|
|
// from the row above: the first draggable on the page can be another panel's.
|
|
const scroller = row.locator('xpath=ancestor::div[contains(concat(" ", normalize-space(@class), " "), " trek-stagger ")]')
|
|
const before = await scroller.evaluate(el => el.scrollTop)
|
|
const box = (await scroller.boundingBox())!
|
|
await page.touchscreen.tap(box.x + box.width / 2, box.y + 40)
|
|
await scroller.evaluate(el => el.scrollBy(0, 200))
|
|
const after = await scroller.evaluate(el => el.scrollTop)
|
|
expect(after, 'places list scrolled').toBeGreaterThan(before)
|
|
await expect(page.getByText('Drop to import')).toHaveCount(0)
|
|
|
|
// 3. The iPad must still get the desktop shell — isMobile stayed width-based. One
|
|
// panel at a time (#2247), so opening Places closed the day plan and left its
|
|
// tab as the way back.
|
|
await expect(page.locator('.leaflet-container')).toBeVisible()
|
|
// The top tab bar has a button named Plan as well; the way back is the panel's
|
|
// own tab, which is accented while its panel is closed.
|
|
await expect(page.locator('button[aria-label="Plan"].bg-accent')).toBeVisible()
|
|
})
|