// Regression for the boosted-navigation link-rebinding bug (reported on blog.rose-ash.com): // home --boosted nav--> a post --click "edit"--> lands on /tags (a HOME footer link), // not //edit. After a boost swap, the swapped-in links carry a STALE binding from // the previous page. Run by run-boost-nav-check.sh against an ephemeral host server // (serve.sh seeds /compose-demo + the home footer's /tags link). const { test, expect } = require('playwright/test'); const BASE = process.env.SX_TEST_URL || 'http://127.0.0.1:8914'; const USER = process.env.SX_ADMIN_USER || 'admin'; const PASS = process.env.SX_ADMIN_PASSWORD || 'letmein'; async function waitReady(page) { await expect(page.locator('html[data-sx-ready="true"]')).toHaveCount(1, { timeout: 45000 }); } async function login(page) { await page.goto(BASE + '/login'); await page.fill('input[name="username"]', USER); await page.fill('input[name="password"]', PASS); await page.click('button[type="submit"]'); await page.waitForURL((u) => !u.pathname.startsWith('/login')); } test.describe('boosted navigation (browser-only)', () => { test('a post link clicked AFTER a boosted nav navigates to the right target (not a stale home link)', async ({ page }) => { test.setTimeout(90000); // 1) load HOME (its footer has a /tags link — the stale target the bug lands on) await page.goto(BASE + '/'); await waitReady(page); await expect(page.locator('a[href="/tags"]')).toHaveCount(1); // home has the /tags link // 2) boosted nav HOME -> the composed post (no full reload) await page.locator('a[href="/compose-demo/"]').first().click(); await expect(page.locator('body')).toContainText('composition object', { timeout: 15000 }); expect(page.url()).toContain('/compose-demo/'); // 3) click the post's "edit" link — brought in by the swap await expect(page.locator('a[href="/compose-demo/edit"]')).toHaveCount(1); await page.locator('a[href="/compose-demo/edit"]').click(); await page.waitForTimeout(3000); // 4) it MUST navigate to the edit route (guarded -> the login view is fine, the URL is // pushed to /compose-demo/edit), and MUST NOT land on the stale /tags link. expect(page.url()).not.toContain('/tags'); expect(page.url()).toContain('/compose-demo/edit'); }); test('a guarded route reached via boost does a clean full-nav to /login (no clobbered SPA), and Home works from there', async ({ page }) => { test.setTimeout(90000); await page.goto(BASE + '/'); await waitReady(page); // boosted nav home -> post await page.locator('a[href="/compose-demo/"]').first().click(); await expect(page.locator('body')).toContainText('composition object', { timeout: 15000 }); // click "edit" (guarded, logged out). A 303 would be followed by the fetch WITHOUT the // SX-Request header -> /login returns the full shell, which morphed into #content // DESTROYS the swap target (then nothing navigates). The fix returns SX-Redirect, so the // engine does a FULL navigation to a real /login page. await page.locator('a[href="/compose-demo/edit"]').click(); await page.waitForTimeout(3500); expect(new URL(page.url()).pathname).toBe('/login'); await expect(page.locator('body')).toContainText('Log in'); // and the login page offers a way back Home that works (the reported "Home does nothing"). await page.locator('a[href="/"]').first().click(); await page.waitForTimeout(3000); await expect(page.locator('body')).toContainText('Posts', { timeout: 12000 }); expect(new URL(page.url()).pathname).toBe('/'); }); test('LOGGED IN: the Home nav works after a boosted nav to the edit page', async ({ page }) => { test.setTimeout(90000); await login(page); // authed session await page.goto(BASE + '/'); await waitReady(page); // boosted nav home -> post -> edit (authed, so the real edit form swaps into #content) await page.locator('a[href="/compose-demo/"]').first().click(); await expect(page.locator('body')).toContainText('composition object', { timeout: 15000 }); // the footer "edit" link (there's also a "no relations — add some" link to edit when authed) await page.locator('a[href="/compose-demo/edit"]').last().click(); await expect(page.locator('body')).toContainText('Edit:', { timeout: 15000 }); expect(new URL(page.url()).pathname).toBe('/compose-demo/edit'); // #content must SURVIVE the edit swap (an outerHTML swap would replace it, then no later // nav can find a swap target — the reported "Home does nothing"). expect(await page.locator('#content').count()).toBe(1); // the persistent top-nav Home link must still work on the edit page. await page.locator('nav a[href="/"]').first().click(); await page.waitForTimeout(3000); await expect(page.locator('body')).toContainText('Posts', { timeout: 12000 }); expect(new URL(page.url()).pathname).toBe('/'); }); });