mirror of
https://github.com/penpot/penpot.git
synced 2026-11-06 10:00:38 +00:00
The CJK text options were behind per-language "Enable layout" toggles in the file menu and the settings, each with its own sidebar section. A text now has one language, picked in a single Language panel, and the options of that language show below it. - Rename the `vertical-text-layout` flag and feature to `cjk-support`. - Remove the per-file and per-profile layout toggles, the file options that stored them and the `:set-text-layout` change. - Add the Language panel: a grouped dropdown of Japanese, Korean and the regional Chinese variants, a row to change the language and a button to remove it, "Mixed" when the selected texts differ. Picking a language writes it to every paragraph of the texts. - Render the CJK attrs only under the language that uses them, so a text without language is horizontal and plain. The stored content keeps every attr. - Remember the last picked language for the next new texts. - Add File > Language: the language of the texts without one of their own, applied at render time and laid out again when it changes. - Add the profile text language in Settings > Options: new files start with it as their file language. - Extract the grouped dropdown of the board size presets into a shared component used by the presets and the Language panel. - Name the Japanese readings furigana, drop the redundant prefixes of the furigana and pinyin options, move Distribute below the writing mode row and show only the position sides of the writing mode. AI-assisted-by: claude-opus-5-5
1321 lines
56 KiB
Clojure
1321 lines
56 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 common-tests.files-changes-test
|
|
(:require
|
|
[app.common.features :as ffeat]
|
|
[app.common.files.changes :as ch]
|
|
[app.common.files.changes-builder :as pcb]
|
|
[app.common.geom.point :as gpt]
|
|
[app.common.geom.shapes :as gsh]
|
|
[app.common.schema :as sm]
|
|
[app.common.schema.generators :as sg]
|
|
[app.common.schema.test :as smt]
|
|
[app.common.time :as ct]
|
|
[app.common.types.file :as ctf]
|
|
[app.common.types.shape :as cts]
|
|
[app.common.types.tokens-lib :as ctob]
|
|
[app.common.types.tokens-status :as ctos]
|
|
[app.common.types.typography :as ctt]
|
|
[app.common.uuid :as uuid]
|
|
[clojure.datafy :refer [datafy]]
|
|
[clojure.set :as set]
|
|
[clojure.test :as t]
|
|
[clojure.walk :as walk]
|
|
[common-tests.types.shape-decode-encode-test :refer [json-roundtrip]]))
|
|
|
|
(defn- make-file-data
|
|
[file-id page-id]
|
|
(binding [ffeat/*current* #{"components/v2"}]
|
|
(ctf/make-file-data file-id page-id)))
|
|
|
|
(defn- without-modified-at
|
|
[data]
|
|
(walk/prewalk
|
|
(fn [value]
|
|
;; Expose opaque token types so they compare by value and their nested
|
|
;; timestamps are excluded too.
|
|
(let [value (if (or (ctob/tokens-lib? value)
|
|
(ctob/token-set? value)
|
|
(ctos/tokens-status? value))
|
|
(datafy value)
|
|
value)]
|
|
(if (map? value)
|
|
(dissoc value :modified-at)
|
|
value)))
|
|
data))
|
|
|
|
(defn- assert-idempotent
|
|
[data changes]
|
|
;; Modification timestamps may differ; all other stored data must agree.
|
|
;; Exercise validation and the touched second pass at distinct times.
|
|
(let [once (with-redefs [ct/now (constantly (ct/inst 1000))]
|
|
(ch/process-changes data changes))
|
|
twice (with-redefs [ct/now (constantly (ct/inst 2000))]
|
|
(ch/process-changes once changes))]
|
|
(t/is (= (without-modified-at once)
|
|
(without-modified-at twice))
|
|
"Replaying changes must preserve file data except modification timestamps")
|
|
;; Ordered maps compare equal regardless of iteration order, but set order
|
|
;; determines which token wins when active sets contain the same name.
|
|
(when (:tokens-lib once)
|
|
(t/is (= (vec (ctob/get-set-names (:tokens-lib once)))
|
|
(vec (ctob/get-set-names (:tokens-lib twice))))
|
|
"Replaying changes must preserve token set order"))))
|
|
|
|
(t/deftest library-changes-are-idempotent
|
|
(let [file-id (uuid/custom 10 1)
|
|
page-id (uuid/custom 10 2)
|
|
shape-id (uuid/custom 10 3)
|
|
component-id (uuid/custom 10 4)
|
|
color {:id (uuid/custom 10 5) :name "Red" :color "#ff0000" :opacity 1}
|
|
typography (ctt/make-typography {:id (uuid/custom 10 6)})
|
|
component {:type :add-component
|
|
:id component-id
|
|
:name "Component"
|
|
:path ""
|
|
:main-instance-id shape-id
|
|
:main-instance-page page-id}
|
|
base (ch/process-changes
|
|
(make-file-data file-id page-id)
|
|
[{:type :add-obj
|
|
:id shape-id
|
|
:page-id page-id
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:id shape-id :type :frame :name "Main"
|
|
:frame-id uuid/zero :parent-id uuid/zero
|
|
:component-id component-id :component-file file-id
|
|
:main-instance true :component-root true})}])
|
|
populated (with-redefs [ct/now (constantly (ct/inst 0))]
|
|
(ch/process-changes base
|
|
[component
|
|
{:type :add-color :color color}
|
|
{:type :add-typography :typography typography}]))
|
|
deleted (ch/process-changes populated [{:type :del-component :id component-id}])]
|
|
(doseq [[label data change]
|
|
[["add color" base {:type :add-color :color color}]
|
|
["modify color" populated {:type :mod-color :color (assoc color :name "Renamed")}]
|
|
["delete color" populated {:type :del-color :id (:id color)}]
|
|
["add typography" base {:type :add-typography :typography typography}]
|
|
["modify typography" populated {:type :mod-typography
|
|
:typography (assoc typography :name "Renamed")}]
|
|
["delete typography" populated {:type :del-typography :id (:id typography)}]
|
|
["add component" base component]
|
|
["modify component" populated {:type :mod-component :id component-id :name "Renamed"}]
|
|
["delete component" populated {:type :del-component :id component-id}]
|
|
["delete component permanently" populated {:type :del-component :id component-id
|
|
:skip-undelete? true}]
|
|
["restore component" deleted {:type :restore-component :id component-id :page-id page-id}]
|
|
["purge component" populated {:type :purge-component :id component-id}]
|
|
["set main instance attribute" populated
|
|
{:type :mod-obj :id shape-id :page-id page-id
|
|
:operations [{:type :set :attr :name :val "Renamed"}]}]
|
|
["assign main instance attribute" populated
|
|
{:type :mod-obj :id shape-id :page-id page-id
|
|
:operations [{:type :assign :value {:name "Renamed"}}]}]]]
|
|
(t/testing label
|
|
(assert-idempotent data [change])))))
|
|
|
|
(t/deftest token-changes-are-idempotent
|
|
(let [set-id (uuid/custom 20 1)
|
|
other-id (uuid/custom 20 2)
|
|
token-id (uuid/custom 20 3)
|
|
theme-id (uuid/custom 20 4)
|
|
token-attrs {:id token-id :name "spacing" :type :spacing :value "8"
|
|
:modified-at (ct/inst 0)}
|
|
theme-attrs {:id theme-id :name "Light" :sets #{"base"}
|
|
:modified-at (ct/inst 0)}
|
|
lib (with-redefs [ct/now (constantly (ct/inst 0))]
|
|
(-> (ctob/make-tokens-lib)
|
|
(ctob/add-set (ctob/make-token-set :id set-id :name "base"))
|
|
(ctob/add-set (ctob/make-token-set :id other-id :name "group/other"))
|
|
(ctob/add-token set-id (ctob/make-token token-attrs))
|
|
(ctob/add-theme (ctob/make-token-theme theme-attrs))))
|
|
data (assoc (make-file-data (uuid/custom 20 5) (uuid/custom 20 6)) :tokens-lib lib)]
|
|
(doseq [[label change]
|
|
[["update token with serialized timestamp"
|
|
{:type :set-token :set-id set-id :token-id token-id
|
|
:attrs (assoc token-attrs :value "16")}]
|
|
["delete token"
|
|
{:type :set-token :set-id set-id :token-id token-id :attrs nil}]
|
|
["set token set without optional timestamp"
|
|
{:type :set-token-set :id set-id :attrs {:id set-id :name "base"}}]
|
|
["set token set with timestamp"
|
|
{:type :set-token-set :id set-id
|
|
:attrs {:id set-id :name "base" :modified-at (ct/inst 0)}}]
|
|
["delete token set"
|
|
{:type :set-token-set :id set-id :attrs nil}]
|
|
["update theme with serialized timestamp"
|
|
{:type :set-token-theme :id theme-id
|
|
:attrs (assoc theme-attrs :sets #{"group/other"})}]
|
|
["delete theme"
|
|
{:type :set-token-theme :id theme-id :attrs nil}]
|
|
["set tokens status"
|
|
{:type :set-tokens-status :theme-ids #{theme-id} :set-ids #{set-id}}]
|
|
["reorder token set"
|
|
{:type :move-token-set :from-path ["base"] :to-path ["base"]
|
|
:before-path nil :before-group false}]
|
|
["move token set to another group"
|
|
{:type :move-token-set :from-path ["base"] :to-path ["group" "base"]
|
|
:before-path nil :before-group false}]
|
|
["reorder token set group"
|
|
{:type :move-token-set-group :from-path ["group"] :to-path ["group"]
|
|
:before-path ["base"] :before-group false}]
|
|
["move token set group"
|
|
{:type :move-token-set-group :from-path ["group"] :to-path ["renamed"]
|
|
:before-path nil :before-group false}]
|
|
["rename token set group"
|
|
{:type :rename-token-set-group :set-group-path ["group"] :set-group-fname "renamed"}]
|
|
["rename token set group to its current name"
|
|
{:type :rename-token-set-group :set-group-path ["group"] :set-group-fname "group"}]]]
|
|
(t/testing label
|
|
(assert-idempotent data [change])))))
|
|
|
|
(t/deftest registering-nested-groups-is-idempotent
|
|
(let [file-id (uuid/custom 30 1)
|
|
page-id (uuid/custom 30 2)
|
|
outer-id (uuid/custom 30 3)
|
|
inner-id (uuid/custom 30 4)
|
|
rect-id (uuid/custom 30 5)
|
|
data (ch/process-changes
|
|
(make-file-data file-id page-id)
|
|
(mapv (fn [[id parent-id type]]
|
|
{:type :add-obj :id id :page-id page-id
|
|
:frame-id uuid/zero :parent-id parent-id
|
|
:obj (cts/setup-shape
|
|
{:id id :type type :name (name type)
|
|
:frame-id uuid/zero :parent-id parent-id
|
|
:x 0 :y 0 :width 10 :height 10})})
|
|
[[outer-id uuid/zero :group]
|
|
[inner-id outer-id :group]
|
|
[rect-id inner-id :rect]]))
|
|
moved (gsh/move (get-in data [:pages-index page-id :objects rect-id])
|
|
(gpt/point 100 0))
|
|
data (ch/process-changes
|
|
data
|
|
[{:type :mod-obj :id rect-id :page-id page-id
|
|
:operations (mapv (fn [[attr val]] {:type :set :attr attr :val val})
|
|
(select-keys moved [:x :y :selrect :points]))}])]
|
|
(t/testing "child before parent"
|
|
(assert-idempotent data [{:type :reg-objects :page-id page-id :shapes [inner-id outer-id]}]))
|
|
(t/testing "parent before child"
|
|
(let [changes [{:type :reg-objects :page-id page-id :shapes [outer-id inner-id]}]
|
|
result (ch/process-changes data changes)]
|
|
(t/is (= {:x 100.0 :y 0.0 :width 10.0 :height 10.0}
|
|
(select-keys (get-in result [:pages-index page-id :objects outer-id])
|
|
[:x :y :width :height]))
|
|
"The first application must update the outer group's bounds")
|
|
(assert-idempotent data changes)))))
|
|
|
|
(t/deftest registering-sibling-groups-is-idempotent
|
|
(let [page-id (uuid/custom 31 1)
|
|
outer-id (uuid/custom 31 2)
|
|
group-a (uuid/custom 31 3)
|
|
group-b (uuid/custom 31 4)
|
|
rect-a (uuid/custom 31 5)
|
|
rect-b (uuid/custom 31 6)
|
|
data (ch/process-changes
|
|
(make-file-data (uuid/custom 31 7) page-id)
|
|
(mapv (fn [[id parent-id type]]
|
|
{:type :add-obj :id id :page-id page-id
|
|
:frame-id uuid/zero :parent-id parent-id
|
|
:obj (cts/setup-shape
|
|
{:id id :type type :name (name type)
|
|
:frame-id uuid/zero :parent-id parent-id
|
|
:x 0 :y 0 :width 10 :height 10})})
|
|
[[outer-id uuid/zero :group]
|
|
[group-a outer-id :group]
|
|
[group-b outer-id :group]
|
|
[rect-a group-a :rect]
|
|
[rect-b group-b :rect]]))
|
|
data (ch/process-changes
|
|
data
|
|
(mapv (fn [[id dx]]
|
|
(let [shape (get-in data [:pages-index page-id :objects id])
|
|
moved (gsh/move shape (gpt/point dx 0))]
|
|
{:type :mod-obj :id id :page-id page-id
|
|
:operations (mapv (fn [[attr val]] {:type :set :attr attr :val val})
|
|
(select-keys moved [:x :y :selrect :points]))}))
|
|
[[rect-a 100] [rect-b 200]]))]
|
|
(doseq [shapes [[group-a group-b] [group-b group-a]
|
|
[outer-id group-a group-b] [group-a outer-id group-b]]]
|
|
(t/testing (str "Group order " shapes)
|
|
(let [changes [{:type :reg-objects :page-id page-id :shapes shapes}]
|
|
result (ch/process-changes data changes)]
|
|
(t/is (= {:x 100.0 :y 0.0 :width 110.0 :height 10.0}
|
|
(select-keys (get-in result [:pages-index page-id :objects outer-id])
|
|
[:x :y :width :height]))
|
|
"The parent must use the updated bounds of both children")
|
|
(assert-idempotent data changes))))))
|
|
|
|
(t/deftest default-text-language-round-trips-through-undo
|
|
(let [data (assoc (make-file-data (uuid/custom 2 2) (uuid/custom 1 1))
|
|
:options {:base-font-size "16"})]
|
|
(doseq [[before after] [[nil "ja"] ["ja" "zh-Hant-TW"] ["ko" nil]]]
|
|
(let [data (ctf/set-default-text-language data before)
|
|
changes (-> (pcb/empty-changes)
|
|
(pcb/with-file-data data)
|
|
(pcb/set-default-text-language after))
|
|
edited (ch/process-changes data (:redo-changes changes))
|
|
restored (ch/process-changes edited (:undo-changes changes))]
|
|
(t/is (every? ch/valid-change? (:redo-changes changes)))
|
|
(t/is (= after (ctf/get-default-text-language edited)))
|
|
(t/is (= data restored))
|
|
(t/is (= "16" (get-in edited [:options :base-font-size])))
|
|
(t/is (sm/validate ctf/schema:options (:options edited))))))
|
|
(t/is (not (ch/valid-change? {:type :set-default-text-language :language "not a tag!"}))))
|
|
|
|
(t/deftest add-obj
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)
|
|
id-a (uuid/custom 2 1)
|
|
id-b (uuid/custom 2 2)
|
|
id-c (uuid/custom 2 3)]
|
|
|
|
(t/testing "Adds single object"
|
|
(let [chg {:type :add-obj
|
|
:page-id page-id
|
|
:id id-a
|
|
:parent-id uuid/zero
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:frame-id uuid/zero
|
|
:parent-id uuid/zero
|
|
:id id-a
|
|
:type :rect
|
|
:name "rect"})}
|
|
res (ch/process-changes data [chg])]
|
|
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= 2 (count objects)))
|
|
(t/is (= (:obj chg) (get objects id-a)))
|
|
|
|
(t/is (= [id-a] (get-in objects [uuid/zero :shapes]))))))
|
|
|
|
|
|
(t/testing "Adds several objects with different indexes"
|
|
(let [chg (fn [id index]
|
|
{:type :add-obj
|
|
:page-id page-id
|
|
:id id
|
|
:frame-id uuid/zero
|
|
:index index
|
|
:obj (cts/setup-shape
|
|
{:id id
|
|
:frame-id uuid/zero
|
|
:type :rect
|
|
:name (str id)})})
|
|
res (ch/process-changes data [(chg id-a 0)
|
|
(chg id-b 0)
|
|
(chg id-c 1)])]
|
|
|
|
;; (clojure.pprint/pprint data)
|
|
;; (clojure.pprint/pprint res)
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= 4 (count objects)))
|
|
(t/is (not (nil? (get objects id-a))))
|
|
(t/is (not (nil? (get objects id-b))))
|
|
(t/is (not (nil? (get objects id-c))))
|
|
(t/is (= [id-b id-c id-a] (get-in objects [uuid/zero :shapes]))))))))
|
|
|
|
(t/deftest mod-obj
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)]
|
|
|
|
(t/testing "simple mod-obj"
|
|
(let [chg {:type :mod-obj
|
|
:page-id page-id
|
|
:id uuid/zero
|
|
:operations [{:type :set
|
|
:attr :name
|
|
:val "foobar"}]}
|
|
res (ch/process-changes data [chg])]
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= "foobar" (get-in objects [uuid/zero :name]))))))
|
|
|
|
(t/testing "mod-obj for not existing shape"
|
|
(let [chg {:type :mod-obj
|
|
:page-id page-id
|
|
:id (uuid/next)
|
|
:operations [{:type :set
|
|
:attr :name
|
|
:val "foobar"}]}
|
|
res (ch/process-changes data [chg])]
|
|
(t/is (= res data))))))
|
|
|
|
(t/deftest mod-page
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)]
|
|
|
|
(t/testing "sets background and background-token together"
|
|
(let [chg {:type :mod-page
|
|
:id page-id
|
|
:background "#ffffff"
|
|
:background-token "brand.bg"}
|
|
res (ch/process-changes data [chg])
|
|
page (get-in res [:pages-index page-id])]
|
|
(t/is (= "#ffffff" (:background page)))
|
|
(t/is (= "brand.bg" (:background-token page)))))
|
|
|
|
(t/testing "clears background-token while leaving background untouched"
|
|
(let [data' (-> data
|
|
(assoc-in [:pages-index page-id :background] "#ffffff")
|
|
(assoc-in [:pages-index page-id :background-token] "brand.bg"))
|
|
chg {:type :mod-page
|
|
:id page-id
|
|
:background-token nil}
|
|
res (ch/process-changes data' [chg])
|
|
page (get-in res [:pages-index page-id])]
|
|
(t/is (= "#ffffff" (:background page)))
|
|
(t/is (not (contains? page :background-token)))))))
|
|
|
|
(t/deftest del-obj
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
id (uuid/custom 2 1)
|
|
data (make-file-data file-id page-id)
|
|
data (-> data
|
|
(assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id])
|
|
(assoc-in [:pages-index page-id :objects id]
|
|
{:id id
|
|
:frame-id uuid/zero
|
|
:type :rect
|
|
:name "rect"}))]
|
|
(t/testing "delete"
|
|
(let [chg {:type :del-obj
|
|
:page-id page-id
|
|
:id id}
|
|
res (ch/process-changes data [chg])]
|
|
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= 1 (count objects)))
|
|
(t/is (= [] (get-in objects [uuid/zero :shapes]))))))
|
|
|
|
(t/testing "delete idempotency"
|
|
(let [chg {:type :del-obj
|
|
:page-id page-id
|
|
:id id}
|
|
res1 (ch/process-changes data [chg])
|
|
res2 (ch/process-changes res1 [chg])]
|
|
|
|
(t/is (= res1 res2))
|
|
(let [objects (get-in res1 [:pages-index page-id :objects])]
|
|
(t/is (= 1 (count objects)))
|
|
(t/is (= [] (get-in objects [uuid/zero :shapes]))))))))
|
|
|
|
|
|
(t/deftest move-objects-1
|
|
(let [frame-a-id (uuid/custom 0 1)
|
|
frame-b-id (uuid/custom 0 2)
|
|
group-a-id (uuid/custom 0 3)
|
|
group-b-id (uuid/custom 0 4)
|
|
rect-a-id (uuid/custom 0 5)
|
|
rect-b-id (uuid/custom 0 6)
|
|
rect-c-id (uuid/custom 0 7)
|
|
rect-d-id (uuid/custom 0 8)
|
|
rect-e-id (uuid/custom 0 9)
|
|
|
|
file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)
|
|
|
|
data (update-in data [:pages-index page-id :objects]
|
|
#(-> %
|
|
(assoc-in [uuid/zero :shapes] [frame-a-id frame-b-id])
|
|
(assoc-in [frame-a-id]
|
|
(cts/setup-shape
|
|
{:id frame-a-id
|
|
:parent-id uuid/zero
|
|
:frame-id uuid/zero
|
|
:name "Frame a"
|
|
:shapes [group-a-id group-b-id rect-e-id]
|
|
:type :frame}))
|
|
|
|
(assoc-in [frame-b-id]
|
|
(cts/setup-shape
|
|
{:id frame-b-id
|
|
:parent-id uuid/zero
|
|
:frame-id uuid/zero
|
|
:name "Frame b"
|
|
:shapes []
|
|
:type :frame}))
|
|
|
|
;; Groups
|
|
(assoc-in [group-a-id]
|
|
(cts/setup-shape
|
|
{:id group-a-id
|
|
:name "Group A"
|
|
:type :group
|
|
:parent-id frame-a-id
|
|
:frame-id frame-a-id
|
|
:shapes [rect-a-id rect-b-id rect-c-id]}))
|
|
(assoc-in [group-b-id]
|
|
(cts/setup-shape
|
|
{:id group-b-id
|
|
:name "Group B"
|
|
:type :group
|
|
:parent-id frame-a-id
|
|
:frame-id frame-a-id
|
|
:shapes [rect-d-id]}))
|
|
|
|
;; Shapes
|
|
(assoc-in [rect-a-id]
|
|
(cts/setup-shape
|
|
{:id rect-a-id
|
|
:name "Rect A"
|
|
:type :rect
|
|
:parent-id group-a-id
|
|
:frame-id frame-a-id}))
|
|
|
|
(assoc-in [rect-b-id]
|
|
(cts/setup-shape
|
|
{:id rect-b-id
|
|
:name "Rect B"
|
|
:type :rect
|
|
:parent-id group-a-id
|
|
:frame-id frame-a-id}))
|
|
|
|
(assoc-in [rect-c-id]
|
|
(cts/setup-shape
|
|
{:id rect-c-id
|
|
:name "Rect C"
|
|
:type :rect
|
|
:parent-id group-a-id
|
|
:frame-id frame-a-id}))
|
|
|
|
(assoc-in [rect-d-id]
|
|
(cts/setup-shape
|
|
{:id rect-d-id
|
|
:name "Rect D"
|
|
:parent-id group-b-id
|
|
:type :rect
|
|
:frame-id frame-a-id}))
|
|
|
|
(assoc-in [rect-e-id]
|
|
(cts/setup-shape
|
|
{:id rect-e-id
|
|
:name "Rect E"
|
|
:type :rect
|
|
:parent-id frame-a-id
|
|
:frame-id frame-a-id}))))]
|
|
|
|
(t/testing "Create new group an add objects from the same group"
|
|
(let [new-group-id (uuid/next)
|
|
changes [{:type :add-obj
|
|
:page-id page-id
|
|
:id new-group-id
|
|
:frame-id frame-a-id
|
|
:obj (cts/setup-shape
|
|
{:id new-group-id
|
|
:type :group
|
|
:frame-id frame-a-id
|
|
:name "Group C"})}
|
|
{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id new-group-id
|
|
:shapes [rect-b-id rect-c-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; (clojure.pprint/pprint data)
|
|
;; (println "===============")
|
|
;; (clojure.pprint/pprint res)
|
|
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= [group-a-id group-b-id rect-e-id new-group-id]
|
|
(get-in objects [frame-a-id :shapes])))
|
|
(t/is (= [rect-b-id rect-c-id]
|
|
(get-in objects [new-group-id :shapes])))
|
|
(t/is (= [rect-a-id]
|
|
(get-in objects [group-a-id :shapes]))))))
|
|
|
|
(t/testing "Move elements to an existing group at index"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id group-b-id
|
|
:index 0
|
|
:shapes [rect-a-id rect-c-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= [group-a-id group-b-id rect-e-id]
|
|
(get-in objects [frame-a-id :shapes])))
|
|
(t/is (= [rect-b-id]
|
|
(get-in objects [group-a-id :shapes])))
|
|
(t/is (= [rect-a-id rect-c-id rect-d-id]
|
|
(get-in objects [group-b-id :shapes]))))))
|
|
|
|
(t/testing "Move elements from group and frame to an existing group at index"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id group-b-id
|
|
:index 0
|
|
:shapes [rect-a-id rect-e-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= [group-a-id group-b-id]
|
|
(get-in objects [frame-a-id :shapes])))
|
|
(t/is (= [rect-b-id rect-c-id]
|
|
(get-in objects [group-a-id :shapes])))
|
|
(t/is (= [rect-a-id rect-e-id rect-d-id]
|
|
(get-in objects [group-b-id :shapes]))))))
|
|
|
|
(t/testing "Move elements from several groups"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id group-b-id
|
|
:index 0
|
|
:shapes [rect-a-id rect-e-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= [group-a-id group-b-id]
|
|
(get-in objects [frame-a-id :shapes])))
|
|
(t/is (= [rect-b-id rect-c-id]
|
|
(get-in objects [group-a-id :shapes])))
|
|
(t/is (= [rect-a-id rect-e-id rect-d-id]
|
|
(get-in objects [group-b-id :shapes]))))))
|
|
|
|
(t/testing "Move all elements from a group"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id group-a-id
|
|
:shapes [rect-d-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= [group-a-id group-b-id rect-e-id]
|
|
(get-in objects [frame-a-id :shapes])))
|
|
(t/is (empty? (get-in objects [group-b-id :shapes]))))))
|
|
|
|
(t/testing "Move elements to a group with different frame"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id frame-b-id
|
|
:shapes [group-a-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; (pprint (get-in data [:pages-index page-id :objects]))
|
|
;; (println "==========")
|
|
;; (pprint (get-in res [:pages-index page-id :objects]))
|
|
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
(t/is (= [group-b-id rect-e-id] (get-in objects [frame-a-id :shapes])))
|
|
(t/is (= [group-a-id] (get-in objects [frame-b-id :shapes])))
|
|
(t/is (= frame-b-id (get-in objects [group-a-id :frame-id])))
|
|
(t/is (= frame-b-id (get-in objects [rect-a-id :frame-id])))
|
|
(t/is (= frame-b-id (get-in objects [rect-b-id :frame-id])))
|
|
(t/is (= frame-b-id (get-in objects [rect-c-id :frame-id]))))))
|
|
|
|
(t/testing "Move elements to frame zero"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id uuid/zero
|
|
:shapes [group-a-id]
|
|
:index 0}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
|
;; (pprint (get-in data [:objects uuid/zero]))
|
|
;; (println "==========")
|
|
;; (pprint (get-in objects [uuid/zero]))
|
|
|
|
(t/is (= [group-a-id frame-a-id frame-b-id]
|
|
(get-in objects [uuid/zero :shapes]))))))
|
|
|
|
(t/testing "Don't allow to move inside self"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id group-a-id
|
|
:shapes [group-a-id]}]
|
|
res (ch/process-changes data changes)]
|
|
(t/is (= data res))))))
|
|
|
|
|
|
(t/deftest mov-objects-regression-1
|
|
(let [shape-1-id (uuid/custom 2 1)
|
|
shape-2-id (uuid/custom 2 2)
|
|
shape-3-id (uuid/custom 2 3)
|
|
frame-id (uuid/custom 1 1)
|
|
file-id (uuid/custom 4 4)
|
|
page-id (uuid/custom 0 1)
|
|
|
|
changes [{:type :add-obj
|
|
:id frame-id
|
|
:page-id page-id
|
|
:parent-id uuid/zero
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:type :frame
|
|
:name "Frame"})}
|
|
{:type :add-obj
|
|
:page-id page-id
|
|
:frame-id frame-id
|
|
:parent-id frame-id
|
|
:id shape-1-id
|
|
:obj (cts/setup-shape
|
|
{:type :rect
|
|
:name "Shape 1"})}
|
|
{:type :add-obj
|
|
:page-id page-id
|
|
:id shape-2-id
|
|
:parent-id uuid/zero
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:type :rect
|
|
:name "Shape 2"})}
|
|
|
|
{:type :add-obj
|
|
:page-id page-id
|
|
:id shape-3-id
|
|
:parent-id uuid/zero
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:type :rect
|
|
:name "Shape 3"})}]
|
|
data (make-file-data file-id page-id)
|
|
data (ch/process-changes data changes)]
|
|
|
|
(t/testing "preserve order on multiple shape mov 1"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:shapes [shape-2-id shape-3-id]
|
|
:parent-id uuid/zero
|
|
:index 0}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; (println "==> BEFORE")
|
|
;; (pprint (get-in data [:objects]))
|
|
;; (println "==> AFTER")
|
|
;; (pprint (get-in res [:objects]))
|
|
|
|
(t/is (= [frame-id shape-2-id shape-3-id]
|
|
(get-in data [:pages-index page-id :objects uuid/zero :shapes])))
|
|
(t/is (= [shape-2-id shape-3-id frame-id]
|
|
(get-in res [:pages-index page-id :objects uuid/zero :shapes])))))
|
|
|
|
(t/testing "preserve order on multiple shape mov 1"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:shapes [shape-3-id shape-2-id]
|
|
:parent-id uuid/zero
|
|
:index 0}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; (println "==> BEFORE")
|
|
;; (pprint (get-in data [:objects]))
|
|
;; (println "==> AFTER")
|
|
;; (pprint (get-in res [:objects]))
|
|
|
|
(t/is (= [frame-id shape-2-id shape-3-id]
|
|
(get-in data [:pages-index page-id :objects uuid/zero :shapes])))
|
|
(t/is (= [shape-3-id shape-2-id frame-id]
|
|
(get-in res [:pages-index page-id :objects uuid/zero :shapes])))))
|
|
|
|
(t/testing "move inside->outside-inside"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:shapes [shape-2-id]
|
|
:parent-id frame-id}
|
|
{:type :mov-objects
|
|
:page-id page-id
|
|
:shapes [shape-2-id]
|
|
:parent-id uuid/zero}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
(t/is (= (get-in res [:pages-index page-id :objects shape-1-id :frame-id])
|
|
(get-in data [:pages-index page-id :objects shape-1-id :frame-id])))
|
|
(t/is (= (get-in res [:pages-index page-id :objects shape-2-id :frame-id])
|
|
(get-in data [:pages-index page-id :objects shape-2-id :frame-id])))))))
|
|
|
|
|
|
(t/deftest move-objects-2
|
|
(let [shape-1-id (uuid/custom 1 1)
|
|
shape-2-id (uuid/custom 1 2)
|
|
shape-3-id (uuid/custom 1 3)
|
|
shape-4-id (uuid/custom 1 4)
|
|
group-1-id (uuid/custom 1 5)
|
|
file-id (uuid/custom 1 6)
|
|
page-id (uuid/custom 0 1)
|
|
|
|
changes [{:type :add-obj
|
|
:page-id page-id
|
|
:id shape-1-id
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:id shape-1-id
|
|
:type :rect
|
|
:name "Shape a"})}
|
|
{:type :add-obj
|
|
:page-id page-id
|
|
:id shape-2-id
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:id shape-2-id
|
|
:type :rect
|
|
:name "Shape b"})}
|
|
{:type :add-obj
|
|
:page-id page-id
|
|
:id shape-3-id
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:id shape-3-id
|
|
:type :rect
|
|
:name "Shape c"})}
|
|
{:type :add-obj
|
|
:page-id page-id
|
|
:id shape-4-id
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:id shape-4-id
|
|
:type :rect
|
|
:name "Shape d"})}
|
|
{:type :add-obj
|
|
:page-id page-id
|
|
:id group-1-id
|
|
:frame-id uuid/zero
|
|
:obj (cts/setup-shape
|
|
{:id group-1-id
|
|
:type :group
|
|
:name "Group"})}
|
|
{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id group-1-id
|
|
:shapes [shape-1-id shape-2-id]}]
|
|
|
|
data (make-file-data file-id page-id)
|
|
data (ch/process-changes data changes)]
|
|
|
|
(t/testing "case 1"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id uuid/zero
|
|
:index 2
|
|
:shapes [shape-3-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; Before
|
|
|
|
(t/is (= [shape-3-id shape-4-id group-1-id]
|
|
(get-in data [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
;; After
|
|
|
|
(t/is (= [shape-4-id shape-3-id group-1-id]
|
|
(get-in res [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
;; (pprint (get-in data [:pages-index page-id :objects uuid/zero]))
|
|
;; (pprint (get-in res [:pages-index page-id :objects uuid/zero]))
|
|
))
|
|
|
|
(t/testing "case 2"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id group-1-id
|
|
:index 2
|
|
:shapes [shape-3-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; Before
|
|
|
|
(t/is (= [shape-3-id shape-4-id group-1-id]
|
|
(get-in data [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (= [shape-1-id shape-2-id]
|
|
(get-in data [:pages-index page-id :objects group-1-id :shapes])))
|
|
|
|
;; After:
|
|
|
|
(t/is (= [shape-4-id group-1-id]
|
|
(get-in res [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (= [shape-1-id shape-2-id shape-3-id]
|
|
(get-in res [:pages-index page-id :objects group-1-id :shapes])))
|
|
|
|
;; (pprint (get-in data [:pages-index page-id :objects group-1-id]))
|
|
;; (pprint (get-in res [:pages-index page-id :objects group-1-id]))
|
|
))
|
|
|
|
(t/testing "case 3"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id group-1-id
|
|
:index 1
|
|
:shapes [shape-3-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; Before
|
|
|
|
(t/is (= [shape-3-id shape-4-id group-1-id]
|
|
(get-in data [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (= [shape-1-id shape-2-id]
|
|
(get-in data [:pages-index page-id :objects group-1-id :shapes])))
|
|
|
|
;; After
|
|
|
|
(t/is (= [shape-4-id group-1-id]
|
|
(get-in res [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (= [shape-1-id shape-3-id shape-2-id]
|
|
(get-in res [:pages-index page-id :objects group-1-id :shapes])))
|
|
|
|
;; (pprint (get-in data [:pages-index page-id :objects group-1-id]))
|
|
;; (pprint (get-in res [:pages-index page-id :objects group-1-id]))
|
|
))
|
|
|
|
(t/testing "case 4"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id group-1-id
|
|
:index 0
|
|
:shapes [shape-3-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; Before
|
|
|
|
(t/is (= [shape-3-id shape-4-id group-1-id]
|
|
(get-in data [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (= [shape-1-id shape-2-id]
|
|
(get-in data [:pages-index page-id :objects group-1-id :shapes])))
|
|
|
|
;; After
|
|
|
|
(t/is (= [shape-4-id group-1-id]
|
|
(get-in res [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (= [shape-3-id shape-1-id shape-2-id]
|
|
(get-in res [:pages-index page-id :objects group-1-id :shapes])))
|
|
|
|
;; (pprint (get-in data [:pages-index page-id :objects group-1-id]))
|
|
;; (pprint (get-in res [:pages-index page-id :objects group-1-id]))
|
|
))
|
|
|
|
(t/testing "case 5"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id uuid/zero
|
|
:index 0
|
|
:shapes [shape-2-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; (pprint (get-in data [:pages-index page-id :objects uuid/zero]))
|
|
;; (pprint (get-in res [:pages-index page-id :objects uuid/zero]))
|
|
|
|
;; (pprint (get-in data [:pages-index page-id :objects group-1-id]))
|
|
;; (pprint (get-in res [:pages-index page-id :objects group-1-id]))
|
|
|
|
;; Before
|
|
|
|
(t/is (= [shape-3-id shape-4-id group-1-id]
|
|
(get-in data [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (= [shape-1-id shape-2-id]
|
|
(get-in data [:pages-index page-id :objects group-1-id :shapes])))
|
|
|
|
;; After
|
|
|
|
(t/is (= [shape-2-id shape-3-id shape-4-id group-1-id]
|
|
(get-in res [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (= [shape-1-id]
|
|
(get-in res [:pages-index page-id :objects group-1-id :shapes])))))
|
|
|
|
(t/testing "case 6"
|
|
(let [changes [{:type :mov-objects
|
|
:page-id page-id
|
|
:parent-id uuid/zero
|
|
:index 0
|
|
:shapes [shape-2-id shape-1-id]}]
|
|
res (ch/process-changes data changes)]
|
|
|
|
;; (pprint (get-in data [:pages-index page-id :objects uuid/zero]))
|
|
;; (pprint (get-in res [:pages-index page-id :objects uuid/zero]))
|
|
|
|
;; (pprint (get-in data [:pages-index page-id :objects group-1-id]))
|
|
;; (pprint (get-in res [:pages-index page-id :objects group-1-id]))
|
|
|
|
;; Before
|
|
|
|
(t/is (= [shape-3-id shape-4-id group-1-id]
|
|
(get-in data [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (= [shape-1-id shape-2-id]
|
|
(get-in data [:pages-index page-id :objects group-1-id :shapes])))
|
|
|
|
;; After
|
|
|
|
(t/is (= [shape-2-id shape-1-id shape-3-id shape-4-id group-1-id]
|
|
(get-in res [:pages-index page-id :objects uuid/zero :shapes])))
|
|
|
|
(t/is (not= nil
|
|
(get-in res [:pages-index page-id :objects group-1-id])))))))
|
|
|
|
(t/deftest set-guide-json-encode-decode
|
|
(let [schema ch/schema:set-guide-change
|
|
encode (sm/encoder schema (sm/json-transformer))
|
|
decode (sm/decoder schema (sm/json-transformer))]
|
|
(smt/check!
|
|
(smt/for [data (sg/generator schema)]
|
|
(let [data-1 (encode data)
|
|
data-2 (json-roundtrip data-1)
|
|
data-3 (decode data-2)]
|
|
;; (app.common.pprint/pprint data-2)
|
|
;; (app.common.pprint/pprint data-3)
|
|
(= data data-3)))
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-guide-1
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)]
|
|
|
|
(smt/check!
|
|
(smt/for [change (sg/generator ch/schema:set-guide-change)]
|
|
(let [change (assoc change :page-id page-id)
|
|
result (ch/process-changes data [change])]
|
|
(= (:params change)
|
|
(get-in result [:pages-index page-id :guides (:id change)]))))
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-guide-2
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)]
|
|
|
|
(smt/check!
|
|
(smt/for [change (->> (sg/generator ch/schema:set-guide-change)
|
|
(sg/filter :params))]
|
|
(let [change1 (assoc change :page-id page-id)
|
|
result1 (ch/process-changes data [change1])
|
|
|
|
change2 (assoc change1 :params nil)
|
|
result2 (ch/process-changes result1 [change2])]
|
|
|
|
(and (some? (:params change1))
|
|
(= (:params change1)
|
|
(get-in result1 [:pages-index page-id :guides (:id change1)]))
|
|
|
|
(nil? (:params change2))
|
|
(nil? (get-in result2 [:pages-index page-id :guides])))))
|
|
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-comment-thread-position
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
thread-id (uuid/custom 3 1)
|
|
frame-id (uuid/custom 4 1)
|
|
data (make-file-data file-id page-id)]
|
|
|
|
(t/testing "stores position and frame-id"
|
|
(let [change {:type :set-comment-thread-position
|
|
:page-id page-id
|
|
:comment-thread-id thread-id
|
|
:frame-id frame-id
|
|
:position (gpt/point 10 20)}
|
|
res (ch/process-changes data [change])]
|
|
(t/is (= {:frame-id frame-id :position (gpt/point 10 20)}
|
|
(get-in res [:pages-index page-id :comment-thread-positions thread-id])))))
|
|
|
|
(t/testing "removes the position when frame-id and position are nil"
|
|
(let [data (ch/process-changes data [{:type :set-comment-thread-position
|
|
:page-id page-id
|
|
:comment-thread-id thread-id
|
|
:frame-id frame-id
|
|
:position (gpt/point 10 20)}])
|
|
res (ch/process-changes data [{:type :set-comment-thread-position
|
|
:page-id page-id
|
|
:comment-thread-id thread-id
|
|
:frame-id nil
|
|
:position nil}])]
|
|
(t/is (nil? (get-in res [:pages-index page-id :comment-thread-positions thread-id])))))
|
|
|
|
(t/testing "builder round-trips the position through undo and redo"
|
|
(let [data (ch/process-changes data [{:type :set-comment-thread-position
|
|
:page-id page-id
|
|
:comment-thread-id thread-id
|
|
:frame-id frame-id
|
|
:position (gpt/point 10 20)}])
|
|
page (get-in data [:pages-index page-id])
|
|
changes (-> (pcb/empty-changes)
|
|
(pcb/with-page page)
|
|
(pcb/set-comment-thread-position {:id thread-id
|
|
:frame-id frame-id
|
|
:position (gpt/point 100 200)}))
|
|
redone (ch/process-changes data (:redo-changes changes))
|
|
undone (ch/process-changes redone (:undo-changes changes))]
|
|
(t/is (= (gpt/point 100 200)
|
|
(get-in redone [:pages-index page-id :comment-thread-positions thread-id :position])))
|
|
(t/is (= (gpt/point 10 20)
|
|
(get-in undone [:pages-index page-id :comment-thread-positions thread-id :position])))))))
|
|
|
|
(t/deftest set-plugin-data-json-encode-decode
|
|
(let [schema ch/schema:set-plugin-data-change
|
|
encode (sm/encoder schema (sm/json-transformer))
|
|
decode (sm/decoder schema (sm/json-transformer))]
|
|
(smt/check!
|
|
(smt/for [data (sg/generator schema)]
|
|
(let [data-1 (encode data)
|
|
data-2 (json-roundtrip data-1)
|
|
data-3 (decode data-2)]
|
|
(= data data-3)))
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-plugin-data-gen-and-validate
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)]
|
|
(smt/check!
|
|
(smt/for [change (sg/generator ch/schema:set-plugin-data-change)]
|
|
(sm/validate ch/schema:set-plugin-data-change change))
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-flow-json-encode-decode
|
|
(let [schema ch/schema:set-flow-change
|
|
encode (sm/encoder schema (sm/json-transformer))
|
|
decode (sm/decoder schema (sm/json-transformer))]
|
|
(smt/check!
|
|
(smt/for [data (sg/generator schema)]
|
|
(let [data-1 (encode data)
|
|
data-2 (json-roundtrip data-1)
|
|
data-3 (decode data-2)]
|
|
;; (app.common.pprint/pprint data-2)
|
|
;; (app.common.pprint/pprint data-3)
|
|
(= data data-3)))
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-flow-1
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)]
|
|
|
|
(smt/check!
|
|
(smt/for [change (sg/generator ch/schema:set-flow-change)]
|
|
(let [change (assoc change :page-id page-id)
|
|
result (ch/process-changes data [change])]
|
|
(= (:params change)
|
|
(get-in result [:pages-index page-id :flows (:id change)]))))
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-flow-2
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)]
|
|
|
|
(smt/check!
|
|
(smt/for [change (->> (sg/generator ch/schema:set-flow-change)
|
|
(sg/filter :params))]
|
|
(let [change1 (assoc change :page-id page-id)
|
|
result1 (ch/process-changes data [change1])
|
|
|
|
change2 (assoc change1 :params nil)
|
|
result2 (ch/process-changes result1 [change2])]
|
|
|
|
(and (some? (:params change1))
|
|
(= (:params change1)
|
|
(get-in result1 [:pages-index page-id :flows (:id change1)]))
|
|
|
|
(nil? (:params change2))
|
|
(nil? (get-in result2 [:pages-index page-id :flows])))))
|
|
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-default-grid-json-encode-decode
|
|
(let [schema ch/schema:set-default-grid-change
|
|
encode (sm/encoder schema (sm/json-transformer))
|
|
decode (sm/decoder schema (sm/json-transformer))]
|
|
(smt/check!
|
|
(smt/for [data (sg/generator schema)]
|
|
(let [data-1 (encode data)
|
|
data-2 (json-roundtrip data-1)
|
|
data-3 (decode data-2)]
|
|
;; (println "==========")
|
|
;; (app.common.pprint/pprint data-2)
|
|
;; (app.common.pprint/pprint data-3)
|
|
;; (println "==========")
|
|
(= data data-3)))
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-default-grid-1
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)]
|
|
|
|
(smt/check!
|
|
(smt/for [change (sg/generator ch/schema:set-default-grid-change)]
|
|
(let [change (assoc change :page-id page-id)
|
|
result (ch/process-changes data [change])]
|
|
;; (app.common.pprint/pprint change)
|
|
(= (:params change)
|
|
(get-in result [:pages-index page-id :default-grids (:grid-type change)]))))
|
|
{:num 1000})))
|
|
|
|
(t/deftest set-default-grid-2
|
|
(let [file-id (uuid/custom 2 2)
|
|
page-id (uuid/custom 1 1)
|
|
data (make-file-data file-id page-id)]
|
|
|
|
(smt/check!
|
|
(smt/for [change (->> (sg/generator ch/schema:set-default-grid-change)
|
|
(sg/filter :params))]
|
|
(let [change1 (assoc change :page-id page-id)
|
|
result1 (ch/process-changes data [change1])
|
|
|
|
change2 (assoc change1 :params nil)
|
|
result2 (ch/process-changes result1 [change2])]
|
|
|
|
;; (app.common.pprint/pprint change1)
|
|
|
|
(and (some? (:params change1))
|
|
(= (:params change1)
|
|
(get-in result1 [:pages-index page-id :default-grids (:grid-type change1)]))
|
|
|
|
(nil? (:params change2))
|
|
(nil? (get-in result2 [:pages-index page-id :default-grids])))))
|
|
|
|
{:num 1000})))
|
|
|
|
;; --- Candidate object ids
|
|
|
|
(defn- add-shape-change
|
|
[page-id [id parent-id frame-id type]]
|
|
{:type :add-obj :id id :page-id page-id
|
|
:frame-id frame-id :parent-id parent-id
|
|
:obj (cts/setup-shape
|
|
{:id id :type type :name (name type)
|
|
:frame-id frame-id :parent-id parent-id
|
|
:x 0 :y 0 :width 10 :height 10})})
|
|
|
|
(defn- make-tree-data
|
|
"A board with a group of two rects, a rect at the root, and two nested
|
|
groups around a rect."
|
|
[page-id ids]
|
|
(ch/process-changes
|
|
(make-file-data (uuid/custom 40 99) page-id)
|
|
(mapv (partial add-shape-change page-id)
|
|
[[(ids :board) uuid/zero uuid/zero :frame]
|
|
[(ids :group) (ids :board) (ids :board) :group]
|
|
[(ids :rect-a) (ids :group) (ids :board) :rect]
|
|
[(ids :rect-b) (ids :group) (ids :board) :rect]
|
|
[(ids :rect-c) uuid/zero uuid/zero :rect]
|
|
[(ids :outer) uuid/zero uuid/zero :group]
|
|
[(ids :inner) (ids :outer) uuid/zero :group]
|
|
[(ids :rect-d) (ids :inner) uuid/zero :rect]])))
|
|
|
|
(def ^:private tree-ids
|
|
(zipmap [:board :group :rect-a :rect-b :rect-c :outer :inner :rect-d :new]
|
|
(map #(uuid/custom 40 %) (range 1 10))))
|
|
|
|
(defn- get-changed-ids
|
|
[old-objects new-objects]
|
|
(into #{}
|
|
(filter #(not= (get old-objects %) (get new-objects %)))
|
|
(set/union (set (keys old-objects)) (set (keys new-objects)))))
|
|
|
|
(defn- apply-and-get-candidates
|
|
"Applies `changes`, checks that the candidates cover every changed id,
|
|
and returns the candidates."
|
|
[data page-id changes]
|
|
(let [old-objects (get-in data [:pages-index page-id :objects])
|
|
result (ch/process-changes data changes)
|
|
new-objects (get-in result [:pages-index page-id :objects])
|
|
candidates (ch/get-candidate-object-ids old-objects new-objects page-id changes)]
|
|
(t/is (set/subset? (get-changed-ids old-objects new-objects) candidates)
|
|
"Every changed object must be a candidate")
|
|
candidates))
|
|
|
|
(t/deftest candidate-ids-of-added-shapes
|
|
(let [page-id (uuid/custom 40 100)
|
|
ids tree-ids
|
|
data (make-tree-data page-id ids)]
|
|
(t/testing "A shape added at the root gives the shape and the root"
|
|
(t/is (= #{(ids :new) uuid/zero}
|
|
(apply-and-get-candidates
|
|
data page-id
|
|
[(add-shape-change page-id [(ids :new) uuid/zero uuid/zero :rect])]))))
|
|
(t/testing "A shape added inside a board gives the shape and the board"
|
|
(t/is (= #{(ids :new) (ids :board)}
|
|
(apply-and-get-candidates
|
|
data page-id
|
|
[(add-shape-change page-id [(ids :new) (ids :board) (ids :board) :rect])]))))))
|
|
|
|
(t/deftest candidate-ids-of-modified-shape
|
|
(let [page-id (uuid/custom 40 100)
|
|
ids tree-ids
|
|
data (make-tree-data page-id ids)]
|
|
(t/is (= #{(ids :rect-a)}
|
|
(apply-and-get-candidates
|
|
data page-id
|
|
[{:type :mod-obj :id (ids :rect-a) :page-id page-id
|
|
:operations [{:type :set :attr :name :val "renamed"}]}])))))
|
|
|
|
(t/deftest candidate-ids-of-deleted-group
|
|
(let [page-id (uuid/custom 40 100)
|
|
ids tree-ids
|
|
data (make-tree-data page-id ids)
|
|
candidates (apply-and-get-candidates
|
|
data page-id
|
|
[{:type :del-obj :id (ids :group) :page-id page-id}])]
|
|
(t/is (set/subset? #{(ids :group) (ids :rect-a) (ids :rect-b) (ids :board)}
|
|
candidates))))
|
|
|
|
(t/deftest candidate-ids-of-moved-group
|
|
(let [page-id (uuid/custom 40 100)
|
|
ids tree-ids
|
|
data (make-tree-data page-id ids)
|
|
candidates (apply-and-get-candidates
|
|
data page-id
|
|
[{:type :mov-objects :page-id page-id
|
|
:parent-id (ids :board) :shapes [(ids :outer)]}])]
|
|
(t/is (set/subset? #{(ids :outer) (ids :inner) (ids :rect-d) (ids :board) uuid/zero}
|
|
candidates))))
|
|
|
|
(t/deftest candidate-ids-of-registered-objects
|
|
(let [page-id (uuid/custom 40 100)
|
|
ids tree-ids
|
|
data (make-tree-data page-id ids)
|
|
rect (get-in data [:pages-index page-id :objects (ids :rect-d)])
|
|
moved (gsh/move rect (gpt/point 100 0))
|
|
data (ch/process-changes
|
|
data
|
|
[{:type :mod-obj :id (ids :rect-d) :page-id page-id
|
|
:operations (mapv (fn [[attr val]] {:type :set :attr attr :val val})
|
|
(select-keys moved [:x :y :selrect :points]))}])]
|
|
(t/is (set/subset? #{(ids :inner) (ids :outer)}
|
|
(apply-and-get-candidates
|
|
data page-id
|
|
[{:type :reg-objects :page-id page-id :shapes [(ids :rect-d)]}])))))
|
|
|
|
(t/deftest candidate-ids-of-reordered-children
|
|
(let [page-id (uuid/custom 40 100)
|
|
ids tree-ids
|
|
data (make-tree-data page-id ids)]
|
|
(t/is (= #{(ids :group)}
|
|
(apply-and-get-candidates
|
|
data page-id
|
|
[{:type :reorder-children :page-id page-id
|
|
:parent-id (ids :group) :shapes [(ids :rect-b) (ids :rect-a)]}])))))
|
|
|
|
(t/deftest candidate-ids-ignore-other-pages
|
|
(let [page-id (uuid/custom 40 100)
|
|
ids tree-ids
|
|
data (make-tree-data page-id ids)]
|
|
(t/is (= #{}
|
|
(ch/get-candidate-object-ids
|
|
(get-in data [:pages-index page-id :objects])
|
|
(get-in data [:pages-index page-id :objects])
|
|
page-id
|
|
[{:type :mod-obj :id (ids :rect-a) :page-id (uuid/custom 40 101)
|
|
:operations [{:type :set :attr :name :val "renamed"}]}
|
|
{:type :set-plugin-data :object-type :shape :object-id (ids :rect-a)
|
|
:page-id page-id :namespace "ns" :key "k" :value "v"}])))))
|