penpot/frontend/test/frontend_tests/util_queue_test.cljs
2026-09-23 15:05:00 +02:00

103 lines
3.9 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.util-queue-test
(:require
[app.util.queue :as q]
[beicon.v2.core :as rx]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.async :as async]))
(defn- same-key?
[request item]
(= (unchecked-get request "key")
(unchecked-get item "key")))
(defn- new-queue
[]
;; A high threshold lets queued items run back to back.
(q/create same-key? 10000))
(defn- request
[key tag]
#js {:key key :tag tag})
(defn- work
"Returns a render fn that records `id` in `calls` and emits it."
[calls id]
(fn []
(swap! calls conj id)
(rx/of id)))
(defn- collect
"Returns a promise resolving with the values `ob` emitted."
[ob]
(let [values (atom [])]
(-> (async/observe ob :on-next #(swap! values conj %) :timeout-ms 5000)
(.then (fn [_] @values)))))
(defn- pending
[queue]
(.-length (unchecked-get queue "items")))
(t/deftest ^:async matching-requests-share-one-run-of-the-latest-work
;; Scenario: two requests for the same key are queued before the queue
;; runs. Proves: one item is queued, only the latest work runs, and
;; both callers receive its result.
(let [queue (new-queue)
calls (atom [])
req-1 (q/enqueue-unique queue (request "a" "component") (work calls :first))
req-2 (q/enqueue-unique queue (request "a" "component") (work calls :second))]
(t/is (= 1 (pending queue)))
(let [results (await (js/Promise.all #js [(collect req-1) (collect req-2)]))]
(t/is (= [[:second] [:second]] (js->clj results)))
(t/is (= [:second] @calls)))
(q/clear! queue)))
(t/deftest ^:async different-requests-run-separately
;; Scenario: two requests with different keys. Proves: both are queued
;; and each caller receives its own result.
(let [queue (new-queue)
calls (atom [])
a (q/enqueue-unique queue (request "a" "component") (work calls :a))
b (q/enqueue-unique queue (request "b" "component") (work calls :b))]
(t/is (= 2 (pending queue)))
(let [results (await (js/Promise.all #js [(collect a) (collect b)]))]
(t/is (= [[:a] [:b]] (js->clj results)))
(t/is (= [:a :b] @calls)))
(q/clear! queue)))
(t/deftest ^:async frame-requests-run-first-and-merge
;; Scenario: a component request, then two frame requests for the same
;; key. Proves: the frames merge into one item that runs before the
;; component, using the latest frame work.
(let [queue (new-queue)
calls (atom [])
component (q/enqueue-unique queue (request "a" "component") (work calls :component))
frame-1 (q/enqueue-unique queue (request "b" "frame") (work calls :frame-1))
frame-2 (q/enqueue-unique queue (request "b" "frame") (work calls :frame-2))]
(t/is (= 2 (pending queue)))
(let [results (await (js/Promise.all #js [(collect component)
(collect frame-1)
(collect frame-2)]))]
(t/is (= [[:component] [:frame-2] [:frame-2]] (js->clj results)))
(t/is (= [:frame-2 :component] @calls)))
(q/clear! queue)))
(t/deftest ^:async request-after-run-starts-is-queued-again
;; Scenario: a second matching request arrives once the first one has
;; left the queue. Proves: it is queued as new work, not merged into
;; work that already ran.
(let [queue (new-queue)
calls (atom [])
req-1 (q/enqueue-unique queue (request "a" "component") (work calls :first))]
(t/is (= [:first] (await (collect req-1))))
(let [req-2 (q/enqueue-unique queue (request "a" "component") (work calls :second))]
(t/is (= 1 (pending queue)))
(t/is (= [:second] (await (collect req-2))))
(t/is (= [:first :second] @calls)))
(q/clear! queue)))