diff --git a/tl/src/tl/scene.cljs b/tl/src/tl/scene.cljs index ce5cb37..77356cf 100644 --- a/tl/src/tl/scene.cljs +++ b/tl/src/tl/scene.cljs @@ -248,7 +248,11 @@ at)) (defn- restore-mark [m] + ;; keep :id keyworded in lockstep with the refs that target it: a nested + ;; annotation's :ref is another mark's :id, and JSON makes both strings — if we + ;; keyword one but not the other they stop matching (ref looks "deleted"). (cond-> m + (:id m) (update :id keyword) (get-in m [:start :ref]) (update-in [:start :ref] keyword) (get-in m [:end :ref]) (update-in [:end :ref] keyword) (:track m) (update :track keyword))) diff --git a/tl/test/tl/scene_test.cljs b/tl/test/tl/scene_test.cljs index f44cb99..3e1b4af 100644 --- a/tl/test/tl/scene_test.cljs +++ b/tl/test/tl/scene_test.cljs @@ -249,6 +249,32 @@ (let [scene (assoc-in base [:groups :ann-1] g)] (is (= [0 100] (:src (first (s/resolve scene :ann-1))))))))) +(defn- json-roundtrip [x] + (js->clj (js/JSON.parse (js/JSON.stringify (clj->js x))) :keywordize-keys true)) + +(deftest annotations-survive-json-roundtrip + (testing "restore-annotations is the exact inverse of the JSON wire trip — guards + against any keyword-valued field (id/ref/type/parent/track) being missed" + (let [anns {:ann-p {:type :annotation :parent :root :name "p" :color "#abc" :content "hi" + :marks [{:id :m-1 :start {:ref :clip-a :at 0} :end {:ref :clip-a :at -1} :track :t0}]} + :ann-c {:type :annotation :parent :ann-p :name "c" + :marks [{:id :m-2 :start {:ref :m-1 :at 0} :end {:ref :m-1 :at 5}}]}}] + (is (= anns (s/restore-annotations (json-roundtrip anns))))))) + +(deftest restore-keeps-nested-refs-matching-ids + (testing "a nested annotation's ref to a parent mark survives JSON (id + ref both keyworded)" + (let [json-anns {:ann-p {:type "annotation" :parent "root" + :marks [{:id "m-parent" :start {:ref "clip-a" :at 0} + :end {:ref "clip-a" :at -1}}]} + :ann-c {:type "annotation" :parent "ann-p" + :marks [{:id "m-child" :start {:ref "m-parent" :at 0} + :end {:ref "m-parent" :at 50}}]}} + scene (update base :groups merge (s/restore-annotations json-anns))] + (is (= :m-parent (get-in scene [:groups :ann-p :marks 0 :id]))) ; id keyworded + (is (= :m-parent (get-in scene [:groups :ann-c :marks 0 :start :ref]))) ; ref keyworded to match + (is (empty? (s/broken-marks scene :ann-c))) ; so it isn't "deleted" + (is (= [0 50] (:src (first (s/resolve scene :ann-c)))))))) + (deftest marks-to-rows-normalises-frames (testing "marks->rows is 1:1 with marks and normalises :at -1 to the length" (let [rows (s/marks->rows base [{:id :m :start {:ref :clip-a :at 0}