diff --git a/backend/test/backend_tests/binfile_test.clj b/backend/test/backend_tests/binfile_test.clj index 232a44f87c..84310241d4 100644 --- a/backend/test/backend_tests/binfile_test.clj +++ b/backend/test/backend_tests/binfile_test.clj @@ -10,6 +10,7 @@ [app.binfile.common :as bfc] [app.binfile.v3 :as v3] [app.common.features :as cfeat] + [app.common.files.validate :as cfv] [app.common.pprint :as pp] [app.common.thumbnails :as thc] [app.common.types.shape :as cts] @@ -86,6 +87,102 @@ (dissoc file :data))) +(def ^:private svg-raw-page-id (uuid/custom 1 1)) +(def ^:private svg-raw-root-id (uuid/custom 3 1)) +(def ^:private svg-raw-child-id (uuid/custom 3 2)) + +(defn- prepare-svg-raw-file + "A file containing an svg-raw subtree (an svg-raw parent with an + svg-raw child), which is what importing an SVG produces." + [profile] + (let [page-id svg-raw-page-id + root-id svg-raw-root-id + child-id svg-raw-child-id + + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false})] + (update-file! + :file-id (:id file) + :profile-id (:id profile) + :revn 0 + :vern 0 + :changes + [{:type :add-page + :name "page 1" + :id page-id}]) + + (update-file! + :file-id (:id file) + :profile-id (:id profile) + :revn 0 + :vern 0 + :changes + [{:type :add-obj + :page-id page-id + :id root-id + :parent-id uuid/zero + :frame-id uuid/zero + :components-v2 true + :obj (cts/setup-shape + {:id root-id + :name "svg-root" + :frame-id uuid/zero + :parent-id uuid/zero + :type :svg-raw + :content {:tag :svg :attrs {} :content []}})} + {:type :add-obj + :page-id page-id + :id child-id + :parent-id root-id + :frame-id uuid/zero + :components-v2 true + :obj (cts/setup-shape + {:id child-id + :name "svg-text" + :frame-id uuid/zero + :parent-id root-id + :type :svg-raw + :content {:tag :text :attrs {} :content []}})}]) + + (dissoc file :data))) + +(t/deftest import-binfile-v3-preserves-svg-raw-children + (let [profile (th/create-profile* 1) + file (prepare-svg-raw-file profile) + output (tmp/tempfile :suffix ".zip")] + + (v3/export-files! + (-> th/*system* + (assoc ::bfc/ids #{(:id file)}) + (assoc ::bfc/embed-assets false) + (assoc ::bfc/include-libraries false)) + (io/output-stream output)) + + (let [result (-> th/*system* + (assoc ::bfc/project-id (:default-project-id profile)) + (assoc ::bfc/profile-id (:id profile)) + (assoc ::bfc/input output) + (v3/import-files!)) + imported (:result (th/command! {::th/type :get-file + ::rpc/profile-id (:id profile) + :id (first result) + :components-v2 true})) + root (get-in imported [:data :pages-index svg-raw-page-id + :objects svg-raw-root-id])] + + (t/is (= (count result) 1)) + + ;; The child ids of an svg-raw shape must survive the JSON round + ;; trip as uuids; when they came back as plain strings they no + ;; longer resolved against the objects map. + (t/is (every? uuid? (:shapes root))) + (t/is (= [svg-raw-child-id] (vec (:shapes root)))) + + ;; ...so the imported file passes referential integrity instead + ;; of failing with :child-not-found on the next update-file. + (t/is (nil? (cfv/validate-file imported [])))))) + (t/deftest export-binfile-v3 (let [profile (th/create-profile* 1) file (prepare-simple-file profile) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index d645e72ae1..46f608f390 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -259,7 +259,14 @@ [:map {:title "CircleAttrs"}]) (def ^:private schema:svg-raw-attrs - [:map {:title "SvgRawAttrs"}]) + [:map {:title "SvgRawAttrs"} + ;; An svg-raw shape can be a container: importing an SVG builds a + ;; tree of svg-raw shapes, and `cfh/group-like-shape?` treats an + ;; svg-raw with children as group-like. Declaring `:shapes` here + ;; keeps the child ids typed as uuid, so a JSON round trip (binfile + ;; export/import) decodes them back to uuids instead of leaving + ;; strings that no longer resolve against the objects map. + [:shapes {:optional true} [:vector {:gen/max 10} ::sm/uuid]]]) (def schema:image-attrs [:map {:title "ImageAttrs"}