From d92f71f691b6e4011e028f5df54609dcdf24f623 Mon Sep 17 00:00:00 2001 From: mwiegand Date: Sun, 17 May 2026 18:39:51 +0200 Subject: [PATCH] test(files): cover routed new-file flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clicks `+ new file` at the overlay root, fills the routed editor's filename + CM6 content, and clicks Create. Asserts the modal closes, the file lands on disk, AND the new row appears in the live tree after the debounced HTMX refresh — the last assertion catches the class of bug where /files/save persists but scheduleRefresh(parentOf(fullPath)) never lands a fresh listing. The new-file modal reuses overlay_file_editor.html with is_new=True; this test exercises the branch in routedSaveClicked that composes fullPath from filename + data-at-folder, distinct from the rename-on-save path the edit-mode test covers. Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- l4d2web/tests/e2e/test_files_overlay.py | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/l4d2web/tests/e2e/test_files_overlay.py b/l4d2web/tests/e2e/test_files_overlay.py index adb538a..e1c9b54 100644 --- a/l4d2web/tests/e2e/test_files_overlay.py +++ b/l4d2web/tests/e2e/test_files_overlay.py @@ -73,3 +73,46 @@ def test_edit_text_file_save_round_trip(page: Page, files_overlay_server) -> Non # Modal close is async (await postJson then closeRouted()). expect(page.locator("#modal-container")).to_be_hidden(timeout=5000) assert (overlay_root / "server.cfg").read_text() == new_content + + +def test_create_new_file_routed(page: Page, files_overlay_server) -> None: + """Click `+ new file` at the overlay root, fill the routed + new-file editor, click Create, and assert the file lands on disk + AND the row appears in the tree. + + The routed new-file modal is the SAME template as the edit modal + (overlay_file_editor.html with is_new=True). The differences this + test exercises: + * textarea has data-rel-path="" — routedSaveClicked branches on + the empty value into the "compose fullPath from filename" + path, not the rename-on-save path. + * Save button reads "Create", not "Save". + * scheduleRefresh(parentOf(fullPath)) fires after POST — for a + root-level file that's refreshFolder("") which re-fetches the + whole root listing; the new row should appear without a page + reload. + """ + base = files_overlay_server["base_url"] + overlay_id = files_overlay_server["overlay_id"] + overlay_root = files_overlay_server["overlay_root"] + _open_overlay(page, base, overlay_id) + + page.click('button[data-action="new-file"][data-target-path=""]') + _wait_for_routed_editor(page) + # Confirm the fragment opened in is_new mode (empty rel-path). + assert page.locator('textarea[data-rel-path=""]').count() == 1 + + new_filename = "new-file.cfg" + new_content = 'sv_cheats 1\nmp_gamemode versus\n' + page.fill('input[data-editor-filename]', new_filename) + page.evaluate("(text) => window.__filesEditor.setContent(text)", new_content) + + page.click(".files-editor-save") + + expect(page.locator("#modal-container")).to_be_hidden(timeout=5000) + assert (overlay_root / new_filename).read_text() == new_content + # Tree refresh is debounced 50ms then HTMX-fetched — wait for the + # new row to appear rather than asserting synchronously. + expect( + page.locator(f'.file-tree-row-file[data-target-path="{new_filename}"]') + ).to_be_visible(timeout=5000)