;; 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 performance-tests.render-wasm.builder-test "Tests canonical scene construction and the shared seeded number generator. Fixed random sequences preserve scene draws during generator extraction." (:require [app.common.uuid :as uuid] [benches.render-wasm.builder :as b :include-macros true] [benches.render-wasm.declarations :as decl] [benches.render-wasm.random :as random] [benches.render-wasm.scenes.rects] [benches.render-wasm.snapshot :as snapshot] [cljs.test :as t :include-macros true] [performance-tests.render-wasm.test-helpers :as helpers])) (defn- build ([] (build {})) ([params] ((:build (decl/registered-scene :rects)) (merge {:seed 42 :count 5} params)))) (defn- add-tile "Helper defined outside a scene scope, called from inside one." [index] (b/rect [:tile index] {:x (* 10 index)})) (t/deftest repeatable-builds-are-identical (t/is (= (build) (build))) (t/is (not= (build) (build {:seed 43})))) (t/deftest shared-generator-retains-scene-draws-and-independent-state (doseq [[seed expected] [[0 [1144304738 1416247 958946056 627933444 2007157716]] [42 [2581720956 1925393290 3661312704 2876485805 750819978]] [4294967295 [3850105811 813802916 3073704848 4054706436 3630262831]]]] (let [first-source (random/create seed) second-source (random/create seed)] (t/is (= expected (mapv #(* 4294967296 %) (repeatedly 5 first-source)))) (t/is (= expected (mapv #(* 4294967296 %) (repeatedly 5 second-source)))) (t/is (every? #(and (<= 0 %) (< % 1)) (repeatedly 1000 first-source))))) (let [rng (:rng @(b/start {:seed 42}))] (t/is (= [2581720956 1925393290 3661312704 2876485805 750819978] (mapv #(* 4294967296 %) (repeatedly 5 rng)))))) (t/deftest shared-generator-rejects-invalid-seeds (doseq [seed [-1 4294967296 1.5 nil false "42" js/NaN js/Infinity]] (t/is (= ::random/invalid-seed (:type (helpers/failure-data #(random/create seed))))))) (t/deftest rectangle-workload-keeps-default-count (let [instance (build {:seed 42 :count 1000}) rects (helpers/root-shapes instance)] (t/is (= 1000 (count rects))) (t/is (< 1 (count (distinct (map :x rects))))))) (t/deftest default-workload-is-canonical (let [instance (build {:count 25}) rects (helpers/root-shapes instance)] (t/is (= 25 (count rects))) (t/is (every? #(= :rect (:type %)) rects)) (t/is (every? (fn [rect] (and (= 1 (count (:fills rect))) (= 1 (count (:strokes rect))) (= 10 (:stroke-width (first (:strokes rect)))) (= :center (:stroke-alignment (first (:strokes rect)))))) rects)) (t/is (every? (fn [rect] (and (re-matches #"^#[0-9a-f]{6}$" (:fill-color (first (:fills rect)))) (<= 0.1 (:fill-opacity (first (:fills rect))) 0.999) (every? #(<= 0 % 24) [(:r1 rect) (:r2 rect) (:r3 rect) (:r4 rect)]))) rects)))) (t/deftest geometry-is-page-relative-and-in-range (let [params {:seed 7 :count 40 :width 800 :height 600 :min-size 5 :max-size 15} instance (build params) rects (helpers/root-shapes instance)] (t/is (every? #(<= 0 (:x %) 800) rects)) (t/is (every? #(<= 0 (:y %) 600) rects)) (t/is (every? #(<= 5 (:width %) 15) rects)) (t/is (every? #(<= 5 (:height %) 15) rects)) (t/is (= 800 (get-in instance [:objects uuid/zero :width]))) (t/is (= 600 (get-in instance [:objects uuid/zero :height]))))) (t/deftest labels-are-snapshot-local (let [instance (b/scene {:seed 1} (b/rect) (b/rect :hero {}) (b/rect [:tile 1] {})) refs (:refs instance) children (helpers/child-ids instance uuid/zero)] (t/is (= #{:hero [:tile 1]} (set (keys refs)))) (t/is (= (get refs :hero) (second children))) (t/is (= (get refs [:tile 1]) (nth children 2))))) (t/deftest attrs-override-generated-values (let [instance (b/scene {:seed 1 :defaults {:rect {:x (b/gen-int 0 100)}}} (b/rect {:x 7 :fills []})) rect (first (helpers/root-shapes instance))] (t/is (= 7 (:x rect))) (t/is (= [] (:fills rect))))) (t/deftest ordinary-forms-work-inside-the-scope (let [instance (b/scene {:seed 1} (let [first-id (b/rect)] (t/is (uuid? first-id))) (doseq [index (range 3)] (add-tile index)) (b/rect :last {:x 99})) refs (:refs instance) rects (helpers/root-shapes instance)] (t/is (= 5 (count rects))) (t/is (= [0 10 20] (mapv #(:x (get-in instance [:objects (get refs [:tile %])])) (range 3)))) (t/is (= 99 (:x (get-in instance [:objects (get refs :last)])))))) (t/deftest duplicate-labels-are-rejected (let [data (helpers/failure-data #(b/scene {:seed 1} (b/rect :dup {}) (b/rect :dup {})))] (t/is (= ::b/duplicate-label (:type data))) (t/is (= :dup (:label data))))) (t/deftest failed-builds-leak-no-state (let [clean (build)] (helpers/failure-data #(b/scene {:seed 99} (b/rect :dup {}) (b/rect :dup {}))) (t/is (= clean (build))))) (t/deftest rect-outside-scope-is-rejected (let [data (helpers/failure-data #(b/rect))] (t/is (= ::b/outside-scope (:type data))))) (t/deftest nested-scopes-are-rejected (let [data (helpers/failure-data #(b/scene {:seed 1} (b/scene {:seed 2} (b/rect))))] (t/is (= ::b/nested-scope (:type data))))) (t/deftest invalid-seeds-are-rejected (t/is (= ::b/invalid-seed (:type (helpers/failure-data #(b/scene {} (b/rect)))))) (t/is (= ::b/invalid-seed (:type (helpers/failure-data #(b/scene {:seed -1} (b/rect)))))) (t/is (= ::b/invalid-seed (:type (helpers/failure-data #(b/scene {:seed 4294967296} (b/rect))))))) (t/deftest runtime-attrs-maps-are-resolved (let [attrs {:x 11} instance (b/scene {:seed 1} (b/rect attrs)) rect (first (helpers/root-shapes instance))] (t/is (= 11 (:x rect))))) (t/deftest single-label-form-is-supported (let [instance (b/scene {:seed 1} (b/rect :solo)) id (get (:refs instance) :solo)] (t/is (uuid? id)) (t/is (= [id] (helpers/child-ids instance uuid/zero))))) (t/deftest built-snapshot-feeds-the-scene-api (let [instance (b/scene {:seed 1} (b/rect :hero {})) order (snapshot/upload-order instance) hero-id (snapshot/ref-id instance :hero)] (t/is (= [uuid/zero hero-id] (mapv :id order))))) (t/deftest invalid-labels-are-rejected (let [data (helpers/failure-data #(b/scene {:seed 1} (b/rect "hero" {})))] (t/is (= ::b/invalid-label (:type data))))) (t/deftest protected-keys-cannot-be-overridden (let [other (uuid/custom 9 9) instance (b/scene {:seed 1} (b/rect {:type :circle :id other :parent-id other})) rect (first (helpers/root-shapes instance))] (t/is (= :rect (:type rect))) (t/is (not= other (:id rect))) (t/is (= uuid/zero (:parent-id rect))))) (t/deftest root-protected-keys-cannot-be-overridden (let [other (uuid/custom 9 8) instance (b/scene {:seed 1 :root {:id other :type :rect :width 10}} (b/rect)) root (get-in instance [:objects uuid/zero])] (t/is (= uuid/zero (:id root))) (t/is (= :frame (:type root))) (t/is (= 10 (:width root))))) (t/deftest attrs-override-generated-fills (let [fill {:fill-color "#ffffff" :fill-opacity 1} instance (b/scene {:seed 1} (b/rect {:fills [fill]})) rect (first (helpers/root-shapes instance))] (t/is (= [fill] (:fills rect))))) (t/deftest generator-helpers-produce-expected-values (let [instance (b/scene {:seed 3 :defaults {:rect {:opacity (b/gen-float 0.5 0.6) :x (b/gen-one-of [4 5]) :r1 (b/gen-int 3 5) :fills (b/gen-vector (b/gen-fill))}}} (b/rect)) rect (first (helpers/root-shapes instance))] (t/is (<= 0.5 (:opacity rect) 0.6)) (t/is (contains? #{4 5} (:x rect))) (t/is (<= 3 (:r1 rect) 4)) (t/is (re-matches #"^#[0-9a-f]{6}$" (:fill-color (first (:fills rect))))))) (t/deftest empty-one-of-is-rejected (let [data (helpers/failure-data #(b/gen-one-of []))] (t/is (= ::b/no-values (:type data))))) (t/deftest circle-creates-ellipse-type (let [instance (b/scene {:seed 1} (b/circle {:x 10 :y 20 :width 30 :height 40})) circle (first (helpers/root-shapes instance))] (t/is (= :circle (:type circle))) (t/is (= [10 20 30 40] [(:x circle) (:y circle) (:width circle) (:height circle)])) (t/is (= 1 (count (helpers/child-ids instance uuid/zero)))))) (t/deftest circle-uses-circle-defaults (let [instance (b/scene {:seed 3 :defaults {:circle {:x (b/gen-int 10 12)}}} (b/circle)) circle (first (helpers/root-shapes instance))] (t/is (contains? #{10 11} (:x circle))))) (t/deftest circle-ignores-rect-defaults (let [instance (b/scene {:seed 1 :defaults {:rect {:x 999}}} (b/circle)) circle (first (helpers/root-shapes instance))] (t/is (not= 999 (:x circle))))) (t/deftest circle-geometry-is-page-relative-and-in-range (let [instance (b/scene {:seed 7 :defaults {:circle {:x (b/gen-int 0 800) :y (b/gen-int 0 600) :width (b/gen-int 5 15) :height (b/gen-int 5 15)}}} (doseq [_ (range 40)] (b/circle))) circles (helpers/root-shapes instance)] (t/is (= 40 (count circles))) (t/is (every? #(<= 0 (:x %) 800) circles)) (t/is (every? #(<= 0 (:y %) 600) circles)) (t/is (every? #(<= 5 (:width %) 15) circles)) (t/is (every? #(<= 5 (:height %) 15) circles)))) (t/deftest circle-preserves-radius-attrs (let [instance (b/scene {:seed 1} (b/circle {:x 0 :y 0 :width 20 :height 20 :rx 5 :ry 7})) circle (first (helpers/root-shapes instance))] (t/is (= [5 7] [(:rx circle) (:ry circle)]))))