Touch gestures: pull to check for new items, swipe between items
- Pull the item list down from its top: checks the feed (or every feed, on All Subscriptions) for new items, which arrive as they do from the scan button. overscroll-behavior keeps the browser's own pull-to-reload out of it. - Swipe the item you are reading left for the next, right for the one before, or back to the list from the first. A vertical move is a scroll; something that scrolls sideways, or takes typing, keeps its own swipe. Touch events only, so a mouse never sets them off. Closes #22. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1129,3 +1129,51 @@ test('a feed error is marked in the same column as the folder triangles', async
|
||||
expect(new Set(xs).size, JSON.stringify(xs)).toBe(1);
|
||||
await page.reload(); // put the real list back
|
||||
});
|
||||
|
||||
test.describe('touch gestures on a phone', () => {
|
||||
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true });
|
||||
|
||||
// Playwright's touchscreen only taps; a drag goes through the DevTools protocol.
|
||||
async function drag(page, from, to) {
|
||||
const cdp = await page.context().newCDPSession(page);
|
||||
const steps = 8;
|
||||
await cdp.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [from] });
|
||||
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: [] });
|
||||
}
|
||||
|
||||
test('a swipe moves between items, and right from the first goes back to the list', async ({ page }) => {
|
||||
await page.locator('#burger').click();
|
||||
await page.locator('.feed', { hasText: 'Test Show' }).click();
|
||||
await page.locator('.tabs button', { hasText: 'All' }).first().click();
|
||||
await expect(page.locator('.ep').nth(1)).toBeVisible({ timeout: 20_000 });
|
||||
const titles = await page.locator('.ep .t').allTextContents();
|
||||
await page.locator('.ep').first().click();
|
||||
const shown = page.locator('#detail .dt');
|
||||
await expect(shown).toHaveText(titles[0]);
|
||||
|
||||
await drag(page, { x: 300, y: 400 }, { x: 80, y: 410 }); // left: the next item
|
||||
await expect(shown).toHaveText(titles[1]);
|
||||
await drag(page, { x: 300, y: 400 }, { x: 80, y: 410 }); // left on the last: stays
|
||||
await expect(shown).toHaveText(titles[1]);
|
||||
await drag(page, { x: 80, y: 400 }, { x: 300, y: 410 }); // right: the one before
|
||||
await expect(shown).toHaveText(titles[0]);
|
||||
await drag(page, { x: 200, y: 300 }, { x: 210, y: 600 }); // down: a scroll, not a swipe
|
||||
await expect(shown).toHaveText(titles[0]);
|
||||
await drag(page, { x: 80, y: 400 }, { x: 300, y: 410 }); // right on the first: the list
|
||||
await expect(page.locator('body')).not.toHaveClass(/reading/);
|
||||
});
|
||||
|
||||
test('pulling the list down from its top checks the feed for new items', async ({ page }) => {
|
||||
await page.locator('#burger').click();
|
||||
await page.locator('.feed', { hasText: 'Test Show' }).click();
|
||||
await expect(page.locator('.ep').first()).toBeVisible({ timeout: 20_000 });
|
||||
const box = await page.locator('#list').boundingBox();
|
||||
const fetch = page.waitForRequest(r => r.url().endsWith('/api/fetch') && r.method() === 'POST');
|
||||
await drag(page, { x: 200, y: box.y + 20 }, { x: 200, y: box.y + 220 });
|
||||
expect((await fetch).postDataJSON()).toEqual({ feed: 'test-show', force: true });
|
||||
await expect(page.locator('#pulltip')).toHaveCount(0); // the note goes on letting go
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user