mirror of
https://github.com/penpot/penpot.git
synced 2026-09-24 04:46:14 +00:00
* 🐛 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>
130 lines
5.4 KiB
Clojure
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" {})))))
|