Eva Marco 41efc3558a
🐛 Fix nested libraries vanishing from the libraries modal (#12031)
* 🐛 Fix nested libraries vanishing from the libraries modal

The "This file" tab nests each linked library under a linked library
that uses it, taken from the team shared files. A library with such a
parent was removed from the top level and only drawn under its parent
if the parent was at the top level itself. So:

- In a chain of two or more levels (file -> A -> B -> C), C was lost.
- In a cycle (A uses B and B uses A), both had a parent, none was at
  the top level, and both were lost.

The libraries showed while the shared files were loading, and vanished
when they arrived.

Build the list as a tree instead: direct libraries and libraries not
used by any other linked library are roots, nested libraries follow
their parent at any depth, and each library is added only once, even
when the dependencies form a cycle.

AI-assisted-by: claude-opus-5-5

* ♻️ Move the linked libraries tree to sort-linked-libraries

Extract the code that orders the linked libraries as a tree from the
file tab to a public function, so it can be tested on its own. Add
tests for a cycle with a direct root, a cycle with no root, a deep
chain and no dependencies yet.

AI-assisted-by: claude-opus-5-5

* ♻️ Use connected-to to find library parents when sorting

The caller already computes :connected-to for each linked library, the
reverse index of the dependencies map. sort-linked-libraries rebuilt
the same relation from dependencies. Read it from :connected-to
instead, so the function takes only the libraries and the component
memo no longer depends on dependencies.

Reword the cycle-without-root test to say it models the unknown direct
flag, and add tests for a diamond, a self-dependency, a cycle of three
without root, a parent that is not a linked library, and for no
library being lost or duplicated.

AI-assisted-by: claude-sonnet-5-5

---------

Co-authored-by: Andrés Moya <andres.moya@kaleidos.net>
2026-10-08 09:54:46 +02:00

424 lines
16 KiB
Clojure

(ns frontend-tests.runner
(:require
[app.common.logging :as l]
[cljs.test :as t]
[clojure.string :as str]
[clojure.tools.cli :refer [parse-opts]]
[frontend-tests.basic-shapes-test]
[frontend-tests.code-gen-style-test]
[frontend-tests.composable-tests.comp.sync-test]
[frontend-tests.copy-as-svg-test]
[frontend-tests.data.comments-filters-test]
[frontend-tests.data.dashboard-test]
[frontend-tests.data.event-test]
[frontend-tests.data.exports-assets-test]
[frontend-tests.data.fix-deleted-fonts-test]
[frontend-tests.data.nitrate-test]
[frontend-tests.data.persistence-retry-test]
[frontend-tests.data.persistence-test]
[frontend-tests.data.profile-test]
[frontend-tests.data.repo-test]
[frontend-tests.data.store-test]
[frontend-tests.data.svg-upload-test]
[frontend-tests.data.uploads-test]
[frontend-tests.data.viewer-test]
[frontend-tests.data.workspace-colors-test]
[frontend-tests.data.workspace-comments-test]
[frontend-tests.data.workspace-context-menu-test]
[frontend-tests.data.workspace-interactions-test]
[frontend-tests.data.workspace-mcp-test]
[frontend-tests.data.workspace-media-test]
[frontend-tests.data.workspace-pages-test]
[frontend-tests.data.workspace-path-edition-test]
[frontend-tests.data.workspace-reflow-test]
[frontend-tests.data.workspace-shortcuts-test]
[frontend-tests.data.workspace-texts-test]
[frontend-tests.data.workspace-thumbnails-test]
[frontend-tests.data.workspace-versions-test]
[frontend-tests.debug-logging-test]
[frontend-tests.errors-governor-test]
[frontend-tests.errors-test]
[frontend-tests.fonts-test]
[frontend-tests.helpers-shapes-test]
[frontend-tests.logic.comp-remove-swap-slots-test]
[frontend-tests.logic.components-and-tokens]
[frontend-tests.logic.copy-paste-typography-test]
[frontend-tests.logic.copying-and-duplicating-test]
[frontend-tests.logic.frame-guides-test]
[frontend-tests.logic.groups-test]
[frontend-tests.logic.nudge-selected-shapes-test]
[frontend-tests.logic.pasting-in-containers-test]
[frontend-tests.logic.path-actions-test]
[frontend-tests.logic.path-clipboard-test]
[frontend-tests.logic.path-helpers-test]
[frontend-tests.logic.path-lifecycle-test]
[frontend-tests.logic.path-tools-test]
[frontend-tests.logic.sidebar-transform-coalescing-test]
[frontend-tests.logic.update-position-test]
[frontend-tests.logic.wasm-modifiers-nil-id-test]
[frontend-tests.logic.wasm-pixel-snap-test]
[frontend-tests.main-errors-test]
[frontend-tests.main.refs-test]
[frontend-tests.plugins.comments-test]
[frontend-tests.plugins.context-shapes-test]
[frontend-tests.plugins.events-test]
[frontend-tests.plugins.file-test]
[frontend-tests.plugins.flex-test]
[frontend-tests.plugins.format-test]
[frontend-tests.plugins.grid-test]
[frontend-tests.plugins.interactions-test]
[frontend-tests.plugins.library-test]
[frontend-tests.plugins.local-storage-test]
[frontend-tests.plugins.page-active-validation-test]
[frontend-tests.plugins.page-test]
[frontend-tests.plugins.parser-test]
[frontend-tests.plugins.register-test]
[frontend-tests.plugins.shape-bugfixes-test]
[frontend-tests.plugins.text-test]
[frontend-tests.plugins.tokens-test]
[frontend-tests.plugins.user-test]
[frontend-tests.plugins.utils-test]
[frontend-tests.plugins.value-objects-test]
[frontend-tests.render-dimensions-test]
[frontend-tests.render-wasm.process-objects-test]
[frontend-tests.render-wasm.serialization-test]
[frontend-tests.render-wasm.text-editor-apply-styles-test]
[frontend-tests.render-wasm.text-editor-caret-color-test]
[frontend-tests.router-test]
[frontend-tests.svg-fills-test]
[frontend-tests.svg-filters-test]
[frontend-tests.text-editor-paste-guard-test]
[frontend-tests.tokens.context-menu-test]
[frontend-tests.tokens.copy-paste-props-test]
[frontend-tests.tokens.import-export-test]
[frontend-tests.tokens.logic.token-actions-test]
[frontend-tests.tokens.logic.token-data-test]
[frontend-tests.tokens.logic.token-remapping-test]
[frontend-tests.tokens.logic.tokens-status-test]
[frontend-tests.tokens.style-dictionary-test]
[frontend-tests.tokens.token-errors-test]
[frontend-tests.tokens.workspace-tokens-remap-test]
[frontend-tests.ui.check-updates-test]
[frontend-tests.ui.color-token-position-test]
[frontend-tests.ui.colorpicker-token-set-order-test]
[frontend-tests.ui.comment-input-ime-test]
[frontend-tests.ui.comments-clustering-test]
[frontend-tests.ui.comments-position-modifier-test]
[frontend-tests.ui.drawarea-test]
[frontend-tests.ui.ds-controls-numeric-input-test]
[frontend-tests.ui.gradient-handlers-test]
[frontend-tests.ui.history-test]
[frontend-tests.ui.inspect-geometry-test]
[frontend-tests.ui.inspect-stroke-tokens-test]
[frontend-tests.ui.layout-container-multiple-test]
[frontend-tests.ui.measurements-test]
[frontend-tests.ui.measures-menu-props-test]
[frontend-tests.ui.organization-team-switch-test]
[frontend-tests.ui.routes-test]
[frontend-tests.ui.settings-password-schema-test]
[frontend-tests.ui.settings-shortcuts-test]
[frontend-tests.ui.shortcuts-labels-test]
[frontend-tests.ui.sidebar-scroll-test]
[frontend-tests.ui.stroke-menu-test]
[frontend-tests.ui.workspace-libraries-test]
[frontend-tests.util-clipboard-test]
[frontend-tests.util-object-test]
[frontend-tests.util-queue-test]
[frontend-tests.util-range-tree-test]
[frontend-tests.util-simple-math-test]
[frontend-tests.util-text-editor-test]
[frontend-tests.util-webapi-test]
[frontend-tests.util-zip-test]
[frontend-tests.util.dom.dnd-test]
[frontend-tests.worker-snap-test]
[goog.object :as gobj]))
(enable-console-print!)
(defmethod t/report [:cljs.test/default :begin-test-var] [m]
(let [v (:var m)]
(println (str " ▸ " (:ns (meta v)) "/" (:name (meta v))))))
(defmethod t/report [:cljs.test/default :end-run-tests] [m]
(if (cljs.test/successful? m)
(.exit js/process 0)
(.exit js/process 1)))
(def test-namespaces
['frontend-tests.basic-shapes-test
'frontend-tests.code-gen-style-test
'frontend-tests.composable-tests.comp.sync-test
'frontend-tests.copy-as-svg-test
'frontend-tests.data.comments-filters-test
'frontend-tests.data.dashboard-test
'frontend-tests.data.event-test
'frontend-tests.data.fix-deleted-fonts-test
'frontend-tests.data.nitrate-test
'frontend-tests.data.persistence-retry-test
'frontend-tests.data.persistence-test
'frontend-tests.data.profile-test
'frontend-tests.data.repo-test
'frontend-tests.data.store-test
'frontend-tests.data.exports-assets-test
'frontend-tests.data.svg-upload-test
'frontend-tests.data.uploads-test
'frontend-tests.data.viewer-test
'frontend-tests.data.workspace-colors-test
'frontend-tests.data.workspace-comments-test
'frontend-tests.data.workspace-context-menu-test
'frontend-tests.data.workspace-interactions-test
'frontend-tests.data.workspace-mcp-test
'frontend-tests.data.workspace-media-test
'frontend-tests.data.workspace-pages-test
'frontend-tests.data.workspace-path-edition-test
'frontend-tests.data.workspace-reflow-test
'frontend-tests.data.workspace-shortcuts-test
'frontend-tests.data.workspace-texts-test
'frontend-tests.data.workspace-thumbnails-test
'frontend-tests.data.workspace-versions-test
'frontend-tests.debug-logging-test
'frontend-tests.errors-governor-test
'frontend-tests.errors-test
'frontend-tests.fonts-test
'frontend-tests.helpers-shapes-test
'frontend-tests.logic.comp-remove-swap-slots-test
'frontend-tests.logic.components-and-tokens
'frontend-tests.logic.copy-paste-typography-test
'frontend-tests.logic.copying-and-duplicating-test
'frontend-tests.logic.frame-guides-test
'frontend-tests.logic.groups-test
'frontend-tests.logic.nudge-selected-shapes-test
'frontend-tests.logic.path-actions-test
'frontend-tests.logic.path-clipboard-test
'frontend-tests.logic.path-helpers-test
'frontend-tests.logic.path-lifecycle-test
'frontend-tests.logic.path-tools-test
'frontend-tests.logic.pasting-in-containers-test
'frontend-tests.main.refs-test
'frontend-tests.main-errors-test
'frontend-tests.logic.sidebar-transform-coalescing-test
'frontend-tests.logic.update-position-test
'frontend-tests.logic.wasm-modifiers-nil-id-test
'frontend-tests.logic.wasm-pixel-snap-test
'frontend-tests.plugins.comments-test
'frontend-tests.plugins.context-shapes-test
'frontend-tests.plugins.events-test
'frontend-tests.plugins.file-test
'frontend-tests.plugins.flex-test
'frontend-tests.plugins.format-test
'frontend-tests.plugins.grid-test
'frontend-tests.plugins.interactions-test
'frontend-tests.plugins.library-test
'frontend-tests.plugins.local-storage-test
'frontend-tests.plugins.page-active-validation-test
'frontend-tests.plugins.page-test
'frontend-tests.plugins.parser-test
'frontend-tests.plugins.register-test
'frontend-tests.plugins.shape-bugfixes-test
'frontend-tests.plugins.text-test
'frontend-tests.plugins.tokens-test
'frontend-tests.plugins.user-test
'frontend-tests.plugins.utils-test
'frontend-tests.plugins.value-objects-test
'frontend-tests.render-wasm.process-objects-test
'frontend-tests.render-wasm.serialization-test
'frontend-tests.render-wasm.text-editor-apply-styles-test
'frontend-tests.render-wasm.text-editor-caret-color-test
'frontend-tests.router-test
'frontend-tests.svg-fills-test
'frontend-tests.svg-filters-test
'frontend-tests.tokens.copy-paste-props-test
'frontend-tests.tokens.context-menu-test
'frontend-tests.tokens.import-export-test
'frontend-tests.tokens.logic.token-actions-test
'frontend-tests.tokens.logic.token-data-test
'frontend-tests.tokens.logic.token-remapping-test
'frontend-tests.tokens.style-dictionary-test
'frontend-tests.tokens.token-errors-test
'frontend-tests.tokens.logic.tokens-status-test
'frontend-tests.tokens.workspace-tokens-remap-test
'frontend-tests.ui.check-updates-test
'frontend-tests.ui.color-token-position-test
'frontend-tests.ui.colorpicker-token-set-order-test
'frontend-tests.ui.comment-input-ime-test
'frontend-tests.ui.comments-clustering-test
'frontend-tests.ui.comments-position-modifier-test
'frontend-tests.ui.drawarea-test
'frontend-tests.ui.ds-controls-numeric-input-test
'frontend-tests.ui.gradient-handlers-test
'frontend-tests.ui.history-test
'frontend-tests.ui.inspect-geometry-test
'frontend-tests.ui.inspect-stroke-tokens-test
'frontend-tests.ui.layout-container-multiple-test
'frontend-tests.ui.measurements-test
'frontend-tests.ui.measures-menu-props-test
'frontend-tests.ui.organization-team-switch-test
'frontend-tests.ui.routes-test
'frontend-tests.render-dimensions-test
'frontend-tests.text-editor-paste-guard-test
'frontend-tests.ui.settings-password-schema-test
'frontend-tests.ui.settings-shortcuts-test
'frontend-tests.ui.shortcuts-labels-test
'frontend-tests.ui.sidebar-scroll-test
'frontend-tests.ui.stroke-menu-test
'frontend-tests.ui.workspace-libraries-test
'frontend-tests.util-clipboard-test
'frontend-tests.util-object-test
'frontend-tests.util-queue-test
'frontend-tests.util-range-tree-test
'frontend-tests.util-simple-math-test
'frontend-tests.util-text-editor-test
'frontend-tests.util-webapi-test
'frontend-tests.util.dom.dnd-test
'frontend-tests.util-zip-test
'frontend-tests.worker-snap-test])
(assert (every? find-ns-obj test-namespaces)
"test-namespaces contains a namespace that isn't required in runner.cljs")
;; This runner intentionally mirrors common-tests.runner. Both runners need
;; forwarded CLI args, focused namespace/var execution, fixture preservation,
;; and app log-level setup. A shared helper could own those mechanics, but we
;; keep the logic local while there are only two test targets because sharing
;; it would add cross-module test classpath coupling.
(def ^:private log-levels
#{:trace :debug :info :warn :error})
(def cli-options
[["-f" "--focus FOCUS" "Run one test namespace or one test var, e.g. frontend-tests.logic.components-and-tokens/change-token-in-main"]
["-l" "--log-level LEVEL" "Set app logger level: trace|debug|info|warn|error"
:parse-fn keyword
:validate [log-levels "must be one of trace, debug, info, warn, error"]]
["-h" "--help"]])
(defn- argv
[]
(let [args (->> (.-argv js/process)
(array-seq)
(drop 2))]
;; `pnpm run test -- --focus ...` forwards the separator to the node
;; process, so drop one leading `--` before handing args to tools.cli.
(cond-> args
(= "--" (first args)) rest)))
(defn- usage
[summary]
(str "Usage: pnpm run test -- [options]\n\n"
"Options:\n"
summary "\n\n"
"Focus examples:\n"
" pnpm run test -- --focus frontend-tests.logic.components-and-tokens\n"
" pnpm run test -- --focus frontend-tests.logic.components-and-tokens/change-token-in-main\n\n"
"Log level example (quiets app logging during the run):\n"
" pnpm run test -- --focus frontend-tests.logic.groups-test --log-level warn"))
(defn- fail!
[message]
(js/console.error message)
(.exit js/process 1))
(defn- parse-focus
[focus]
(let [[ns-name test-name & extra] (str/split focus #"/")]
(cond
(or (str/blank? ns-name) (seq extra))
(fail! (str "Invalid --focus value: " focus))
(some? test-name)
{:ns (symbol ns-name) :test test-name}
:else
{:ns (symbol ns-name)})))
(defn- fixture-value
[ns-obj fixture-name]
(let [value (gobj/get ns-obj (munge fixture-name))]
(when-not (undefined? value)
value)))
(defn- ns-test-vars
[ns-sym]
(when-let [ns-obj (find-ns-obj ns-sym)]
(->> (js-keys ns-obj)
(keep (fn [key]
(some-> (gobj/get ns-obj key)
(.-cljs$lang$var))))
(filter (comp :test meta))
(sort-by (comp :line meta)))))
(defn- ns-fixtures
[ns-sym vars]
(when-let [ns-obj (find-ns-obj ns-sym)]
(let [ns-key (or (some-> vars first meta :ns) ns-sym)
once-fixtures (fixture-value ns-obj "cljs-test-once-fixtures")
each-fixtures (fixture-value ns-obj "cljs-test-each-fixtures")]
{:once (when once-fixtures {ns-key once-fixtures})
:each (when each-fixtures {ns-key each-fixtures})})))
(defn- selected-tests
[{:keys [ns test]}]
(when-not (some #{ns} test-namespaces)
(fail! (str "Unknown test namespace: " ns)))
(let [vars (vec (ns-test-vars ns))]
(when (empty? vars)
(fail! (str "No tests found in namespace: " ns)))
(if test
(let [test-sym (symbol test)
test-var (some #(when (= test-sym (:name (meta %))) %) vars)]
(if test-var
{:vars [test-var]
:fixtures (ns-fixtures ns [test-var])}
(fail! (str "Unknown test var: " ns "/" test))))
{:vars vars
:fixtures (ns-fixtures ns vars)})))
(defn- merge-fixtures
[fixtures]
{:once (apply merge (keep :once fixtures))
:each (apply merge (keep :each fixtures))})
(defn- run-test-vars!
[tests]
(let [vars (vec (mapcat :vars tests))
fixtures (merge-fixtures (map :fixtures tests))
env (assoc (t/empty-env)
:once-fixtures (:once fixtures)
:each-fixtures (:each fixtures))
summary (volatile! {:test 0 :pass 0 :fail 0 :error 0 :type :summary})]
(t/set-env! env)
(t/run-block
(concat (t/test-vars-block vars)
[(fn []
(vswap! summary
(partial merge-with +)
(:report-counters (t/get-current-env))))
(fn []
(t/report @summary)
(t/report (assoc @summary :type :end-run-tests)))]))))
(defn- run-focused-test!
[focus]
(run-test-vars! [(selected-tests (parse-focus focus))]))
(defn init
[]
(let [{:keys [options errors summary]} (parse-opts (argv) cli-options)]
(cond
(seq errors)
(fail! (str/join "\n" errors))
(:help options)
(do
(println (usage summary))
(.exit js/process 0))
:else
(do
(l/setup! {:app (or (:log-level options) :warn)})
(if (:focus options)
(run-focused-test! (:focus options))
(run-test-vars! (map #(selected-tests {:ns %}) test-namespaces)))))))