🐛 Keep a committed property name from being sent again

When the variant property panel went away (selecting another layer),
input-with-meta* sent a blur from the last input it had rendered, to
commit an edit still open. After an edit was already committed that
input is gone and keeps the text it had: reordering the properties
and then selecting a variant renamed the property now in that row
after the old one, "Property 1" becoming "Property 2 (1)"
(PENPOT-2588). The input is forgotten once its edit is committed, so
the blur is sent only for an edit still open.

ds-input-with-meta-test mounts the component in a jsdom document: a
committed edit is not sent again when the value changes and the input
goes away; an edit still open is committed.

AI-assisted-by: claude-opus-5-5
This commit is contained in:
alonso.torres 2026-10-16 10:46:48 +02:00
parent 03c6e3b006
commit 5fdf0c036f
3 changed files with 77 additions and 0 deletions

View File

@ -51,6 +51,8 @@
(fn [event]
(dom/stop-propagation event)
(reset! editing* false)
;; the edit is committed: the cleanup has nothing left to send
(mf/set-ref-val! last-node* nil)
(when on-blur
(on-blur event))))

View File

@ -140,6 +140,7 @@
[frontend-tests.ui.dashboard-import-linking-test]
[frontend-tests.ui.drawarea-test]
[frontend-tests.ui.ds-controls-numeric-input-test]
[frontend-tests.ui.ds-input-with-meta-test]
[frontend-tests.ui.error-boundary-test]
[frontend-tests.ui.gradient-handlers-test]
[frontend-tests.ui.history-test]
@ -323,6 +324,7 @@
'frontend-tests.ui.dashboard-import-linking-test
'frontend-tests.ui.drawarea-test
'frontend-tests.ui.ds-controls-numeric-input-test
'frontend-tests.ui.ds-input-with-meta-test
'frontend-tests.ui.error-boundary-test
'frontend-tests.ui.gradient-handlers-test
'frontend-tests.ui.history-test

View File

@ -0,0 +1,73 @@
;; 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.ds-input-with-meta-test
"The blur of `input-with-meta*`, mounted in a jsdom document since the
runner has no DOM. React loads before the test sets up the document, so
it cannot dispatch DOM events here: the container stays out of the
document (autofocus does nothing) and a blur calls the input's handler."
(:require
["jsdom" :refer [JSDOM]]
["react" :as react]
["react-dom/client" :as rdc]
[app.main.ui.ds.product.input-with-meta :refer [input-with-meta*]]
[cljs.test :as t :include-macros true]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(def ^:private dom-globals ["window" "document" "IS_REACT_ACT_ENVIRONMENT"])
(defn- with-dom
"Call `f` with a container of a jsdom document set as the global one."
[f]
(let [window (.-window (JSDOM. "<!DOCTYPE html><body></body>"))
previous (into {} (map (fn [k] [k (unchecked-get js/globalThis k)])) dom-globals)]
(doseq [[k v] {"window" window
"document" (.-document window)
"IS_REACT_ACT_ENVIRONMENT" true}]
(unchecked-set js/globalThis k v))
(try
(f (.createElement (.-document window) "div"))
(finally
(doseq [[k v] previous]
(unchecked-set js/globalThis k v))))))
(defn- blur!
"Blur the input of `container` the way React would on a real blur."
[container]
(let [input (.querySelector container "input")
props (->> (js/Object.keys input)
(some #(when (str/starts-with? % "__reactProps$") (unchecked-get input %))))]
(.onBlur props #js {:target input :currentTarget input :stopPropagation (fn [])})))
(defn- element
[value on-blur]
(mf/element input-with-meta* #js {:value value :isEditing true :onBlur on-blur}))
(t/deftest unmount-after-a-committed-edit-sends-no-blur
;; The edit commits on blur; the value then changes (the rows the inputs
;; belong to are reordered) and the input goes away. The committed edit
;; must not be sent again with the text it had.
(with-dom
(fn [container]
(let [blurs (atom [])
on-blur (fn [event] (swap! blurs conj (.. event -target -value)))
root (rdc/createRoot container)]
(react/act #(.render root (element "Property 2" on-blur)))
(react/act #(blur! container))
(react/act #(.render root (element "Property 1" on-blur)))
(react/act #(.unmount root))
(t/is (= ["Property 2"] @blurs))))))
(t/deftest unmount-during-an-edit-commits-it
(with-dom
(fn [container]
(let [blurs (atom [])
on-blur (fn [event] (swap! blurs conj (.. event -target -value)))
root (rdc/createRoot container)]
(react/act #(.render root (element "Property 1" on-blur)))
(react/act #(.unmount root))
(t/is (= ["Property 1"] @blurs))))))