An open page takes up a new version by itself after a deploy (#388)

The live stream already came back after a deploy and the lists caught
up, but the page kept running the script it had loaded, in a tab or the
home-screen app, until reloaded by hand. Back after a drop, the page now
asks for / again without the cache; another app.js named there is a new
version. With nothing to lose it reloads, and the item that was open,
carried across in sessionStorage whole since a read one is off the
Unread list, is opened again. While an episode plays, a dialog is open,
a search is set or a field is typed in, it offers a reload in a toast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 00:04:55 +00:00
parent e5388b443f
commit 742f76969e
4 changed files with 102 additions and 1 deletions

View File

@@ -629,6 +629,53 @@ test('marking an OPML subscription read covers the feeds inside it', async ({ pa
await expect(folder.locator('.badge')).toHaveText('0');
});
// A deploy of ipx-web: the page asks for itself again after the live stream comes back (#388).
// The new version is faked by answering / once with another app.js named in it.
async function newVersionOnce(page) {
await page.route(u => new URL(u).pathname === '/', async route => {
const r = await route.fetch();
await route.fulfill({ response: r, body: (await r.text()).replace(/\/app\.js\?v=[0-9a-f]+/, '/app.js?v=0000000000ff') });
}, { times: 1 });
}
test('a new version reloads the page and opens the item that was open, or offers a reload while a dialog is open', async ({ page }) => {
await page.locator('.feed', { hasText: 'Test Show' }).click();
await page.locator('.tabs button', { hasText: 'Unread' }).first().click();
const first = page.locator('.ep').first();
if (!(await first.count())) {
// Earlier tests read everything; one unread item is enough.
await page.locator('.tabs button', { hasText: 'All' }).first().click();
await page.locator('.ep').first().locator('[data-a="read"]').click();
await page.locator('.tabs button', { hasText: 'Unread' }).first().click();
}
await expect(first).toBeVisible({ timeout: 20_000 });
const title = await first.locator('.t').textContent();
await first.click();
await expect(page.locator('#detail .dt')).toHaveText(title);
// A dialog open: nothing is thrown away, a reload is offered.
await page.locator('#content .acts [data-a="settings"]').click();
await expect(page.locator('#modal')).toHaveClass(/\bon\b/);
await newVersionOnce(page);
await page.evaluate(() => checkVersion());
const offer = page.locator('.toast', { hasText: 'A new version of iPX is ready' });
await expect(offer).toBeVisible();
await page.evaluate(() => closeModal());
// Idle: it reloads by itself, and the item read, now off the Unread list, is open again.
await newVersionOnce(page);
const reloaded = page.waitForEvent('load');
await page.evaluate(() => checkVersion());
await reloaded;
await expect(page.locator('#detail .dt')).toHaveText(title, { timeout: 20_000 });
await expect(page.locator('.toast', { hasText: 'A new version' })).toHaveCount(0);
// The same version again: nothing happens.
await page.evaluate(() => checkVersion());
await page.waitForTimeout(500);
await expect(page.locator('#detail .dt')).toHaveText(title);
});
test.describe('on a phone', () => {
test.use({ viewport: { width: 390, height: 844 } });