diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 2e0db5d324..f55d63372e 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -311,12 +311,16 @@ (defn error-messages [explain] - (->> (:errors explain) - (reduce csm/interpret-schema-problem {}) - (flatten-error-map) - (map (fn [[field message]] - (tr "plugins.validation.message" field message))) - (str/join ". "))) + (let [msg (->> (:errors explain) + (reduce csm/interpret-schema-problem {}) + (flatten-error-map) + (map (fn [[field message]] + (tr "plugins.validation.message" field message))) + (str/join ". "))] + ;; Return nil (not "") when the explain has no mappable errors, so + ;; `handle-error` can fall back to a non-empty message instead of + ;; surfacing a bare "Value not valid. Code: :error" (#9692). + (when-not (str/blank? msg) msg))) (defn handle-error "Function to be used in plugin proxies methods to handle errors and print a readable @@ -340,8 +344,8 @@ (if explain (do (js/console.error (sm/humanize-explain explain)) - (error-messages explain)) - (ex-data cause))] + (or (error-messages explain) (pr-str explain))) + (or (ex-data cause) (ex-message cause) (str cause)))] (js/console.log (.-stack cause)) (not-valid plugin-id :error message)))))) diff --git a/frontend/test/frontend_tests/plugins/utils_test.cljs b/frontend/test/frontend_tests/plugins/utils_test.cljs index 8fbf4e4f40..a2a1e98124 100644 --- a/frontend/test/frontend_tests/plugins/utils_test.cljs +++ b/frontend/test/frontend_tests/plugins/utils_test.cljs @@ -54,3 +54,36 @@ ;; No validation errors -> no output (callers join with ". " and would ;; otherwise emit an empty string, which is fine). (t/is (empty? (flatten-error-map {})))) + +;; --------------------------------------------------------------------- +;; Issue #9692 — `handle-error` must surface a useful message instead of a +;; bare "Value not valid. Code: :error". +;; +;; `not-valid` is redefined to capture the rendered message directly, so the +;; assertions don't depend on `st/state` or the console. + +(t/deftest test-handle-error-plain-js-error + ;; A plain JS error has no `::sm/explain` and CLJS `ex-data` returns nil, so + ;; the handler must fall back to the error's own message rather than nil. + (let [captured (atom ::none)] + (with-redefs [plugins.utils/not-valid (fn [_plugin-id _code value] + (reset! captured value) nil)] + ((plugins.utils/handle-error #uuid "00000000-0000-0000-0000-000000000000") + (js/Error. "boom: not a function"))) + (t/is (= "boom: not a function" @captured)))) + +(t/deftest test-handle-error-empty-explain + ;; An explain whose errors don't render any message must not produce an + ;; empty string; the handler falls back to the raw explain. + (let [captured (atom ::none) + cause (ex-info "invalid" {:app.common.schema/explain {:errors [] :value 1}})] + (with-redefs [plugins.utils/not-valid (fn [_plugin-id _code value] + (reset! captured value) nil)] + ((plugins.utils/handle-error #uuid "00000000-0000-0000-0000-000000000000") cause)) + (t/is (string? @captured)) + (t/is (not= "" @captured)))) + +(t/deftest test-error-messages-empty-returns-nil + ;; `error-messages` returns nil (not "") on an explain with no mappable + ;; errors, so `handle-error` can distinguish "no message" from a real one. + (t/is (nil? (plugins.utils/error-messages {:errors []}))))