penpot/frontend/test/frontend_tests/ui/comment_input_ime_test.cljs
makesomethingshit d7527b63a2
🐛 Fix Japanese IME Enter duplication in comment input (#11768)
* 🐛 Fix Japanese IME Enter duplication in comment input

Comment keydown handler treated every Enter as a Penpot
line-break action, so confirming an IME composition
duplicated the text with an extra newline and a
zero-width space. Guard the whole custom keydown
processing while the event belongs to an active IME
composition, mirroring the v3 text-editor precedent.

Closes #11757

Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>
AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Keep composing Escape from closing comment thread

The parent floating-thread keydown handler closed the
thread on every Escape, including one that cancels an
active IME composition. Apply the same composition
guard so composing Escape stays owned by the IME while
plain Escape still closes the thread.

Closes #11757

Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>
AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Test comment IME guard through key-action resolver

The composition predicate test only verified the
predicate itself, so a guard moved to the wrong place
or a handler bypassing it would stay green. Resolve
comment and thread keydowns through a pure
resolve-comment-key-action seam and verify the
observable behavior: composing keys yield :ime-owned
with zero Penpot side effects while the same plain
keys keep their existing commands.

Closes #11757

Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>
AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Test comment IME handlers through direct calls

The key-action resolver only verified a return value,
so handler wiring regressions would stay green, and it
read the mention snapshot before handle-select ran,
changing the existing ordering. Remove the resolver,
extract the two handler bodies as directly callable
fns with the original select-first ordering, and
assert the fired side effects instead.

Closes #11757

Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>
AI-assisted-by: muse-spark-1.3-contributor

* 📚 Remove text-editor v3 references from comment IME docs

The comment IME guard is specific to the comment editor, so the
docstrings no longer present it as following a v3 text-editor or
render-engine precedent. Reviewers read that wording as tying this
comment bug fix to unrelated subsystems.

Only docstring text changes; handler logic and test assertions are
untouched.

Closes #11757

AI-assisted-by: deepseek-v4.1-flash
Signed-off-by: makesomethingshit <junsoo1172@gmail.com>

* 🐛 Review comments, and fix edge case

---------

Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>
Signed-off-by: makesomethingshit <junsoo1172@gmail.com>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-09-23 15:16:17 +02:00

130 lines
5.4 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns frontend-tests.ui.comment-input-ime-test
"Keydown handling of the comment input and floating thread: keys owned
by an IME composition run nothing, all other keys keep their
commands."
(:require
[app.main.ui.comments :as cmt]
[beicon.v2.core :as rx]
[cljs.test :as t :include-macros true]))
(defn- keydown
"Fake keyboard event. preventDefault/stopPropagation append to `log`."
[key {:keys [composing? key-code mod? log]}]
#js {:key key
:keyCode (or key-code 0)
:ctrlKey (boolean mod?)
:metaKey (boolean mod?)
:preventDefault #(some-> log (swap! conj :prevent-default))
:stopPropagation #(some-> log (swap! conj :stop-propagation))
:nativeEvent #js {:isComposing (boolean composing?)}})
(def ^:private composition-modes
{"isComposing" {:composing? true}
"keyCode 229" {:key-code 229}})
(def ^:private input-keys
["Enter" "Escape" "ArrowDown" "ArrowUp" "Backspace"])
(defn- run-input!
"Runs the comment-input handler for `key` and returns the side-effect
log. Mention-panel commands are observed on a real subject and the
open mention lives in an atom, as in the component."
[key {:keys [event-opts mention select-sets-mention node span on-esc? on-ctrl-enter?]
:or {node :node span [:span 3] on-esc? true on-ctrl-enter? true}}]
(let [log (atom [])
cur-mention (atom mention)
mentions-s (rx/subject)
sub (rx/sub! mentions-s #(swap! log conj [:mention (:type %)]))
event (keydown key (assoc event-opts :log log))]
(cmt/handle-comment-input-key-down
event
{:node node
:cur-mention cur-mention
:mentions-s mentions-s
:on-select (fn [e]
(t/is (identical? event e))
(swap! log conj :select)
(some->> select-sets-mention (reset! cur-mention)))
:get-span (fn [n]
(t/is (= node n))
span)
:on-esc (when on-esc? #(swap! log conj :on-esc))
:on-ctrl-enter (when on-ctrl-enter? #(swap! log conj :on-ctrl-enter))
:on-newline #(swap! log conj [:newline (dissoc % :event)])
:on-backspace #(swap! log conj [:backspace (dissoc % :event)])})
(rx/dispose! sub)
@log))
(defn- run-thread!
[key event-opts]
(let [log (atom [])]
(cmt/handle-thread-key-down (keydown key event-opts)
#(swap! log conj :close))
@log))
(def ^:private ctx {:node :node :span-node :span :offset 3})
(t/deftest composing-keys-run-nothing
(doseq [[mode opts] composition-modes
key input-keys
mention [nil "@bob"]]
(t/testing (str key " via " mode ", mention " (pr-str mention))
(t/is (= [] (run-input! key {:event-opts opts :mention mention}))))))
(t/deftest plain-keys-keep-their-commands
(t/testing "Enter inserts a line break at the caret span"
(t/is (= [:select [:newline ctx]] (run-input! "Enter" {}))))
(t/testing "mod+Enter submits"
(t/is (= [:select :on-ctrl-enter]
(run-input! "Enter" {:event-opts {:mod? true}}))))
(t/testing "mod+Enter without on-ctrl-enter falls back to a line break"
(t/is (= [:select [:newline ctx]]
(run-input! "Enter" {:event-opts {:mod? true} :on-ctrl-enter? false}))))
(t/testing "Escape calls on-esc"
(t/is (= [:select :on-esc] (run-input! "Escape" {}))))
(t/testing "Escape without on-esc does nothing else"
(t/is (= [:select] (run-input! "Escape" {:on-esc? false}))))
(t/testing "Backspace runs the mention-deletion check"
(t/is (= [:select [:backspace ctx]] (run-input! "Backspace" {}))))
(t/testing "other keys only sync the selection"
(t/is (= [:select] (run-input! "a" {})))))
(t/deftest open-mention-routes-panel-keys
(doseq [[key cmd] {"Enter" :insert-selected-mention
"ArrowDown" :insert-next-mention
"ArrowUp" :insert-prev-mention
"Escape" :hide-mentions}]
(t/testing key
(t/is (= [:select :prevent-default :stop-propagation [:mention cmd]]
(run-input! key {:mention "@bob"})))))
(t/testing "Backspace is not a panel key"
(t/is (= [:select [:backspace ctx]]
(run-input! "Backspace" {:mention "@bob"})))))
(t/deftest mention-is-read-after-select
(t/testing "a mention opened by on-select routes the same key"
(t/is (= [:select :prevent-default :stop-propagation
[:mention :insert-selected-mention]]
(run-input! "Enter" {:select-sets-mention "@new"})))))
(t/deftest missing-caret-target-runs-only-select
(t/testing "no input node"
(t/is (= [:select] (run-input! "Enter" {:node nil}))))
(t/testing "caret outside a text span"
(t/is (= [:select] (run-input! "Enter" {:span nil})))))
(t/deftest floating-thread-escape
(doseq [[mode opts] composition-modes]
(t/testing (str "Escape via " mode " keeps the thread open")
(t/is (= [] (run-thread! "Escape" opts)))))
(t/testing "plain Escape closes the thread"
(t/is (= [:close] (run-thread! "Escape" {}))))
(t/testing "other keys do nothing"
(t/is (= [] (run-thread! "Enter" {})))))