diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 5c17effec4..9b4aa777d9 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -961,6 +961,7 @@ :file-id (str file-id)) (bfc/update-file! cfg {:id file-id + :features (:features file) :migrations (:migrations file) :revn (inc (:revn file)) :data (:data file) diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj index c885219fc2..db82002200 100644 --- a/backend/test/backend_tests/rpc_file_test.clj +++ b/backend/test/backend_tests/rpc_file_test.clj @@ -2261,6 +2261,61 @@ (t/is (nil? (:error out))) (check-tokens-absorbed prepared))) +(t/deftest unpublishing-a-library-keeps-its-consumers-readable + ;; The test helpers enable all supported features, so these files use + ;; fdata/pointer-map and the absorbed page is stored as a fragment + (let [profile (th/create-profile* 1 {:is-active true}) + library (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared true}) + consumer (th/create-file* 2 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + _ (th/link-file-to-library* {:file-id (:id consumer) :library-id (:id library)}) + color-id (uuid/random) + page-id (first (get-in consumer [:data :pages])) + shape-id (uuid/random)] + + (update-file! :file-id (:id library) + :profile-id (:id profile) + :revn 0 + :changes [{:type :add-color + :color {:id color-id :name "Brand" :color "#abcdef" :opacity 1}}]) + (update-file! + :file-id (:id consumer) + :profile-id (:id profile) + :revn 0 + :changes + [{:type :add-obj + :page-id page-id + :id shape-id + :parent-id uuid/zero + :frame-id uuid/zero + :components-v2 true + :obj (cts/setup-shape {:id shape-id :name "Rect" :frame-id uuid/zero + :parent-id uuid/zero :type :rect + :fills [{:fill-color "#abcdef" + :fill-opacity 1 + :fill-color-ref-id color-id + :fill-color-ref-file (:id library)}]})}]) + + (let [out (th/command! {::th/type :set-file-shared + ::rpc/profile-id (:id profile) + :id (:id library) + :is-shared false})] + (t/is (nil? (:error out)))) + + ;; The absorption changed the page of the shape: the consumer reads + ;; it back, with the fill now linked to its own color + (let [out (th/command! {::th/type :get-file + ::rpc/profile-id (:id profile) + :id (:id consumer)}) + data (-> out :result :data) + fill (get-in data [:pages-index page-id :objects shape-id :fills 0])] + (t/is (nil? (:error out))) + (t/is (contains? (:colors data) color-id)) + (t/is (= (:id consumer) (:fill-color-ref-file fill)))))) + (t/deftest deleted-files-permanently-delete (let [prof (th/create-profile* 1 {:is-active true}) team-id (:default-team-id prof) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 0d589eba41..444484b68f 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -683,7 +683,8 @@ (defn find-asset-type-usages "Find all usages of an asset in a file (may be in pages or in the components - of the local library). + of the local library, deleted ones included, since they keep their shapes + to be restored and to resolve their remaining copies). Returns a list ((asset ((container shapes) (container shapes)...))...)" [file-data library-data asset-type] @@ -699,12 +700,17 @@ (when (d/not-empty? instances) [[container instances]]))) + containers + (concat (containers-seq file-data) + (map #(ctn/make-container % :component) + (ctkl/deleted-components-seq file-data))) + find-asset-usages - (fn [file-data asset] - (mapcat #(find-usages-in-container % asset) (containers-seq file-data)))] + (fn [asset] + (mapcat #(find-usages-in-container % asset) containers))] (mapcat (fn [asset] - (let [instances (find-asset-usages file-data asset)] + (let [instances (find-asset-usages asset)] (when (d/not-empty? instances) [[asset instances]]))) assets))) diff --git a/common/test/common_tests/types/absorb_assets_test.cljc b/common/test/common_tests/types/absorb_assets_test.cljc index 8e8ee2ee36..6d2e46e0db 100644 --- a/common/test/common_tests/types/absorb_assets_test.cljc +++ b/common/test/common_tests/types/absorb_assets_test.cljc @@ -7,6 +7,9 @@ (ns common-tests.types.absorb-assets-test (:require [app.common.data :as d] + [app.common.files.changes-builder :as pcb] + [app.common.files.validate :as cfv] + [app.common.logic.shapes :as cls] [app.common.test-helpers.components :as thc] [app.common.test-helpers.compositions :as tho] [app.common.test-helpers.files :as thf] @@ -57,6 +60,50 @@ (t/is (ctk/is-main-of? main-root' copy-root')) (t/is (ctk/main-instance-of? (:id main-root') (:id (second pages')) component')))) +(t/deftest absorb-components-used-in-deleted-components + ;; A deleted component keeps its shapes in `:objects`, to be able to + ;; restore it and to resolve its remaining copies. When those shapes + ;; use library components, they must also be remapped to the local + ;; file, or they will not match the copies in the pages. + (let [;; Setup + library (-> (thf/sample-file :library :is-shared true) + (tho/add-simple-component :icon :icon-main :icon-rect)) + + file (-> (thf/sample-file :file) + (tho/add-frame :card-main :name "Card") + (thc/instantiate-component :icon :card-icon + :library library + :parent-label :card-main) + (thc/make-component :card :card-main) + (thc/instantiate-component :card :card-copy)) + + page (thf/current-page file) + changes (-> (pcb/empty-changes nil (:id page)) + (cls/generate-delete-shapes file page (:objects page) + #{(thi/id :card-main)} {}) + (second)) + file (thf/apply-changes file changes :validate? false) + errors (cfv/validate-file file {(:id library) library}) + + ;; Action + file' (ctf/update-file-data + file + #(ctf/absorb-assets % (:data library))) + + ;; Get + data' (ctf/file-data file') + card' (ctkl/get-deleted-component data' (thi/id :card)) + shapes' (concat (mapcat (comp vals :objects) (ctpl/pages-seq data')) + (mapcat (comp vals :objects) (vals (:components data')))) + library-refs' (filter #(= (thi/id :library) (:component-file %)) shapes') + errors' (cfv/validate-file file' {})] + + ;; Check + (t/is (nil? errors) "the file is valid before absorbing the library") + (t/is (some? (:objects card')) "the deleted component keeps its shapes") + (t/is (empty? library-refs') "no shape points to the library") + (t/is (nil? errors')))) + (t/deftest absorb-colors (let [;; Setup library (-> (thf/sample-file :library :is-shared true)