mirror of
https://github.com/penpot/penpot.git
synced 2026-10-30 06:20:32 +00:00
Switching sidebar tabs unmounts the layers panel. The search text and filters lived only in component state, so on return the still open search bar was treated as a new search and cleared. Keep that state per file in a session atom, as the assets panel already does. Closing or clearing the search mounts the tree container again, but the restore hook only ran on mount or page change, so the tree went back to the top. Let use-restore-scroll take an active? flag so each list restores its own position whenever it is shown again, and drop the saved search position on close so a new search starts at the top. Closes #11972 AI-assisted-by: claude-opus-5-5
94 lines
3.7 KiB
Clojure
94 lines
3.7 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.sidebar-scroll-test
|
|
(:require
|
|
[app.main.ui.workspace.sidebar.scroll :as sc]
|
|
[app.util.timers :as tm]
|
|
[cljs.test :as t :include-macros true]))
|
|
|
|
(def ^:private restore-loop! @#'sc/restore-loop!)
|
|
(def ^:private restore-position! @#'sc/restore-position!)
|
|
|
|
(defn- scroll-event
|
|
[scroll-top]
|
|
#js {:target #js {:scrollTop scroll-top}})
|
|
|
|
(t/deftest save-scroll-stores-position-per-key
|
|
(let [store (atom {})]
|
|
(sc/save-scroll! store [:layers :page-1] (scroll-event 250))
|
|
(sc/save-scroll! store [:assets :file-1] (scroll-event 80))
|
|
(t/is (= {[:layers :page-1] 250
|
|
[:assets :file-1] 80}
|
|
@store))))
|
|
|
|
(t/deftest save-scroll-ignores-events-without-target
|
|
(let [store (atom {})]
|
|
(sc/save-scroll! store [:layers :page-1] nil)
|
|
(sc/save-scroll! store [:layers :page-1] #js {})
|
|
(t/is (= {} @store))))
|
|
|
|
(t/deftest forget-scroll-drops-only-that-key
|
|
(let [store (atom {[:layers :page-1] 250
|
|
[:layers-search :page-1] 90})]
|
|
(sc/forget-scroll! store [:layers-search :page-1])
|
|
(t/is (= {[:layers :page-1] 250} @store))
|
|
(sc/forget-scroll! store [:layers-search :page-1])
|
|
(t/is (= {[:layers :page-1] 250} @store))))
|
|
|
|
(t/deftest restore-scroll-writes-saved-position
|
|
(let [node #js {:scrollTop 0}]
|
|
(sc/restore-scroll! node 320)
|
|
(t/is (= 320 (.-scrollTop node)))))
|
|
|
|
(t/deftest needs-restore-retry
|
|
(t/is (true? (sc/needs-restore-retry? -1 300 0)))
|
|
(t/is (true? (sc/needs-restore-retry? 100 300 9)))
|
|
(t/is (false? (sc/needs-restore-retry? 300 300 0)))
|
|
(t/is (false? (sc/needs-restore-retry? 100 300 10))))
|
|
|
|
(t/deftest restore-loop-writes-once-when-content-settled
|
|
(let [node #js {:scrollTop 0 :scrollHeight 300}
|
|
scheduled (atom [])
|
|
raf* (volatile! nil)]
|
|
(with-redefs [tm/raf (fn [f] (swap! scheduled conj f) 1)]
|
|
(restore-loop! node 250 300 0 raf*)
|
|
(t/is (= 250 (.-scrollTop node)))
|
|
(t/is (empty? @scheduled) "no follow-up when height is stable")
|
|
(t/is (nil? @raf*)))))
|
|
|
|
(t/deftest restore-loop-retries-while-content-grows
|
|
(let [node #js {:scrollTop 0 :scrollHeight 300}
|
|
scheduled (atom [])
|
|
raf* (volatile! nil)]
|
|
(with-redefs [tm/raf (fn [f] (swap! scheduled conj f) 1)]
|
|
(restore-loop! node 250 -1 0 raf*)
|
|
(t/is (= 250 (.-scrollTop node)))
|
|
(t/is (= 1 (count @scheduled)) "follow-up scheduled while growing")
|
|
(t/is (= 1 @raf*) "frame id tracked for cancellation")
|
|
;; Next frame: content settled at the same height, retry stops.
|
|
((first @scheduled))
|
|
(t/is (= 250 (.-scrollTop node)))
|
|
(t/is (= 1 (count @scheduled)) "no further frames once settled"))))
|
|
|
|
(t/deftest restore-position-without-saved-value-goes-to-top
|
|
(let [node #js {:scrollTop 400 :scrollHeight 900}
|
|
scheduled (atom [])
|
|
raf* (volatile! nil)]
|
|
(with-redefs [tm/raf (fn [f] (swap! scheduled conj f) 1)]
|
|
(restore-position! node nil raf*)
|
|
(t/is (= 0 (.-scrollTop node)) "a node kept mounted drops the old position")
|
|
(t/is (empty? @scheduled) "no retry loop for the top"))))
|
|
|
|
(t/deftest restore-position-with-saved-value-runs-loop
|
|
(let [node #js {:scrollTop 0 :scrollHeight 900}
|
|
scheduled (atom [])
|
|
raf* (volatile! nil)]
|
|
(with-redefs [tm/raf (fn [f] (swap! scheduled conj f) 1)]
|
|
(restore-position! node 250 raf*)
|
|
(t/is (= 250 (.-scrollTop node)))
|
|
(t/is (= 1 (count @scheduled)) "first pass schedules a settle check"))))
|