diff --git a/l4d2web/tests/e2e/test_files_overlay.py b/l4d2web/tests/e2e/test_files_overlay.py index e9752df..a1ee693 100644 --- a/l4d2web/tests/e2e/test_files_overlay.py +++ b/l4d2web/tests/e2e/test_files_overlay.py @@ -14,6 +14,8 @@ docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md. """ from __future__ import annotations +import urllib.parse + import pytest from playwright.sync_api import Page, expect @@ -383,3 +385,30 @@ def test_filename_rename_on_save(page: Page, files_overlay_server) -> None: expect( page.locator('.file-tree-row-file[data-target-path="server.cfg"]') ).to_have_count(0) + + +def test_share_url_deep_link_reopens_editor(page: Page, files_overlay_server) -> None: + """Navigate directly to /overlays/?modal= + and assert the routed editor auto-opens for the right file. This + is the central guarantee of the URL-addressable modals spec — copy + the current URL, paste it in a new tab (or share it), and the + modal reopens. Without this test, a regression in modals.js's + DOMContentLoaded bootstrap (the `URLSearchParams.get("modal") → + fetchAndShowRouted` chain) would silently make every shared link + dead. + + The `?modal=` param holds the UNENCODED routed path; URLSearchParams + decodes it for us. We pass the URL-encoded version through quote() + so the surrounding URL stays valid. + """ + base = files_overlay_server["base_url"] + overlay_id = files_overlay_server["overlay_id"] + + login(page, base) + modal_path = f"/overlays/{overlay_id}/files/edit?path=server.cfg" + page.goto( + f"{base}/overlays/{overlay_id}?modal={urllib.parse.quote(modal_path)}" + ) + + _wait_for_routed_editor(page) + assert page.locator('textarea[data-rel-path="server.cfg"]').count() == 1