♻️ Recycle frontend tests with wasm mocks (#8681)

This commit is contained in:
Andrey Antukh
2026-03-23 12:11:27 +01:00
committed by GitHub
parent 884cdbbf8d
commit 43cdb91063
6 changed files with 484 additions and 238 deletions

View File

@@ -0,0 +1,192 @@
;; 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 INC
(ns frontend-tests.helpers.wasm
"Test helpers for mocking WASM API boundary functions.
In the Node.js test environment the WASM binary is not available,
but the `render-wasm/v1` feature flag is enabled by default, so
every geometry-modifying event takes the WASM code path.
This namespace provides lightweight mock implementations that let
the Clojure-side logic execute normally while stubbing out every
call that would touch the WASM heap.
Each mock tracks how many times it was called via `call-counts`.
Use `(call-count :propagate-modifiers)` in test assertions to
verify the WASM code path was exercised."
(:require
[app.common.data :as d]
[app.render-wasm.api :as wasm.api]
[app.render-wasm.api.fonts :as wasm.fonts]))
;; --- Call tracking ---------------------------------------------------
(def ^:private call-counts
"Atom holding a map of mock-name → number of calls since last reset."
(atom {}))
(defn- track!
"Increment the call count for `mock-name`."
[mock-name]
(swap! call-counts update mock-name (fnil inc 0)))
(defn call-count
"Return how many times mock `mock-name` was called since setup.
`mock-name` is a keyword, e.g. `:propagate-modifiers`."
[mock-name]
(get @call-counts mock-name 0))
(defn reset-call-counts!
"Reset all call counts to zero."
[]
(reset! call-counts {}))
;; --- Mock implementations --------------------------------------------
(defn- mock-propagate-modifiers
"Passthrough mock for `wasm.api/propagate-modifiers`.
Receives `entries` — a vector of `[uuid {:transform matrix, :kind kw}]`
pairs produced by `parse-geometry-modifiers` — and returns a vector
of `[uuid matrix]` pairs that `apply-wasm-modifiers` converts to a
transforms map via `(into {} result)`.
This effectively tells the caller \"apply exactly the transform that
was requested\", which is what the real WASM engine does for simple
moves / resizes without constraints."
[entries _pixel-precision]
(track! :propagate-modifiers)
(when (d/not-empty? entries)
(into []
(map (fn [[id data]]
(d/vec2 id (:transform data))))
entries)))
(defn- mock-clean-modifiers
[]
(track! :clean-modifiers)
nil)
(defn- mock-set-structure-modifiers
[_entries]
(track! :set-structure-modifiers)
nil)
(defn- mock-set-shape-grow-type
[_grow-type]
(track! :set-shape-grow-type)
nil)
(defn- mock-set-shape-text-content
[_shape-id _content]
(track! :set-shape-text-content)
nil)
(defn- mock-set-shape-text-images
([_shape-id _content]
(track! :set-shape-text-images)
nil)
([_shape-id _content _thumbnail?]
(track! :set-shape-text-images)
nil))
(defn- mock-get-text-dimensions
([]
(track! :get-text-dimensions)
{:x 0 :y 0 :width 100 :height 20 :max-width 100})
([_id]
(track! :get-text-dimensions)
{:x 0 :y 0 :width 100 :height 20 :max-width 100}))
(defn- mock-font-stored?
[_font-data _emoji?]
(track! :font-stored?)
true)
(defn- mock-make-font-data
[font]
(track! :make-font-data)
{:wasm-id 0
:weight (or (:font-weight font) "400")
:style (or (:font-style font) "normal")
:emoji? false})
(defn- mock-get-content-fonts
[_content]
(track! :get-content-fonts)
[])
;; --- Persistent mock installation via `set!` --------------------------
;;
;; These use `set!` to directly mutate the module-level JS vars, making
;; the mocks persist across async boundaries. They are intended to be
;; used with `t/use-fixtures :each` which correctly sequences `:after`
;; to run only after the async test's `done` callback fires.
(def ^:private originals
"Stores the original WASM function values so they can be restored."
(atom {}))
(defn setup-wasm-mocks!
"Install WASM mocks via `set!` that persist across async boundaries.
Also resets call counts. Call `teardown-wasm-mocks!` to restore."
[]
;; Reset call tracking
(reset-call-counts!)
;; Save originals
(reset! originals
{:clean-modifiers wasm.api/clean-modifiers
:set-structure-modifiers wasm.api/set-structure-modifiers
:propagate-modifiers wasm.api/propagate-modifiers
:set-shape-grow-type wasm.api/set-shape-grow-type
:set-shape-text-content wasm.api/set-shape-text-content
:set-shape-text-images wasm.api/set-shape-text-images
:get-text-dimensions wasm.api/get-text-dimensions
:font-stored? wasm.fonts/font-stored?
:make-font-data wasm.fonts/make-font-data
:get-content-fonts wasm.fonts/get-content-fonts})
;; Install mocks
(set! wasm.api/clean-modifiers mock-clean-modifiers)
(set! wasm.api/set-structure-modifiers mock-set-structure-modifiers)
(set! wasm.api/propagate-modifiers mock-propagate-modifiers)
(set! wasm.api/set-shape-grow-type mock-set-shape-grow-type)
(set! wasm.api/set-shape-text-content mock-set-shape-text-content)
(set! wasm.api/set-shape-text-images mock-set-shape-text-images)
(set! wasm.api/get-text-dimensions mock-get-text-dimensions)
(set! wasm.fonts/font-stored? mock-font-stored?)
(set! wasm.fonts/make-font-data mock-make-font-data)
(set! wasm.fonts/get-content-fonts mock-get-content-fonts))
(defn teardown-wasm-mocks!
"Restore the original WASM functions saved by `setup-wasm-mocks!`."
[]
(let [orig @originals]
(set! wasm.api/clean-modifiers (:clean-modifiers orig))
(set! wasm.api/set-structure-modifiers (:set-structure-modifiers orig))
(set! wasm.api/propagate-modifiers (:propagate-modifiers orig))
(set! wasm.api/set-shape-grow-type (:set-shape-grow-type orig))
(set! wasm.api/set-shape-text-content (:set-shape-text-content orig))
(set! wasm.api/set-shape-text-images (:set-shape-text-images orig))
(set! wasm.api/get-text-dimensions (:get-text-dimensions orig))
(set! wasm.fonts/font-stored? (:font-stored? orig))
(set! wasm.fonts/make-font-data (:make-font-data orig))
(set! wasm.fonts/get-content-fonts (:get-content-fonts orig)))
(reset! originals {}))
(defn with-wasm-mocks*
"Calls `(thunk)` with all WASM API boundary functions replaced by
safe mocks, restoring the originals when the thunk returns.
NOTE: Teardown happens synchronously when `thunk` returns. For
async tests (e.g. those using `tohs/run-store-async`), use
`setup-wasm-mocks!` / `teardown-wasm-mocks!` via
`t/use-fixtures :each` instead."
[thunk]
(setup-wasm-mocks!)
(try
(thunk)
(finally
(teardown-wasm-mocks!))))

View File

@@ -23,11 +23,15 @@
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
[frontend-tests.helpers.wasm :as thw]
[frontend-tests.tokens.helpers.state :as tohs]
[frontend-tests.tokens.helpers.tokens :as toht]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
{:before (fn []
(thp/reset-idmap!)
(thw/setup-wasm-mocks!))
:after thw/teardown-wasm-mocks!})
(defn- setup-base-file
[]
@@ -426,7 +430,10 @@
(t/is (mth/close? (get c-frame1' :width) 200))
(t/is (mth/close? (get c-frame1' :height) 200))
(t/is (empty? (:touched c-frame1'))))))))]
(t/is (empty? (:touched c-frame1')))
(t/testing "WASM mocks were exercised"
(t/is (pos? (thw/call-count :propagate-modifiers)))))))))]
(tohs/run-store-async
store step2 events identity))))

View File

@@ -13,7 +13,8 @@
[app.main.data.workspace.guides :as-alias dwg]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]))
[frontend-tests.helpers.state :as ths]
[frontend-tests.helpers.wasm :as thw]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
@@ -22,35 +23,43 @@
(t/deftest test-remove-swap-slot-copy-paste-blue1-to-root
(t/async
done
(let [;; ==== Setup
file (-> (cthf/sample-file :file1)
(ctho/add-frame :frame1))
store (ths/setup-store file)
frame1 (cths/get-shape file :frame1)
(thw/with-wasm-mocks*
(fn []
(let [;; ==== Setup
file (-> (cthf/sample-file :file1)
(ctho/add-frame :frame1))
store (ths/setup-store file)
frame1 (cths/get-shape file :frame1)
guide {:axis :x
:frame-id (:id frame1)
:id (uuid/next)
:position 0}
guide {:axis :x
:frame-id (:id frame1)
:id (uuid/next)
:position 0}
;; ==== Action
events
[(dw/update-guides guide)
(dw/update-position (:id frame1) {:x 100})]]
;; ==== Action
events
[(dw/update-guides guide)
(dw/update-position (:id frame1) {:x 100})]]
(ths/run-store
store done events
(fn [new-state]
(let [;; ==== Get
file' (ths/get-file-from-state new-state)
page' (cthf/current-page file')
(ths/run-store
store done events
(fn [new-state]
(let [;; ==== Get
file' (ths/get-file-from-state new-state)
page' (cthf/current-page file')
guide' (-> page'
:guides
(vals)
(first))]
;; ==== Check
;; guide has moved
(t/is (= (:position guide') 100))
;; WASM mocks were exercised
(t/is (pos? (thw/call-count :clean-modifiers)))
(t/is (pos? (thw/call-count :set-structure-modifiers)))
(t/is (pos? (thw/call-count :propagate-modifiers)))))))))))
guide' (-> page'
:guides
(vals)
(first))]
;; ==== Check
;; guide has moved
(t/is (= (:position guide') 100))))))))

View File

@@ -12,242 +12,250 @@
[app.main.store :as st]
[app.plugins.api :as api]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.state :as ths]))
[frontend-tests.helpers.state :as ths]
[frontend-tests.helpers.wasm :as thw]))
(t/deftest test-common-shape-properties
(let [;; ==== Setup
store (ths/setup-store (cthf/sample-file :file1 :page-label :page1))
(thw/with-wasm-mocks*
(fn []
(let [;; ==== Setup
store (ths/setup-store (cthf/sample-file :file1 :page-label :page1))
^js context (api/create-context "00000000-0000-0000-0000-000000000000")
^js context (api/create-context "00000000-0000-0000-0000-000000000000")
_ (set! st/state store)
_ (set! st/state store)
^js file (. context -currentFile)
^js page (. context -currentPage)
^js shape (.createRectangle context)
^js file (. context -currentFile)
^js page (. context -currentPage)
^js shape (.createRectangle context)
get-shape-path
#(vector :files (aget file "$id") :data :pages-index (aget page "$id") :objects (aget shape "$id") %)]
get-shape-path
#(vector :files (aget file "$id") :data :pages-index (aget page "$id") :objects (aget shape "$id") %)]
(t/testing "Basic shape properites"
(t/testing " - name"
(set! (.-name shape) "TEST")
(t/is (= (.-name shape) "TEST"))
(t/is (= (get-in @store (get-shape-path :name)) "TEST")))
(t/testing "Basic shape properites"
(t/testing " - name"
(set! (.-name shape) "TEST")
(t/is (= (.-name shape) "TEST"))
(t/is (= (get-in @store (get-shape-path :name)) "TEST")))
(t/testing " - x"
(set! (.-x shape) 10)
(t/is (= (.-x shape) 10))
(t/is (= (get-in @store (get-shape-path :x)) 10))
(t/testing " - x"
(set! (.-x shape) 10)
(t/is (= (.-x shape) 10))
(t/is (= (get-in @store (get-shape-path :x)) 10))
(set! (.-x shape) "fail")
(t/is (= (.-x shape) 10))
(t/is (= (get-in @store (get-shape-path :x)) 10)))
(set! (.-x shape) "fail")
(t/is (= (.-x shape) 10))
(t/is (= (get-in @store (get-shape-path :x)) 10)))
(t/testing " - y"
(set! (.-y shape) 50)
(t/is (= (.-y shape) 50))
(t/is (= (get-in @store (get-shape-path :y)) 50))
(t/testing " - y"
(set! (.-y shape) 50)
(t/is (= (.-y shape) 50))
(t/is (= (get-in @store (get-shape-path :y)) 50))
(set! (.-y shape) "fail")
(t/is (= (.-y shape) 50))
(t/is (= (get-in @store (get-shape-path :y)) 50)))
(set! (.-y shape) "fail")
(t/is (= (.-y shape) 50))
(t/is (= (get-in @store (get-shape-path :y)) 50)))
(t/testing " - resize"
(.resize shape 250 300)
(t/is (= (.-width shape) 250))
(t/is (= (.-height shape) 300))
(t/is (= (get-in @store (get-shape-path :width)) 250))
(t/is (= (get-in @store (get-shape-path :height)) 300))
(t/testing " - resize"
(.resize shape 250 300)
(t/is (= (.-width shape) 250))
(t/is (= (.-height shape) 300))
(t/is (= (get-in @store (get-shape-path :width)) 250))
(t/is (= (get-in @store (get-shape-path :height)) 300))
(.resize shape 0 0)
(t/is (= (.-width shape) 250))
(t/is (= (.-height shape) 300))
(t/is (= (get-in @store (get-shape-path :width)) 250))
(t/is (= (get-in @store (get-shape-path :height)) 300)))
(.resize shape 0 0)
(t/is (= (.-width shape) 250))
(t/is (= (.-height shape) 300))
(t/is (= (get-in @store (get-shape-path :width)) 250))
(t/is (= (get-in @store (get-shape-path :height)) 300)))
(t/testing " - blocked"
(set! (.-blocked shape) true)
(t/is (= (.-blocked shape) true))
(t/is (= (get-in @store (get-shape-path :blocked)) true))
(t/testing " - blocked"
(set! (.-blocked shape) true)
(t/is (= (.-blocked shape) true))
(t/is (= (get-in @store (get-shape-path :blocked)) true))
(set! (.-blocked shape) false)
(t/is (= (.-blocked shape) false))
(t/is (= (get-in @store (get-shape-path :blocked)) false)))
(set! (.-blocked shape) false)
(t/is (= (.-blocked shape) false))
(t/is (= (get-in @store (get-shape-path :blocked)) false)))
(t/testing " - hidden"
(set! (.-hidden shape) true)
(t/is (= (.-hidden shape) true))
(t/is (= (get-in @store (get-shape-path :hidden)) true))
(t/testing " - hidden"
(set! (.-hidden shape) true)
(t/is (= (.-hidden shape) true))
(t/is (= (get-in @store (get-shape-path :hidden)) true))
(set! (.-hidden shape) false)
(t/is (= (.-hidden shape) false))
(t/is (= (get-in @store (get-shape-path :hidden)) false)))
(set! (.-hidden shape) false)
(t/is (= (.-hidden shape) false))
(t/is (= (get-in @store (get-shape-path :hidden)) false)))
(t/testing " - proportionLock"
(set! (.-proportionLock shape) true)
(t/is (= (.-proportionLock shape) true))
(t/is (= (get-in @store (get-shape-path :proportion-lock)) true)))
(t/testing " - proportionLock"
(set! (.-proportionLock shape) true)
(t/is (= (.-proportionLock shape) true))
(t/is (= (get-in @store (get-shape-path :proportion-lock)) true)))
(t/testing " - constraintsHorizontal"
(set! (.-constraintsHorizontal shape) "fail")
(t/is (not= (.-constraintsHorizontal shape) "fail"))
(t/is (not= (get-in @store (get-shape-path :constraints-h)) "fail"))
(t/testing " - constraintsHorizontal"
(set! (.-constraintsHorizontal shape) "fail")
(t/is (not= (.-constraintsHorizontal shape) "fail"))
(t/is (not= (get-in @store (get-shape-path :constraints-h)) "fail"))
(set! (.-constraintsHorizontal shape) "right")
(t/is (= (.-constraintsHorizontal shape) "right"))
(t/is (= (get-in @store (get-shape-path :constraints-h)) :right)))
(set! (.-constraintsHorizontal shape) "right")
(t/is (= (.-constraintsHorizontal shape) "right"))
(t/is (= (get-in @store (get-shape-path :constraints-h)) :right)))
(t/testing " - constraintsVertical"
(set! (.-constraintsVertical shape) "fail")
(t/is (not= (.-constraintsVertical shape) "fail"))
(t/is (not= (get-in @store (get-shape-path :constraints-v)) "fail"))
(t/testing " - constraintsVertical"
(set! (.-constraintsVertical shape) "fail")
(t/is (not= (.-constraintsVertical shape) "fail"))
(t/is (not= (get-in @store (get-shape-path :constraints-v)) "fail"))
(set! (.-constraintsVertical shape) "bottom")
(t/is (= (.-constraintsVertical shape) "bottom"))
(t/is (= (get-in @store (get-shape-path :constraints-v)) :bottom)))
(set! (.-constraintsVertical shape) "bottom")
(t/is (= (.-constraintsVertical shape) "bottom"))
(t/is (= (get-in @store (get-shape-path :constraints-v)) :bottom)))
(t/testing " - borderRadius"
(set! (.-borderRadius shape) 10)
(t/is (= (.-borderRadius shape) 10))
(t/is (= (get-in @store (get-shape-path :r1)) 10))
(t/testing " - borderRadius"
(set! (.-borderRadius shape) 10)
(t/is (= (.-borderRadius shape) 10))
(t/is (= (get-in @store (get-shape-path :r1)) 10))
(set! (.-borderRadiusTopLeft shape) 20)
(t/is (= (.-borderRadiusTopLeft shape) 20))
(t/is (= (get-in @store (get-shape-path :r1)) 20))
(t/is (= (get-in @store (get-shape-path :r2)) 10))
(t/is (= (get-in @store (get-shape-path :r3)) 10))
(t/is (= (get-in @store (get-shape-path :r4)) 10))
(set! (.-borderRadiusTopLeft shape) 20)
(t/is (= (.-borderRadiusTopLeft shape) 20))
(t/is (= (get-in @store (get-shape-path :r1)) 20))
(t/is (= (get-in @store (get-shape-path :r2)) 10))
(t/is (= (get-in @store (get-shape-path :r3)) 10))
(t/is (= (get-in @store (get-shape-path :r4)) 10))
(set! (.-borderRadiusTopRight shape) 30)
(set! (.-borderRadiusBottomRight shape) 40)
(set! (.-borderRadiusBottomLeft shape) 50)
(t/is (= (.-borderRadiusTopRight shape) 30))
(t/is (= (.-borderRadiusBottomRight shape) 40))
(t/is (= (.-borderRadiusBottomLeft shape) 50))
(set! (.-borderRadiusTopRight shape) 30)
(set! (.-borderRadiusBottomRight shape) 40)
(set! (.-borderRadiusBottomLeft shape) 50)
(t/is (= (.-borderRadiusTopRight shape) 30))
(t/is (= (.-borderRadiusBottomRight shape) 40))
(t/is (= (.-borderRadiusBottomLeft shape) 50))
(t/is (= (get-in @store (get-shape-path :r1)) 20))
(t/is (= (get-in @store (get-shape-path :r2)) 30))
(t/is (= (get-in @store (get-shape-path :r3)) 40))
(t/is (= (get-in @store (get-shape-path :r4)) 50)))
(t/is (= (get-in @store (get-shape-path :r1)) 20))
(t/is (= (get-in @store (get-shape-path :r2)) 30))
(t/is (= (get-in @store (get-shape-path :r3)) 40))
(t/is (= (get-in @store (get-shape-path :r4)) 50)))
(t/testing " - opacity"
(set! (.-opacity shape) 0.5)
(t/is (= (.-opacity shape) 0.5))
(t/is (= (get-in @store (get-shape-path :opacity)) 0.5)))
(t/testing " - opacity"
(set! (.-opacity shape) 0.5)
(t/is (= (.-opacity shape) 0.5))
(t/is (= (get-in @store (get-shape-path :opacity)) 0.5)))
(t/testing " - blendMode"
(set! (.-blendMode shape) "multiply")
(t/is (= (.-blendMode shape) "multiply"))
(t/is (= (get-in @store (get-shape-path :blend-mode)) :multiply))
(t/testing " - blendMode"
(set! (.-blendMode shape) "multiply")
(t/is (= (.-blendMode shape) "multiply"))
(t/is (= (get-in @store (get-shape-path :blend-mode)) :multiply))
(set! (.-blendMode shape) "fail")
(t/is (= (.-blendMode shape) "multiply"))
(t/is (= (get-in @store (get-shape-path :blend-mode)) :multiply)))
(set! (.-blendMode shape) "fail")
(t/is (= (.-blendMode shape) "multiply"))
(t/is (= (get-in @store (get-shape-path :blend-mode)) :multiply)))
(t/testing " - shadows"
(let [shadow #js {:style "drop-shadow"
:color #js {:color "#FABADA" :opacity 1}}]
(set! (.-shadows shape) #js [shadow])
(let [shadow-id (uuid/uuid (aget (aget (aget shape "shadows") 0) "id"))]
(t/is (= (-> (. shape -shadows) (aget 0) (aget "style")) "drop-shadow"))
(t/is (= (get-in @store (get-shape-path :shadow)) [{:id shadow-id
:style :drop-shadow
:offset-x 4
:offset-y 4
:blur 4
:spread 0
:color {:color "#fabada" :opacity 1}
:hidden false}]))))
(let [shadow #js {:style "fail"}]
(set! (.-shadows shape) #js [shadow])
(t/is (= (-> (. shape -shadows) (aget 0) (aget "style")) "drop-shadow"))))
(t/testing " - shadows"
(let [shadow #js {:style "drop-shadow"
:color #js {:color "#FABADA" :opacity 1}}]
(set! (.-shadows shape) #js [shadow])
(let [shadow-id (uuid/uuid (aget (aget (aget shape "shadows") 0) "id"))]
(t/is (= (-> (. shape -shadows) (aget 0) (aget "style")) "drop-shadow"))
(t/is (= (get-in @store (get-shape-path :shadow)) [{:id shadow-id
:style :drop-shadow
:offset-x 4
:offset-y 4
:blur 4
:spread 0
:color {:color "#fabada" :opacity 1}
:hidden false}]))))
(let [shadow #js {:style "fail"}]
(set! (.-shadows shape) #js [shadow])
(t/is (= (-> (. shape -shadows) (aget 0) (aget "style")) "drop-shadow"))))
(t/testing " - blur"
(set! (.-blur shape) #js {:value 10})
(t/is (= (-> (. shape -blur) (aget "type")) "layer-blur"))
(t/is (= (-> (. shape -blur) (aget "value")) 10))
(t/is (= (-> (. shape -blur) (aget "hidden")) false))
(let [id (-> (. shape -blur) (aget "id") uuid/uuid)]
(t/is (= (get-in @store (get-shape-path :blur)) {:id id :type :layer-blur :value 10 :hidden false}))))
(t/testing " - blur"
(set! (.-blur shape) #js {:value 10})
(t/is (= (-> (. shape -blur) (aget "type")) "layer-blur"))
(t/is (= (-> (. shape -blur) (aget "value")) 10))
(t/is (= (-> (. shape -blur) (aget "hidden")) false))
(let [id (-> (. shape -blur) (aget "id") uuid/uuid)]
(t/is (= (get-in @store (get-shape-path :blur)) {:id id :type :layer-blur :value 10 :hidden false}))))
(t/testing " - exports"
(set! (.-exports shape) #js [#js {:type "pdf" :scale 2 :suffix "test"}])
(t/is (= (-> (. shape -exports) (aget 0) (aget "type")) "pdf"))
(t/is (= (-> (. shape -exports) (aget 0) (aget "scale")) 2))
(t/is (= (-> (. shape -exports) (aget 0) (aget "suffix")) "test"))
(t/is (= (get-in @store (get-shape-path :exports)) [{:type :pdf :scale 2 :suffix "test" :skip-children false}]))
(t/testing " - exports"
(set! (.-exports shape) #js [#js {:type "pdf" :scale 2 :suffix "test"}])
(t/is (= (-> (. shape -exports) (aget 0) (aget "type")) "pdf"))
(t/is (= (-> (. shape -exports) (aget 0) (aget "scale")) 2))
(t/is (= (-> (. shape -exports) (aget 0) (aget "suffix")) "test"))
(t/is (= (get-in @store (get-shape-path :exports)) [{:type :pdf :scale 2 :suffix "test" :skip-children false}]))
(set! (.-exports shape) #js [#js {:type 10 :scale 2 :suffix "test"}])
(t/is (= (get-in @store (get-shape-path :exports)) [{:type :pdf :scale 2 :suffix "test" :skip-children false}])))
(set! (.-exports shape) #js [#js {:type 10 :scale 2 :suffix "test"}])
(t/is (= (get-in @store (get-shape-path :exports)) [{:type :pdf :scale 2 :suffix "test" :skip-children false}])))
(t/testing " - flipX"
(set! (.-flipX shape) true)
(t/is (= (.-flipX shape) true))
(t/is (= (get-in @store (get-shape-path :flip-x)) true)))
(t/testing " - flipX"
(set! (.-flipX shape) true)
(t/is (= (.-flipX shape) true))
(t/is (= (get-in @store (get-shape-path :flip-x)) true)))
(t/testing " - flipY"
(set! (.-flipY shape) true)
(t/is (= (.-flipY shape) true))
(t/is (= (get-in @store (get-shape-path :flip-y)) true)))
(t/testing " - flipY"
(set! (.-flipY shape) true)
(t/is (= (.-flipY shape) true))
(t/is (= (get-in @store (get-shape-path :flip-y)) true)))
(t/testing " - rotation"
(set! (.-rotation shape) 45)
(t/is (= (.-rotation shape) 45))
(t/is (= (get-in @store (get-shape-path :rotation)) 45))
(t/testing " - rotation"
(set! (.-rotation shape) 45)
(t/is (= (.-rotation shape) 45))
(t/is (= (get-in @store (get-shape-path :rotation)) 45))
(set! (.-rotation shape) 0)
(t/is (= (.-rotation shape) 0))
(t/is (= (get-in @store (get-shape-path :rotation)) 0)))
(set! (.-rotation shape) 0)
(t/is (= (.-rotation shape) 0))
(t/is (= (get-in @store (get-shape-path :rotation)) 0)))
(t/testing " - fills"
(set! (.-fills shape) #js [#js {:fillColor 100}])
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#B1B2B5" :fill-opacity 1}]))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#B1B2B5"))
(t/testing " - fills"
(set! (.-fills shape) #js [#js {:fillColor 100}])
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#B1B2B5" :fill-opacity 1}]))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#B1B2B5"))
(set! (.-fills shape) #js [#js {:fillColor "#fabada" :fillOpacity 1}])
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#fabada" :fill-opacity 1}]))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#fabada"))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillOpacity")) 1)))
(set! (.-fills shape) #js [#js {:fillColor "#fabada" :fillOpacity 1}])
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#fabada" :fill-opacity 1}]))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#fabada"))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillOpacity")) 1)))
(t/testing " - strokes"
(set! (.-strokes shape) #js [#js {:strokeColor "#fabada" :strokeOpacity 1 :strokeWidth 5}])
(t/is (= (get-in @store (get-shape-path :strokes)) [{:stroke-color "#fabada" :stroke-opacity 1 :stroke-width 5}]))
(t/is (= (-> (. ^js shape -strokes) (aget 0) (aget "strokeColor")) "#fabada"))
(t/is (= (-> (. ^js shape -strokes) (aget 0) (aget "strokeOpacity")) 1))
(t/is (= (-> (. ^js shape -strokes) (aget 0) (aget "strokeWidth")) 5))))
(t/testing " - strokes"
(set! (.-strokes shape) #js [#js {:strokeColor "#fabada" :strokeOpacity 1 :strokeWidth 5}])
(t/is (= (get-in @store (get-shape-path :strokes)) [{:stroke-color "#fabada" :stroke-opacity 1 :stroke-width 5}]))
(t/is (= (-> (. ^js shape -strokes) (aget 0) (aget "strokeColor")) "#fabada"))
(t/is (= (-> (. ^js shape -strokes) (aget 0) (aget "strokeOpacity")) 1))
(t/is (= (-> (. ^js shape -strokes) (aget 0) (aget "strokeWidth")) 5))))
(t/testing "Relative properties"
(let [board (.createBoard context)]
(set! (.-x board) 100)
(set! (.-y board) 200)
(t/is (= (.-x board) 100))
(t/is (= (.-y board) 200))
(.appendChild board shape)
(t/testing "Relative properties"
(let [board (.createBoard context)]
(set! (.-x board) 100)
(set! (.-y board) 200)
(t/is (= (.-x board) 100))
(t/is (= (.-y board) 200))
(.appendChild board shape)
(t/testing " - boardX"
(set! (.-boardX ^js shape) 10)
(t/is (m/close? (.-boardX ^js shape) 10))
(t/is (m/close? (.-x shape) 110))
(t/is (m/close? (get-in @store (get-shape-path :x)) 110)))
(t/testing " - boardX"
(set! (.-boardX ^js shape) 10)
(t/is (m/close? (.-boardX ^js shape) 10))
(t/is (m/close? (.-x shape) 110))
(t/is (m/close? (get-in @store (get-shape-path :x)) 110)))
(t/testing " - boardY"
(set! (.-boardY ^js shape) 20)
(t/is (m/close? (.-boardY ^js shape) 20))
(t/is (m/close? (.-y shape) 220))
(t/is (m/close? (get-in @store (get-shape-path :y)) 220)))
(t/testing " - boardY"
(set! (.-boardY ^js shape) 20)
(t/is (m/close? (.-boardY ^js shape) 20))
(t/is (m/close? (.-y shape) 220))
(t/is (m/close? (get-in @store (get-shape-path :y)) 220)))
(t/testing " - parentX"
(set! (.-parentX ^js shape) 30)
(t/is (m/close? (.-parentX ^js shape) 30))
(t/is (m/close? (.-x shape) 130))
(t/is (m/close? (get-in @store (get-shape-path :x)) 130)))
(t/testing " - parentX"
(set! (.-parentX ^js shape) 30)
(t/is (m/close? (.-parentX ^js shape) 30))
(t/is (m/close? (.-x shape) 130))
(t/is (m/close? (get-in @store (get-shape-path :x)) 130)))
(t/testing " - parentY"
(set! (.-parentY ^js shape) 40)
(t/is (m/close? (.-parentY ^js shape) 40))
(t/is (m/close? (.-y shape) 240))
(t/is (m/close? (get-in @store (get-shape-path :y)) 240)))))
(t/testing " - parentY"
(set! (.-parentY ^js shape) 40)
(t/is (m/close? (.-parentY ^js shape) 40))
(t/is (m/close? (.-y shape) 240))
(t/is (m/close? (get-in @store (get-shape-path :y)) 240)))))
(t/testing "Clone")
(t/testing "Remove")))
(t/testing "Clone")
(t/testing "Remove")
(t/testing "WASM mocks were exercised"
(t/is (pos? (thw/call-count :clean-modifiers)))
(t/is (pos? (thw/call-count :set-structure-modifiers)))
(t/is (pos? (thw/call-count :propagate-modifiers))))))))

View File

@@ -30,10 +30,6 @@
(.exit js/process 0)
(.exit js/process 1)))
;; FIXME: workaround, wasn is temporarily disabled for unit tests
(set! app.main.features/global-enabled-features
(disj app.main.features/global-enabled-features "render-wasm/v1"))
(defn init
[]
(t/run-tests

View File

@@ -17,11 +17,15 @@
[cuerdas.core :as str]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
[frontend-tests.helpers.wasm :as thw]
[frontend-tests.tokens.helpers.state :as tohs]
[frontend-tests.tokens.helpers.tokens :as toht]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
{:before (fn []
(thp/reset-idmap!)
(thw/setup-wasm-mocks!))
:after thw/teardown-wasm-mocks!})
(defn setup-file []
(cthf/sample-file :file-1 :page-label :page-1))
@@ -273,7 +277,9 @@
(t/is (= (:height (:applied-tokens rect-1')) (:name token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))))
(t/is (= (:height rect-1') 100)))
(t/testing "WASM mocks were exercised"
(t/is (pos? (thw/call-count :propagate-modifiers)))))))))))
(t/deftest test-apply-padding
(t/testing "applies padding token to shapes with layout"
@@ -346,7 +352,9 @@
(t/is (= (:height (:applied-tokens rect-1')) (:name token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))))
(t/is (= (:height rect-1') 100)))
(t/testing "WASM mocks were exercised"
(t/is (pos? (thw/call-count :propagate-modifiers)))))))))))
(t/deftest test-apply-opacity
(t/testing "applies opacity token and updates the shapes opacity"
@@ -431,7 +439,9 @@
rect-1' (cths/get-shape file' :rect-1)]
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rotation (:applied-tokens rect-1')) (:name token-target')))
(t/is (= (:rotation rect-1') 120)))))))))
(t/is (= (:rotation rect-1') 120))
(t/testing "WASM mocks were exercised"
(t/is (pos? (thw/call-count :propagate-modifiers)))))))))))
(t/deftest test-apply-stroke-width
(t/testing "applies stroke-width token and updates the shapes with stroke"
@@ -540,7 +550,10 @@
(:styles))]
(t/is (some? (:applied-tokens text-1')))
(t/is (= (:font-size (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:font-size style-text-blocks) "24")))))))))
(t/is (= (:font-size style-text-blocks) "24"))
(t/testing "WASM text mocks were exercised"
(t/is (pos? (thw/call-count :set-shape-text-content)))
(t/is (pos? (thw/call-count :get-text-dimensions)))))))))))
(t/deftest test-apply-line-height
(t/testing "applies line-height token and updates the text line-height"
@@ -575,7 +588,10 @@
(:styles))]
(t/is (some? (:applied-tokens text-1')))
(t/is (= (:line-height (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:line-height style-text-blocks) 1.5)))))))))
(t/is (= (:line-height style-text-blocks) 1.5))
(t/testing "WASM text mocks were exercised"
(t/is (pos? (thw/call-count :set-shape-text-content)))
(t/is (pos? (thw/call-count :get-text-dimensions)))))))))))
(t/deftest test-apply-letter-spacing
(t/testing "applies letter-spacing token and updates the text letter-spacing"
@@ -610,7 +626,10 @@
(:styles))]
(t/is (some? (:applied-tokens text-1')))
(t/is (= (:letter-spacing (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:letter-spacing style-text-blocks) "2")))))))))
(t/is (= (:letter-spacing style-text-blocks) "2"))
(t/testing "WASM text mocks were exercised"
(t/is (pos? (thw/call-count :set-shape-text-content)))
(t/is (pos? (thw/call-count :get-text-dimensions)))))))))))
(t/deftest test-apply-font-family
(t/testing "applies font-family token and updates the text font-family"
@@ -645,7 +664,10 @@
(:styles))]
(t/is (some? (:applied-tokens text-1')))
(t/is (= (:font-family (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:font-family style-text-blocks) (:font-id txt/default-text-attrs))))))))))
(t/is (= (:font-family style-text-blocks) (:font-id txt/default-text-attrs)))
(t/testing "WASM text mocks were exercised"
(t/is (pos? (thw/call-count :set-shape-text-content)))
(t/is (pos? (thw/call-count :get-text-dimensions)))))))))))
(t/deftest test-apply-text-case
(t/testing "applies text-case token and updates the text transform"
@@ -750,7 +772,10 @@
(:styles))]
(t/is (some? (:applied-tokens text-1')))
(t/is (= (:font-weight (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:font-weight style-text-blocks) "400")))))))))
(t/is (= (:font-weight style-text-blocks) "400"))
(t/testing "WASM text mocks were exercised"
(t/is (pos? (thw/call-count :set-shape-text-content)))
(t/is (pos? (thw/call-count :get-text-dimensions)))))))))))
(t/deftest test-toggle-token-none
(t/testing "should apply token to all selected items, where no item has the token applied"
@@ -973,7 +998,10 @@
(t/is (= (:font-family style-text-blocks) "sourcesanspro"))
(t/is (= (:letter-spacing style-text-blocks) "2"))
(t/is (= (:text-transform style-text-blocks) "uppercase"))
(t/is (= (:text-decoration style-text-blocks) "underline")))))))))
(t/is (= (:text-decoration style-text-blocks) "underline"))
(t/testing "WASM text mocks were exercised"
(t/is (pos? (thw/call-count :set-shape-text-content)))
(t/is (pos? (thw/call-count :get-text-dimensions)))))))))))
(t/deftest test-apply-reference-typography-token
(t/testing "applies typography (composite) tokens with references"
@@ -1018,7 +1046,10 @@
(t/is (= (:typography (:applied-tokens text-1')) "typography"))
(t/is (= (:font-size style-text-blocks) "100"))
(t/is (= (:font-family style-text-blocks) "Arial")))))))))
(t/is (= (:font-family style-text-blocks) "Arial"))
(t/testing "WASM text mocks were exercised"
(t/is (pos? (thw/call-count :set-shape-text-content)))
(t/is (pos? (thw/call-count :get-text-dimensions)))))))))))
(t/deftest test-unapply-atomic-tokens-on-composite-apply
(t/testing "unapplies atomic typography tokens when applying composite token"
@@ -1172,4 +1203,7 @@
(t/is (nil? (:typography-ref-id paragraph-3)))
(t/is (nil? (:typography-ref-file paragraph-3)))
(t/is (nil? (:typography-ref-id text-node-3)))
(t/is (nil? (:typography-ref-file text-node-3))))))))))
(t/is (nil? (:typography-ref-file text-node-3)))
(t/testing "WASM text mocks were exercised"
(t/is (pos? (thw/call-count :set-shape-text-content)))
(t/is (pos? (thw/call-count :get-text-dimensions)))))))))))