mirror of
https://github.com/penpot/penpot.git
synced 2026-09-10 22:19:19 +00:00
♻️ Merge :thumbnails and :thumbnails-meta into single state key (#10021)
* ♻️ Merge :thumbnails and :thumbnails-meta into single state key ♻️ Unify thumbnail refs in a single ref 🐛 Fix test * ♻️ Update tests
This commit is contained in:
parent
b3b3ea97db
commit
46f5346045
@ -275,7 +275,7 @@
|
|||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(-> state
|
(-> state
|
||||||
(assoc :thumbnails thumbnails)
|
(assoc :thumbnails (d/update-vals thumbnails (fn [uri] {:uri uri :rendered-at nil})))
|
||||||
(update :files assoc file-id file)))))
|
(update :files assoc file-id file)))))
|
||||||
|
|
||||||
(defn zoom-to-frame
|
(defn zoom-to-frame
|
||||||
|
|||||||
@ -800,7 +800,8 @@
|
|||||||
(ptk/reify ::library-thumbnails-fetched
|
(ptk/reify ::library-thumbnails-fetched
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(update state :thumbnails merge thumbnails))))
|
(update state :thumbnails merge
|
||||||
|
(d/update-vals thumbnails (fn [uri] {:uri uri :rendered-at nil}))))))
|
||||||
|
|
||||||
(defn fetch-library-thumbnails
|
(defn fetch-library-thumbnails
|
||||||
[library-id]
|
[library-id]
|
||||||
@ -1541,7 +1542,8 @@
|
|||||||
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"})
|
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"})
|
||||||
(rx/map (fn [thumbnails]
|
(rx/map (fn [thumbnails]
|
||||||
(fn [state]
|
(fn [state]
|
||||||
(update state :thumbnails merge thumbnails))))))))))
|
(update state :thumbnails merge
|
||||||
|
(d/update-vals thumbnails (fn [uri] {:uri uri :rendered-at nil}))))))))))))
|
||||||
|
|
||||||
(defn link-file-to-library
|
(defn link-file-to-library
|
||||||
[file-id library-id]
|
[file-id library-id]
|
||||||
|
|||||||
@ -134,12 +134,11 @@
|
|||||||
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [uri (dm/get-in state [:thumbnails object-id])]
|
(let [uri (dm/get-in state [:thumbnails object-id :uri])]
|
||||||
(l/dbg :hint "clear-thumbnail" :object-id object-id :uri uri)
|
(l/dbg :hint "clear-thumbnail" :object-id object-id :uri uri)
|
||||||
(-> state
|
(-> state
|
||||||
(update ::thumbnails-deletion-queue assoc object-id uri)
|
(update ::thumbnails-deletion-queue assoc object-id uri)
|
||||||
(update :thumbnails dissoc object-id)
|
(update :thumbnails dissoc object-id))))
|
||||||
(update :thumbnails-meta dissoc object-id))))
|
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ stream]
|
(watch [_ _ stream]
|
||||||
@ -156,13 +155,12 @@
|
|||||||
(ptk/reify ::assoc-thumbnail
|
(ptk/reify ::assoc-thumbnail
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [prev-uri (dm/get-in state [:thumbnails object-id])
|
(let [prev-entry (dm/get-in state [:thumbnails object-id])
|
||||||
now (.now js/Date)]
|
now (ct/now)]
|
||||||
(some->> prev-uri (vreset! prev-uri*))
|
(some->> prev-entry :uri (vreset! prev-uri*))
|
||||||
(l/trc :hint "assoc thumbnail" :object-id object-id :uri uri)
|
(l/trc :hint "assoc thumbnail" :object-id object-id :uri uri)
|
||||||
(-> state
|
(-> state
|
||||||
(update :thumbnails assoc object-id uri)
|
(update :thumbnails assoc object-id {:uri uri :rendered-at now}))))
|
||||||
(update :thumbnails-meta assoc object-id {:rendered-at now}))))
|
|
||||||
|
|
||||||
ptk/EffectEvent
|
ptk/EffectEvent
|
||||||
(effect [_ _ _]
|
(effect [_ _ _]
|
||||||
|
|||||||
@ -168,7 +168,7 @@
|
|||||||
(ptk/reify ::persist-thumbnail
|
(ptk/reify ::persist-thumbnail
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [data-uri (dm/get-in state [:thumbnails object-id])]
|
(let [data-uri (dm/get-in state [:thumbnails object-id :uri])]
|
||||||
(if (and (some? data-uri)
|
(if (and (some? data-uri)
|
||||||
(str/starts-with? data-uri "data:"))
|
(str/starts-with? data-uri "data:"))
|
||||||
(let [blob (wapi/data-uri->blob data-uri)]
|
(let [blob (wapi/data-uri->blob data-uri)]
|
||||||
|
|||||||
@ -580,14 +580,9 @@
|
|||||||
[object-id]
|
[object-id]
|
||||||
(l/derived
|
(l/derived
|
||||||
(fn [state]
|
(fn [state]
|
||||||
(some-> (dm/get-in state [:thumbnails object-id])
|
(when-let [entry (dm/get-in state [:thumbnails object-id])]
|
||||||
(cf/resolve-media)))
|
(cond-> entry
|
||||||
st/state))
|
(:uri entry) (update :uri cf/resolve-media))))
|
||||||
|
|
||||||
(defn workspace-thumbnail-rendered-at
|
|
||||||
[object-id]
|
|
||||||
(l/derived
|
|
||||||
#(dm/get-in % [:thumbnails-meta object-id :rendered-at])
|
|
||||||
st/state))
|
st/state))
|
||||||
|
|
||||||
(def workspace-text-modifier
|
(def workspace-text-modifier
|
||||||
|
|||||||
@ -136,10 +136,11 @@
|
|||||||
width (dm/get-prop bounds :width)
|
width (dm/get-prop bounds :width)
|
||||||
height (dm/get-prop bounds :height)
|
height (dm/get-prop bounds :height)
|
||||||
|
|
||||||
thumbnail-uri* (mf/with-memo [file-id page-id frame-id]
|
thumbnail-data* (mf/with-memo [file-id page-id frame-id]
|
||||||
(let [object-id (thc/fmt-object-id file-id page-id frame-id "frame")]
|
(let [object-id (thc/fmt-object-id file-id page-id frame-id "frame")]
|
||||||
(refs/workspace-thumbnail-by-id object-id)))
|
(refs/workspace-thumbnail-by-id object-id)))
|
||||||
thumbnail-uri (mf/deref thumbnail-uri*)
|
thumbnail-data (mf/deref thumbnail-data*)
|
||||||
|
thumbnail-uri (:uri thumbnail-data)
|
||||||
|
|
||||||
modifiers-ref (mf/with-memo [frame-id]
|
modifiers-ref (mf/with-memo [frame-id]
|
||||||
(refs/workspace-modifiers-by-frame-id frame-id))
|
(refs/workspace-modifiers-by-frame-id frame-id))
|
||||||
|
|||||||
@ -328,19 +328,18 @@
|
|||||||
(mf/with-memo [file-id page-id root-id]
|
(mf/with-memo [file-id page-id root-id]
|
||||||
(thc/fmt-object-id file-id page-id root-id "component"))
|
(thc/fmt-object-id file-id page-id root-id "component"))
|
||||||
|
|
||||||
thumbnail-uri*
|
thumbnail-data*
|
||||||
(mf/with-memo [object-id]
|
(mf/with-memo [object-id]
|
||||||
(refs/workspace-thumbnail-by-id object-id))
|
(refs/workspace-thumbnail-by-id object-id))
|
||||||
|
|
||||||
|
thumbnail-data
|
||||||
|
(mf/deref thumbnail-data*)
|
||||||
|
|
||||||
thumbnail-uri
|
thumbnail-uri
|
||||||
(mf/deref thumbnail-uri*)
|
(:uri thumbnail-data)
|
||||||
|
|
||||||
rendered-at*
|
thumbnail-rendered-at
|
||||||
(mf/with-memo [object-id]
|
(:rendered-at thumbnail-data)
|
||||||
(refs/workspace-thumbnail-rendered-at object-id))
|
|
||||||
|
|
||||||
rendered-at
|
|
||||||
(mf/deref rendered-at*)
|
|
||||||
|
|
||||||
modified-at
|
modified-at
|
||||||
(some-> (:modified-at component) (.getTime))
|
(some-> (:modified-at component) (.getTime))
|
||||||
@ -349,9 +348,9 @@
|
|||||||
;; or the component was modified after the last render
|
;; or the component was modified after the last render
|
||||||
stale?
|
stale?
|
||||||
(and (some? thumbnail-uri)
|
(and (some? thumbnail-uri)
|
||||||
(or (nil? rendered-at)
|
(or (nil? thumbnail-rendered-at)
|
||||||
(and (some? modified-at)
|
(and (some? modified-at)
|
||||||
(> modified-at rendered-at))))
|
(> modified-at thumbnail-rendered-at))))
|
||||||
|
|
||||||
on-error
|
on-error
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
|||||||
@ -258,7 +258,7 @@
|
|||||||
|
|
||||||
dest-shape (cond-> dest-shape
|
dest-shape (cond-> dest-shape
|
||||||
(some? thumbnail-data)
|
(some? thumbnail-data)
|
||||||
(assoc :thumbnail thumbnail-data))]
|
(assoc :thumbnail (:uri thumbnail-data)))]
|
||||||
[:g {:on-pointer-down start-move-position
|
[:g {:on-pointer-down start-move-position
|
||||||
:on-pointer-enter #(reset! is-hover-disabled true)
|
:on-pointer-enter #(reset! is-hover-disabled true)
|
||||||
:on-pointer-leave #(reset! is-hover-disabled false)}
|
:on-pointer-leave #(reset! is-hover-disabled false)}
|
||||||
|
|||||||
@ -9,6 +9,9 @@
|
|||||||
[app.common.thumbnails :as thc]
|
[app.common.thumbnails :as thc]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.workspace.thumbnails :as thumbnails]
|
[app.main.data.workspace.thumbnails :as thumbnails]
|
||||||
|
[app.main.repo :as rp]
|
||||||
|
[app.util.timers :as tm]
|
||||||
|
[app.util.webapi :as wapi]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[cljs.test :as t :include-macros true]
|
[cljs.test :as t :include-macros true]
|
||||||
[frontend-tests.helpers.mock :as mock]
|
[frontend-tests.helpers.mock :as mock]
|
||||||
@ -42,257 +45,267 @@
|
|||||||
:component-root true}}}}}]
|
:component-root true}}}}}]
|
||||||
(t/is (= #{["frame" root-id]
|
(t/is (= #{["frame" root-id]
|
||||||
["component" shape-b-id]}
|
["component" shape-b-id]}
|
||||||
(#'thumbnails/extract-frame-changes page-id [event [old-data new-data]]))))
|
(#'thumbnails/extract-frame-changes page-id [event [old-data new-data]])))))
|
||||||
|
|
||||||
;; --- Batch deletion queue state management ---
|
;; --- Batch deletion queue state management ---
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-adds-to-deletion-queue
|
(t/deftest clear-thumbnail-adds-to-deletion-queue
|
||||||
(let [file-id (uuid/next)
|
(let [file-id (uuid/next)
|
||||||
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
uri "blob:http://localhost/test-thumb"
|
uri "blob:http://localhost/test-thumb"
|
||||||
event (thumbnails/clear-thumbnail file-id object-id)
|
event (thumbnails/clear-thumbnail file-id object-id)
|
||||||
state {:thumbnails {object-id uri}}
|
state {:thumbnails {object-id {:uri uri :rendered-at nil}}}
|
||||||
result (ptk/update event state)]
|
result (ptk/update event state)]
|
||||||
;; Thumbnail removed from the map
|
;; Thumbnail removed from the map
|
||||||
(t/is (nil? (get-in result [:thumbnails object-id])))
|
(t/is (nil? (get-in result [:thumbnails object-id])))
|
||||||
;; Object-id added to the deletion queue with its URI
|
;; Object-id added to the deletion queue with its URI
|
||||||
(t/is (= uri (get-in result [deletion-queue-key object-id])))))
|
(t/is (= uri (get-in result [deletion-queue-key object-id])))))
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-keeps-other-thumbnails
|
(t/deftest clear-thumbnail-keeps-other-thumbnails
|
||||||
(let [file-id (uuid/next)
|
(let [file-id (uuid/next)
|
||||||
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
uri1 "blob:http://localhost/thumb-1"
|
uri1 "blob:http://localhost/thumb-1"
|
||||||
uri2 "blob:http://localhost/thumb-2"
|
uri2 "blob:http://localhost/thumb-2"
|
||||||
event (thumbnails/clear-thumbnail file-id object-id1)
|
event (thumbnails/clear-thumbnail file-id object-id1)
|
||||||
state {:thumbnails {object-id1 uri1 object-id2 uri2}}
|
state {:thumbnails {object-id1 {:uri uri1 :rendered-at nil}
|
||||||
result (ptk/update event state)]
|
object-id2 {:uri uri2 :rendered-at nil}}}
|
||||||
;; Only the cleared thumbnail is removed
|
result (ptk/update event state)]
|
||||||
(t/is (nil? (get-in result [:thumbnails object-id1])))
|
;; Only the cleared thumbnail is removed
|
||||||
(t/is (= uri2 (get-in result [:thumbnails object-id2])))
|
(t/is (nil? (get-in result [:thumbnails object-id1])))
|
||||||
;; Only the cleared thumbnail is queued
|
(t/is (= uri2 (get-in result [:thumbnails object-id2 :uri])))
|
||||||
(t/is (= uri1 (get-in result [deletion-queue-key object-id1])))
|
;; Only the cleared thumbnail is queued
|
||||||
(t/is (nil? (get-in result [deletion-queue-key object-id2])))))
|
(t/is (= uri1 (get-in result [deletion-queue-key object-id1])))
|
||||||
|
(t/is (nil? (get-in result [deletion-queue-key object-id2])))))
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-accumulates-in-queue
|
(t/deftest clear-thumbnail-accumulates-in-queue
|
||||||
(let [file-id (uuid/next)
|
(let [file-id (uuid/next)
|
||||||
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
uri1 "blob:http://localhost/thumb-1"
|
uri1 "blob:http://localhost/thumb-1"
|
||||||
uri2 "blob:http://localhost/thumb-2"
|
uri2 "blob:http://localhost/thumb-2"
|
||||||
event1 (thumbnails/clear-thumbnail file-id object-id1)
|
event1 (thumbnails/clear-thumbnail file-id object-id1)
|
||||||
event2 (thumbnails/clear-thumbnail file-id object-id2)
|
event2 (thumbnails/clear-thumbnail file-id object-id2)
|
||||||
state {:thumbnails {object-id1 uri1 object-id2 uri2}}
|
state {:thumbnails {object-id1 {:uri uri1 :rendered-at nil}
|
||||||
state1 (ptk/update event1 state)
|
object-id2 {:uri uri2 :rendered-at nil}}}
|
||||||
state2 (ptk/update event2 state1)]
|
state1 (ptk/update event1 state)
|
||||||
;; Both removed from thumbnails
|
state2 (ptk/update event2 state1)]
|
||||||
(t/is (nil? (get-in state2 [:thumbnails object-id1])))
|
;; Both removed from thumbnails
|
||||||
(t/is (nil? (get-in state2 [:thumbnails object-id2])))
|
(t/is (nil? (get-in state2 [:thumbnails object-id1])))
|
||||||
;; Both accumulated in the queue
|
(t/is (nil? (get-in state2 [:thumbnails object-id2])))
|
||||||
(t/is (= uri1 (get-in state2 [deletion-queue-key object-id1])))
|
;; Both accumulated in the queue
|
||||||
(t/is (= uri2 (get-in state2 [deletion-queue-key object-id2])))
|
(t/is (= uri1 (get-in state2 [deletion-queue-key object-id1])))
|
||||||
(t/is (= 2 (count (get state2 deletion-queue-key))))))
|
(t/is (= uri2 (get-in state2 [deletion-queue-key object-id2])))
|
||||||
|
(t/is (= 2 (count (get state2 deletion-queue-key))))))
|
||||||
|
|
||||||
(t/deftest remove-from-deletion-queue-removes-entry
|
(t/deftest remove-from-deletion-queue-removes-entry
|
||||||
(let [file-id (uuid/next)
|
(let [file-id (uuid/next)
|
||||||
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
event (thumbnails/remove-from-deletion-queue object-id)
|
event (thumbnails/remove-from-deletion-queue object-id)
|
||||||
state {deletion-queue-key {object-id "blob:http://localhost/thumb"}}
|
state {deletion-queue-key {object-id "blob:http://localhost/thumb"}}
|
||||||
result (ptk/update event state)]
|
result (ptk/update event state)]
|
||||||
(t/is (nil? (get-in result [deletion-queue-key object-id])))
|
(t/is (nil? (get-in result [deletion-queue-key object-id])))
|
||||||
(t/is (empty? (get result deletion-queue-key)))))
|
(t/is (empty? (get result deletion-queue-key)))))
|
||||||
|
|
||||||
(t/deftest remove-from-deletion-queue-keeps-other-entries
|
(t/deftest remove-from-deletion-queue-keeps-other-entries
|
||||||
(let [file-id (uuid/next)
|
(let [file-id (uuid/next)
|
||||||
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
uri1 "blob:http://localhost/thumb-1"
|
uri1 "blob:http://localhost/thumb-1"
|
||||||
uri2 "blob:http://localhost/thumb-2"
|
uri2 "blob:http://localhost/thumb-2"
|
||||||
event (thumbnails/remove-from-deletion-queue object-id1)
|
event (thumbnails/remove-from-deletion-queue object-id1)
|
||||||
state {deletion-queue-key {object-id1 uri1
|
state {deletion-queue-key {object-id1 uri1
|
||||||
object-id2 uri2}}
|
object-id2 uri2}}
|
||||||
result (ptk/update event state)]
|
result (ptk/update event state)]
|
||||||
;; Only the specified entry is removed
|
;; Only the specified entry is removed
|
||||||
(t/is (nil? (get-in result [deletion-queue-key object-id1])))
|
(t/is (nil? (get-in result [deletion-queue-key object-id1])))
|
||||||
(t/is (= uri2 (get-in result [deletion-queue-key object-id2])))
|
(t/is (= uri2 (get-in result [deletion-queue-key object-id2])))
|
||||||
(t/is (= 1 (count (get result deletion-queue-key))))))
|
(t/is (= 1 (count (get result deletion-queue-key))))))
|
||||||
|
|
||||||
(t/deftest remove-before-clear-cancels-pending-delete
|
(t/deftest remove-before-clear-cancels-pending-delete
|
||||||
|
(let [file-id (uuid/next)
|
||||||
|
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
|
uri "blob:http://localhost/thumb"
|
||||||
|
;; Step 1: clear-thumbnail queues the delete
|
||||||
|
state1 (ptk/update (thumbnails/clear-thumbnail file-id object-id)
|
||||||
|
{:thumbnails {object-id {:uri uri :rendered-at nil}}})
|
||||||
|
;; Step 2: remove-from-deletion-queue cancels the pending delete
|
||||||
|
state2 (ptk/update (thumbnails/remove-from-deletion-queue object-id)
|
||||||
|
state1)]
|
||||||
|
;; Thumbnail was removed from :thumbnails map by clear-thumbnail
|
||||||
|
(t/is (nil? (get-in state2 [:thumbnails object-id])))
|
||||||
|
;; But the deletion queue entry was cancelled by remove-from-deletion-queue
|
||||||
|
(t/is (nil? (get-in state2 [deletion-queue-key object-id])))
|
||||||
|
(t/is (empty? (get state2 deletion-queue-key)))))
|
||||||
|
|
||||||
|
(t/deftest clear-thumbnail-batch-drains-queue
|
||||||
|
(let [file-id (uuid/next)
|
||||||
|
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
|
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
|
uri1 "blob:http://localhost/thumb-1"
|
||||||
|
uri2 "blob:http://localhost/thumb-2"
|
||||||
|
;; Build up the queue state manually (simulating accumulated clear-thumbnails)
|
||||||
|
state {deletion-queue-key {object-id1 uri1 object-id2 uri2}}
|
||||||
|
event (#'thumbnails/clear-thumbnail-batch)
|
||||||
|
result (ptk/update event state)]
|
||||||
|
;; The queue is drained from application state
|
||||||
|
(t/is (empty? (get result deletion-queue-key)))))
|
||||||
|
|
||||||
|
(t/deftest clear-thumbnail-batch-empty-queue-noop
|
||||||
|
(let [state {deletion-queue-key {}}
|
||||||
|
event (#'thumbnails/clear-thumbnail-batch)
|
||||||
|
result (ptk/update event state)]
|
||||||
|
;; Queue key removed from state; rest of state unchanged
|
||||||
|
(t/is (empty? (get result deletion-queue-key)))
|
||||||
|
(t/is (= (dissoc state deletion-queue-key) (dissoc result deletion-queue-key)))))
|
||||||
|
|
||||||
|
(t/deftest assoc-thumbnail-adds-to-map
|
||||||
|
(let [object-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame")
|
||||||
|
uri "blob:http://localhost/new-thumb"
|
||||||
|
event (#'thumbnails/assoc-thumbnail object-id uri)
|
||||||
|
state {:thumbnails {}}
|
||||||
|
result (ptk/update event state)]
|
||||||
|
(t/is (= uri (get-in result [:thumbnails object-id :uri])))
|
||||||
|
(t/is (some? (get-in result [:thumbnails object-id :rendered-at])))))
|
||||||
|
|
||||||
|
(t/deftest duplicate-thumbnail-copies-entry
|
||||||
|
(let [old-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame")
|
||||||
|
new-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame")
|
||||||
|
uri "blob:http://localhost/dup-thumb"
|
||||||
|
entry {:uri uri :rendered-at nil}
|
||||||
|
event (thumbnails/duplicate-thumbnail old-id new-id)
|
||||||
|
state {:thumbnails {old-id entry}}
|
||||||
|
result (ptk/update event state)]
|
||||||
|
(t/is (= entry (get-in result [:thumbnails old-id])))
|
||||||
|
(t/is (= entry (get-in result [:thumbnails new-id])))))
|
||||||
|
|
||||||
|
;; --- Async WatchEvent tests ---
|
||||||
|
|
||||||
|
(defn- make-obj-ids
|
||||||
|
"Helper to create n properly-formatted object-ids for a single file."
|
||||||
|
[file-id n]
|
||||||
|
(vec (repeatedly n #(thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame"))))
|
||||||
|
|
||||||
|
(t/deftest clear-thumbnail-batch-watch-calls-rpc-with-object-ids
|
||||||
|
(t/async
|
||||||
|
done
|
||||||
|
(let [file-id (uuid/next)
|
||||||
|
oids (make-obj-ids file-id 3)
|
||||||
|
state {deletion-queue-key (zipmap oids (repeat "blob:http://test"))}
|
||||||
|
event (#'thumbnails/clear-thumbnail-batch)]
|
||||||
|
(ptk/update event state)
|
||||||
|
(mock/with-mocks
|
||||||
|
{rp/cmd! mock/rpc-cmd-mock
|
||||||
|
tm/schedule-on-idle mock/schedule-on-idle-mock}
|
||||||
|
(fn [done']
|
||||||
|
(->> (ptk/watch event state nil)
|
||||||
|
(rx/reduce conj [])
|
||||||
|
(rx/subs!
|
||||||
|
(fn [_] nil)
|
||||||
|
(fn [err] (t/is (nil? err)) (done'))
|
||||||
|
(fn [_]
|
||||||
|
(t/is (= 1 (count @mock/rpc-calls)))
|
||||||
|
(let [[{:keys [cmd params]}] @mock/rpc-calls]
|
||||||
|
(t/is (= :delete-file-object-thumbnails cmd))
|
||||||
|
(t/is (= (vec oids) (:object-ids params))))
|
||||||
|
(done')))))
|
||||||
|
done))))
|
||||||
|
|
||||||
|
(t/deftest clear-thumbnail-batch-watch-partitions-large-batch
|
||||||
|
(t/async
|
||||||
|
done
|
||||||
|
(let [file-id (uuid/next)
|
||||||
|
oids (make-obj-ids file-id 250)
|
||||||
|
state {deletion-queue-key (zipmap oids (repeat "blob:http://test"))}
|
||||||
|
event (#'thumbnails/clear-thumbnail-batch)]
|
||||||
|
(ptk/update event state)
|
||||||
|
(mock/with-mocks
|
||||||
|
{rp/cmd! mock/rpc-cmd-mock
|
||||||
|
tm/schedule-on-idle mock/schedule-on-idle-mock}
|
||||||
|
(fn [done']
|
||||||
|
(->> (ptk/watch event state nil)
|
||||||
|
(rx/reduce conj [])
|
||||||
|
(rx/subs!
|
||||||
|
(fn [_] nil)
|
||||||
|
(fn [err] (t/is (nil? err)) (done'))
|
||||||
|
(fn [_]
|
||||||
|
(t/is (= 2 (count @mock/rpc-calls)))
|
||||||
|
(let [[c1 c2] @mock/rpc-calls]
|
||||||
|
(t/is (= :delete-file-object-thumbnails (:cmd c1)))
|
||||||
|
(t/is (= :delete-file-object-thumbnails (:cmd c2)))
|
||||||
|
(t/is (= 200 (count (:object-ids (:params c1)))))
|
||||||
|
(t/is (= 50 (count (:object-ids (:params c2)))))
|
||||||
|
(t/is (= (set oids)
|
||||||
|
(set (concat (:object-ids (:params c1))
|
||||||
|
(:object-ids (:params c2)))))))
|
||||||
|
(done')))))
|
||||||
|
done))))
|
||||||
|
|
||||||
|
(t/deftest clear-thumbnail-batch-watch-revokes-blob-uris
|
||||||
|
(t/async
|
||||||
|
done
|
||||||
|
(let [file-id (uuid/next)
|
||||||
|
oids (make-obj-ids file-id 2)
|
||||||
|
uris ["blob:http://localhost/thumb-1"
|
||||||
|
"blob:http://localhost/thumb-2"]
|
||||||
|
state {deletion-queue-key (zipmap oids uris)}
|
||||||
|
event (#'thumbnails/clear-thumbnail-batch)]
|
||||||
|
(ptk/update event state)
|
||||||
|
(mock/with-mocks
|
||||||
|
{rp/cmd! mock/rpc-cmd-mock
|
||||||
|
wapi/revoke-uri mock/revoke-uri-mock
|
||||||
|
tm/schedule-on-idle mock/schedule-on-idle-mock}
|
||||||
|
(fn [done']
|
||||||
|
(->> (ptk/watch event state nil)
|
||||||
|
(rx/reduce conj [])
|
||||||
|
(rx/subs!
|
||||||
|
(fn [_] nil)
|
||||||
|
(fn [err] (t/is (nil? err)) (done'))
|
||||||
|
(fn [_]
|
||||||
|
(t/is (= (set uris) (set @mock/revoked-uris)))
|
||||||
|
(done')))))
|
||||||
|
done))))
|
||||||
|
|
||||||
|
(t/deftest clear-thumbnail-batch-watch-empty-queue-no-rpc
|
||||||
|
(t/async
|
||||||
|
done
|
||||||
|
(let [event (#'thumbnails/clear-thumbnail-batch)
|
||||||
|
state {}]
|
||||||
|
(ptk/update event state)
|
||||||
|
(mock/with-mocks
|
||||||
|
{rp/cmd! mock/rpc-cmd-mock
|
||||||
|
tm/schedule-on-idle mock/schedule-on-idle-mock}
|
||||||
|
(fn [done']
|
||||||
|
(->> (ptk/watch event state nil)
|
||||||
|
(rx/reduce conj [])
|
||||||
|
(rx/subs!
|
||||||
|
(fn [_] nil)
|
||||||
|
(fn [err] (t/is (nil? err)) (done'))
|
||||||
|
(fn [_]
|
||||||
|
(t/is (empty? @mock/rpc-calls))
|
||||||
|
(done')))))
|
||||||
|
done))))
|
||||||
|
|
||||||
|
(t/deftest clear-thumbnail-watch-emits-batch-after-debounce
|
||||||
|
(t/async
|
||||||
|
done
|
||||||
(let [file-id (uuid/next)
|
(let [file-id (uuid/next)
|
||||||
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
||||||
uri "blob:http://localhost/thumb"
|
uri "blob:http://localhost/thumb"
|
||||||
;; Step 1: clear-thumbnail queues the delete
|
state {:thumbnails {object-id {:uri uri :rendered-at nil}}}
|
||||||
state1 (ptk/update (thumbnails/clear-thumbnail file-id object-id)
|
event (thumbnails/clear-thumbnail file-id object-id)]
|
||||||
{:thumbnails {object-id uri}})
|
(ptk/update event state)
|
||||||
;; Step 2: remove-from-deletion-queue cancels the pending delete
|
(mock/with-mocks
|
||||||
state2 (ptk/update (thumbnails/remove-from-deletion-queue object-id)
|
{rx/timer mock/timer-mock}
|
||||||
state1)]
|
(fn [done']
|
||||||
;; Thumbnail was removed from :thumbnails map by clear-thumbnail
|
(let [stream (rx/subject)]
|
||||||
(t/is (nil? (get-in state2 [:thumbnails object-id])))
|
(->> (ptk/watch event state stream)
|
||||||
;; But the deletion queue entry was cancelled by remove-from-deletion-queue
|
|
||||||
(t/is (nil? (get-in state2 [deletion-queue-key object-id])))
|
|
||||||
(t/is (empty? (get state2 deletion-queue-key)))))
|
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-batch-drains-queue
|
|
||||||
(let [file-id (uuid/next)
|
|
||||||
object-id1 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
|
||||||
object-id2 (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
|
||||||
uri1 "blob:http://localhost/thumb-1"
|
|
||||||
uri2 "blob:http://localhost/thumb-2"
|
|
||||||
;; Build up the queue state manually (simulating accumulated clear-thumbnails)
|
|
||||||
state {deletion-queue-key {object-id1 uri1 object-id2 uri2}}
|
|
||||||
event (#'thumbnails/clear-thumbnail-batch)
|
|
||||||
result (ptk/update event state)]
|
|
||||||
;; The queue is drained from application state
|
|
||||||
(t/is (empty? (get result deletion-queue-key)))))
|
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-batch-empty-queue-noop
|
|
||||||
(let [state {deletion-queue-key {}}
|
|
||||||
event (#'thumbnails/clear-thumbnail-batch)
|
|
||||||
result (ptk/update event state)]
|
|
||||||
;; State unchanged when queue is already empty
|
|
||||||
(t/is (empty? (get result deletion-queue-key)))
|
|
||||||
(t/is (= state (dissoc result deletion-queue-key)))))
|
|
||||||
|
|
||||||
(t/deftest assoc-thumbnail-adds-to-map
|
|
||||||
(let [object-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame")
|
|
||||||
uri "blob:http://localhost/new-thumb"
|
|
||||||
event (#'thumbnails/assoc-thumbnail object-id uri)
|
|
||||||
state {:thumbnails {}}
|
|
||||||
result (ptk/update event state)]
|
|
||||||
(t/is (= uri (get-in result [:thumbnails object-id])))))
|
|
||||||
|
|
||||||
(t/deftest duplicate-thumbnail-copies-entry
|
|
||||||
(let [old-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame")
|
|
||||||
new-id (thc/fmt-object-id (uuid/next) (uuid/next) (uuid/next) "frame")
|
|
||||||
uri "blob:http://localhost/dup-thumb"
|
|
||||||
event (thumbnails/duplicate-thumbnail old-id new-id)
|
|
||||||
state {:thumbnails {old-id uri}}
|
|
||||||
result (ptk/update event state)]
|
|
||||||
(t/is (= uri (get-in result [:thumbnails old-id])))
|
|
||||||
(t/is (= uri (get-in result [:thumbnails new-id])))))
|
|
||||||
|
|
||||||
;; --- Async WatchEvent tests ---
|
|
||||||
|
|
||||||
(defn- make-obj-ids
|
|
||||||
"Helper to create n properly-formatted object-ids for a single file."
|
|
||||||
[file-id n]
|
|
||||||
(vec (repeatedly n #(thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame"))))
|
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-batch-watch-calls-rpc-with-object-ids
|
|
||||||
(t/async done
|
|
||||||
(let [file-id (uuid/next)
|
|
||||||
oids (make-obj-ids file-id 3)
|
|
||||||
state {deletion-queue-key (zipmap oids (repeat "blob:http://test"))}
|
|
||||||
event (#'thumbnails/clear-thumbnail-batch)]
|
|
||||||
(ptk/update event state)
|
|
||||||
(mock/with-mocks
|
|
||||||
{app.main.repo/cmd! mock/rpc-cmd-mock
|
|
||||||
app.util.timers/schedule-on-idle mock/schedule-on-idle-mock}
|
|
||||||
(fn [done']
|
|
||||||
(->> (ptk/watch event state nil)
|
|
||||||
(rx/reduce conj [])
|
(rx/reduce conj [])
|
||||||
(rx/subs!
|
(rx/subs!
|
||||||
(fn [_] nil)
|
|
||||||
(fn [err] (t/is (nil? err)) (done'))
|
|
||||||
(fn [_]
|
|
||||||
(t/is (= 1 (count @mock/rpc-calls)))
|
|
||||||
(let [[{:keys [cmd params]}] @mock/rpc-calls]
|
|
||||||
(t/is (= :delete-file-object-thumbnails cmd))
|
|
||||||
(t/is (= (vec oids) (:object-ids params))))
|
|
||||||
(done')))))
|
|
||||||
done))))
|
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-batch-watch-partitions-large-batch
|
|
||||||
(t/async done
|
|
||||||
(let [file-id (uuid/next)
|
|
||||||
oids (make-obj-ids file-id 250)
|
|
||||||
state {deletion-queue-key (zipmap oids (repeat "blob:http://test"))}
|
|
||||||
event (#'thumbnails/clear-thumbnail-batch)]
|
|
||||||
(ptk/update event state)
|
|
||||||
(mock/with-mocks
|
|
||||||
{app.main.repo/cmd! mock/rpc-cmd-mock
|
|
||||||
app.util.timers/schedule-on-idle mock/schedule-on-idle-mock}
|
|
||||||
(fn [done']
|
|
||||||
(->> (ptk/watch event state nil)
|
|
||||||
(rx/reduce conj [])
|
|
||||||
(rx/subs!
|
|
||||||
(fn [_] nil)
|
|
||||||
(fn [err] (t/is (nil? err)) (done'))
|
|
||||||
(fn [_]
|
|
||||||
(t/is (= 2 (count @mock/rpc-calls)))
|
|
||||||
(let [[c1 c2] @mock/rpc-calls]
|
|
||||||
(t/is (= :delete-file-object-thumbnails (:cmd c1)))
|
|
||||||
(t/is (= :delete-file-object-thumbnails (:cmd c2)))
|
|
||||||
(t/is (= 200 (count (:object-ids (:params c1)))))
|
|
||||||
(t/is (= 50 (count (:object-ids (:params c2)))))
|
|
||||||
(t/is (= (set oids)
|
|
||||||
(set (concat (:object-ids (:params c1))
|
|
||||||
(:object-ids (:params c2)))))))
|
|
||||||
(done')))))
|
|
||||||
done))))
|
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-batch-watch-revokes-blob-uris
|
|
||||||
(t/async done
|
|
||||||
(let [file-id (uuid/next)
|
|
||||||
oids (make-obj-ids file-id 2)
|
|
||||||
uris ["blob:http://localhost/thumb-1"
|
|
||||||
"blob:http://localhost/thumb-2"]
|
|
||||||
state {deletion-queue-key (zipmap oids uris)}
|
|
||||||
event (#'thumbnails/clear-thumbnail-batch)]
|
|
||||||
(ptk/update event state)
|
|
||||||
(mock/with-mocks
|
|
||||||
{app.main.repo/cmd! (fn [_ _] (rx/of nil))
|
|
||||||
app.util.webapi/revoke-uri mock/revoke-uri-mock
|
|
||||||
app.util.timers/schedule-on-idle mock/schedule-on-idle-mock}
|
|
||||||
(fn [done']
|
|
||||||
(->> (ptk/watch event state nil)
|
|
||||||
(rx/reduce conj [])
|
|
||||||
(rx/subs!
|
|
||||||
(fn [_] nil)
|
|
||||||
(fn [err] (t/is (nil? err)) (done'))
|
|
||||||
(fn [_]
|
|
||||||
(t/is (= (set uris) (set @mock/revoked-uris)))
|
|
||||||
(done')))))
|
|
||||||
done))))
|
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-batch-watch-empty-queue-no-rpc
|
|
||||||
(t/async done
|
|
||||||
(let [event (#'thumbnails/clear-thumbnail-batch)
|
|
||||||
state {}]
|
|
||||||
(ptk/update event state)
|
|
||||||
(mock/with-mocks
|
|
||||||
{app.main.repo/cmd! mock/rpc-cmd-mock
|
|
||||||
app.util.timers/schedule-on-idle mock/schedule-on-idle-mock}
|
|
||||||
(fn [done']
|
|
||||||
(->> (ptk/watch event state nil)
|
|
||||||
(rx/reduce conj [])
|
|
||||||
(rx/subs!
|
|
||||||
(fn [_] nil)
|
|
||||||
(fn [err] (t/is (nil? err)) (done'))
|
|
||||||
(fn [_]
|
|
||||||
(t/is (empty? @mock/rpc-calls))
|
|
||||||
(done')))))
|
|
||||||
done))))
|
|
||||||
|
|
||||||
(t/deftest clear-thumbnail-watch-emits-batch-after-debounce
|
|
||||||
(t/async done
|
|
||||||
(let [file-id (uuid/next)
|
|
||||||
object-id (thc/fmt-object-id file-id (uuid/next) (uuid/next) "frame")
|
|
||||||
uri "blob:http://localhost/thumb"
|
|
||||||
state {:thumbnails {object-id uri}}
|
|
||||||
event (thumbnails/clear-thumbnail file-id object-id)]
|
|
||||||
(ptk/update event state)
|
|
||||||
(mock/with-mocks
|
|
||||||
{beicon.v2.core/timer mock/timer-mock}
|
|
||||||
(fn [done']
|
|
||||||
(->> (ptk/watch event state nil)
|
|
||||||
(rx/reduce conj [])
|
|
||||||
(rx/subs!
|
|
||||||
(fn [_] nil)
|
|
||||||
(fn [err] (t/is (nil? err)) (done'))
|
|
||||||
(fn [events]
|
(fn [events]
|
||||||
(t/is (= 1 (count events)))
|
(t/is (= 1 (count events)))
|
||||||
(t/is (ptk/event? (first events)))
|
(t/is (ptk/event? (first events)))
|
||||||
(done')))))
|
(done'))
|
||||||
done)))))
|
(fn [err] (t/is (nil? err)) (done'))
|
||||||
|
nil))))
|
||||||
|
done))))
|
||||||
|
|||||||
@ -106,9 +106,12 @@
|
|||||||
|
|
||||||
(defn rpc-cmd-mock
|
(defn rpc-cmd-mock
|
||||||
"Records [cmd params] in [[rpc-calls]], returns `(rx/of nil)`."
|
"Records [cmd params] in [[rpc-calls]], returns `(rx/of nil)`."
|
||||||
[cmd params]
|
([cmd params]
|
||||||
(swap! rpc-calls conj {:cmd cmd :params params})
|
(swap! rpc-calls conj {:cmd cmd :params params})
|
||||||
(rx/of nil))
|
(rx/of nil))
|
||||||
|
([cmd params _opts]
|
||||||
|
(swap! rpc-calls conj {:cmd cmd :params params})
|
||||||
|
(rx/of nil)))
|
||||||
|
|
||||||
(defn revoke-uri-mock
|
(defn revoke-uri-mock
|
||||||
"Records `uri` in [[revoked-uris]]."
|
"Records `uri` in [[revoked-uris]]."
|
||||||
@ -117,8 +120,10 @@
|
|||||||
|
|
||||||
(defn schedule-on-idle-mock
|
(defn schedule-on-idle-mock
|
||||||
"Calls `f` immediately instead of deferring to the idle queue."
|
"Calls `f` immediately instead of deferring to the idle queue."
|
||||||
[f]
|
([_ms f]
|
||||||
(f))
|
(f))
|
||||||
|
([f]
|
||||||
|
(f)))
|
||||||
|
|
||||||
(defn timer-mock
|
(defn timer-mock
|
||||||
"Returns `(rx/of :immediate)` so debounce timers fire instantly
|
"Returns `(rx/of :immediate)` so debounce timers fire instantly
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user