Files
hermes-webui/tests/test_workspace_tree_rename.py
T
nesquena-hermes b5e8e67d71 fix(workspace): preserve single-click open + double-click rename on filename (#1707)
Closes #1707 — single-click on a workspace tree filename did nothing.

#1698 was a regression where the filename's dblclick rename handler was
unreachable because the row's el.onclick (openFile) fired synchronously
on the first click. The fix in #1702 stopped click propagation on nameEl
— but that broke single-click activation entirely (#1707): clicking the
filename now did nothing, you had to click the icon or row whitespace
to open the file.

Restored fix preserves both intents via a 300ms debounced delegator:

  let _nameClickTimer = null;
  nameEl.onclick = (e) => {
    e.stopPropagation();
    if (_nameClickTimer) { clearTimeout(_nameClickTimer); _nameClickTimer = null; }
    _nameClickTimer = setTimeout(() => {
      _nameClickTimer = null;
      if (typeof el.onclick === 'function') el.onclick(e);
    }, 300);
  };
  nameEl.ondblclick = (e) => {
    e.stopPropagation();
    if (_nameClickTimer) { clearTimeout(_nameClickTimer); _nameClickTimer = null; }
    // ... existing rename body
  };

Single-click on nameEl schedules a setTimeout that calls el.onclick(e)
after the dblclick threshold passes (300ms — matches the OS dblclick
threshold on most platforms). Double-click cancels the pending timer
and triggers the existing rename input.

Cost: 300ms latency on file-open clicks. Acceptable trade for keeping
rename reachable on single-click.

Also updated tests/test_workspace_tree_rename.py to accept both the
pre-#1707 (pure stopPropagation) and post-#1707 (debounced delegator)
shapes — the original assertion was too narrow and would have rejected
the correct fix.

9 new regression tests in tests/test_1707_workspace_filename_click.py:
  - 6 source-level static-analysis checks on the patched handler shape
  - 3 behavioral tests via Node VM (synthesize click → 300ms delay,
    click → dblclick within tick → assert rename mounts + openFile
    is not called).

7 of 9 tests fail on master pre-fix (verified); all 9 pass after.
2026-05-05 16:13:58 +00:00

38 lines
1.8 KiB
Python

from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8")
def test_workspace_file_name_click_does_not_immediately_bubble():
"""Clicking a file name must not synchronously bubble to the row open handler
before dblclick can fire. The fix originally landed as pure stopPropagation
(#1698), then evolved to a 300ms debounce that delegates to el.onclick (#1707
— the pure-stopPropagation form broke single-click activation entirely).
Either shape satisfies the #1698 invariant. Accept both:
- pre-#1707 shape: `nameEl.onclick=(e)=>e.stopPropagation();`
- post-#1707 shape: any `nameEl.onclick=(e)=>{...stopPropagation()...setTimeout...}`
"""
name_start = UI_JS.index("const nameEl=document.createElement('span');")
dblclick_idx = UI_JS.index("nameEl.ondblclick=(e)=>", name_start)
block = UI_JS[name_start:dblclick_idx]
assert "nameEl.onclick" in block, (
"workspace file-tree name span must bind nameEl.onclick to prevent the "
"first click of a dblclick from triggering the row's openFile (#1698)"
)
# The bound handler must call stopPropagation (either the original simple form
# or the post-#1707 debounce form that contains stopPropagation in its body).
assert "stopPropagation" in block, (
"nameEl.onclick must call stopPropagation so the row's el.onclick does not "
"fire on the first click of a dblclick (#1698)"
)
def test_workspace_file_row_click_still_opens_file_preview():
"""The row-level openFile binding must still exist — the nameEl handler delegates
to it (post-#1707) or sits beneath it as a pure barrier (pre-#1707)."""
assert "el.onclick=async()=>openFile(item.path);" in UI_JS