;; This Source Code Form is subject to the terms of the Mozilla Public ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; ;; Copyright (c) KALEIDOS INC (ns frontend-tests.copy-as-svg-test "Regression tests for the Copy as SVG action (issue #838). The bug: when multiple shapes were selected, `generate-markup` emitted several sibling `` roots concatenated with newlines. External SVG parsers (Inkscape, browsers) only read the first root, so multi-shape selection appeared to copy only one shape. The fix wraps 2+ shapes in a single `` root with a combined viewBox." (:require [app.common.test-helpers.files :as cthf] [app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.shapes :as cths] [app.common.types.shape :as cts] [app.util.code-gen.markup-svg :as svg] [cljs.test :refer [deftest is testing] :include-macros true])) (defn- setup-shapes "Build a file with `n` sample rectangles on the current page. Returns a map with `:objects` and `:shapes` keys, mirroring the inputs that `copy-selected-svg` feeds into `generate-markup`." [labels] (let [file (reduce (fn [f label] (cths/add-sample-shape f label)) (cthf/sample-file :file1 :page-label :page1) labels) page (cthf/current-page file) objects (:objects page) shapes (mapv #(get objects (cthi/id %)) labels)] {:objects objects :shapes shapes})) (defn- count-matches [re s] (count (re-seq re s))) (defn- setup-vertical-text [] (let [shape (-> (cts/setup-shape {:type :text :x 10 :y 20 :width 40 :height 100}) (assoc :name "Vertical text" :position-data [{:x 20 :y 100 :width 24 :height 80 :fills [{:fill-color "#112233" :fill-opacity 1}] :font-family "Noto Sans CJK JP" :font-size "20" :font-weight "400" :writing-mode "vertical-rl" :text-orientation "upright" :font-features "vpal" :text "うA"}])) file (cths/add-sample-shape (cthf/sample-file :file1 :page-label :page1) :vertical-text shape) page (cthf/current-page file) objects (:objects page)] {:objects objects :shapes [(get objects (cthi/id :vertical-text))]})) (defn- setup-vertical-ruby-text [] (let [shape (-> (cts/setup-shape {:type :text :x 10 :y 20 :width 60 :height 100}) (assoc :name "Vertical ruby text" :position-data [{:x 20 :y 100 :width 24 :height 80 :fills [{:fill-color "#112233" :fill-opacity 1}] :font-family "Noto Sans CJK JP" :font-size "20" :font-weight "400" :writing-mode "vertical-rl" :text-orientation "upright" :text "漢字" :ruby "かんじ"}])) file (cths/add-sample-shape (cthf/sample-file :file1 :page-label :page1) :vertical-ruby-text shape) page (cthf/current-page file) objects (:objects page)] {:objects objects :shapes [(get objects (cthi/id :vertical-ruby-text))]})) (defn- setup-vertical-emphasis-text [] (let [shape (-> (cts/setup-shape {:type :text :x 10 :y 20 :width 60 :height 100}) (assoc :name "Vertical emphasis text" :position-data [{:x 20 :y 100 :width 24 :height 80 :fills [{:fill-color "#112233" :fill-opacity 1}] :font-family "Noto Sans CJK JP" :font-size "20" :font-weight "400" :writing-mode "vertical-rl" :text-orientation "upright" :text "強調 あ" :text-emphasis "filled-dot"}])) file (cths/add-sample-shape (cthf/sample-file :file1 :page-label :page1) :vertical-emphasis-text shape) page (cthf/current-page file) objects (:objects page)] {:objects objects :shapes [(get objects (cthi/id :vertical-emphasis-text))]})) (defn- setup-horizontal-emphasis-text [] (let [shape (-> (cts/setup-shape {:type :text :x 10 :y 20 :width 120 :height 40}) (assoc :name "Horizontal emphasis text" :position-data [{:x 20 :y 60 :width 100 :height 20 :fills [{:fill-color "#112233" :fill-opacity 1}] :font-family "Noto Sans CJK JP" :font-size "20" :font-weight "400" :writing-mode "horizontal-tb" :text "強調、 あ" :text-emphasis "filled-dot"}])) file (cths/add-sample-shape (cthf/sample-file :file1 :page-label :page1) :horizontal-emphasis-text shape) page (cthf/current-page file) objects (:objects page)] {:objects objects :shapes [(get objects (cthi/id :horizontal-emphasis-text))]})) (defn- setup-horizontal-stacked-annotations [] (let [shape (-> (cts/setup-shape {:type :text :x 10 :y 20 :width 120 :height 40}) (assoc :name "Horizontal stacked annotations" :position-data [{:x 20 :y 60 :width 100 :height 20 :fills [{:fill-color "#112233" :fill-opacity 1}] :font-family "Noto Sans CJK JP" :font-size "20" :font-weight "400" :writing-mode "horizontal-tb" :text "漢字" :ruby "かんじ" :text-emphasis "filled-dot" :annotation-clearance "auto" :annotation-has-ruby true}])) file (cths/add-sample-shape (cthf/sample-file :file1 :page-label :page1) :horizontal-stacked-annotations shape) page (cthf/current-page file) objects (:objects page)] {:objects objects :shapes [(get objects (cthi/id :horizontal-stacked-annotations))]})) (defn- setup-vertical-warichu-text ([] (setup-vertical-warichu-text "割注入り")) ([text] (let [shape (-> (cts/setup-shape {:type :text :x 10 :y 20 :width 60 :height 100}) (assoc :name "Vertical warichu text" :position-data [{:x 20 :y 100 :width 24 :height 40 :fills [{:fill-color "#112233" :fill-opacity 1}] :font-family "Noto Sans CJK JP" :font-size "20" :font-weight "400" :writing-mode "vertical-rl" :text-orientation "upright" :text text :warichu "warichu"}])) file (cths/add-sample-shape (cthf/sample-file :file1 :page-label :page1) :vertical-warichu-text shape) page (cthf/current-page file) objects (:objects page)] {:objects objects :shapes [(get objects (cthi/id :vertical-warichu-text))]}))) (defn- setup-horizontal-warichu-text ([] (setup-horizontal-warichu-text "割注入り")) ([text] (let [shape (-> (cts/setup-shape {:type :text :x 10 :y 20 :width 120 :height 40}) (assoc :name "Horizontal warichu text" :position-data [{:x 20 :y 60 :width 40 :height 20 :fills [{:fill-color "#112233" :fill-opacity 1}] :font-family "Noto Sans CJK JP" :font-size "20" :font-weight "400" :writing-mode "horizontal-tb" :text text :warichu "warichu"}])) file (cths/add-sample-shape (cthf/sample-file :file1 :page-label :page1) :horizontal-warichu-text shape) page (cthf/current-page file) objects (:objects page)] {:objects objects :shapes [(get objects (cthi/id :horizontal-warichu-text))]}))) (deftest empty-selection-yields-empty-string (is (= "" (svg/generate-markup {} [])))) (deftest single-shape-produces-one-svg-root (testing "Regression guard: the single-shape path stays unchanged" (let [{:keys [objects shapes]} (setup-shapes [:rect-1]) markup (svg/generate-markup objects shapes)] (is (string? markup)) (is (pos? (count markup))) (is (= 1 (count-matches #" root")))) (deftest multi-shape-produces-single-svg-root (testing "Fix for #838: multiple shapes share one outer " (let [{:keys [objects shapes]} (setup-shapes [:rect-1 :rect-2 :rect-3]) markup (svg/generate-markup objects shapes)] (is (string? markup)) (is (pos? (count markup))) (is (= 1 (count-matches #" roots") (is (= 1 (count-matches #"" markup)) "multi-select must NOT emit multiple closing tags")))) (deftest vertical-text-svg-preserves-browser-layout-properties (testing "Static SVG carries the vertical writing properties used by browser exports" (let [{:keys [objects shapes]} (setup-vertical-text) markup (svg/generate-markup objects shapes)] (is (re-find #"writing-mode:vertical-rl" markup)) (is (re-find #"text-orientation:upright" markup)) (is (re-find #"text-autospace:normal" markup)) (is (re-find #"font-feature-settings:"vpal"" markup)) (is (not (re-find #"