;; 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 Sucursal en España SL

(ns common-tests.logic.variants-switch-test
  (:require
   [app.common.files.changes-builder :as pcb]
   [app.common.files.helpers :as cfh]
   [app.common.geom.point :as gpt]
   [app.common.geom.shapes :as gsh]
   [app.common.logic.libraries :as cll]
   [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]
   [app.common.test-helpers.ids-map :as thi]
   [app.common.test-helpers.shapes :as ths]
   [app.common.test-helpers.variants :as thv]
   [app.common.types.component :as ctk]
   [clojure.test :as t]))

(t/use-fixtures :each thi/test-fixture)

;; ============================================================
;; BASIC SWITCH TESTS (no overrides)
;; ============================================================

(t/deftest test-basic-switch
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant
                       :v01 :c01 :m01 :c02 :m02
                       {:variant1-params {:width 5}
                        :variant2-params  {:width 15}})

                      (thc/instantiate-component :c01
                                                 :copy01))
        copy01 (ths/get-shape file :copy01)

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})

        copy01'   (ths/get-shape file' :copy01)]
    (thf/dump-file file :keys [:width])
    ;; The copy had width 5 before the switch
    (t/is (= (:width copy01) 5))
    ;; The copy has width 15 after the switch
    (t/is (= (:width copy01') 15))))

(t/deftest test-simple-switch
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 5}
                        :child2-params  {:width 15}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})

        page'     (thf/current-page file')
        copy01'   (ths/get-shape file' :copy01)
        rect01'      (get-in page' [:objects (-> copy01' :shapes first)])]
    ;; The rect had width 5 before the switch
    (t/is (= (:width rect01) 5))
    ;; The rect has width 15 after the switch
    (t/is (= (:width rect01') 15))))

;; ============================================================
;; SIMPLE ATTRIBUTE OVERRIDES (identical variants)
;; ============================================================

(t/deftest test-basic-switch-override
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant
                       :v01 :c01 :m01 :c02 :m02
                       {:variant1-params {:width 5}
                        :variant2-params  {:width 5}})

                      (thc/instantiate-component :c01
                                                 :copy01))
        copy01 (ths/get-shape file :copy01)

        ;; Change width of copy
        page   (thf/current-page file)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id copy01)}
                                            (fn [shape]
                                              (assoc shape :width 25))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        copy01 (ths/get-shape file :copy01)

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})

        copy01'   (ths/get-shape file' :copy01)]
    (thf/dump-file file :keys [:width])
    ;; The copy had width 25 before the switch
    (t/is (= (:width copy01) 25))
    ;; The override is keept: The copy still has width 25 after the switch
    (t/is (= (:width copy01') 25))))

(t/deftest test-switch-with-override
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 5}
                        :child2-params  {:width 5}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :width 25))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})

        page'     (thf/current-page file')
        copy01'   (ths/get-shape file' :copy01)
        rect01'   (get-in page' [:objects (-> copy01' :shapes first)])]

    ;; The rect had width 25 before the switch
    (t/is (= (:width rect01) 25))
    ;; The override is keept: The rect still has width 25 after the switch
    (t/is (= (:width rect01') 25))))

;; ============================================================
;; SIMPLE ATTRIBUTE OVERRIDES (different variants)
;; ============================================================

(t/deftest test-switch-with-no-override
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 5}
                        :child2-params  {:width 15}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :width 25))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})

        page'     (thf/current-page file')
        copy01'   (ths/get-shape file' :copy01)
        rect01'   (get-in page' [:objects (-> copy01' :shapes first)])]

    ;; The rect had width 25 before the switch
    (t/is (= (:width rect01) 25))
    ;; The override isn't keept, because the property is different in the mains
    ;; The rect has width 15 after the switch
    (t/is (= (:width rect01') 15))))

;; ============================================================
;; NESTED COPY SWITCH (no overrides)
;; ============================================================

(t/deftest test-nested-switch-in-main
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant
                       :v01 :c01 :m01 :c02 :m02
                       {:variant1-params {:width 5}
                        :variant2-params  {:width 15}})

                      (tho/add-frame :m03)
                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :parent-label :m03)
                      (thc/make-component :c03 :m03))

        copy01 (ths/get-shape file :copy01)

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})

        copy01'   (ths/get-shape file' :copy01)]

    (thf/dump-file file :keys [:width])

    ;; The copy had width 5 before the switch
    (t/is (= (:width copy01) 5))
    ;; The copy has width 15 after the switch
    (t/is (= (:width copy01') 15))
    ;; The copy is not touched but has swap slot
    (t/is (= (count (:touched copy01')) 1))
    (t/is (= (ctk/get-swap-slot copy01') (thi/id :copy01)))))

(t/deftest test-nested-switch-in-copy
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant
                       :v01 :c01 :m01 :c02 :m02
                       {:variant1-params {:width 5}
                        :variant2-params  {:width 15}})

                      (tho/add-frame :m03)
                      (thc/instantiate-component :c01
                                                 :nested01
                                                 :parent-label :m03)
                      (thc/make-component :c03 :m03)

                      (thc/instantiate-component :c03
                                                 :nested02
                                                 :children-labels [:child01]))

        child01 (ths/get-shape file :child01)

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :child01 :c02 {:keep-touched? true})

        child01'   (ths/get-shape file' :child01)]

    (thf/dump-file file :keys [:width])

    ;; The copy had width 5 before the switch
    (t/is (= (:width child01) 5))
    ;; The copy has width 15 after the switch
    (t/is (= (:width child01') 15))
    ;; The copy is not touched but has swap slot
    (t/is (= (count (:touched child01')) 1))
    (t/is (= (ctk/get-swap-slot child01') (thi/id :nested01)))))

;; ============================================================
;; NESTED COPY SWITCH (with overrides)
;; ============================================================

(t/deftest test-nested-switch-in-main-with-override
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant
                       :v01 :c01 :m01 :c02 :m02
                       {:variant1-params {:width 5}
                        :variant2-params  {:width 15}})

                      (tho/add-frame :m03)
                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :parent-label :m03)
                      (thc/make-component :c03 :m03))

        page    (thf/current-page file)
        fills   (ths/sample-fills-color :fill-color "#fabada")
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(thi/id :copy01)}
                                            (fn [shape]
                                              (assoc shape
                                                     :width 25
                                                     :fills fills))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)

        copy01 (ths/get-shape file :copy01)

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})

        copy01'   (ths/get-shape file' :copy01)]

    (thf/dump-file file :keys [:width :touched])

    ;; The copy had fill color before the switch
    (t/is (= (:fills copy01) fills))
    ;; The copy still has fill color after the switch
    (t/is (= (:fills copy01') fills))
    ;; The copy had width 25 before the switch
    (t/is (= (:width copy01) 25))
    ;; The copy gets the switched variant width 15, because this is the value changed in the variant
    (t/is (= (:width copy01') 15))
    ;; The copy is fills touched and has swap slot
    (t/is (= (count (:touched copy01')) 2))
    (t/is (= (ctk/get-swap-slot copy01') (thi/id :copy01)))
    (t/is (contains? (:touched copy01') :fill-group))))

(t/deftest test-nested-switch-in-copy-with-override
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant
                       :v01 :c01 :m01 :c02 :m02
                       {:variant1-params {:width 5}
                        :variant2-params  {:width 15}})

                      (tho/add-frame :m03)
                      (thc/instantiate-component :c01
                                                 :nested01
                                                 :parent-label :m03)
                      (thc/make-component :c03 :m03)

                      (thc/instantiate-component :c03
                                                 :copy02
                                                 :children-labels [:nested02]))

        page      (thf/current-page file)
        fills     (ths/sample-fills-color :fill-color "#fabada")
        changes   (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                              #{(thi/id :nested02)}
                                              (fn [shape]
                                                (assoc shape
                                                       :width 25
                                                       :fills fills))
                                              (:objects page)
                                              {})

        file      (thf/apply-changes file changes)

        nested02  (ths/get-shape file :nested02)

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :nested02 :c02 {:keep-touched? true})

        nested02' (ths/get-shape file' :nested02)]

    (thf/dump-file file :keys [:width])

    ;; The copy had fill color before the switch
    (t/is (= (:fills nested02) fills))
    ;; The copy still has fill color after the switch
    (t/is (= (:fills nested02') fills))
    ;; The copy had width 5 before the switch
    (t/is (not= (:width nested02) 5))
    ;; The copy gets the switched variant width 15, because this is the value changed in the variant
    (t/is (= (:width nested02') 15))
    ;; The copy is fills touched and has swap slot
    (t/is (= (count (:touched nested02')) 2))
    (t/is (= (ctk/get-swap-slot nested02') (thi/id :nested01)))
    (t/is (contains? (:touched nested02') :fill-group))))

;; ============================================================
;; TEXT OVERRIDES (identical variants)
;; ============================================================

(def font-size-path-paragraph [:content :children 0 :children 0 :font-size])
(def font-size-path-0 [:content :children 0 :children 0 :children 0 :font-size])
(def font-size-path-1 [:content :children 0 :children 0 :children 1 :font-size])

(def text-path-0 [:content :children 0 :children 0 :children 0 :text])
(def text-path-1 [:content :children 0 :children 0 :children 1 :text])
(def text-lines-path [:content :children 0 :children 0 :children])

(defn- update-attr
  [file label path value]
  (let [page    (thf/current-page file)
        shape   (ths/get-shape file label)
        changes (cls/generate-update-shapes
                 (pcb/empty-changes nil (:id page))
                 #{(:id shape)}
                 (fn [shape]
                   (cond-> (assoc-in shape path value)
                     (or (= path font-size-path-0) (= path font-size-path-1))
                     (assoc-in font-size-path-paragraph value)))
                 (:objects page)
                 {})]
    (thf/apply-changes file changes)))

(defn- change-structure
  [file label]
  (let [page    (thf/current-page file)
        shape   (ths/get-shape file label)
        line1   (-> (get-in shape text-lines-path)
                    first
                    (assoc :text "new line 1"))
        line2   (assoc line1 :text "new line 2")
        changes (cls/generate-update-shapes
                 (pcb/empty-changes nil (:id page))
                 #{(:id shape)}
                 (fn [shape]
                   (assoc-in shape text-lines-path [line1 line2]))
                 (:objects page)
                 {})]
    (thf/apply-changes file changes)))

(t/deftest test-switch-with-identical-text-override
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 ;; Both components are identical: have the same text and props
                 (thv/add-variant-with-text
                  :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world")
                 (thc/instantiate-component :c01
                                            :copy-clean
                                            :children-labels [:copy-clean-t])
                 (thc/instantiate-component :c01
                                            :copy-font-size
                                            :children-labels [:copy-font-size-t])
                 (thc/instantiate-component :c01
                                            :copy-text
                                            :children-labels [:copy-text-t])
                 (thc/instantiate-component :c01
                                            :copy-both
                                            :children-labels [:copy-both-t]))


        ;; The copy clean has no overrides
        copy-clean-t     (ths/get-shape file :copy-clean-t)

        ;; Override font size on copy-font-size
        file             (update-attr file :copy-font-size-t font-size-path-0 "25")
        copy-font-size-t (ths/get-shape file :copy-font-size-t)

        ;; Override text on copy-text
        file             (update-attr file :copy-text-t text-path-0 "text overriden")
        copy-text-t      (ths/get-shape file :copy-text-t)

        ;; Override both on copy-both
        file             (update-attr file :copy-both-t font-size-path-0 "25")
        file             (update-attr file :copy-both-t text-path-0 "text overriden")
        copy-both-t      (ths/get-shape file :copy-both-t)


        ;; ==== Action: Switch all the copies


        file' (-> file
                  (tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
        page'             (thf/current-page file')
        copy-clean'       (ths/get-shape file' :copy-clean-2)
        copy-clean-t'     (get-in page' [:objects (-> copy-clean' :shapes first)])

        copy-font-size'   (ths/get-shape file' :copy-font-size-2)
        copy-font-size-t' (get-in page' [:objects (-> copy-font-size' :shapes first)])

        copy-text'        (ths/get-shape file' :copy-text-2)
        copy-text-t'      (get-in page' [:objects (-> copy-text' :shapes first)])

        copy-both'        (ths/get-shape file' :copy-both-2)
        copy-both-t'      (get-in page' [:objects (-> copy-both' :shapes first)])]

    (thf/dump-file file' {:keys [:name #_:content]})


    ;;;;;;;;;;; Clean copy
    ;; Before the switch:
    ;;   * font size 14
    ;;   * text "hello world"


    (t/is (= (get-in copy-clean-t font-size-path-0) "14"))
    (t/is (= (get-in copy-clean-t text-path-0) "hello world"))

    ;; After the switch:
    ;;   * font size 25 (value of c02, because there was no override)
    ;;   * text "hello world" (value of c02, because there was no override)
    (t/is (= (get-in copy-clean-t' font-size-path-0) "14"))
    (t/is (= (get-in copy-clean-t' text-path-0) "hello world"))


    ;;;;;;;;;;; Font size copy
    ;; Before the switch:
    ;;   * font size 25
    ;;   * text "hello world"


    (t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
    (t/is (= (get-in copy-font-size-t text-path-0) "hello world"))

    ;; After the switch:
    ;;   * font size 25 (the override is preserved)
    ;;   * text "hello world" (value of c02, because there was no override)
    (t/is (= (get-in copy-font-size-t' font-size-path-0) "25"))
    (t/is (= (get-in copy-font-size-t' text-path-0) "hello world"))

    ;;;;;;;;;;; Text copy
    ;; Before the switch:
    ;;   * font size 14
    ;;   * text "text overriden"
    (t/is (= (get-in copy-text-t font-size-path-0) "14"))
    (t/is (= (get-in copy-text-t text-path-0) "text overriden"))

    ;; After the switch:
    ;;   * font size 14 (value of c02, because there was no override)
    ;;   * text "text overriden" (the override is preserved)
    (t/is (= (get-in copy-text-t' font-size-path-0) "14"))
    (t/is (= (get-in copy-text-t' text-path-0) "text overriden"))

    ;;;;;;;;;;; Both copy
    ;; Before the switch:
    ;;   * font size 25
    ;;   * text "text overriden"
    (t/is (= (get-in copy-both-t font-size-path-0) "25"))
    (t/is (= (get-in copy-both-t text-path-0) "text overriden"))

    ;; After the switch:
    ;;   * font size 25 (the override is preserved)
    ;;   * text "text overriden" (the override is preserved)
    (t/is (= (get-in copy-both-t' font-size-path-0) "25"))
    (t/is (= (get-in copy-both-t' text-path-0) "text overriden"))))

;; ============================================================
;; TEXT OVERRIDES (different property)
;; ============================================================

(t/deftest test-switch-with-different-prop-text-override
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 ;; The second component has a different prop
                 (thv/add-variant-with-text
                  :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world")
                 (update-attr :t02 font-size-path-0 "50")

                 (thc/instantiate-component :c01
                                            :copy-clean
                                            :children-labels [:copy-clean-t])
                 (thc/instantiate-component :c01
                                            :copy-font-size
                                            :children-labels [:copy-font-size-t])
                 (thc/instantiate-component :c01
                                            :copy-text
                                            :children-labels [:copy-text-t])
                 (thc/instantiate-component :c01
                                            :copy-both
                                            :children-labels [:copy-both-t]))


        ;; The copy clean has no overrides
        copy-clean-t     (ths/get-shape file :copy-clean-t)

        ;; Override font size on copy-font-size
        file             (update-attr file :copy-font-size-t font-size-path-0 "25")
        copy-font-size-t (ths/get-shape file :copy-font-size-t)

        ;; Override text on copy-text
        file             (update-attr file :copy-text-t text-path-0 "text overriden")
        copy-text-t      (ths/get-shape file :copy-text-t)

        ;; Override both on copy-both
        file             (update-attr file :copy-both-t font-size-path-0 "25")
        file             (update-attr file :copy-both-t text-path-0 "text overriden")
        copy-both-t      (ths/get-shape file :copy-both-t)


        ;; ==== Action: Switch all the copies


        file' (-> file
                  (tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
        page'             (thf/current-page file')
        copy-clean'       (ths/get-shape file' :copy-clean-2)
        copy-clean-t'     (get-in page' [:objects (-> copy-clean' :shapes first)])

        copy-font-size'   (ths/get-shape file' :copy-font-size-2)
        copy-font-size-t' (get-in page' [:objects (-> copy-font-size' :shapes first)])

        copy-text'        (ths/get-shape file' :copy-text-2)
        copy-text-t'      (get-in page' [:objects (-> copy-text' :shapes first)])

        copy-both'        (ths/get-shape file' :copy-both-2)
        copy-both-t'      (get-in page' [:objects (-> copy-both' :shapes first)])]

    (thf/dump-file file' {:keys [:name #_:content]})


    ;;;;;;;;;;; Clean copy
    ;; Before the switch:
    ;;   * font size 14
    ;;   * text "hello world"


    (t/is (= (get-in copy-clean-t font-size-path-0) "14"))
    (t/is (= (get-in copy-clean-t text-path-0) "hello world"))

    ;; After the switch:
    ;;   * font size 50 (value of c02, because there was no override)
    ;;   * text "hello world" (value of c02, because there was no override)
    (t/is (= (get-in copy-clean-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-clean-t' text-path-0) "hello world"))


    ;;;;;;;;;;; Font size copy
    ;; Before the switch:
    ;;   * font size 25
    ;;   * text "hello world"


    (t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
    (t/is (= (get-in copy-font-size-t text-path-0) "hello world"))

    ;; After the switch:
    ;;   * font size 50 (value of c02: the override is not preserved)
    ;;   * text "hello world" (value of c02, because there was no override)
    (t/is (= (get-in copy-font-size-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-font-size-t' text-path-0) "hello world"))

    ;;;;;;;;;;; Text copy
    ;; Before the switch:
    ;;   * font size 14
    ;;   * text "text overriden"
    (t/is (= (get-in copy-text-t font-size-path-0) "14"))
    (t/is (= (get-in copy-text-t text-path-0) "text overriden"))

    ;; After the switch:
    ;;   * font size 50 (value of c02, because there was no override)
    ;;   * text "text overriden" (the override is preserved)
    (t/is (= (get-in copy-text-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-text-t' text-path-0) "text overriden"))

    ;;;;;;;;;;; Both copy
    ;; Before the switch:
    ;;   * font size 25
    ;;   * text "text overriden"
    (t/is (= (get-in copy-both-t font-size-path-0) "25"))
    (t/is (= (get-in copy-both-t text-path-0) "text overriden"))

    ;; After the switch:
    ;;   * font size 50 (value of c02: the override is not preserved)
    ;;   * text "text overriden" (the override is preserved)
    (t/is (= (get-in copy-both-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-both-t' text-path-0) "text overriden"))))

;; ============================================================
;; TEXT OVERRIDES (different text)
;; ============================================================

(t/deftest test-switch-with-different-text-text-override
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 ;; Second comp has different text
                 (thv/add-variant-with-text
                  :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "bye")
                 (thc/instantiate-component :c01
                                            :copy-clean
                                            :children-labels [:copy-clean-t])
                 (thc/instantiate-component :c01
                                            :copy-font-size
                                            :children-labels [:copy-font-size-t])
                 (thc/instantiate-component :c01
                                            :copy-text
                                            :children-labels [:copy-text-t])
                 (thc/instantiate-component :c01
                                            :copy-both
                                            :children-labels [:copy-both-t]))


        ;; The copy clean has no overrides
        copy-clean-t     (ths/get-shape file :copy-clean-t)

        ;; Override font size on copy-font-size
        file             (update-attr file :copy-font-size-t font-size-path-0 "25")
        copy-font-size-t (ths/get-shape file :copy-font-size-t)

        ;; Override text on copy-text
        file             (update-attr file :copy-text-t text-path-0 "text overriden")
        copy-text-t      (ths/get-shape file :copy-text-t)

        ;; Override both on copy-both
        file             (update-attr file :copy-both-t font-size-path-0 "25")
        file             (update-attr file :copy-both-t text-path-0 "text overriden")
        copy-both-t      (ths/get-shape file :copy-both-t)


        ;; ==== Action: Switch all the copies


        file' (-> file
                  (tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
        page'             (thf/current-page file')
        copy-clean'       (ths/get-shape file' :copy-clean-2)
        copy-clean-t'     (get-in page' [:objects (-> copy-clean' :shapes first)])

        copy-font-size'   (ths/get-shape file' :copy-font-size-2)
        copy-font-size-t' (get-in page' [:objects (-> copy-font-size' :shapes first)])

        copy-text'        (ths/get-shape file' :copy-text-2)
        copy-text-t'      (get-in page' [:objects (-> copy-text' :shapes first)])

        copy-both'        (ths/get-shape file' :copy-both-2)
        copy-both-t'      (get-in page' [:objects (-> copy-both' :shapes first)])]

    (thf/dump-file file' {:keys [:name #_:content]})


    ;;;;;;;;;;; Clean copy
    ;; Before the switch:
    ;;   * font size 14
    ;;   * text "hello world"


    (t/is (= (get-in copy-clean-t font-size-path-0) "14"))
    (t/is (= (get-in copy-clean-t text-path-0) "hello world"))

    ;; After the switch:
    ;;   * font size 25 (value of c02, because there was no override)
    ;;   * text "bye" (value of c02, because there was no override)
    (t/is (= (get-in copy-clean-t' font-size-path-0) "14"))
    (t/is (= (get-in copy-clean-t' text-path-0) "bye"))


    ;;;;;;;;;;; Font size copy
    ;; Before the switch:
    ;;   * font size 25
    ;;   * text "hello world"


    (t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
    (t/is (= (get-in copy-font-size-t text-path-0) "hello world"))

    ;; After the switch:
    ;;   * font size 25 (the override is preserved)
    ;;   * text "bye" (value of c02, because there was no override)
    (t/is (= (get-in copy-font-size-t' font-size-path-0) "25"))
    (t/is (= (get-in copy-font-size-t' text-path-0) "bye"))

    ;;;;;;;;;;; Text copy
    ;; Before the switch:
    ;;   * font size 14
    ;;   * text "text overriden"
    (t/is (= (get-in copy-text-t font-size-path-0) "14"))
    (t/is (= (get-in copy-text-t text-path-0) "text overriden"))

    ;; After the switch:
    ;;   * font size 14 (value of c02, because there was no override)
    ;;   * text "text overriden" (value of c02: the override is not preserved)
    (t/is (= (get-in copy-text-t' font-size-path-0) "14"))
    (t/is (= (get-in copy-text-t' text-path-0) "bye"))

    ;;;;;;;;;;; Both copy
    ;; Before the switch:
    ;;   * font size 25
    ;;   * text "text overriden"
    (t/is (= (get-in copy-both-t font-size-path-0) "25"))
    (t/is (= (get-in copy-both-t text-path-0) "text overriden"))

    ;; After the switch:
    ;;   * font size 25 (the override is preserved)
    ;;   * text "text overriden" (value of c02: the override is not preserved)
    (t/is (= (get-in copy-both-t' font-size-path-0) "25"))
    (t/is (= (get-in copy-both-t' text-path-0) "bye"))))

;; ============================================================
;; TEXT OVERRIDES (different text AND property)
;; ============================================================

(t/deftest test-switch-with-different-text-and-prop-text-override
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 ;; The second component has a different text and prop
                 (thv/add-variant-with-text
                  :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "bye")
                 (update-attr :t02 font-size-path-0 "50")

                 (thc/instantiate-component :c01
                                            :copy-clean
                                            :children-labels [:copy-clean-t])
                 (thc/instantiate-component :c01
                                            :copy-font-size
                                            :children-labels [:copy-font-size-t])
                 (thc/instantiate-component :c01
                                            :copy-text
                                            :children-labels [:copy-text-t])
                 (thc/instantiate-component :c01
                                            :copy-both
                                            :children-labels [:copy-both-t]))


        ;; The copy clean has no overrides
        copy-clean-t     (ths/get-shape file :copy-clean-t)

        ;; Override font size on copy-font-size
        file             (update-attr file :copy-font-size-t font-size-path-0 "25")
        copy-font-size-t (ths/get-shape file :copy-font-size-t)

        ;; Override text on copy-text
        file             (update-attr file :copy-text-t text-path-0 "text overriden")
        copy-text-t      (ths/get-shape file :copy-text-t)

        ;; Override both on copy-both
        file             (update-attr file :copy-both-t font-size-path-0 "25")
        file             (update-attr file :copy-both-t text-path-0 "text overriden")
        copy-both-t      (ths/get-shape file :copy-both-t)


        ;; ==== Action: Switch all the copies


        file' (-> file
                  (tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
        page'             (thf/current-page file')
        copy-clean'       (ths/get-shape file' :copy-clean-2)
        copy-clean-t'     (get-in page' [:objects (-> copy-clean' :shapes first)])

        copy-font-size'   (ths/get-shape file' :copy-font-size-2)
        copy-font-size-t' (get-in page' [:objects (-> copy-font-size' :shapes first)])

        copy-text'        (ths/get-shape file' :copy-text-2)
        copy-text-t'      (get-in page' [:objects (-> copy-text' :shapes first)])

        copy-both'        (ths/get-shape file' :copy-both-2)
        copy-both-t'      (get-in page' [:objects (-> copy-both' :shapes first)])]

    (thf/dump-file file' {:keys [:name #_:content]})


    ;;;;;;;;;;; Clean copy
    ;; Before the switch:
    ;;   * font size 14
    ;;   * text "hello world"


    (t/is (= (get-in copy-clean-t font-size-path-0) "14"))
    (t/is (= (get-in copy-clean-t text-path-0) "hello world"))

    ;; After the switch:
    ;;   * font size 50 (value of c02, because there was no override)
    ;;   * text "bye" (value of c02, because there was no override)
    (t/is (= (get-in copy-clean-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-clean-t' text-path-0) "bye"))


    ;;;;;;;;;;; Font size copy
    ;; Before the switch:
    ;;   * font size 25
    ;;   * text "hello world"


    (t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
    (t/is (= (get-in copy-font-size-t text-path-0) "hello world"))

    ;; After the switch:
    ;;   * font size 50 (value of c02: the override is not preserved)
    ;;   * text "bye" (value of c02, because there was no override)
    (t/is (= (get-in copy-font-size-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-font-size-t' text-path-0) "bye"))

    ;;;;;;;;;;; Text copy
    ;; Before the switch:
    ;;   * font size 14
    ;;   * text "text overriden"
    (t/is (= (get-in copy-text-t font-size-path-0) "14"))
    (t/is (= (get-in copy-text-t text-path-0) "text overriden"))

    ;; After the switch:
    ;;   * font size 50 (value of c02, because there was no override)
    ;;   * text "bye" (value of c02: the override is not preserved)
    (t/is (= (get-in copy-text-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-text-t' text-path-0) "bye"))

    ;;;;;;;;;;; Both copy
    ;; Before the switch:
    ;;   * font size 25
    ;;   * text "text overriden"
    (t/is (= (get-in copy-both-t font-size-path-0) "25"))
    (t/is (= (get-in copy-both-t text-path-0) "text overriden"))

    ;; After the switch:
    ;;   * font size 50 (value of c02: the override is not preserved)
    ;;   * text "bye" (value of c02: the override is not preserved)
    (t/is (= (get-in copy-both-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-both-t' text-path-0) "bye"))))

;; ============================================================
;; TEXT STRUCTURE OVERRIDES (identical variants)
;; ============================================================

(t/deftest test-switch-with-identical-structure-text-override
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 ;; Both components are identical: have the same text and props
                 (thv/add-variant-with-text
                  :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world")
                 (thc/instantiate-component :c01
                                            :copy-structure-clean
                                            :children-labels [:copy-structure-clean-t])
                 (thc/instantiate-component :c01
                                            :copy-structure-unif
                                            :children-labels [:copy-structure-unif-t])
                 (thc/instantiate-component :c01
                                            :copy-structure-mixed
                                            :children-labels [:copy-structure-mixed-t]))



        ;; Duplicate a text line in copy-structure-clean


        file                   (change-structure file :copy-structure-clean-t)
        copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)

        ;; Duplicate a text line in copy-structure-clean, updating
        ;; both lines with the same attrs
        file                   (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
                                   (change-structure :copy-structure-unif-t))
        copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)

        ;; Duplicate a text line in copy-structure-clean, updating
        ;; each line with a different attr
        file                   (-> (change-structure file :copy-structure-mixed-t)
                                   (update-attr :copy-structure-mixed-t font-size-path-0 "35")
                                   (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
        copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)


        ;; ==== Action: Switch all the copies


        file' (-> file
                  (tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
        page'                   (thf/current-page file')
        copy-structure-clean'   (ths/get-shape file' :copy-structure-clean-2)
        copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])

        copy-structure-unif'    (ths/get-shape file' :copy-structure-unif-2)
        copy-structure-unif-t'  (get-in page' [:objects (-> copy-structure-unif' :shapes first)])

        copy-structure-mixed'   (ths/get-shape file' :copy-structure-mixed-2)
        copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]

    (thf/dump-file file' {:keys [:name #_:content]})

    ;;;;;;;;;;; Copy structure clean
    ;; Before the switch, first line:
    ;;   * font size 14
    ;;   * text "new line 1"
    ;; Second line:
    ;;   * font size 14
    ;;   * text "new line 2"
    (t/is (= (get-in copy-structure-clean-t font-size-path-0) "14"))
    (t/is (= (get-in copy-structure-clean-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-clean-t font-size-path-1) "14"))
    (t/is (= (get-in copy-structure-clean-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 14 (value of c02, because there was no override)
    ;;   * text "new line 1" (the override is preserved)
    ;; Second line:
    ;;   * font size 14 (value of c02, because there was no override)
    ;;   * text "new line 2" (the override is preserved)
    (t/is (= (get-in copy-structure-clean-t' font-size-path-0) "14"))
    (t/is (= (get-in copy-structure-clean-t' text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-clean-t' font-size-path-1) "14"))
    (t/is (= (get-in copy-structure-clean-t' text-path-1) "new line 2"))

    ;;;;;;;;;;; Copy structure unif
    ;; Before the switch, first line:
    ;;   * font size 25
    ;;   * text "new line 1"
    ;; Second line:
    ;;   * font size 25
    ;;   * text "new line 2"
    (t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
    (t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
    (t/is (= (get-in copy-structure-unif-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 25 (the override is preserved)
    ;;   * text "new line 1" (the override is preserved)
    ;; Second line:
    ;;   * font size 25 (the override is preserved)
    ;;   * text "new line 2" (the override is preserved)
    (t/is (= (get-in copy-structure-unif-t' font-size-path-0) "25"))
    (t/is (= (get-in copy-structure-unif-t' text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-unif-t' font-size-path-1) "25"))
    (t/is (= (get-in copy-structure-unif-t' text-path-1) "new line 2"))

    ;;;;;;;;;;; Copy structure mixed
    ;; Before the switch, first line:
    ;;   * font size 35
    ;;   * text "new line 1"
    ;; Before the switch, second line:
    ;;   * font size 40
    ;;   * text "new line 2"
    (t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
    (t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
    (t/is (= (get-in copy-structure-mixed-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 35 (the override is preserved)
    ;;   * text "new line 1" (the override is preserved)
    ;; Second line:
    ;;   * font size 40 (the override is preserved)
    ;;   * text "new line 2" (the override is preserved)
    (t/is (= (get-in copy-structure-mixed-t' font-size-path-0) "35"))
    (t/is (= (get-in copy-structure-mixed-t' text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-mixed-t' font-size-path-1) "40"))
    (t/is (= (get-in copy-structure-mixed-t' text-path-1) "new line 2"))))

;; ============================================================
;; TEXT STRUCTURE OVERRIDES (different property)
;; ============================================================

(t/deftest test-switch-with-different-prop-structure-text-override
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 ;; The second component has a different prop
                 (thv/add-variant-with-text
                  :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world")
                 (update-attr :t02 font-size-path-0 "50")
                 (thc/instantiate-component :c01
                                            :copy-structure-clean
                                            :children-labels [:copy-structure-clean-t])
                 (thc/instantiate-component :c01
                                            :copy-structure-unif
                                            :children-labels [:copy-structure-unif-t])
                 (thc/instantiate-component :c01
                                            :copy-structure-mixed
                                            :children-labels [:copy-structure-mixed-t]))



        ;; Duplicate a text line in copy-structure-clean


        file                   (change-structure file :copy-structure-clean-t)
        copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)

        ;; Duplicate a text line in copy-structure-clean, updating
        ;; both lines with the same attrs
        file                   (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
                                   (change-structure :copy-structure-unif-t))
        copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)

        ;; Duplicate a text line in copy-structure-clean, updating
        ;; each line with a different attr
        file                   (-> (change-structure file :copy-structure-mixed-t)
                                   (update-attr :copy-structure-mixed-t font-size-path-0 "35")
                                   (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
        copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)


        ;; ==== Action: Switch all the copies


        file' (-> file
                  (tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
        page'                   (thf/current-page file')
        copy-structure-clean'   (ths/get-shape file' :copy-structure-clean-2)
        copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])

        copy-structure-unif'    (ths/get-shape file' :copy-structure-unif-2)
        copy-structure-unif-t'  (get-in page' [:objects (-> copy-structure-unif' :shapes first)])

        copy-structure-mixed'   (ths/get-shape file' :copy-structure-mixed-2)
        copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]

    (thf/dump-file file' {:keys [:name #_:content]})

    ;;;;;;;;;;; Copy structure clean
    ;; Before the switch, first line:
    ;;   * font size 14
    ;;   * text "new line 1"
    ;; Second line:
    ;;   * font size 14
    ;;   * text "new line 2"
    (t/is (= (get-in copy-structure-clean-t font-size-path-0) "14"))
    (t/is (= (get-in copy-structure-clean-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-clean-t font-size-path-1) "14"))
    (t/is (= (get-in copy-structure-clean-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 50 (value of c02, because there was no override)
    ;;   * text "new line 1" (the override is preserved)
    ;; Second line:
    ;;   * font size 50 (value of c02, because there was no override)
    ;;   * text "new line 2" (the override is preserved)
    (t/is (= (get-in copy-structure-clean-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-structure-clean-t' text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-clean-t' font-size-path-1) "50"))
    (t/is (= (get-in copy-structure-clean-t' text-path-1) "new line 2"))

    ;;;;;;;;;;; Copy structure unif
    ;; Before the switch, first line:
    ;;   * font size 25
    ;;   * text "new line 1"
    ;; Second line:
    ;;   * font size 25
    ;;   * text "new line 2"
    (t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
    (t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
    (t/is (= (get-in copy-structure-unif-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 50 (the override is not preserved)
    ;;   * text "new line 1" (the override is preserved)
    ;; Second line:
    ;;   * font size 50 (the override is not preserved)
    ;;   * text "new line 2" (the override is preserved)
    (t/is (= (get-in copy-structure-unif-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-structure-unif-t' text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-unif-t' font-size-path-1) "50"))
    (t/is (= (get-in copy-structure-unif-t' text-path-1) "new line 2"))

    ;;;;;;;;;;; Copy structure mixed
    ;; Before the switch, first line:
    ;;   * font size 35
    ;;   * text "new line 1"
    ;; Before the switch, second line:
    ;;   * font size 40
    ;;   * text "new line 2"
    (t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
    (t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
    (t/is (= (get-in copy-structure-mixed-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 50 (the override is not preserved)
    ;;   * text "hello world" (the override is not preserved)
    ;; No second line
    (t/is (= (get-in copy-structure-mixed-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-structure-mixed-t' text-path-0) "hello world"))
    (t/is (nil? (get-in copy-structure-mixed-t' font-size-path-1)))))

;; ============================================================
;; TEXT STRUCTURE OVERRIDES (different text)
;; ============================================================

(t/deftest test-switch-with-different-text-structure-text-override
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 ;; Second comp has different text
                 (thv/add-variant-with-text
                  :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "bye")
                 (thc/instantiate-component :c01
                                            :copy-structure-clean
                                            :children-labels [:copy-structure-clean-t])
                 (thc/instantiate-component :c01
                                            :copy-structure-unif
                                            :children-labels [:copy-structure-unif-t])
                 (thc/instantiate-component :c01
                                            :copy-structure-mixed
                                            :children-labels [:copy-structure-mixed-t]))



        ;; Duplicate a text line in copy-structure-clean


        file                   (change-structure file :copy-structure-clean-t)
        copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)

        ;; Duplicate a text line in copy-structure-clean, updating
        ;; both lines with the same attrs
        file                   (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
                                   (change-structure :copy-structure-unif-t))
        copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)

        ;; Duplicate a text line in copy-structure-clean, updating
        ;; each line with a different attr
        file                   (-> (change-structure file :copy-structure-mixed-t)
                                   (update-attr :copy-structure-mixed-t font-size-path-0 "35")
                                   (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
        copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)


        ;; ==== Action: Switch all the copies


        file' (-> file
                  (tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
        page'                   (thf/current-page file')
        copy-structure-clean'   (ths/get-shape file' :copy-structure-clean-2)
        copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])

        copy-structure-unif'    (ths/get-shape file' :copy-structure-unif-2)
        copy-structure-unif-t'  (get-in page' [:objects (-> copy-structure-unif' :shapes first)])

        copy-structure-mixed'   (ths/get-shape file' :copy-structure-mixed-2)
        copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]

    (thf/dump-file file' {:keys [:name #_:content]})

    ;;;;;;;;;;; Copy structure clean
    ;; Before the switch, first line:
    ;;   * font size 14
    ;;   * text "new line 1"
    ;; Second line:
    ;;   * font size 14
    ;;   * text "new line 2"
    (t/is (= (get-in copy-structure-clean-t font-size-path-0) "14"))
    (t/is (= (get-in copy-structure-clean-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-clean-t font-size-path-1) "14"))
    (t/is (= (get-in copy-structure-clean-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 14 (value of c02, because there was no override)
    ;;   * text "bye" (the override is not preserved)
    ;; No second line
    (t/is (= (get-in copy-structure-clean-t' font-size-path-0) "14"))
    (t/is (= (get-in copy-structure-clean-t' text-path-0) "bye"))
    (t/is (nil? (get-in copy-structure-clean-t' font-size-path-1)))


    ;;;;;;;;;;; Copy structure unif
    ;; Before the switch, first line:
    ;;   * font size 25
    ;;   * text "new line 1"
    ;; Second line:
    ;;   * font size 25
    ;;   * text "new line 2"


    (t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
    (t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
    (t/is (= (get-in copy-structure-unif-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 25 (the override is preserved)
    ;;   * text "bye" (the override is not preserved)
    ;; No second line
    (t/is (= (get-in copy-structure-unif-t' font-size-path-0) "25"))
    (t/is (= (get-in copy-structure-unif-t' text-path-0) "bye"))
    (t/is (nil? (get-in copy-structure-unif-t' font-size-path-1)))


    ;;;;;;;;;;; Copy structure mixed
    ;; Before the switch, first line:
    ;;   * font size 35
    ;;   * text "new line 1"
    ;; Before the switch, second line:
    ;;   * font size 40
    ;;   * text "new line 2"


    (t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
    (t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
    (t/is (= (get-in copy-structure-mixed-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 14 (the override is not preserved)
    ;;   * text "bye" (the override is not preserved)
    ;; No second line
    (t/is (= (get-in copy-structure-mixed-t' font-size-path-0) "14"))
    (t/is (= (get-in copy-structure-mixed-t' text-path-0) "bye"))
    (t/is (nil? (get-in copy-structure-mixed-t' font-size-path-1)))))

;; ============================================================
;; TEXT STRUCTURE OVERRIDES (different text AND property)
;; ============================================================

(t/deftest test-switch-with-different-text-and-prop-structure-text-override
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 ;; The second component has a different text and prop
                 (thv/add-variant-with-text
                  :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "bye")
                 (update-attr :t02 font-size-path-0 "50")
                 (thc/instantiate-component :c01
                                            :copy-structure-clean
                                            :children-labels [:copy-structure-clean-t])
                 (thc/instantiate-component :c01
                                            :copy-structure-unif
                                            :children-labels [:copy-structure-unif-t])
                 (thc/instantiate-component :c01
                                            :copy-structure-mixed
                                            :children-labels [:copy-structure-mixed-t]))



        ;; Duplicate a text line in copy-structure-clean


        file                   (change-structure file :copy-structure-clean-t)
        copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)

        ;; Duplicate a text line in copy-structure-clean, updating
        ;; both lines with the same attrs
        file                   (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
                                   (change-structure :copy-structure-unif-t))
        copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)

        ;; Duplicate a text line in copy-structure-clean, updating
        ;; each line with a different attr
        file                   (-> (change-structure file :copy-structure-mixed-t)
                                   (update-attr :copy-structure-mixed-t font-size-path-0 "35")
                                   (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
        copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)


        ;; ==== Action: Switch all the copies


        file' (-> file
                  (tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
                  (tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
        page'                   (thf/current-page file')
        copy-structure-clean'   (ths/get-shape file' :copy-structure-clean-2)
        copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])

        copy-structure-unif'    (ths/get-shape file' :copy-structure-unif-2)
        copy-structure-unif-t'  (get-in page' [:objects (-> copy-structure-unif' :shapes first)])

        copy-structure-mixed'   (ths/get-shape file' :copy-structure-mixed-2)
        copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]

    (thf/dump-file file' {:keys [:name #_:content]})

    ;;;;;;;;;;; Copy structure clean
    ;; Before the switch, first line:
    ;;   * font size 14
    ;;   * text "new line 1"
    ;; Second line:
    ;;   * font size 14
    ;;   * text "new line 2"
    (t/is (= (get-in copy-structure-clean-t font-size-path-0) "14"))
    (t/is (= (get-in copy-structure-clean-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-clean-t font-size-path-1) "14"))
    (t/is (= (get-in copy-structure-clean-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 50 (value of c02, because there was no override)
    ;;   * text "bye" (the override is not preserved)
    ;; No second line
    (t/is (= (get-in copy-structure-clean-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-structure-clean-t' text-path-0) "bye"))
    (t/is (nil? (get-in copy-structure-clean-t' font-size-path-1)))


    ;;;;;;;;;;; Copy structure unif
    ;; Before the switch, first line:
    ;;   * font size 25
    ;;   * text "new line 1"
    ;; Second line:
    ;;   * font size 25
    ;;   * text "new line 2"


    (t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
    (t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
    (t/is (= (get-in copy-structure-unif-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 50 (the override is not preserved)
    ;;   * text "bye" (the override is not preserved)
    ;; No second line
    (t/is (= (get-in copy-structure-unif-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-structure-unif-t' text-path-0) "bye"))
    (t/is (nil? (get-in copy-structure-unif-t' font-size-path-1)))


    ;;;;;;;;;;; Copy structure mixed
    ;; Before the switch, first line:
    ;;   * font size 35
    ;;   * text "new line 1"
    ;; Before the switch, second line:
    ;;   * font size 40
    ;;   * text "new line 2"


    (t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
    (t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
    (t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
    (t/is (= (get-in copy-structure-mixed-t text-path-1) "new line 2"))

    ;; After the switch, first line:
    ;;   * font size 50 (the override is not preserved)
    ;;   * text "bye" (the override is not preserved)
    ;; No second line
    (t/is (= (get-in copy-structure-mixed-t' font-size-path-0) "50"))
    (t/is (= (get-in copy-structure-mixed-t' text-path-0) "bye"))
    (t/is (nil? (get-in copy-structure-mixed-t' font-size-path-1)))))

;; ============================================================
;; NESTED COMPONENTS (with same component in both variants)
;; ============================================================

(t/deftest test-switch-variant-for-other-with-same-nested-component
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (tho/add-simple-component :external01 :external01-root :external01-child)
                      (tho/add-simple-component :external02 :external02-root :external02-child)
                      (thv/add-variant-with-copy
                       :v01 :c01 :m01 :c02 :m02 :cp01 :cp02 :external01)

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-cp01]))

        page   (thf/current-page file)
        copy-cp01 (ths/get-shape file :copy-cp01)
        copy-cp01-rect-id (-> copy-cp01 :shapes first)


        ;; On :copy-cp01, change the width of the rect


        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{copy-cp01-rect-id}
                                            (fn [shape]
                                              (assoc shape :width 25))
                                            (:objects page)
                                            {})
        file (thf/apply-changes file changes)
        copy-cp01-rect (ths/get-shape-by-id file copy-cp01-rect-id)

        ;; ==== Action
        ;; Switch :c01 for :c02
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
        copy02    (ths/get-shape file' :copy02)
        copy-cp02' (ths/get-shape-by-id file' (-> copy02 :shapes first))
        copy-cp02-rect' (ths/get-shape-by-id file' (-> copy-cp02' :shapes first))]

    ;; The width of copy-cp01-rect was 25
    (t/is (= (:width copy-cp01-rect) 25))

    ;; The width of copy-cp02-rect' is 25 (change is preserved)
    (t/is (= (:width copy-cp02-rect') 25))))

;; ============================================================
;; SWAPPED COPIES (switching variants that contain swapped components)
;; ============================================================

(t/deftest test-switch-variant-that-has-swaped-copy
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (tho/add-simple-component :external01 :external01-root :external01-child)
                      (tho/add-simple-component :external02 :external02-root :external02-child)
                      (thv/add-variant-with-copy
                       :v01 :c01 :m01 :c02 :m02 :cp01 :cp02 :external01)

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-cp01]))

        copy01 (ths/get-shape file :copy01)
        external02 (thc/get-component file :external02)

        ;; On :c01, swap the copy of :external01 for a copy of :external02
        file (-> file
                 (tho/swap-component-in-shape :copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false}))
        copy-cp02 (ths/get-shape file :copy-cp02)

        ;; ==== Action
        ;; Switch :c01 for :c02
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        copy02'    (ths/get-shape file' :copy02)
        copy-cp02' (ths/get-shape file' :copy-cp02)]
    (thf/dump-file file')
    ;;copy-cp02 is a copy of external02
    (t/is (= (:component-id copy-cp02) (:id external02)))
    ;;copy-01 had copy-cp02 as child
    (t/is (= (-> copy01 :shapes first) (:id copy-cp02)))

    ;;copy-cp02' is a copy of external02
    (t/is (= (:component-id copy-cp02') (:id external02)))
    ;;copy-02' had copy-cp02' as child
    (t/is (= (-> copy02' :shapes first) (:id copy-cp02')))))

(t/deftest test-switch-variant-that-has-swaped-copy-with-changed-attr
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (tho/add-simple-component :external01 :external01-root :external01-child)
                      (tho/add-simple-component :external02 :external02-root :external02-child)
                      (thv/add-variant-with-copy
                       :v01 :c01 :m01 :c02 :m02 :cp01 :cp02 :external01)

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-cp01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        external02 (thc/get-component file :external02)

        ;; On :c01, swap the copy of :external01 for a copy of :external02
        file (-> file
                 (tho/swap-component-in-shape :copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false}))
        copy-cp02 (ths/get-shape file :copy-cp02)
        copy-cp02-rect-id (-> copy-cp02 :shapes first)

        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{copy-cp02-rect-id}
                                            (fn [shape]
                                              (assoc shape :width 25))
                                            (:objects page)
                                            {})
        file (thf/apply-changes file changes)
        copy-cp02-rect (ths/get-shape-by-id file copy-cp02-rect-id)

        ;; ==== Action
        ;; Switch :c01 for :c02
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        copy02'    (ths/get-shape file' :copy02)
        copy-cp02' (ths/get-shape file' :copy-cp02)
        copy-cp02-rect' (ths/get-shape-by-id file' (-> copy-cp02' :shapes first))]
    (thf/dump-file file')
    ;;copy-cp02 is a copy of external02
    (t/is (= (:component-id copy-cp02) (:id external02)))
    ;;copy-01 had copy-cp02 as child
    (t/is (= (-> copy01 :shapes first) (:id copy-cp02)))
    ;; The width of copy-cp02-rect was 25
    (t/is (= (:width copy-cp02-rect) 25))

    ;;copy-cp02' is a copy of external02
    (t/is (= (:component-id copy-cp02') (:id external02)))
    ;;copy-02' had copy-cp02' as child
    (t/is (= (-> copy02' :shapes first) (:id copy-cp02')))
    ;; The width of copy-cp02-rect' is 25 (change is preserved)
    (t/is (= (:width copy-cp02-rect') 25))))

;; ============================================================
;; TOUCHED PARENT (switch without touched but with touched parent)
;; ============================================================

(t/deftest test-switch-variant-without-touched-but-touched-parent
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 5}
                        :child2-params  {:width 5}})
                      (tho/add-simple-component :external01 :external01-root :external01-child)

                      (thc/instantiate-component :c01
                                                 :c01-in-root
                                                 :children-labels [:r01-in-c01-in-root]
                                                 :parent-label :external01-root))

        ;; Make a change on r01-in-c01-in-root so it is touched
        page               (thf/current-page file)
        r01-in-c01-in-root (ths/get-shape file :r01-in-c01-in-root)

        changes            (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                                       #{(:id r01-in-c01-in-root)}
                                                       (fn [shape]
                                                         (assoc shape :width 25))
                                                       (:objects page)
                                                       {})

        file               (thf/apply-changes file changes)


        ;; Instantiate the component :external01


        file        (thc/instantiate-component file
                                               :external01
                                               :external-copy01
                                               :children-labels [:external-copy01-rect :c01-in-copy])
        page        (thf/current-page file)
        c01-in-copy (ths/get-shape file :c01-in-copy)
        rect01      (get-in page [:objects (-> c01-in-copy :shapes first)])


        ;; ==== Action


        file'        (tho/swap-component-in-shape file :c01-in-copy :c02 {:new-shape-label :c02-in-copy :keep-touched? true})

        page'        (thf/current-page file')
        c02-in-copy' (ths/get-shape file' :c02-in-copy)
        rect02'      (get-in page' [:objects (-> c02-in-copy' :shapes first)])]

    (thf/dump-file file :keys [:width :touched])
    ;; The rect had width 25 before the switch
    (t/is (= (:width rect01) 25))
    ;; The rect still has width 25 after the switch
    (t/is (= (:width rect02') 25))))

;; ============================================================
;; LAYOUT ITEM SIZING - HORIZONTAL (fix, auto, fill, none)
;; ============================================================

(t/deftest test-switch-with-layout-item-h-sizing-fix
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      ;; Create a variant with a child that has layout-item-h-sizing :fix
                      ;; When :fix is set, the width should NOT be preserved on switch
                      ;; but should take the new component's width
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100
                                        :height 50
                                        :layout-item-h-sizing :fix}
                        :child2-params {:width 200
                                        :height 50
                                        :layout-item-h-sizing :fix}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change width of the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :width 150))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had width 150 before the switch (with override)
    (t/is (= (:width rect01) 150))
    ;; With layout-item-h-sizing :fix, the width should be taken from the new component
    ;; (not preserving the override), so it should be 200
    (t/is (= (:width rect02') 200))
    ;; Verify layout-item-h-sizing is still :fix after switch
    (t/is (= (:layout-item-h-sizing rect02') :fix))))

(t/deftest test-switch-with-layout-item-h-sizing-auto
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      ;; Create a variant with a child that has layout-item-h-sizing :auto
                      ;; When :auto is set, the width override SHOULD be preserved on switch
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100
                                        :height 50
                                        :layout-item-h-sizing :auto}
                        :child2-params {:width 200
                                        :height 50
                                        :layout-item-h-sizing :auto}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change width of the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :width 150))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had width 150 before the switch (with override)
    (t/is (= (:width rect01) 150))
    ;; With layout-item-h-sizing :auto, since the two variants have different widths (100 vs 200),
    ;; the override is not preserved and the new component's width (200) is used
    (t/is (= (:width rect02') 200))
    ;; Verify layout-item-h-sizing is still :auto after switch
    (t/is (= (:layout-item-h-sizing rect02') :auto))))

(t/deftest test-switch-with-layout-item-h-sizing-fill
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      ;; Create a variant with a child that has layout-item-h-sizing :fill
                      ;; When :fill is set, the width override SHOULD be preserved on switch
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100
                                        :height 50
                                        :layout-item-h-sizing :fill}
                        :child2-params {:width 200
                                        :height 50
                                        :layout-item-h-sizing :fill}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change width of the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :width 150))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had width 150 before the switch (with override)
    (t/is (= (:width rect01) 150))
    ;; With layout-item-h-sizing :fill, since the two variants have different widths (100 vs 200),
    ;; the override is not preserved and the new component's width (200) is used
    (t/is (= (:width rect02') 200))
    ;; Verify layout-item-h-sizing is still :fill after switch
    (t/is (= (:layout-item-h-sizing rect02') :fill))))

(t/deftest test-switch-without-layout-item-h-sizing
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      ;; Create a variant with a child without layout-item-h-sizing
                      ;; When not set, the width override SHOULD be preserved on switch
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100
                                        :height 50}
                        :child2-params {:width 200
                                        :height 50}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change width of the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :width 150))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had width 150 before the switch (with override)
    (t/is (= (:width rect01) 150))
    ;; Without layout-item-h-sizing, since the two variants have different widths (100 vs 200),
    ;; the override is not preserved and the new component's width (200) is used
    (t/is (= (:width rect02') 200))
    ;; Verify layout-item-h-sizing is still nil after switch
    (t/is (nil? (:layout-item-h-sizing rect02')))))

;; ============================================================
;; LAYOUT ITEM SIZING - VERTICAL (fix, auto, fill, none)
;; ============================================================

(t/deftest test-switch-with-layout-item-v-sizing-fix
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      ;; Create a variant with a child that has layout-item-v-sizing :fix
                      ;; When :fix is set, the height should NOT be preserved on switch
                      ;; but should take the new component's height
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 50
                                        :height 100
                                        :layout-item-v-sizing :fix}
                        :child2-params {:width 50
                                        :height 200
                                        :layout-item-v-sizing :fix}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change height of the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :height 150))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had height 150 before the switch (with override)
    (t/is (= (:height rect01) 150))
    ;; With layout-item-v-sizing :fix, the height should be taken from the new component
    ;; (not preserving the override), so it should be 200
    (t/is (= (:height rect02') 200))
    ;; Verify layout-item-v-sizing is still :fix after switch
    (t/is (= (:layout-item-v-sizing rect02') :fix))))

(t/deftest test-switch-with-layout-item-v-sizing-auto
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      ;; Create a variant with a child that has layout-item-v-sizing :auto
                      ;; When :auto is set, the height override SHOULD be preserved on switch
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 50
                                        :height 100
                                        :layout-item-v-sizing :auto}
                        :child2-params {:width 50
                                        :height 200
                                        :layout-item-v-sizing :auto}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change height of the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :height 150))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had height 150 before the switch (with override)
    (t/is (= (:height rect01) 150))
    ;; With layout-item-v-sizing :auto, since the two variants have different heights (100 vs 200),
    ;; the override is not preserved and the new component's height (200) is used
    (t/is (= (:height rect02') 200))
    ;; Verify layout-item-v-sizing is still :auto after switch
    (t/is (= (:layout-item-v-sizing rect02') :auto))))

(t/deftest test-switch-with-layout-item-v-sizing-fill
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      ;; Create a variant with a child that has layout-item-v-sizing :fill
                      ;; When :fill is set, the height override SHOULD be preserved on switch
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 50
                                        :height 100
                                        :layout-item-v-sizing :fill}
                        :child2-params {:width 50
                                        :height 200
                                        :layout-item-v-sizing :fill}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change height of the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :height 150))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had height 150 before the switch (with override)
    (t/is (= (:height rect01) 150))
    ;; With layout-item-v-sizing :fill, since the two variants have different heights (100 vs 200),
    ;; the override is not preserved and the new component's height (200) is used
    (t/is (= (:height rect02') 200))
    ;; Verify layout-item-v-sizing is still :fill after switch
    (t/is (= (:layout-item-v-sizing rect02') :fill))))

(t/deftest test-switch-without-layout-item-v-sizing
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      ;; Create a variant with a child without layout-item-v-sizing
                      ;; When not set, the height override SHOULD be preserved on switch
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 50
                                        :height 100}
                        :child2-params {:width 50
                                        :height 200}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change height of the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :height 150))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had height 150 before the switch (with override)
    (t/is (= (:height rect01) 150))
    ;; Without layout-item-v-sizing, since the two variants have different heights (100 vs 200),
    ;; the override is not preserved and the new component's height (200) is used
    (t/is (= (:height rect02') 200))
    ;; Verify layout-item-v-sizing is still nil after switch
    (t/is (nil? (:layout-item-v-sizing rect02')))))

;; ============================================================
;; ROTATION OVERRIDES
;; ============================================================

(t/deftest test-switch-with-rotation-override
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100
                                        :height 100
                                        :rotation 0}
                        :child2-params {:width 100
                                        :height 100
                                        :rotation 0}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Apply rotation to the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :rotation 45))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had rotation 45 before the switch (with override)
    (t/is (= (:rotation rect01) 45))
    ;; The rotation override should be preserved after switch since both variants have the same rotation
    (t/is (= (:rotation rect02') 45))
    ;; The transform matrix should also be preserved
    (t/is (some? (:transform rect02')))))

(t/deftest test-switch-with-rotation-different-variants
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100
                                        :height 100
                                        :rotation 0}
                        :child2-params {:width 100
                                        :height 100
                                        :rotation 90}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Apply rotation to the child rect (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :rotation 45))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had rotation 45 before the switch (with override)
    (t/is (= (:rotation rect01) 45))
    ;; The override should NOT be preserved since the two variants have different rotations (0 vs 90)
    ;; The new rotation should be 90 (from c02)
    (t/is (= (:rotation rect02') 90))))

;; ============================================================
;; SPECIAL CASES (auto-text, geometry, touched attributes, position data)
;; ============================================================

(t/deftest test-switch-with-auto-text-geometry-not-copied
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      ;; Create variants with auto-text (grow-type :auto-width or :auto-height)
                      (thv/add-variant-with-text
                       :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello" "world"))

        page   (thf/current-page file)
        ;; Modify the first text shape to have grow-type :auto-width
        t01    (ths/get-shape file :t01)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id t01)}
                                            (fn [shape]
                                              (assoc shape :grow-type :auto-width))
                                            (:objects page)
                                            {})
        file   (thf/apply-changes file changes)

        ;; Also modify t02
        page   (thf/current-page file)
        t02    (ths/get-shape file :t02)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id t02)}
                                            (fn [shape]
                                              (assoc shape :grow-type :auto-width))
                                            (:objects page)
                                            {})
        file   (thf/apply-changes file changes)

        ;; Now create a copy and modify its width
        file      (thc/instantiate-component file :c01
                                             :copy01
                                             :children-labels [:copy-t01])

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        text01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change width of the text (creating an override)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id text01)}
                                            (fn [shape]
                                              (assoc shape :width 200))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        text01 (get-in page [:objects (:id text01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        text02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The text had width 200 before the switch (with override)
    (t/is (= (:width text01) 200))
    ;; For auto-text shapes, geometry attributes like width should NOT be copied on switch
    ;; So the width should be from the new component (t02's width)
    (t/is (not= (:width text02') 200))
    ;; Verify grow-type is preserved
    (t/is (= (:grow-type text02') :auto-width))))

(t/deftest test-switch-different-shape-types-content-not-copied
  (let [;; ==== Setup - Create a variant with a rect in first component
        ;; This test is simplified to just test attributes, not changing shape types
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100 :height 100 :type :rect}
                        :child2-params {:width 100 :height 100 :type :rect}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; ==== Action - Try to switch to a component with different shape type
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        child02'  (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; Verify the shapes are still rects
    (t/is (= (:type rect01) :rect))
    (t/is (= (:type child02') :rect))
    ;; This test demonstrates that content with different types isn't copied
    ;; In practice this means proper attribute filtering
    (t/is (= (:width child02') 100))))

(t/deftest test-switch-with-path-shape-geometry-override
  (let [;; ==== Setup - Create variants with path shapes
        ;; Using rect shapes as path shapes are complex - the principle is the same
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100 :height 100 :type :rect}
                        :child2-params {:width 200 :height 200 :type :rect}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-path01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        path01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Resize the path (creating an override by changing selrect)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id path01)}
                                            (fn [shape]
                                              (assoc shape :width 150))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        path01 (get-in page [:objects (:id path01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        path02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had width 150 before the switch
    (t/is (= (:width path01) 150))
    ;; For shapes with geometry changes, the transformed geometry is applied
    ;; Since variants have different widths (100 vs 200), override is discarded
    (t/is (= (:width path02') 200))
    ;; Verify it's still a rect type
    (t/is (= (:type path02') :rect))))

(t/deftest test-switch-preserves-touched-attributes-only
  (let [;; ==== Setup - Test that only touched attributes are copied
        ;; Use opacity since it's a simpler attribute than fill-color
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100
                                        :height 100
                                        :opacity 1}
                        :child2-params {:width 200
                                        :height 200
                                        :opacity 1}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change the opacity (creating a touched attribute)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (assoc shape :opacity 0.5))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had opacity 0.5 before the switch (touched)
    (t/is (= (:opacity rect01) 0.5))
    ;; The rect had width 100 before the switch (not touched)
    (t/is (= (:width rect01) 100))

    ;; After switch:
    ;; - opacity override SHOULD be preserved because:
    ;;   1. It was touched
    ;;   2. Both variants have same opacity (1)
    (t/is (= (:opacity rect02') 0.5))
    ;; - width should NOT be preserved (it wasn't touched, and variants have different widths)
    (t/is (= (:width rect02') 200))
    ;; - height should match the new variant
    (t/is (= (:height rect02') 200))))

(t/deftest test-switch-with-equal-values-not-copied
  (let [;; ==== Setup - Test that when previous-shape and current-shape have equal values,
        ;; no copy operation occurs (optimization in update-attrs-on-switch)
        ;; Both variants start with opacity 0.5
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100
                                        :height 100
                                        :opacity 0.5}
                        :child2-params {:width 100
                                        :height 100
                                        :opacity 0.5}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had opacity 0.5 before the switch
    (t/is (= (:opacity rect01) 0.5))
    ;; After switch, opacity should still be 0.5
    ;; This validates that the equality check works correctly
    (t/is (= (:opacity rect02') 0.5))))

(t/deftest test-switch-with-position-data-reset
  (let [;; ==== Setup - Test that position-data is reset when geometry-group is touched
        file      (-> (thf/sample-file :file1)
                      ;; Create variants with text shapes
                      (thv/add-variant-with-text
                       :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world"))

        page   (thf/current-page file)
        ;; Modify the first text shape to have specific geometry
        t01    (ths/get-shape file :t01)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id t01)}
                                            (fn [shape]
                                              (assoc shape :width 200))
                                            (:objects page)
                                            {})
        file   (thf/apply-changes file changes)

        ;; Create a copy and modify its geometry (touching geometry-group)
        file      (thc/instantiate-component file :c01
                                             :copy01
                                             :children-labels [:copy-t01])

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        text01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Change width of the text (touching geometry)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id text01)}
                                            (fn [shape]
                                              (assoc shape :width 300))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        text01 (get-in page [:objects (:id text01)])
        old-position-data (:position-data text01)

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        text02'   (get-in page' [:objects (-> copy02' :shapes first)])
        new-position-data (:position-data text02')]

    ;; position-data should be reset (nil or different) when geometry group is touched
    ;; This allows the system to recalculate it based on the new geometry
    ;; Note: old-position-data may be nil initially, which is fine
    ;; After switch with geometry changes, if old data existed and was different,
    ;; or if it needs recalculation, the test validates the behavior
    (t/is (or (nil? old-position-data)
              (nil? new-position-data)
              (not= old-position-data new-position-data)))))

;; ============================================================
;; SELRECT CONSISTENCY TESTS
;; These tests verify that after a variant switch, the composite
;; geometry attributes (:selrect, :points) stay consistent with
;; the scalar attributes (:width, :height) that are kept.
;; ============================================================

(t/deftest test-switch-selrect-consistent-no-sizing-different-widths
  ;;  When no :fix sizing and variants have different widths,
  ;; :width is correctly skipped (stays at new component width),
  ;; but :selrect was being copied from the old shape, leaving
  ;; selrect.width inconsistent with :width. This test verifies the fix.
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100 :height 50}
                        :child2-params {:width 200 :height 50}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Override width AND selrect consistently (simulating a real resize)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (let [new-width 150
                                                    sr        (:selrect shape)
                                                    new-sr    (-> sr
                                                                  (assoc :width new-width)
                                                                  (assoc :x2 (+ (:x1 sr) new-width)))]
                                                (-> shape
                                                    (assoc :width new-width)
                                                    (assoc :selrect new-sr))))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had the width override before the switch
    (t/is (= (:width rect01) 150))
    (t/is (= (get-in rect01 [:selrect :width]) 150))
    ;; Since the variants have different widths (100 vs 200), the override is not preserved
    (t/is (= (:width rect02') 200))
    ;; The selrect must be consistent with :width
    (t/is (= (get-in rect02' [:selrect :width]) 200))))

(t/deftest test-switch-selrect-consistent-no-sizing-different-heights
  ;; Same as above but for height.
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 50 :height 100}
                        :child2-params {:width 50 :height 200}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Override height AND selrect consistently
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (let [new-height 150
                                                    sr         (:selrect shape)
                                                    new-sr     (-> sr
                                                                   (assoc :height new-height)
                                                                   (assoc :y2 (+ (:y1 sr) new-height)))]
                                                (-> shape
                                                    (assoc :height new-height)
                                                    (assoc :selrect new-sr))))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had the height override before the switch
    (t/is (= (:height rect01) 150))
    (t/is (= (get-in rect01 [:selrect :height]) 150))
    ;; Since the variants have different heights (100 vs 200), the override is not preserved
    (t/is (= (:height rect02') 200))
    ;; The selrect must be consistent with :height
    (t/is (= (get-in rect02' [:selrect :height]) 200))))

(t/deftest test-switch-with-v-sizing-fix-selrect-consistent-different-widths
  ;; mixed-sizing scenario: v-sizing=:fix but variants differ in WIDTH.
  ;; switch-fixed-layout-geom-change-value is triggered (because v-sizing=:fix).
  ;; Without the fix, the function returned prev-width for the non-:fix dimension,
  ;; leaving selrect.width inconsistent with :width.
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100 :height 50 :layout-item-v-sizing :fix}
                        :child2-params {:width 200 :height 50 :layout-item-v-sizing :fix}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Override width AND selrect consistently
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (let [new-width 150
                                                    sr        (:selrect shape)
                                                    new-sr    (-> sr
                                                                  (assoc :width new-width)
                                                                  (assoc :x2 (+ (:x1 sr) new-width)))]
                                                (-> shape
                                                    (assoc :width new-width)
                                                    (assoc :selrect new-sr))))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had the width override before the switch
    (t/is (= (:width rect01) 150))
    (t/is (= (get-in rect01 [:selrect :width]) 150))
    ;; Since the variants have different widths (100 vs 200), the override is not preserved
    ;; (v-sizing=:fix does not affect the horizontal dimension)
    (t/is (= (:width rect02') 200))
    ;; The selrect must be consistent with :width
    (t/is (= (get-in rect02' [:selrect :width]) 200))
    ;; v-sizing is preserved
    (t/is (= (:layout-item-v-sizing rect02') :fix))))

(t/deftest test-switch-with-h-sizing-fix-selrect-consistent-different-heights
  ;; mixed-sizing scenario: h-sizing=:fix but variants differ in HEIGHT.
  ;; switch-fixed-layout-geom-change-value is triggered (because h-sizing=:fix).
  ;; Without the fix, the function returned prev-height for the non-:fix dimension,
  ;; leaving selrect.height inconsistent with :height.
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 50 :height 100 :layout-item-h-sizing :fix}
                        :child2-params {:width 50 :height 200 :layout-item-h-sizing :fix}})

                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Override height AND selrect consistently
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (let [new-height 150
                                                    sr         (:selrect shape)
                                                    new-sr     (-> sr
                                                                   (assoc :height new-height)
                                                                   (assoc :y2 (+ (:y1 sr) new-height)))]
                                                (-> shape
                                                    (assoc :height new-height)
                                                    (assoc :selrect new-sr))))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had the height override before the switch
    (t/is (= (:height rect01) 150))
    (t/is (= (get-in rect01 [:selrect :height]) 150))
    ;; Since the variants have different heights (100 vs 200), the override is not preserved
    ;; (h-sizing=:fix does not affect the vertical dimension)
    (t/is (= (:height rect02') 200))
    ;; The selrect must be consistent with :height
    (t/is (= (get-in rect02' [:selrect :height]) 200))
    ;; h-sizing is preserved
    (t/is (= (:layout-item-h-sizing rect02') :fix))))

;; ============================================================
;; FIXED-SIZING: "SAME-SIZE → PRESERVE OVERRIDE" PATH TESTS
;; These tests exercise the branch inside switch-fixed-layout-geom-change-value
;; where variants share the same value in the non-:fix dimension:
;;   (if (= origin-dim current-dim) prev-dim current-dim)
;; When origin-dim == current-dim the user's override for that dimension
;; must be preserved after the switch.
;; ============================================================

(t/deftest test-switch-with-h-sizing-fix-same-height-override-preserved
  ;; h-sizing=:fix, variants have SAME height (non-:fix dim, same-size).
  ;; switch-fixed-layout-geom-change-value must return prev-height for the
  ;; non-:fix dimension because origin-height == current-height.
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100 :height 50 :layout-item-h-sizing :fix}
                        :child2-params {:width 200 :height 50 :layout-item-h-sizing :fix}})
                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Override height (the non-:fix dimension) and selrect consistently
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (let [new-height 75
                                                    sr         (:selrect shape)
                                                    new-sr     (-> sr
                                                                   (assoc :height new-height)
                                                                   (assoc :y2 (+ (:y1 sr) new-height)))]
                                                (-> shape
                                                    (assoc :height new-height)
                                                    (assoc :selrect new-sr))))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had the height override 75 before the switch
    (t/is (= (:height rect01) 75))
    ;; h-sizing=:fix means width always takes the new component's value
    (t/is (= (:width rect02') 200))
    ;; Height (non-:fix dim) is preserved because both variants have same height (50)
    (t/is (= (:height rect02') 75))
    ;; selrect must be consistent with the preserved height
    (t/is (= (get-in rect02' [:selrect :height]) 75))
    (t/is (= (get-in rect02' [:selrect :width]) 200))
    ;; h-sizing is preserved
    (t/is (= (:layout-item-h-sizing rect02') :fix))))

(t/deftest test-switch-with-v-sizing-fix-same-width-override-preserved
  ;; v-sizing=:fix, variants have SAME width (non-:fix dim, same-size).
  ;; switch-fixed-layout-geom-change-value must return prev-width for the
  ;; non-:fix dimension because origin-width == current-width.
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100 :height 50 :layout-item-v-sizing :fix}
                        :child2-params {:width 100 :height 100 :layout-item-v-sizing :fix}})
                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Override width (the non-:fix dimension) and selrect consistently
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (let [new-width 150
                                                    sr        (:selrect shape)
                                                    new-sr    (-> sr
                                                                  (assoc :width new-width)
                                                                  (assoc :x2 (+ (:x1 sr) new-width)))]
                                                (-> shape
                                                    (assoc :width new-width)
                                                    (assoc :selrect new-sr))))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had the width override 150 before the switch
    (t/is (= (:width rect01) 150))
    ;; Width (non-:fix dim) is preserved because both variants have same width (100)
    (t/is (= (:width rect02') 150))
    ;; selrect must be consistent with the preserved width
    (t/is (= (get-in rect02' [:selrect :width]) 150))
    ;; v-sizing=:fix means height always takes the new component's value
    (t/is (= (:height rect02') 100))
    (t/is (= (get-in rect02' [:selrect :height]) 100))
    ;; v-sizing is preserved
    (t/is (= (:layout-item-v-sizing rect02') :fix))))

(t/deftest test-switch-with-both-sizing-fix-overrides-discarded
  ;; When both h-sizing=:fix and v-sizing=:fix, switch-fixed-layout-geom-change-value
  ;; always uses current-width and current-height (the new component's values).
  ;; Both width and height overrides are discarded because :fix always
  ;; defers to the new component's dimension regardless of same-size or not.
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100 :height 50
                                        :layout-item-h-sizing :fix
                                        :layout-item-v-sizing :fix}
                        :child2-params {:width 200 :height 100
                                        :layout-item-h-sizing :fix
                                        :layout-item-v-sizing :fix}})
                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Override both width and height (and selrect) consistently
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (let [new-width  150
                                                    new-height 75
                                                    sr         (:selrect shape)
                                                    new-sr     (-> sr
                                                                   (assoc :width new-width)
                                                                   (assoc :height new-height)
                                                                   (assoc :x2 (+ (:x1 sr) new-width))
                                                                   (assoc :y2 (+ (:y1 sr) new-height)))]
                                                (-> shape
                                                    (assoc :width new-width)
                                                    (assoc :height new-height)
                                                    (assoc :selrect new-sr))))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had both overrides before the switch
    (t/is (= (:width rect01) 150))
    (t/is (= (:height rect01) 75))
    ;; With both sizing :fix, both dimensions take the new component's values
    (t/is (= (:width rect02') 200))
    (t/is (= (:height rect02') 100))
    ;; selrect must be consistent
    (t/is (= (get-in rect02' [:selrect :width]) 200))
    (t/is (= (get-in rect02' [:selrect :height]) 100))
    (t/is (= (:layout-item-h-sizing rect02') :fix))
    (t/is (= (:layout-item-v-sizing rect02') :fix))))

(t/deftest test-switch-same-size-variants-geometry-override-preserved
  ;; When both variants have IDENTICAL dimensions (width=100, height=50),
  ;; the guard that skips :selrect/:points must NOT fire
  ;; (its condition `(or (not= origin.width current.width) ...)` is false).
  ;; A geometry override should therefore be carried through correctly.
  (let [;; ==== Setup
        file      (-> (thf/sample-file :file1)
                      (thv/add-variant-with-child
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02
                       {:child1-params {:width 100 :height 50}
                        :child2-params {:width 100 :height 50}})   ; same size!
                      (thc/instantiate-component :c01
                                                 :copy01
                                                 :children-labels [:copy-r01]))

        page   (thf/current-page file)
        copy01 (ths/get-shape file :copy01)
        rect01 (get-in page [:objects (-> copy01 :shapes first)])

        ;; Override width AND selrect consistently (simulating a real resize)
        changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                            #{(:id rect01)}
                                            (fn [shape]
                                              (let [new-width 150
                                                    sr        (:selrect shape)
                                                    new-sr    (-> sr
                                                                  (assoc :width new-width)
                                                                  (assoc :x2 (+ (:x1 sr) new-width)))]
                                                (-> shape
                                                    (assoc :width new-width)
                                                    (assoc :selrect new-sr))))
                                            (:objects page)
                                            {})

        file   (thf/apply-changes file changes)
        page   (thf/current-page file)
        rect01 (get-in page [:objects (:id rect01)])

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The rect had the width override 150 before the switch
    (t/is (= (:width rect01) 150))
    (t/is (= (get-in rect01 [:selrect :width]) 150))
    ;; Both variants are identical in size (100x50), so the override IS preserved
    (t/is (= (:width rect02') 150))
    ;; The guard must not have suppressed :selrect — it should be consistent
    (t/is (= (get-in rect02' [:selrect :width]) 150))))


(t/deftest test-switch-skips-composite-geometry-with-subpixel-drift
  ;; Regression: when the previous-shape's geometry only has sub-pixel drift
  ;; relative to its source master (a state produced by interactive transform
  ;; modifiers, e.g. alt-drag duplicate of a variant whose children are
  ;; component copies), equal-geometry? must classify it as unchanged and skip
  ;; copying composite geometry. Otherwise, :selrect/:points can carry stale
  ;; absolute positions from the source variant onto the freshly-instantiated
  ;; target, producing the visible "cut off" symptom.
  (let [;; ==== Setup
        ;; A self-contained Input/Button-like component, plus a variant
        ;; container whose two variants each instance that component
        ;; at different y positions. This mirrors the production setup
        ;; where the dragged variant's children are themselves component
        ;; copies (and thus carry :touched on geometry within the master).
        file    (-> (thf/sample-file :file1)
                    (tho/add-simple-component :btn-comp :btn-root :btn-rect)
                    (thv/add-variant-with-copy
                     :v01 :c01 :m01 :c02 :m02 :child1 :child2 :btn-comp))

        ;; Position child1 at y=101 (in m01) and child2 at y=73 (in m02).
        ;; Use gsh/absolute-move so :selrect/:points stay consistent with
        ;; :y — a plain (assoc :y …) would leave them out of sync and
        ;; produce a different (artificial) failure mode.
        page    (thf/current-page file)
        child1  (ths/get-shape file :child1)
        child2  (ths/get-shape file :child2)
        changes (-> (pcb/empty-changes nil (:id page))
                    (cls/generate-update-shapes
                     #{(:id child1)}
                     #(gsh/absolute-move % (gpt/point (:x %) 101))
                     (:objects page) {})
                    (cls/generate-update-shapes
                     #{(:id child2)}
                     #(gsh/absolute-move % (gpt/point (:x %) 73))
                     (:objects page) {}))
        file    (thf/apply-changes file changes)
        file    (thc/instantiate-component file :c01 :copy01)

        ;; The copy carries an Input/Button instance (Frame1). Introduce
        ;; sub-pixel drift in its :width and :selrect.width — the kind of
        ;; floating-point error produced by the alt-drag modifier path in
        ;; production. The drift is small enough to be treated as unchanged
        ;; geometry by equal-geometry?.
        page          (thf/current-page file)
        copy01        (ths/get-shape file :copy01)
        copy-btn-id   (->> (cfh/get-children-ids-with-self (:objects page) (:id copy01))
                           (map #(get-in page [:objects %]))
                           (filter #(= "Frame1" (:name %)))
                           first :id)
        drift         0.00001
        changes       (cls/generate-update-shapes
                       (pcb/empty-changes nil (:id page))
                       #{copy-btn-id}
                       (fn [shape]
                         (let [w (+ (:width shape) drift)
                               sr (:selrect shape)]
                           (-> shape
                               (assoc :width w)
                               (assoc :selrect (-> sr
                                                   (assoc :width w)
                                                   (assoc :x2 (+ (:x1 sr) w)))))))
                       (:objects page) {})
        file          (thf/apply-changes file changes)
        m02           (ths/get-shape file :m02)
        child2        (ths/get-shape file :child2)

        target-rel-y  (- (:y child2) (:y m02))

        ;; ==== Action
        file'         (tho/swap-component-in-shape file :copy01 :c02
                                                   {:new-shape-label :copy02
                                                    :keep-touched? true})

        page'         (thf/current-page file')
        copy02        (ths/get-shape file' :copy02)
        post-btn      (->> (cfh/get-children-ids-with-self (:objects page') (:id copy02))
                           (map #(get-in page' [:objects %]))
                           (filter #(= "Frame1" (:name %)))
                           first)
        post-btn-rel-y (- (:y post-btn) (:y copy02))
        post-btn-selrect-rel-y (- (get-in post-btn [:selrect :y])
                                  (get-in copy02 [:selrect :y]))]

    ;; The post-switch button must sit at the target master's relative y.
    ;; Its :y field already does (the per-attr :y skip handles that
    ;; correctly); the failure is on :selrect.
    (t/is (= target-rel-y post-btn-rel-y)
          (str "Child's :y should match target master layout (" target-rel-y ")"))

    ;; The bug: :selrect.y is overwritten with the previous shape's value,
    ;; not regenerated from the target master's layout. After fix, this
    ;; assertion should pass.
    (t/is (= target-rel-y post-btn-selrect-rel-y)
          (str ":selrect.y should match target master layout (expected "
               target-rel-y " got " post-btn-selrect-rel-y ")"))

    ;; And :y must agree with :selrect.y — a shape whose :y disagrees with
    ;; its :selrect.y is internally inconsistent and renders incorrectly.
    (t/is (= post-btn-rel-y post-btn-selrect-rel-y)
          ":y and :selrect.y must agree after switch")))

(t/deftest test-switch-does-not-override-path-content-when-only-repositioned
  ;; Regression: when a path shape inside a variant has :geometry-group touched
  ;; (e.g. because auto-layout repositioned it after the copy's parent was
  ;; resized), switching variants must NOT copy the old variant's path position
  ;; to the new variant. The path should stay at the new variant's default position.
  ;;
  ;; Root cause: equal-geometry? did not handle the :content attr for path shapes,
  ;; so switch-path-change-value was always invoked and placed the new path at the
  ;; pre-switch absolute position instead of the target master's default position.
  (let [;; A small closed triangle path whose bounding box is 24x14 px,
        ;; anchored at absolute position (x0, y0).
        triangle (fn [x0 y0]
                   [{:command :move-to :params {:x x0 :y y0}}
                    {:command :line-to :params {:x (+ x0 24) :y y0}}
                    {:command :line-to :params {:x (+ x0 12) :y (+ y0 14)}}
                    {:command :close-path}])

        ;; V1 has the path at y=10; V2 has the same-shape path at y=30.
        file     (-> (thf/sample-file :file1)
                     (thv/add-variant :v01 :c01 :m01 :c02 :m02
                                      {:variant1-params {:width 100 :height 100}
                                       :variant2-params {:width 100 :height 100}})
                     (ths/add-sample-shape :path1 :type :path
                                           :parent-label :m01
                                           :content (triangle 0 10))
                     (ths/add-sample-shape :path2 :type :path
                                           :parent-label :m02
                                           :content (triangle 0 30))
                     (thc/instantiate-component :c01 :copy01))

        ;; Simulate auto-layout repositioning the path inside the copy by
        ;; moving it to y=50. This touches :geometry-group on the copy's path.
        page     (thf/current-page file)
        copy01   (ths/get-shape file :copy01)
        copy-path (->> (cfh/get-children-with-self (:objects page) (:id copy01))
                       (filter #(= :path (:type %)))
                       first)
        changes  (cls/generate-update-shapes
                  (pcb/empty-changes nil (:id page))
                  #{(:id copy-path)}
                  #(gsh/absolute-move % (gpt/point (:x %) 50))
                  (:objects page) {})
        file     (thf/apply-changes file changes)

        ;; Switch copy01 from V1 (c01) to V2 (c02).
        file'    (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})

        page'      (thf/current-page file')
        copy01'    (ths/get-shape file' :copy01)
        copy-path' (->> (cfh/get-children-with-self (:objects page') (:id copy01'))
                        (filter #(= :path (:type %)))
                        first)

        ;; Expected: V2's path sits at y=30 (its master default), not y=50
        ;; (the pre-switch repositioned position).
        m02      (ths/get-shape file :m02)
        path2    (ths/get-shape file :path2)
        target-rel-y (- (-> path2 :selrect :y) (-> m02 :selrect :y))
        actual-rel-y (- (-> copy-path' :selrect :y) (-> copy01' :selrect :y))]

    (t/is (some? copy-path') "path should exist in switched copy")
    (t/is (= target-rel-y actual-rel-y)
          (str "path :selrect.y should match target master layout (expected "
               target-rel-y " got " actual-rel-y ")"))))


(t/deftest test-switch-preserves-size-override-at-target-position
  (let [move-to   (fn [shape x y]
                    (gsh/move shape (gpt/point (- x (:x shape))
                                               (- y (:y shape)))))

        ;; ==== Setup: each variant contains the same nested component instance.
        ;; The nested instance has identical size in both variants, but a different
        ;; position relative to the variant root.
        file      (-> (thf/sample-file :file1)
                      (tho/add-simple-component
                       :nested-component :nested-main :nested-label
                       :root-params {:width 100 :height 50}
                       :child-params {:width 30 :height 10})
                      (thv/add-variant-with-copy
                       :v01 :c01 :m01 :c02 :m02 :r01 :r02 :nested-component))

        page      (thf/current-page file)
        r01       (ths/get-shape file :r01)
        r02       (ths/get-shape file :r02)
        changes   (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                              #{(:id r01) (:id r02)}
                                              (fn [shape]
                                                (cond
                                                  (= (:id shape) (:id r01)) (move-to shape 20 100)
                                                  (= (:id shape) (:id r02)) (move-to shape 20 70)
                                                  :else shape))
                                              (:objects page)
                                              {})
        file      (thf/apply-changes file changes)

        file      (thc/instantiate-component file :c01
                                             :copy01
                                             :children-labels [:copy-r01])
        page      (thf/current-page file)
        copy01    (ths/get-shape file :copy01)
        copy-r01  (get-in page [:objects (-> copy01 :shapes first)])

        ;; This is a real geometry override, not float drift. The switch should
        ;; preserve the overridden size while anchoring composite geometry to
        ;; the target variant's position.
        changes   (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
                                              #{(:id copy-r01)}
                                              (fn [shape]
                                                (let [new-width 150
                                                      sr        (:selrect shape)
                                                      new-sr    (-> sr
                                                                    (assoc :width new-width)
                                                                    (assoc :x2 (+ (:x1 sr) new-width)))]
                                                  (-> shape
                                                      (assoc :width new-width)
                                                      (assoc :selrect new-sr)
                                                      (assoc :touched #{:geometry-group}))))
                                              (:objects page)
                                              {})
        file      (thf/apply-changes file changes)

        ;; ==== Action
        file'     (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})

        page'     (thf/current-page file')
        copy02'   (ths/get-shape file' :copy02)
        rect02'   (get-in page' [:objects (-> copy02' :shapes first)])]

    ;; The width override is preserved, but the target variant position remains
    ;; authoritative for absolute composite geometry.
    (t/is (= 150 (:width rect02')))
    (t/is (= (+ (:y copy02') 70) (:y rect02')))
    (t/is (= (:y rect02') (get-in rect02' [:selrect :y])))))

;; ============================================================
;; PRESERVE TEXT SUB-TOUCHED FLAGS ACROSS VARIANT SWITCH
;; ============================================================

(t/deftest test-switch-preserves-text-sub-touched-flags
  ;; 1. Creates a component with text "hello world" + font-size "14", variant with font-size "20"
  ;; 2. Overrides only text on the copy → verifies :text-content-text in touched
  ;; 3. Switches to variant → verifies text override preserved, font-size updated, :text-content-text preserved
  ;; 4. Updates main font-size to "30" and syncs → verifies font-size synced but text override preserved
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 ;; c01 has text "hello world" font-size "14"
                 ;; c02 has text "hello world" font-size "20" (same text, different font-size)
                 (thv/add-variant-with-text
                  :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world")
                 (update-attr :t02 font-size-path-0 "20")
                 (thc/instantiate-component :c01
                                            :copy01
                                            :children-labels [:copy-t01]))

        ;; Override only the TEXT on the copy (not font-size)
        file       (update-attr file :copy-t01 text-path-0 "custom text")
        copy-t01   (ths/get-shape file :copy-t01)]

    ;; Verify the copy has the text override and correct touched flags
    (t/is (= (get-in copy-t01 text-path-0) "custom text"))
    (t/is (= (get-in copy-t01 font-size-path-0) "14"))
    (t/is (contains? (:touched copy-t01) :content-group))
    (t/is (contains? (:touched copy-t01) :text-content-text))
    (t/is (not (contains? (:touched copy-t01) :text-content-attribute)))
    (t/is (not (contains? (:touched copy-t01) :text-content-structure)))

    ;; ==== Action: Switch copy to c02 variant (same text, different font-size)
    (let [file' (tho/swap-component-in-shape file :copy01 :c02
                                             {:new-shape-label :copy02
                                              :keep-touched? true})
          page'       (thf/current-page file')
          copy02'     (ths/get-shape file' :copy02)
          copy-t02'   (get-in page' [:objects (-> copy02' :shapes first)])]

      ;; After switch: text override preserved (same text between variants),
      ;; font-size updated from variant, touched preserves text-content-text
      (t/is (= (get-in copy-t02' text-path-0) "custom text"))
      (t/is (= (get-in copy-t02' font-size-path-0) "20"))
      (t/is (contains? (:touched copy-t02') :content-group))
      (t/is (contains? (:touched copy-t02') :text-content-text))
      (t/is (not (contains? (:touched copy-t02') :text-content-attribute)))

      ;; ==== Now test subsequent component sync
      ;; Modify the main component's font-size to "30" (keeping text "hello world")
      (let [main-text  (ths/get-shape file' :t02)
            changes1   (cls/generate-update-shapes (pcb/empty-changes nil (:id page'))
                                                   #{(:id main-text)}
                                                   (fn [shape]
                                                     (assoc-in shape font-size-path-0 "30"))
                                                   (:objects page')
                                                   {})
            updated-file (thf/apply-changes file' changes1)

            changes2     (cll/generate-sync-file-changes (pcb/empty-changes)
                                                         nil
                                                         :components
                                                         (:id updated-file)
                                                         (thi/id :c02)
                                                         (:id updated-file)
                                                         {(:id updated-file) updated-file}
                                                         (:id updated-file))

            synced-file   (thf/apply-changes updated-file changes2)
            synced-copy   (ths/get-shape synced-file :copy02)
            synced-t      (get-in (thf/current-page synced-file)
                                  [:objects (-> synced-copy :shapes first)])]

        ;; The text override is preserved and font-size is synced
        (t/is (= (get-in synced-t text-path-0) "custom text"))
        (t/is (= (get-in synced-t font-size-path-0) "30"))
        (t/is (contains? (:touched synced-t) :content-group))
        (t/is (contains? (:touched synced-t) :text-content-text))))))
