Files
odysseus/tests/test_editor_clipboard_paste_static.py
T

61 lines
3.2 KiB
Python

from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "static/js/editor/clipboard-and-drop.js"
def test_editor_paste_owns_clipboard_images_while_editor_open():
"""Pasting an image with the editor open must not fall through to chat.
The editor wires a window-capture ``paste`` listener. The global chat
attach listener in ``static/app.js`` also listens on ``window`` (bubble)
and stages any ``item.kind === 'file'`` clipboard entry into the chatbox.
If the editor's listener bailed whenever the event target was *any*
input/textarea/``[role=dialog]``, then a paste whose focus sat on the chat
composer behind the editor modal went to the chatbox and never reached the
canvas. The guard must only defer to editable fields that live INSIDE the
editor container — and it must NOT include ``[role=dialog]``, because the
editor itself sits inside the gallery modal's ``role=dialog`` content.
"""
src = SRC.read_text(encoding="utf-8")
assert "wireClipboardAndDrop" in src
# The bail-out must be scoped to targets inside the editor.
assert "const insideEditor = target && container && container.contains(target);" in src
assert (
"if (insideEditor && (target.isContentEditable || "
"target.closest?.('input, textarea, select'))) return;"
) in src
# ``[role="dialog"]`` must NOT appear in the paste guard (the editor lives
# inside the gallery modal's role=dialog modal-content).
assert "target.closest?.('input, textarea, select, [role=\"dialog\"]')" not in src
# Capture-phase interception must preventDefault + stop the chat listener.
assert "e.stopImmediatePropagation();" in src
assert "e.preventDefault();" in src
def test_editor_ctrl_v_pastes_the_internal_selection_clipboard():
"""Ctrl/Cmd+V must not shadow a newer OS-clipboard image.
Intercepting the keystroke (preventDefault) bypasses the browser's paste
event and always re-pasted ``state.internalClipboard``, so a screenshot
taken outside the gallery could never be pasted. The key handler may only
fall back to the internal copy when the paste event delivered no image
(insecure origin: copy never reaches the OS clipboard).
"""
src = SRC.read_text(encoding="utf-8")
# Shared, hoisted paste helper (also used by the native paste listener).
assert "const pasteAsLayer = (imgSource, label, offset = { x: 0, y: 0 }) => {" in src
# The paste listener must prefer real clipboard images.
assert "const imageItem = Array.from(e.clipboardData?.items || [])" in src
# Deferred keyboard fallback for the internal clipboard.
assert "window.addEventListener('keydown', (e) => {" in src
assert "if (String(e.key || '').toLowerCase() !== 'v') return;" in src
assert "pasteKeyPending = true;" in src
assert "if (!pasteKeyPending) return;" in src
assert "pasteAsLayer(state.internalClipboard, 'Pasted Selection'" in src
# The key handler must NOT swallow the keystroke any more.
keydown = src[src.index("// ── Ctrl/Cmd+V ──"):src.index("// ── Drag-and-drop ──")]
assert "e.preventDefault();" not in keydown
assert "e.stopImmediatePropagation();" not in keydown