From 4b2061f399c6e664efbdecba9f4eca43c389836a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Schr=C3=B6dl?= Date: Wed, 4 Nov 2026 12:04:52 +0100 Subject: [PATCH] :sparkles: Add unit tests for the token forms and token tree (#12781) * :sparkles: Add unit tests for the token form and combobox helpers Several helpers of the token forms and the token value combobox were already pure but had no tests. Cover the reference under the cursor, the order of the token tree, moving through the combobox options, the shadow and typography form values and their validation, which value a form tab validates, and that a token import needs a json file. Signed-off-by: Florian Schroedl * :sparkles: Test the shadow and typography form rules Add tests for the shadow and typography form schemas: a negative blur is rejected, a negative spread and a blank blur or spread are accepted, every shadow needs its offsets and color, a line height needs a font size, and a token can't reference itself. A typography token submitted without any field is rejected. They replace the tests of the validator wrappers, the combobox focus order and the import file check. Signed-off-by: Florian Schroedl --------- Signed-off-by: Florian Schroedl --- frontend/test/frontend_tests/runner.cljs | 8 ++ .../tokens/shadow_form_test.cljs | 105 ++++++++++++++++++ .../tokens/token_parsing_test.cljs | 46 ++++++++ .../tokens/token_tree_test.cljs | 37 ++++++ .../tokens/typography_form_test.cljs | 99 +++++++++++++++++ 5 files changed, 295 insertions(+) create mode 100644 frontend/test/frontend_tests/tokens/shadow_form_test.cljs create mode 100644 frontend/test/frontend_tests/tokens/token_parsing_test.cljs create mode 100644 frontend/test/frontend_tests/tokens/token_tree_test.cljs create mode 100644 frontend/test/frontend_tests/tokens/typography_form_test.cljs diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 63fee9982b..70b1ef0a47 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -112,11 +112,15 @@ [frontend-tests.tokens.logic.tokens-source-test] [frontend-tests.tokens.logic.tokens-status-test] [frontend-tests.tokens.referenced-token-value-test] + [frontend-tests.tokens.shadow-form-test] [frontend-tests.tokens.style-dictionary-test] [frontend-tests.tokens.token-errors-test] + [frontend-tests.tokens.token-parsing-test] [frontend-tests.tokens.token-path-test] [frontend-tests.tokens.token-pill-test] + [frontend-tests.tokens.token-tree-test] [frontend-tests.tokens.tokenscript-test] + [frontend-tests.tokens.typography-form-test] [frontend-tests.tokens.workspace-tokens-remap-test] [frontend-tests.ui.check-updates-test] [frontend-tests.ui.color-token-position-test] @@ -285,11 +289,15 @@ 'frontend-tests.tokens.logic.token-data-test 'frontend-tests.tokens.logic.token-remapping-test 'frontend-tests.tokens.referenced-token-value-test + 'frontend-tests.tokens.shadow-form-test 'frontend-tests.tokens.style-dictionary-test 'frontend-tests.tokens.token-errors-test + 'frontend-tests.tokens.token-parsing-test 'frontend-tests.tokens.token-path-test 'frontend-tests.tokens.token-pill-test + 'frontend-tests.tokens.token-tree-test 'frontend-tests.tokens.tokenscript-test + 'frontend-tests.tokens.typography-form-test 'frontend-tests.tokens.logic.tokens-source-test 'frontend-tests.tokens.logic.tokens-status-test 'frontend-tests.tokens.workspace-tokens-remap-test diff --git a/frontend/test/frontend_tests/tokens/shadow_form_test.cljs b/frontend/test/frontend_tests/tokens/shadow_form_test.cljs new file mode 100644 index 0000000000..f940620095 --- /dev/null +++ b/frontend/test/frontend_tests/tokens/shadow_form_test.cljs @@ -0,0 +1,105 @@ +;; 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.tokens.shadow-form-test + (:require + [app.common.schema :as sm] + [app.common.schema.messages :as csm] + [app.common.types.tokens-lib :as ctob] + [app.main.data.workspace.tokens.errors :as wte] + [app.main.ui.workspace.tokens.management.forms.shadow :as wtsh] + [app.util.i18n :refer [tr]] + [cljs.test :as t :include-macros true])) + +(def ^:private make-schema @#'wtsh/make-schema) +(def ^:private form-value @#'wtsh/make-default-value) +(def ^:private check-self-reference @#'wtsh/check-shadow-token-self-reference) + +(def ^:private shadow + (assoc @#'wtsh/default-token-shadow :color "#000000")) + +(def ^:private token + (ctob/make-token :name "shadow.md" :type :shadow :value [shadow])) + +(defn- schema + [tab] + (make-schema (ctob/get-token-path token) + (ctob/tokens-tree {(:name token) token}) + tab)) + +(defn- form-data + [value] + {:name (:name token) :description "" :value value}) + +(defn- shadows + [& shadows] + (form-data {:reference nil :shadow (vec shadows)})) + +(defn- errors + [tab data] + (csm/collect-schema-errors (schema tab) [] {:data data})) + +(defn- valid? + [tab data] + (sm/validate (schema tab) data)) + +(t/deftest composite-tab + (t/testing "a complete shadow is valid" + (t/is (valid? :composite (shadows shadow)))) + + (t/testing "blur can't be negative" + (t/is (= (tr "workspace.tokens.shadow-token-blur-value-error") + (get-in (errors :composite (shadows (assoc shadow :blur "-2"))) + [:value :shadow 0 :blur :message])))) + + (t/testing "spread can be negative" + (t/is (valid? :composite (shadows (assoc shadow :spread "-2"))))) + + (t/testing "blur and spread can stay blank" + (t/is (valid? :composite (shadows (assoc shadow :blur "" :spread ""))))) + + (t/testing "every shadow needs its offsets and color" + (t/is (not (valid? :composite (shadows shadow (assoc shadow :color ""))))) + (t/is (not (valid? :composite (shadows (assoc shadow :offset-x ""))))) + (t/is (contains? (errors :composite (shadows (dissoc shadow :color))) :value)))) + +(t/deftest reference-tab + (t/testing "a reference is required" + (t/is (not (valid? :reference (form-data {:reference "" :shadow []})))) + (t/is (valid? :reference (form-data {:reference "{shadow.sm}" :shadow []})))) + + (t/testing "the token can't reference itself" + (t/is (= (tr "errors.tokens.self-reference") + (get-in (errors :reference (form-data {:reference "{shadow.md}" :shadow []})) + [:value :reference :message]))))) + +(t/deftest composite-self-reference + (t/testing "the error names the shadow and field that reference the token" + (t/is (= (assoc (wte/get-error-code :error.token/direct-self-reference) + :shadow-key :color + :shadow-index 1) + (check-self-reference + {:name "shadow.md" + :value [shadow (assoc shadow :color "{shadow.md}")]})))) + + (t/testing "references to other tokens pass" + (t/is (nil? (check-self-reference + {:name "shadow.md" + :value [(assoc shadow :color "{color.black}")]}))))) + +(t/deftest initial-form-value + (t/testing "a reference opens on the reference tab" + (t/is (= {:reference "{shadow.sm}" :shadow []} + (form-value "{shadow.sm}")))) + + (t/testing "blank or missing blur and spread show as 0" + (t/is (= {:reference nil :shadow [(assoc shadow :blur "0" :spread "0")]} + (form-value [(assoc shadow :blur "" :spread " ")]) + (form-value [(dissoc shadow :blur :spread)])))) + + (t/testing "a new token starts with one default shadow" + (t/is (= {:reference nil :shadow [@#'wtsh/default-token-shadow]} + (form-value nil))))) diff --git a/frontend/test/frontend_tests/tokens/token_parsing_test.cljs b/frontend/test/frontend_tests/tokens/token_parsing_test.cljs new file mode 100644 index 0000000000..b37e8fe663 --- /dev/null +++ b/frontend/test/frontend_tests/tokens/token_parsing_test.cljs @@ -0,0 +1,46 @@ +;; 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.tokens.token-parsing-test + (:require + [app.main.ui.workspace.tokens.management.forms.controls.token-parsing :as tp] + [cljs.test :as t :include-macros true])) + +(t/deftest extract-partial-token + (t/testing "an open reference at the cursor" + (t/is (= {:start 0 :end 4 :partial "col"} + (tp/extract-partial-token "{col" 4)))) + + (t/testing "the last open reference when an earlier one is closed" + (t/is (= {:start 14 :end 17 :partial "sp"} + (tp/extract-partial-token "{color.red} + {sp" 17)))) + + (t/testing "the reference ends at its closing brace after the cursor" + (t/is (= {:start 0 :end 5 :partial "co"} + (tp/extract-partial-token "{colo}" 3)))) + + (t/testing "nothing after a closed reference" + (t/is (nil? (tp/extract-partial-token "{color} " 8)))) + + (t/testing "nothing without a reference" + (t/is (nil? (tp/extract-partial-token "12" 2))) + (t/is (nil? (tp/extract-partial-token "" 0))))) + +(t/deftest token-at-cursor + (t/testing "the token name when the cursor is inside a reference" + (t/is (= "color.red" (tp/token-at-cursor "{color.red}" 3))) + (t/is (= "color.red" (tp/token-at-cursor "{color.red} + {spacing.md}" 1))) + (t/is (= "spacing.md" (tp/token-at-cursor "{color.red} + {spacing.md}" 18)))) + + (t/testing "nothing for a reference that isn't closed" + (t/is (nil? (tp/token-at-cursor "{color" 3)))) + + (t/testing "nothing for an empty reference or one with spaces" + (t/is (nil? (tp/token-at-cursor "{}" 1))) + (t/is (nil? (tp/token-at-cursor "{color red}" 3)))) + + (t/testing "nothing before the first reference" + (t/is (nil? (tp/token-at-cursor "1 + {color}" 1))))) diff --git a/frontend/test/frontend_tests/tokens/token_tree_test.cljs b/frontend/test/frontend_tests/tokens/token_tree_test.cljs new file mode 100644 index 0000000000..2fa83904a1 --- /dev/null +++ b/frontend/test/frontend_tests/tokens/token_tree_test.cljs @@ -0,0 +1,37 @@ +;; 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.tokens.token-tree-test + (:require + [app.common.path-names :as cpn] + [app.common.types.tokens-lib :as ctob] + [app.main.ui.workspace.tokens.management.token-tree :as wtt] + [cljs.test :as t :include-macros true])) + +(def ^:private sort-nodes @#'wtt/sort-nodes) + +(defn- tree + [& names] + (cpn/build-tree-root + (map #(ctob/make-token :name % :type :spacing :value "4") names) + ctob/token-separator)) + +(defn- sorted-names + [nodes] + (map :name (sort-nodes nodes))) + +(t/deftest sort-nodes-test + (t/testing "tokens come before groups" + (t/is (= ["zeta" "alpha" "beta"] + (sorted-names (tree "alpha.sm" "zeta" "beta.md"))))) + + (t/testing "numbers in names sort by value" + (t/is (= ["size2" "size10" "group2" "group10"] + (sorted-names (tree "size10" "group10.sm" "size2" "group2.sm"))))) + + (t/testing "a token that also has tokens below it sorts with the groups" + (t/is (= ["md" "space"] + (sorted-names (tree "space.sm" "md" "space")))))) diff --git a/frontend/test/frontend_tests/tokens/typography_form_test.cljs b/frontend/test/frontend_tests/tokens/typography_form_test.cljs new file mode 100644 index 0000000000..64cc06be21 --- /dev/null +++ b/frontend/test/frontend_tests/tokens/typography_form_test.cljs @@ -0,0 +1,99 @@ +;; 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.tokens.typography-form-test + (:require + [app.common.schema :as sm] + [app.common.schema.messages :as csm] + [app.common.types.tokens-lib :as ctob] + [app.main.data.workspace.tokens.errors :as wte] + [app.main.ui.workspace.tokens.management.forms.typography :as wtty] + [app.util.i18n :refer [tr]] + [beicon.v2.core :as rx] + [cljs.test :as t :include-macros true])) + +(def ^:private make-schema @#'wtty/make-schema) +(def ^:private form-value @#'wtty/composite-value->form-value) +(def ^:private check-self-reference @#'wtty/check-typography-token-self-reference) +(def ^:private validate-token @#'wtty/validate-typography-token) + +(def ^:private token + (ctob/make-token :name "type.body" :type :typography :value {:font-size "16"})) + +(defn- schema + [tab] + (make-schema (ctob/get-token-path token) + (ctob/tokens-tree {(:name token) token}) + tab)) + +(defn- form-data + [value] + {:name (:name token) :description "" :value value}) + +(defn- errors + [tab data] + (csm/collect-schema-errors (schema tab) [] {:data data})) + +(defn- valid? + [tab data] + (sm/validate (schema tab) data)) + +(t/deftest composite-tab + (t/testing "one field is enough" + (t/is (valid? :composite (form-data {:font-size "16"})))) + + (t/testing "at least one field must be set" + (t/is (not (valid? :composite (form-data {})))) + (t/is (not (valid? :composite (form-data {:font-size "" :font-weight ""}))))) + + (t/testing "a line height needs a font size" + (t/is (= (tr "errors.tokens.composite-line-height-needs-font-size") + (get-in (errors :composite (form-data {:line-height "1.5"})) + [:value :line-height :message]))) + (t/is (valid? :composite (form-data {:line-height "1.5" :font-size "16"}))))) + +(t/deftest reference-tab + (t/testing "a reference is required" + (t/is (not (valid? :reference (form-data {:reference ""})))) + (t/is (valid? :reference (form-data {:reference "{type.heading}"})))) + + (t/testing "the token can't reference itself" + (t/is (= (tr "errors.tokens.self-reference") + (get-in (errors :reference (form-data {:reference "{type.body}"})) + [:value :reference :message]))))) + +(t/deftest composite-self-reference + (let [self-reference (wte/get-error-code :error.token/direct-self-reference)] + (t/testing "the error names the field that references the token" + (t/is (= (assoc self-reference :typography-key :font-size) + (check-self-reference {:name "type.body" :value {:font-size "{type.body}"}})))) + + (t/testing "each font family is checked" + (t/is (= (assoc self-reference :typography-key :font-family) + (check-self-reference {:name "type.body" :value {:font-family ["Inter" "{type.body}"]}})))) + + (t/testing "references to other tokens pass" + (t/is (nil? (check-self-reference {:name "type.body" :value {:font-size "{font.size.md}"}})))))) + +(t/deftest submit-without-fields + (t/async + done + (->> (validate-token {:token-name "type.body" :token-value nil :tokens {}}) + (rx/subs! (fn [_] + (t/is false "a token without fields must be rejected, not saved") + (done)) + (fn [error] + (t/is (= {:errors [(wte/get-error-code :error.token/empty-input)]} error)) + (done)))))) + +(t/deftest initial-form-value + (t/testing "font families show as one comma separated field" + (t/is (= {:font-family "Inter, Arial" :font-size "16"} + (form-value {:font-family ["Inter" "Arial"] :font-size "16"})))) + + (t/testing "only the typography fields are kept" + (t/is (= {:line-height "1.5"} + (form-value {:line-height "1.5" :paragraph-spacing "4"})))))