Files
hermes-webui/tests/test_issue5533_preview_copy_file_path.py
T
nesquena-hermesandRod Boev eef981d3dc release: copyable workspace relative paths (#5548)
Adds 'Copy relative path' to the workspace file-tree context menu + preview header. Folded in gate fixes: preview-header button folds to icon-only on a narrow pane via @container rightpanel (not viewport @media), with data-i18n-title/aria-label so the tooltip+accessible name localize. Full gate: Codex SAFE, Fable SHIP-UX, suite 12147/0, browser clean.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-05 22:26:31 +00:00

123 lines
5.0 KiB
Python

from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
INDEX = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
WORKSPACE_JS = (ROOT / "static" / "workspace.js").read_text(encoding="utf-8")
UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static" / "style.css").read_text(encoding="utf-8")
def _function_body(src: str, name: str) -> str:
start = src.index(f"function {name}(")
brace = src.index("{", start)
depth = 0
in_string = ""
escape = False
for idx in range(brace, len(src)):
ch = src[idx]
if in_string:
if escape:
escape = False
elif ch == "\\":
escape = True
elif ch == in_string:
in_string = ""
continue
if ch in "'\"`":
in_string = ch
continue
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return src[start : idx + 1]
raise AssertionError(f"{name} body did not close")
def _compact(src: str) -> str:
return "".join(src.split())
def test_preview_toolbar_has_copy_relative_path_button():
assert 'id="btnCopyPreviewRelPath"' in INDEX
assert 'onclick="copyPreviewRelativePath()"' in INDEX
assert 'data-i18n="copy_relative_path"' in INDEX
assert "Copy relative path" in INDEX
def test_preview_copy_relative_path_uses_current_preview_path():
body = _function_body(WORKSPACE_JS, "copyPreviewRelativePath")
compact = _compact(body)
assert "_previewCurrentPath" in body
assert "_normalizeWorkspaceRelPath(_previewCurrentPath)" in body
assert "api('/api/file/path'" not in body
assert "constrel=_normalizeWorkspaceRelPath(_previewCurrentPath)||_previewCurrentPath" in compact
def test_preview_copy_relative_path_disables_button_while_request_is_in_flight():
body = _function_body(WORKSPACE_JS, "copyPreviewRelativePath")
compact = _compact(body)
guard = "if(btn&&btn.disabled)return;"
disable = "if(btn)btn.disabled=true;"
enable = "finally{if(btn)btn.disabled=false;}"
assert "$('btnCopyPreviewRelPath')" in body
assert guard in compact
assert disable in compact
assert enable in compact
assert compact.index(guard) < compact.index(disable)
assert compact.index(disable) < compact.index("_normalizeWorkspaceRelPath")
def test_preview_copy_relative_path_reuses_clipboard_fallback_and_toasts():
body = _function_body(WORKSPACE_JS, "copyPreviewRelativePath")
assert "typeof _copyTextWithFallback==='function'" in body
assert "_copyTextWithFallback(rel,t('path_copied'),t('path_copy_failed'))" in body
assert "navigator.clipboard.writeText(rel)" in body
assert "document.execCommand('copy')" in body
assert "t('path_copied')" in body
assert "t('path_copy_failed')" in body
def test_tree_context_menu_keeps_absolute_copy_and_adds_relative_copy():
assert "copyPathItem.textContent=t('copy_file_path')" in UI_JS
assert "copyRelPathItem.textContent=t('copy_relative_path')" in UI_JS
assert "const rel=_normalizeWorkspaceRelPath(item.path)||item.path" in UI_JS
assert "_copyTextWithFallback(rel,t('path_copied'),t('path_copy_failed'))" in UI_JS
def test_preview_toolbar_keeps_copy_button_from_shrinking_path_layout():
assert ".preview-path #btnCopyPreviewRelPath" in STYLE
selector_start = STYLE.index(".preview-path #btnCopyPreviewRelPath")
selector_block = STYLE[selector_start : STYLE.index("}", selector_start) + 1]
assert "flex-shrink:0" in selector_block
assert "white-space:nowrap" in selector_block
def test_preview_copy_button_is_accessible_and_icon_only_on_narrow_pane():
"""The preview-header copy button must stay accessible when its text label is
hidden on a narrow pane (#5548 icon-only fold-in): it carries an aria-label,
its label span is class-tagged, and a narrow-width media query hides that label.
"""
import re
# The button carries an explicit aria-label (screen-reader name survives label-hide).
assert 'id="btnCopyPreviewRelPath"' in INDEX
btn = INDEX[INDEX.index('id="btnCopyPreviewRelPath"'):]
btn = btn[: btn.index("</button>")]
assert 'aria-label="Copy relative path"' in btn
assert 'class="preview-btn-label"' in btn
# Localized tooltip + accessible name (WCAG 2.5.3): the icon-only state must not
# leave a Russian/German user with an English tooltip/screen-reader name.
assert 'data-i18n-title="copy_relative_path"' in btn
assert 'data-i18n-aria-label="copy_relative_path"' in btn
# A narrow-PANE container query (right panel, not viewport) hides the label
# (icon-only), keeping the glyph — so it fires on pane resize even on desktop.
assert re.search(
r"@container\s+rightpanel[^{]*max-width:\s*520px[^{]*\{[^}]*"
r"\.preview-path\s+#btnCopyPreviewRelPath\s+\.preview-btn-label\s*\{\s*display:\s*none",
STYLE,
), "expected a @container rightpanel query hiding the copy-button label on a narrow pane"