diff --git a/.circleci/config.yml b/.circleci/config.yml index e1a6e629f..18d7cf5d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,7 +93,7 @@ jobs: working_directory: "./common" command: | yarn test - clojure -X:dev:test :patterns '["common-tests.*-test"]' + clojure -M:dev:test - run: name: "frontend tests" diff --git a/backend/src/app/rpc/commands/files_create.clj b/backend/src/app/rpc/commands/files_create.clj index cc15830d4..ab386eca0 100644 --- a/backend/src/app/rpc/commands/files_create.clj +++ b/backend/src/app/rpc/commands/files_create.clj @@ -6,13 +6,10 @@ (ns app.rpc.commands.files-create (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.features :as cfeat] - [app.common.files.defaults :refer [version]] [app.common.schema :as sm] [app.common.types.file :as ctf] - [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] [app.features.fdata :as feat.fdata] @@ -40,7 +37,7 @@ (defn create-file [{:keys [::db/conn] :as cfg} {:keys [id name project-id is-shared revn - modified-at deleted-at create-page + modified-at deleted-at create-page page-id ignore-sync-until features] :or {is-shared false revn 0 create-page true} :as params}] @@ -51,23 +48,17 @@ (binding [pmap/*tracked* (pmap/create-tracked) cfeat/*current* features] - (let [id (or id (uuid/next)) - - data (if create-page - (ctf/make-file-data id) - (ctf/make-file-data id nil)) - - file {:id id - :project-id project-id - :name name - :revn revn - :is-shared is-shared - :version version - :data data - :features features - :ignore-sync-until ignore-sync-until - :modified-at modified-at - :deleted-at deleted-at} + (let [file (ctf/make-file {:id id + :project-id project-id + :name name + :revn revn + :is-shared is-shared + :features features + :ignore-sync-until ignore-sync-until + :modified-at modified-at + :deleted-at deleted-at + :create-page create-page + :page-id page-id}) file (if (contains? features "fdata/objects-map") (feat.fdata/enable-objects-map file) @@ -75,9 +66,7 @@ file (if (contains? features "fdata/pointer-map") (feat.fdata/enable-pointer-map file) - file) - - file (d/without-nils file)] + file)] (db/insert! conn :file (-> file @@ -86,9 +75,9 @@ {::db/return-keys false}) (when (contains? features "fdata/pointer-map") - (feat.fdata/persist-pointers! cfg id)) + (feat.fdata/persist-pointers! cfg (:id file))) - (->> (assoc params :file-id id :role :owner) + (->> (assoc params :file-id (:id file) :role :owner) (create-file-role! conn)) (db/update! conn :project diff --git a/common/deps.edn b/common/deps.edn index b014882f9..704021288 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -76,12 +76,8 @@ :ns-default build} :test - {:extra-paths ["test"] - :extra-deps - {io.github.cognitect-labs/test-runner - {:git/tag "v0.5.1" :git/sha "dfb30dd"}} - :main-opts ["-m" "cognitect.test-runner"] - :exec-fn cognitect.test-runner.api/test} + {:main-opts ["-m" "kaocha.runner"] + :extra-deps {lambdaisland/kaocha {:mvn/version "1.88.1376"}}} :shadow-cljs {:main-opts ["-m" "shadow.cljs.devtools.cli"]} diff --git a/common/src/app/common/files/builder.cljc b/common/src/app/common/files/builder.cljc index c30a2e8c8..f5519f736 100644 --- a/common/src/app/common/files/builder.cljc +++ b/common/src/app/common/files/builder.cljc @@ -147,7 +147,7 @@ [file data] (dm/assert! (nil? (:current-component-id file))) (let [page-id (or (:id data) (uuid/next)) - page (-> (ctp/make-empty-page page-id "Page 1") + page (-> (ctp/make-empty-page {:id page-id :name "Page 1"}) (d/deep-merge data))] (-> file (commit-change diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 978cc8edf..a82ab947c 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -578,7 +578,7 @@ (ex/raise :type :conflict :hint "id+name or page should be provided, never both")) (let [page (if (and (string? name) (uuid? id)) - (ctp/make-empty-page id name) + (ctp/make-empty-page {:id id :name name}) page)] (ctpl/add-page data page))) diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index 7bded1492..111343d58 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -13,6 +13,7 @@ [app.common.types.color.generic :as-alias color-generic] [app.common.types.color.gradient :as-alias color-gradient] [app.common.types.color.gradient.stop :as-alias color-gradient-stop] + [app.common.uuid :as uuid] [clojure.test.check.generators :as tgen])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -105,6 +106,22 @@ ;; HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; --- factory + +(defn make-color + [{:keys [id name path value color opacity ref-id ref-file gradient image]}] + (-> {:id (or id (uuid/next)) + :name (or name color "Black") + :path path + :value value + :color (or color "#000000") + :opacity (or opacity 1) + :ref-id ref-id + :ref-file ref-file + :gradient gradient + :image image} + (d/without-nils))) + ;; --- fill (defn fill->shape-color diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 0c5fbf572..d12b759df 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.features :as cfeat] + [app.common.files.defaults :refer [version]] [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] @@ -78,7 +79,7 @@ ([file-id page-id] (let [page (when (some? page-id) - (ctp/make-empty-page page-id "Page 1"))] + (ctp/make-empty-page {:id page-id :name "Page 1"}))] (cond-> (assoc empty-file-data :id file-id) (some? page-id) @@ -87,6 +88,34 @@ (contains? cfeat/*current* "components/v2") (assoc-in [:options :components-v2] true))))) +(defn make-file + [{:keys [id project-id name revn is-shared features + ignore-sync-until modified-at deleted-at + create-page page-id] + :or {is-shared false revn 0 create-page true}}] + + (let [id (or id (uuid/next)) + + data (if create-page + (if page-id + (make-file-data id page-id) + (make-file-data id)) + (make-file-data id nil)) + + file {:id id + :project-id project-id + :name name + :revn revn + :is-shared is-shared + :version version + :data data + :features features + :ignore-sync-until ignore-sync-until + :modified-at modified-at + :deleted-at deleted-at}] + + (d/without-nils file))) + ;; Helpers (defn file-data @@ -457,7 +486,7 @@ (gpt/point 0 0) (ctn/shapes-seq library-page))] [file-data (:id library-page) position]) - (let [library-page (ctp/make-empty-page (uuid/next) "Main components")] + (let [library-page (ctp/make-empty-page {:id (uuid/next) :name "Main components"})] [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)])))) (defn- absorb-components diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 6c1d427df..0b2038928 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -69,10 +69,10 @@ :name "Root Frame"})}}) (defn make-empty-page - [id name] + [{:keys [id name]}] (-> empty-page-data - (assoc :id id) - (assoc :name name))) + (assoc :id (or id (uuid/next))) + (assoc :name (or name "Page 1")))) ;; --- Helpers for flow diff --git a/common/src/app/common/types/typography.cljc b/common/src/app/common/types/typography.cljc index 4fe5c9565..6e216020a 100644 --- a/common/src/app/common/types/typography.cljc +++ b/common/src/app/common/types/typography.cljc @@ -6,8 +6,10 @@ (ns app.common.types.typography (:require + [app.common.data :as d] [app.common.schema :as sm] - [app.common.text :as txt])) + [app.common.text :as txt] + [app.common.uuid :as uuid])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SCHEMA @@ -36,6 +38,23 @@ ;; HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn make-typography + [{:keys [id name path font-id font-family font-variant-id font-size + font-weight font-style line-height letter-spacing text-transform]}] + (-> {:id (or id (uuid/next)) + :name (or name "Typography 1") + :path path + :font-id (or font-id "sourcesanspro") + :font-family (or font-family "sourcesanspro") + :font-variant-id (or font-variant-id "regular") + :font-size (or font-size "14") + :font-weight (or font-weight "480") + :font-style (or font-style "normal") + :line-height (or line-height "1.2") + :letter-spacing (or letter-spacing "0") + :text-transform (or text-transform "none")} + (d/without-nils))) + (defn uses-library-typographies? "Check if the shape uses any typography in the given library." [shape library-id] diff --git a/common/test/common_tests/geom_point_test.cljc b/common/test/common_tests/geom_point_test.cljc index 0490e1d02..6ba7239f0 100644 --- a/common/test/common_tests/geom_point_test.cljc +++ b/common/test/common_tests/geom_point_test.cljc @@ -203,7 +203,7 @@ (t/is (mth/close? 1.5 (:x rs))) (t/is (mth/close? 3.5 (:y rs))))) -(t/deftest transform-point +(t/deftest ^:kaocha/skip transform-point ;;todo ) diff --git a/common/test/common_tests/helpers/components.cljc b/common/test/common_tests/helpers/components.cljc deleted file mode 100644 index 523438c44..000000000 --- a/common/test/common_tests/helpers/components.cljc +++ /dev/null @@ -1,146 +0,0 @@ -;; 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 common-tests.helpers.components - (:require - [app.common.files.helpers :as cfh] - [app.common.types.component :as ctk] - [app.common.types.container :as ctn] - [app.common.types.file :as ctf] - [clojure.test :as t])) - -;; ---- Helpers to manage libraries and synchronization - -(defn check-instance-root - [shape] - (t/is (some? (:shape-ref shape))) - (t/is (some? (:component-id shape))) - (t/is (= (:component-root shape) true))) - -(defn check-instance-subroot - [shape] - (t/is (some? (:shape-ref shape))) - (t/is (some? (:component-id shape))) - (t/is (nil? (:component-root shape)))) - -(defn check-instance-child - [shape] - (t/is (some? (:shape-ref shape))) - (t/is (nil? (:component-id shape))) - (t/is (nil? (:component-file shape))) - (t/is (nil? (:component-root shape)))) - -(defn check-instance-inner - [shape] - (if (some? (:component-id shape)) - (check-instance-subroot shape) - (check-instance-child shape))) - -(defn check-noninstance - [shape] - (t/is (nil? (:shape-ref shape))) - (t/is (nil? (:component-id shape))) - (t/is (nil? (:component-file shape))) - (t/is (nil? (:component-root shape))) - (t/is (nil? (:remote-synced? shape))) - (t/is (nil? (:touched shape)))) - -(defn check-from-file - [shape file] - (t/is (= (:component-file shape) - (:id file)))) - -(defn resolve-instance - "Get the shape with the given id and all its children, and - verify that they are a well constructed instance tree." - [page root-inst-id] - (let [root-inst (ctn/get-shape page root-inst-id) - shapes-inst (cfh/get-children-with-self (:objects page) - root-inst-id)] - (check-instance-root (first shapes-inst)) - (run! check-instance-inner (rest shapes-inst)) - - shapes-inst)) - -(defn resolve-noninstance - "Get the shape with the given id and all its children, and - verify that they are not a component instance." - [page root-inst-id] - (let [root-inst (ctn/get-shape page root-inst-id) - shapes-inst (cfh/get-children-with-self (:objects page) - root-inst-id)] - (run! check-noninstance shapes-inst) - - shapes-inst)) - -(defn resolve-instance-and-main - "Get the shape with the given id and all its children, and also - the main component and all its shapes." - [page root-inst-id libraries] - (let [root-inst (ctn/get-shape page root-inst-id) - - component (ctf/get-component libraries (:component-file root-inst) (:component-id root-inst)) - - shapes-inst (cfh/get-children-with-self (:objects page) root-inst-id) - shapes-main (cfh/get-children-with-self (:objects component) (:shape-ref root-inst)) - - unique-refs (into #{} (map :shape-ref) shapes-inst) - - main-exists? (fn [shape] - (let [component-shape - (ctn/get-component-shape (:objects page) shape) - - component - (ctf/get-component libraries (:component-file component-shape) (:component-id component-shape)) - - main-shape - (ctn/get-shape component (:shape-ref shape))] - - (t/is (some? main-shape))))] - - ;; Validate that the instance tree is well constructed - (check-instance-root (first shapes-inst)) - (run! check-instance-inner (rest shapes-inst)) - (t/is (= (count shapes-inst) - (count shapes-main) - (count unique-refs))) - (run! main-exists? shapes-inst) - - [shapes-inst shapes-main component])) - -(defn resolve-instance-and-main-allow-dangling - "Get the shape with the given id and all its children, and also - the main component and all its shapes. Allows shapes with the - corresponding component shape missing." - [page root-inst-id libraries] - (let [root-inst (ctn/get-shape page root-inst-id) - - component (ctf/get-component libraries (:component-file root-inst) (:component-id root-inst)) - - shapes-inst (cfh/get-children-with-self (:objects page) root-inst-id) - shapes-main (cfh/get-children-with-self (:objects component) (:shape-ref root-inst)) - - unique-refs (into #{} (map :shape-ref) shapes-inst) - - main-exists? (fn [shape] - (let [component-shape - (ctn/get-component-shape (:objects page) shape) - - component - (ctf/get-component libraries (:component-file component-shape) (:component-id component-shape)) - - main-shape - (ctn/get-shape component (:shape-ref shape))] - - (t/is (some? main-shape))))] - - ;; Validate that the instance tree is well constructed - (check-instance-root (first shapes-inst)) - - [shapes-inst shapes-main component])) - - - diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc new file mode 100644 index 000000000..6306fc51a --- /dev/null +++ b/common/test/common_tests/helpers/compositions.cljc @@ -0,0 +1,42 @@ +;; 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 common-tests.helpers.compositions + (:require + [common-tests.helpers.files :as thf])) + +(defn add-rect + [file rect-label] + (thf/add-sample-shape file rect-label + :type :rect + :name "Rect1")) + +(defn add-frame + [file frame-label] + (thf/add-sample-shape file frame-label + :type :frame + :name "Frame1")) + +(defn add-frame-with-child + [file frame-label child-label] + (-> file + (add-frame frame-label) + (thf/add-sample-shape child-label + :type :rect + :name "Rect1" + :parent-label frame-label))) + +(defn add-simple-component + [file component-label root-label child-label] + (-> file + (add-frame-with-child root-label child-label) + (thf/make-component component-label root-label))) + +(defn add-simple-component-with-copy + [file component-label main-root-label main-child-label copy-root-label] + (-> file + (add-simple-component component-label main-root-label main-child-label) + (thf/instantiate-component component-label copy-root-label))) diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index 3f27d7021..306d2ef81 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -6,150 +6,278 @@ (ns common-tests.helpers.files (:require + [app.common.data.macros :as dm] [app.common.features :as ffeat] + [app.common.files.changes :as cfc] + [app.common.files.helpers :as cfh] + [app.common.files.validate :as cfv] [app.common.geom.point :as gpt] + [app.common.pprint :refer [pprint]] + [app.common.types.color :as ctc] [app.common.types.colors-list :as ctcl] [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] + [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] - [app.common.types.typographies-list :as ctyl] - [app.common.uuid :as uuid])) + [app.common.types.typographies-list :as cttl] + [app.common.types.typography :as ctt] + [common-tests.helpers.ids-map :as thi])) -(defn- make-file-data - [file-id page-id] - (binding [ffeat/*current* #{"components/v2"}] - (ctf/make-file-data file-id page-id))) - -(def ^:private idmap (atom {})) - -(defn reset-idmap! - [next] - (reset! idmap {}) - (next)) - -(defn id - [label] - (get @idmap label)) +;; ----- Files (defn sample-file - ([file-id page-id] (sample-file file-id page-id nil)) - ([file-id page-id props] - (merge {:id file-id - :name (get props :name "File1") - :data (make-file-data file-id page-id)} - props))) + [label & {:keys [page-label name] :as params}] + (binding [ffeat/*current* #{"components/v2"}] + (let [params (cond-> params + label + (assoc :id (thi/new-id! label)) + + page-label + (assoc :page-id (thi/new-id! page-label)) + + (nil? name) + (assoc :name "Test file")) + + file (-> (ctf/make-file (dissoc params :page-label)) + (assoc :features #{"components/v2"})) + + page (-> file + :data + (ctpl/pages-seq) + (first))] + + (with-meta file + {:current-page-id (:id page)})))) + +(defn validate-file! + ([file] (validate-file! file {})) + ([file libraries] + (cfv/validate-file-schema! file) + (cfv/validate-file! file libraries))) + +(defn apply-changes + [file changes] + (let [file' (ctf/update-file-data file #(cfc/process-changes % (:redo-changes changes) true))] + (validate-file! file') + file')) + +(declare current-page-id) +(declare get-page) + +(defn dump-file + [file & {:keys [page-label libraries] :as params}] + (let [params (-> params + (or {:show-ids true :show-touched true}) + (dissoc page-label libraries)) + page (if (some? page-label) + (:id (get-page file page-label)) + (current-page-id file)) + libraries (or libraries {})] + + (ctf/dump-tree file page libraries params))) + +(defn pprint-file + [file & {:keys [level length] :or {level 10 length 1000}}] + (pprint file {:level level :length length})) + +;; ----- Pages + +(defn sample-page + [label & {:keys [] :as params}] + (ctp/make-empty-page (assoc params :id (thi/new-id! label)))) + +(defn add-sample-page + [file label & {:keys [] :as params}] + (let [page (sample-page label params)] + (-> file + (ctf/update-file-data #(ctpl/add-page % page)) + (vary-meta assoc :current-page-id (:id page))))) + +(defn get-page + [file label] + (ctpl/get-page (:data file) (thi/id label))) + +(defn current-page-id + [file] + (:current-page-id (meta file))) + +(defn current-page + [file] + (ctpl/get-page (:data file) (current-page-id file))) + +(defn switch-to-page + [file label] + (vary-meta file assoc :current-page-id (thi/id label))) + +;; ----- Shapes (defn sample-shape - [file label type page-id props] - (ctf/update-file-data - file - (fn [file-data] - (let [frame-id (get props :frame-id uuid/zero) - parent-id (get props :parent-id uuid/zero) - shape (cts/setup-shape - (-> {:type type - :width 1 - :height 1} - (merge props)))] + [label & {:keys [type] :as params}] + (let [params (cond-> params + label + (assoc :id (thi/new-id! label)) - (swap! idmap assoc label (:id shape)) + (nil? type) + (assoc :type :rect))] + + (cts/setup-shape params))) + +(defn add-sample-shape + [file label & {:keys [parent-label] :as params}] + (let [page (current-page file) + shape (sample-shape label (dissoc params :parent-label)) + parent-id (when parent-label + (thi/id parent-label)) + parent (when parent-id + (ctst/get-shape page parent-id)) + frame-id (if (cfh/frame-shape? parent) + (:id parent) + (:frame-id parent))] + (ctf/update-file-data + file + (fn [file-data] (ctpl/update-page file-data - page-id + (:id page) #(ctst/add-shape (:id shape) shape % frame-id parent-id - 0 + nil true)))))) -(defn sample-component - [file label page-id shape-id] - (ctf/update-file-data - file - (fn [file-data] - (let [page (ctpl/get-page file-data page-id) +(defn get-shape + [file label & {:keys [page-label]}] + (let [page (if page-label + (get-page file page-label) + (current-page file))] + (ctst/get-shape page (thi/id label)))) - [component-shape component-shapes updated-shapes] - (ctn/make-component-shape (ctn/get-shape page shape-id) - (:objects page) - (:id file) - true)] +(defn get-shape-by-id + [file id & {:keys [page-label]}] + (let [page (if page-label + (get-page file page-label) + (current-page file))] + (ctst/get-shape page id))) - (swap! idmap assoc label (:id component-shape)) - (-> file-data - (ctpl/update-page page-id - #(reduce (fn [page shape] (ctst/set-shape page shape)) - % - updated-shapes)) - (ctkl/add-component {:id (:id component-shape) - :name (:name component-shape) - :path "" - :main-instance-id shape-id - :main-instance-page page-id - :shapes component-shapes})))))) +;; ----- Components -(defn sample-instance - [file label page-id library component-id] - (ctf/update-file-data - file - (fn [file-data] - (let [[instance-shape instance-shapes] - (ctn/make-component-instance (ctpl/get-page file-data page-id) - (ctkl/get-component (:data library) component-id) - (:data library) - (gpt/point 0 0) - true)] +(defn make-component + [file label root-label] + (let [page (current-page file) + root (get-shape file root-label)] - (swap! idmap assoc label (:id instance-shape)) - (-> file-data - (ctpl/update-page page-id - #(reduce (fn [page shape] - (ctst/add-shape (:id shape) - shape - page - uuid/zero - (:parent-id shape) - 0 - true)) - % - instance-shapes))))))) + (dm/assert! + "Need that root is already a frame" + (cfh/frame-shape? root)) + + (let [[_new-root _new-shapes updated-shapes] + (ctn/convert-shape-in-component root (:objects page) (:id file)) + + updated-root (first updated-shapes)] ; Can't use new-root because it has a new id + + (thi/set-id! label (:component-id updated-root)) + + (ctf/update-file-data + file + (fn [file-data] + (as-> file-data $ + (reduce (fn [file-data shape] + (ctpl/update-page file-data + (:id page) + #(update % :objects assoc (:id shape) shape))) + $ + updated-shapes) + (ctkl/add-component $ + {:id (:component-id updated-root) + :name (:name updated-root) + :main-instance-id (:id updated-root) + :main-instance-page (:id page) + :shapes updated-shapes}))))))) + +(defn get-component + [file label] + (ctkl/get-component (:data file) (thi/id label))) + +(defn get-component-by-id + [file id] + (ctkl/get-component (:data file) id)) + +(defn instantiate-component + [file component-label copy-root-label & {:keys [parent-label library] :as params}] + (let [page (current-page file) + library (or library file) + component (get-component library component-label) + parent-id (when parent-label + (thi/id parent-label)) + parent (when parent-id + (ctst/get-shape page parent-id)) + frame-id (if (cfh/frame-shape? parent) + (:id parent) + (:frame-id parent)) + + [copy-root copy-shapes] + (ctn/make-component-instance page + component + (:data library) + (gpt/point 100 100) + true + {:force-id (thi/new-id! copy-root-label) + :force-frame-id frame-id}) + + copy-root' (cond-> copy-root + (some? parent) + (assoc :parent-id parent-id) + + (some? frame-id) + (assoc :frame-id frame-id) + + (and (some? parent) (ctn/in-any-component? (:objects page) parent)) + (dissoc :component-root))] + + (ctf/update-file-data + file + (fn [file-data] + (as-> file-data $ + (ctpl/update-page $ + (:id page) + #(ctst/add-shape (:id copy-root') + copy-root' + % + frame-id + parent-id + nil + true)) + (reduce (fn [file-data shape] + (ctpl/update-page file-data + (:id page) + #(ctst/add-shape (:id shape) + shape + % + (:parent-id shape) + (:frame-id shape) + nil + true))) + $ + (remove #(= (:id %) (:did copy-root')) copy-shapes))))))) (defn sample-color - [file label props] - (ctf/update-file-data - file - (fn [file-data] - (let [id (uuid/next) - props (merge {:id id - :name "Color 1" - :color "#000000" - :opacity 1} - props)] - (swap! idmap assoc label id) - (ctcl/add-color file-data props))))) + [label & {:keys [] :as params}] + (ctc/make-color (assoc params :id (thi/new-id! label)))) + +(defn add-sample-color + [file label & {:keys [] :as params}] + (let [color (sample-color label params)] + (ctf/update-file-data file #(ctcl/add-color % color)))) (defn sample-typography - [file label props] - (ctf/update-file-data - file - (fn [file-data] - (let [id (uuid/next) - props (merge {:id id - :name "Typography 1" - :font-id "sourcesanspro" - :font-family "sourcesanspro" - :font-size "14" - :font-style "normal" - :font-variant-id "regular" - :font-weight "400" - :line-height "1.2" - :letter-spacing "0" - :text-transform "none"} - props)] - (swap! idmap assoc label id) - (ctyl/add-typography file-data props))))) + [label & {:keys [] :as params}] + (ctt/make-typography (assoc params :id (thi/new-id! label)))) +(defn add-sample-typography + [file label & {:keys [] :as params}] + (let [typography (sample-typography label params)] + (ctf/update-file-data file #(cttl/add-typography % typography)))) diff --git a/common/test/common_tests/helpers/ids_map.cljc b/common/test/common_tests/helpers/ids_map.cljc new file mode 100644 index 000000000..dc196598d --- /dev/null +++ b/common/test/common_tests/helpers/ids_map.cljc @@ -0,0 +1,36 @@ +;; 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 common-tests.helpers.ids-map + (:require + [app.common.uuid :as uuid])) + +;; ---- Helpers to manage ids as known identifiers + +(def ^:private idmap (atom {})) + +(defn reset-idmap! [] + (reset! idmap {})) + +(defn set-id! + [label id] + (swap! idmap assoc label id)) + +(defn new-id! + [label] + (let [id (uuid/next)] + (set-id! label id) + id)) + +(defn id + [label] + (get @idmap label)) + +(defn test-fixture + ;; Ensure that each test starts with a clean ids map + [f] + (reset-idmap!) + (f)) diff --git a/common/test/common_tests/logic/logic_comp_creation_test.cljc b/common/test/common_tests/logic/logic_comp_creation_test.cljc new file mode 100644 index 000000000..577bef06f --- /dev/null +++ b/common/test/common_tests/logic/logic_comp_creation_test.cljc @@ -0,0 +1,45 @@ +;; 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 common-tests.logic.logic-comp-creation-test + (:require + [app.common.files.changes-builder :as pcb] + [app.common.files.libraries-helpers :as cflh] + [clojure.test :as t] + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi])) + +(t/use-fixtures :each thi/test-fixture) + +(t/deftest test-add-component-from-single-shape + (let [; Setup + file (-> (thf/sample-file :file1) + (thf/add-sample-shape :shape1 :type :frame)) + + page (thf/current-page file) + shape1 (thf/get-shape file :shape1) + + ; Action + [_ component-id changes] + (cflh/generate-add-component (pcb/empty-changes) + [shape1] + (:objects page) + (:id page) + (:id file) + true + nil + nil) + + file' (thf/apply-changes file changes) + + ; Get + component (thf/get-component-by-id file' component-id) + root (thf/get-shape-by-id file' (:main-instance-id component))] + + ; Check + (t/is (some? component)) + (t/is (some? root)) + (t/is (= (:component-id root) (:id component))))) diff --git a/common/test/common_tests/record_test.cljc b/common/test/common_tests/record_test.cljc index 64532078b..cbf62d021 100644 --- a/common/test/common_tests/record_test.cljc +++ b/common/test/common_tests/record_test.cljc @@ -27,7 +27,6 @@ (t/testing "unknown assoc" (let [o (assoc o :c 176)] - (prn o) (t/is (= 1 (:a o))) (t/is (= 2 (:b o))) (t/is (= 176 (:c o))))) diff --git a/common/test/common_tests/types/types_libraries_test.cljc b/common/test/common_tests/types/types_libraries_test.cljc new file mode 100644 index 000000000..0eab0db07 --- /dev/null +++ b/common/test/common_tests/types/types_libraries_test.cljc @@ -0,0 +1,189 @@ +;; 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 common-tests.types.types-libraries-test + (:require + [app.common.data :as d] + [app.common.text :as txt] + [app.common.types.colors-list :as ctcl] + [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.common.types.typographies-list :as ctyl] + [clojure.test :as t] + [common-tests.helpers.compositions :as tho] + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi])) + +(t/use-fixtures :each thi/test-fixture) + +(t/deftest test-create-file + (let [f1 (thf/sample-file :file1) + f2 (thf/sample-file :file2 :page-label :page1) + f3 (thf/sample-file :file3 :name "testing file") + f4 (-> (thf/sample-file :file4 :page-label :page2) + (thf/add-sample-page :page3 :name "testing page") + (thf/add-sample-shape :shape1)) + f5 (-> f4 + (thf/add-sample-shape :shape2) + (thf/switch-to-page :page2) + (thf/add-sample-shape :shape3 :name "testing shape" :width 100)) + s1 (thf/get-shape f4 :shape1) + s2 (thf/get-shape f5 :shape2 :page-label :page3) + s3 (thf/get-shape f5 :shape3)] + + ;; (thf/pprint-file f4) + + (t/is (= (:name f1) "Test file")) + (t/is (= (:name f3) "testing file")) + (t/is (= (:id f2) (thi/id :file2))) + (t/is (= (:id f4) (thi/id :file4))) + (t/is (= (-> f4 :data :pages-index vals first :id) (thi/id :page2))) + (t/is (= (-> f4 :data :pages-index vals first :name) "Page 1")) + (t/is (= (-> f4 :data :pages-index vals second :id) (thi/id :page3))) + (t/is (= (-> f4 :data :pages-index vals second :name) "testing page")) + + (t/is (= (:id (thf/current-page f2)) (thi/id :page1))) + (t/is (= (:id (thf/current-page f4)) (thi/id :page3))) + (t/is (= (:id (thf/current-page f5)) (thi/id :page2))) + + (t/is (= (:id s1) (thi/id :shape1))) + (t/is (= (:name s1) "Rectangle")) + (t/is (= (:id s2) (thi/id :shape2))) + (t/is (= (:name s2) "Rectangle")) + (t/is (= (:id s3) (thi/id :shape3))) + (t/is (= (:name s3) "testing shape")) + (t/is (= (:width s3) 100)) + (t/is (= (:width (:selrect s3)) 100)))) + +(t/deftest test-create-components + (let [f1 (-> (thf/sample-file :file1) + (tho/add-simple-component-with-copy :component1 :main-root :main-child :copy-root))] + + #_(thf/dump-file f1) + #_(thf/pprint-file f4) + + (t/is (= (:name f1) "Test file")))) + +(t/deftest test-absorb-components + (let [; Setup + library (-> (thf/sample-file :library :is-shared true) + (tho/add-simple-component :component1 :main-root :rect1)) + + file (-> (thf/sample-file :file) + (thf/instantiate-component :component1 :copy-root :library library)) + + ; Action + file' (ctf/update-file-data + file + #(ctf/absorb-assets % (:data library))) + + _ (thf/validate-file! file') + + ; Get + pages' (ctpl/pages-seq (ctf/file-data file')) + components' (ctkl/components-seq (ctf/file-data file')) + component' (first components') + + copy-root' (thf/get-shape file' :copy-root) + main-root' (ctf/get-ref-shape (ctf/file-data file') component' copy-root')] + + ; Check + (t/is (= (count pages') 2)) + (t/is (= (:name (first pages')) "Page 1")) + (t/is (= (:name (second pages')) "Main components")) + + (t/is (= (count components') 1)) + + (t/is (ctk/instance-of? copy-root' (:id file') (:id component'))) + (t/is (ctk/is-main-of? main-root' copy-root' true)) + (t/is (ctk/main-instance-of? (:id main-root') (:id (second pages')) component')))) + +(t/deftest test-absorb-colors + (let [; Setup + library (-> (thf/sample-file :library :is-shared true) + (thf/add-sample-color :color1 {:name "Test color" + :color "#abcdef"})) + + file (-> (thf/sample-file :file) + (thf/add-sample-shape :shape1 + :type :rect + :name "Rect1" + :fills [{:fill-color "#abcdef" + :fill-opacity 1 + :fill-color-ref-id (thi/id :color1) + :fill-color-ref-file (thi/id :library)}])) + + ; Action + file' (ctf/update-file-data + file + #(ctf/absorb-assets % (:data library))) + + _ (thf/validate-file! file') + + ; Get + colors' (ctcl/colors-seq (ctf/file-data file')) + shape1' (thf/get-shape file' :shape1) + fill' (first (:fills shape1'))] + + ; Check + (t/is (= (count colors') 1)) + (t/is (= (:id (first colors')) (thi/id :color1))) + (t/is (= (:name (first colors')) "Test color")) + (t/is (= (:color (first colors')) "#abcdef")) + + (t/is (= (:fill-color fill') "#abcdef")) + (t/is (= (:fill-color-ref-id fill') (thi/id :color1))) + (t/is (= (:fill-color-ref-file fill') (:id file'))))) + +(t/deftest test-absorb-typographies + (let [; Setup + library (-> (thf/sample-file :library :is-shared true) + (thf/add-sample-typography :typography1 {:name "Test typography"})) + + file (-> (thf/sample-file :file) + (thf/add-sample-shape :shape1 + :type :text + :name "Text1" + :content {:type "root" + :children [{:type "paragraph-set" + :children [{:type "paragraph" + :key "67uep" + :children [{:text "Example text" + :typography-ref-id (thi/id :typography1) + :typography-ref-file (thi/id :library) + :line-height "1.2" + :font-style "normal" + :text-transform "none" + :text-align "left" + :font-id "sourcesanspro" + :font-family "sourcesanspro" + :font-size "14" + :font-weight "400" + :font-variant-id "regular" + :text-decoration "none" + :letter-spacing "0" + :fills [{:fill-color "#000000" + :fill-opacity 1}]}]}]}]})) + ; Action + file' (ctf/update-file-data + file + #(ctf/absorb-assets % (:data library))) + + _ (thf/validate-file! file') + + ; Get + typographies' (ctyl/typographies-seq (ctf/file-data file')) + shape1' (thf/get-shape file' :shape1) + text-node' (d/seek #(some? (:text %)) (txt/node-seq (:content shape1')))] + + (t/is (= (count typographies') 1)) + (t/is (= (:id (first typographies')) (thi/id :typography1))) + (t/is (= (:name (first typographies')) "Test typography")) + + (t/is (= (:typography-ref-id text-node') (thi/id :typography1))) + (t/is (= (:typography-ref-file text-node') (:id file'))))) diff --git a/common/test/common_tests/types_file_test.cljc b/common/test/common_tests/types_file_test.cljc deleted file mode 100644 index c95ea3893..000000000 --- a/common/test/common_tests/types_file_test.cljc +++ /dev/null @@ -1,207 +0,0 @@ -;; 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 common-tests.types-file-test - (:require - [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.common.text :as txt] - [app.common.types.colors-list :as ctcl] - [app.common.types.component :as ctk] - [app.common.types.components-list :as ctkl] - [app.common.types.container :as ctn] - [app.common.types.file :as ctf] - [app.common.types.pages-list :as ctpl] - [app.common.types.shape :as cts] - [app.common.types.shape-tree :as ctst] - [app.common.types.typographies-list :as ctyl] - [app.common.uuid :as uuid] - [clojure.pprint :refer [pprint]] - [clojure.test :as t] - [common-tests.helpers.components :as thk] - [common-tests.helpers.files :as thf] - [cuerdas.core :as str])) - -(t/use-fixtures :each thf/reset-idmap!) - -#_(t/deftest test-absorb-components - (let [library-id (uuid/custom 1 1) - library-page-id (uuid/custom 2 2) - file-id (uuid/custom 3 3) - file-page-id (uuid/custom 4 4) - - library (-> (thf/sample-file library-id library-page-id {:is-shared true}) - (thf/sample-shape :group1 - :group - library-page-id - {:name "Group1"}) - (thf/sample-shape :shape1 - :rect - library-page-id - {:name "Rect1" - :parent-id (thf/id :group1)}) - (thf/sample-component :component1 - library-page-id - (thf/id :group1))) - - file (-> (thf/sample-file file-id file-page-id) - (thf/sample-instance :instance1 - file-page-id - library - (thf/id :component1))) - - absorbed-file (ctf/update-file-data - file - #(ctf/absorb-assets % (:data library))) - - pages (ctpl/pages-seq (ctf/file-data absorbed-file)) - components (ctkl/components-seq (ctf/file-data absorbed-file)) - shapes-1 (ctn/shapes-seq (first pages)) - shapes-2 (ctn/shapes-seq (second pages)) - - [[p-group p-shape] [c-group1 c-shape1] component1] - (thk/resolve-instance-and-main - (first pages) - (:id (second shapes-1)) - {file-id absorbed-file}) - - [[lp-group lp-shape] [c-group2 c-shape2] component2] - (thk/resolve-instance-and-main - (second pages) - (:id (second shapes-2)) - {file-id absorbed-file})] - - ;; Uncomment to debug - - ;; (println "\n===== library") - ;; (ctf/dump-tree (:data library) - ;; library-page-id - ;; {} - ;; true) - - ;; (println "\n===== file") - ;; (ctf/dump-tree (:data file) - ;; file-page-id - ;; {library-id library} - ;; true) - - ;; (println "\n===== absorbed file") - ;; (println (str "\n<" (:name (first pages)) ">")) - ;; (ctf/dump-tree (:data absorbed-file) - ;; (:id (first pages)) - ;; {file-id absorbed-file} - ;; false) - ;; (println (str "\n<" (:name (second pages)) ">")) - ;; (ctf/dump-tree (:data absorbed-file) - ;; (:id (second pages)) - ;; {file-id absorbed-file} - ;; false) - - (t/is (= (count pages) 2)) - (t/is (= (:name (first pages)) "Page 1")) - (t/is (= (:name (second pages)) "Main components")) - - (t/is (= (count components) 1)) - - (t/is (= (:name p-group) "Group1")) - (t/is (ctk/instance-of? p-group file-id (:id component1))) - (t/is (not (:main-instance? p-group))) - (t/is (not (ctk/main-instance-of? (:id p-group) file-page-id component1))) - (t/is (ctk/is-main-of? c-group1 p-group)) - - (t/is (= (:name p-shape) "Rect1")) - (t/is (ctk/is-main-of? c-shape1 p-shape)))) - - -(t/deftest test-absorb-colors - (let [library-id (uuid/custom 1 1) - library-page-id (uuid/custom 2 2) - file-id (uuid/custom 3 3) - file-page-id (uuid/custom 4 4) - - library (-> (thf/sample-file library-id library-page-id {:is-shared true}) - (thf/sample-color :color1 {:name "Test color" - :color "#abcdef"})) - - file (-> (thf/sample-file file-id file-page-id) - (thf/sample-shape :shape1 - :rect - file-page-id - {:name "Rect1" - :fills [{:fill-color "#abcdef" - :fill-opacity 1 - :fill-color-ref-id (thf/id :color1) - :fill-color-ref-file library-id}]})) - - absorbed-file (ctf/update-file-data - file - #(ctf/absorb-assets % (:data library))) - - colors (ctcl/colors-seq (ctf/file-data absorbed-file)) - page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id) - shape1 (ctn/get-shape page (thf/id :shape1)) - fill (first (:fills shape1))] - - (t/is (= (count colors) 1)) - (t/is (= (:id (first colors)) (thf/id :color1))) - (t/is (= (:name (first colors)) "Test color")) - (t/is (= (:color (first colors)) "#abcdef")) - - (t/is (= (:fill-color fill) "#abcdef")) - (t/is (= (:fill-color-ref-id fill) (thf/id :color1))) - (t/is (= (:fill-color-ref-file fill) file-id)))) - -(t/deftest test-absorb-typographies - (let [library-id (uuid/custom 1 1) - library-page-id (uuid/custom 2 2) - file-id (uuid/custom 3 3) - file-page-id (uuid/custom 4 4) - - library (-> (thf/sample-file library-id library-page-id {:is-shared true}) - (thf/sample-typography :typography1 {:name "Test typography"})) - - file (-> (thf/sample-file file-id file-page-id) - (thf/sample-shape :shape1 - :text - file-page-id - {:name "Text1" - :content {:type "root" - :children [{:type "paragraph-set" - :children [{:type "paragraph" - :key "67uep" - :children [{:text "Example text" - :typography-ref-id (thf/id :typography1) - :typography-ref-file library-id - :line-height "1.2" - :font-style "normal" - :text-transform "none" - :text-align "left" - :font-id "sourcesanspro" - :font-family "sourcesanspro" - :font-size "14" - :font-weight "400" - :font-variant-id "regular" - :text-decoration "none" - :letter-spacing "0" - :fills [{:fill-color "#000000" - :fill-opacity 1}]}]}]}]}})) - absorbed-file (ctf/update-file-data - file - #(ctf/absorb-assets % (:data library))) - - typographies (ctyl/typographies-seq (ctf/file-data absorbed-file)) - page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id) - - shape1 (ctn/get-shape page (thf/id :shape1)) - text-node (d/seek #(some? (:text %)) (txt/node-seq (:content shape1)))] - - (t/is (= (count typographies) 1)) - (t/is (= (:id (first typographies)) (thf/id :typography1))) - (t/is (= (:name (first typographies)) "Test typography")) - - (t/is (= (:typography-ref-id text-node) (thf/id :typography1))) - (t/is (= (:typography-ref-file text-node) file-id)))) - diff --git a/common/tests.edn b/common/tests.edn new file mode 100644 index 000000000..9f487a7ea --- /dev/null +++ b/common/tests.edn @@ -0,0 +1,4 @@ +#kaocha/v1 + {:tests [{:id :unit + :test-paths ["test"]}] + :kaocha/reporter [kaocha.report/dots]}