mirror of
https://github.com/penpot/penpot.git
synced 2026-11-05 01:20:38 +00:00
Add distributed alignment to text styles, sidebar controls, CSS export, and the Plugin API. Spread characters across horizontal and vertical lines, including the final line, while preserving graphemes and annotations. Keep fractional line heights consistent across painting, hit testing, selection, and text paths to prevent baseline drift between lines. Add tests for distribution, nonbreaking text, and fractional line geometry. AI-assisted-by: gpt-6
519 lines
27 KiB
Clojure
519 lines
27 KiB
Clojure
;; 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 frontend-tests.ui.text-options-test
|
|
(:require
|
|
["react-dom/server" :as rds]
|
|
[app.common.types.text.cjk-layout :as cjk]
|
|
[app.main.data.workspace.texts :as dwt]
|
|
[app.main.ui.ds.controls.select :as select]
|
|
[app.main.ui.hooks :as hooks]
|
|
[app.main.ui.shapes.text.html-text :as html-text]
|
|
[app.main.ui.shapes.text.styles :as text-styles]
|
|
[app.main.ui.workspace.sidebar.options.common :refer [radio-selected]]
|
|
[app.main.ui.workspace.sidebar.options.menus.text :as text-menu]
|
|
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
|
|
[app.main.ui.workspace.sidebar.options.menus.text-language-layout :as tll]
|
|
[app.util.text.layout :as tl]
|
|
[app.util.text.writing-mode :as wm]
|
|
[cljs.test :as t :include-macros true]
|
|
[cuerdas.core :as str]
|
|
[rumext.v2 :as mf]))
|
|
|
|
(def ^:private japanese-span-attrs
|
|
[:text-combine-upright
|
|
:text-emphasis
|
|
:ruby
|
|
:ruby-hidden
|
|
:ruby-size
|
|
:ruby-align
|
|
:ruby-overhang
|
|
:ruby-side
|
|
:warichu
|
|
:font-features
|
|
:annotation-clearance])
|
|
|
|
|
|
(t/deftest layout-controls-follow-file-or-profile-configuration
|
|
(with-redefs [wm/vertical-layout-active? (constantly true)]
|
|
(t/is (false? (tl/controls-enabled? {})))
|
|
(t/is (false? (tl/controls-enabled? {:feature-enabled true :file? false :all-files? false})))
|
|
(t/is (true? (tl/controls-enabled? {:feature-enabled true :file? true :all-files? false})))
|
|
(t/is (true? (tl/controls-enabled? {:feature-enabled true :file? false :all-files? true}))))
|
|
(with-redefs [wm/vertical-layout-active? (constantly false)]
|
|
(t/is (false? (tl/controls-enabled? {:feature-enabled true :file? true :all-files? true})))))
|
|
|
|
(t/deftest language-controls-and-menu-actions-respect-independent-preferences
|
|
(with-redefs [wm/vertical-layout-active? (constantly true)]
|
|
(doseq [file? [true false] all-files? [true false]]
|
|
(let [config {:feature-enabled true :file? file? :all-files? all-files?}]
|
|
(t/is (= (or file? all-files?) (tl/controls-enabled? config)))
|
|
(t/is (= (not all-files?) (tl/file-action-visible? config true false)))
|
|
(t/is (false? (tl/file-action-visible? config false false)))
|
|
(t/is (false? (tl/file-action-visible? config true true)))
|
|
(t/is (false? (tl/controls-enabled? (assoc config :feature-enabled false)))))))
|
|
(with-redefs [wm/vertical-layout-active? (constantly false)]
|
|
(t/is (false? (tl/controls-enabled? {:feature-enabled true :file? true})))
|
|
(t/is (false? (tl/file-action-visible? {:feature-enabled true :file? true} true false)))))
|
|
|
|
(t/deftest choosing-a-profile-only-changes-the-paragraph-language
|
|
(t/is (= {:text-language "ko"} (tll/profile-change "ko")))
|
|
(t/is (= {:text-language nil} (tll/profile-change "")))
|
|
(t/is (tl/profile-matches? :japanese "ja-JP"))
|
|
(t/is (tl/profile-matches? :chinese "zh-Hant-TW"))
|
|
(t/is (tl/profile-matches? :korean "KO-kr"))
|
|
(t/is (not (tl/profile-matches? :chinese "ja")))
|
|
(t/is (not (tl/profile-matches? :korean :multiple)))
|
|
(t/is (not (tl/profile-matches? :japanese nil))))
|
|
|
|
(t/deftest profile-choices-keep-mixed-and-other-language-values-visible
|
|
(let [mixed (last (tll/profile-options :korean :multiple identity))
|
|
other (last (tll/profile-options :chinese "ja" identity))]
|
|
(t/is (= {:id "multiple" :label "labels.mixed-values" :disabled true :dimmed true} mixed))
|
|
(t/is (= {:id "ja" :label "ja" :disabled true :dimmed true} other))
|
|
(t/is (= ["" "zh-Hans" "zh-Hant" "zh-Hans-CN" "zh-Hant-CN" "zh-Hant-TW" "zh-Hant-HK"]
|
|
(mapv :id (tll/profile-options :chinese nil identity))))))
|
|
|
|
(t/deftest korean-word-wrapping-is-distinct-from-the-saved-experiment
|
|
(t/is (= ["syllable" "keep-words"]
|
|
(mapv :id (tll/korean-wrap-options nil identity))))
|
|
(t/is (= ["syllable" "keep-words" "spaces-only"]
|
|
(mapv :id (tll/korean-wrap-options "spaces-only" identity))))
|
|
(t/is (= "workspace.options.text-options.profile-chinese-taiwan"
|
|
(:label (select/get-option (tll/profile-options :chinese "zh-Hant-TW" identity) "zh-Hant-TW"))))
|
|
(t/is (= {:text-language "zh-Hant-CN"} (tll/profile-change "zh-Hant-CN"))))
|
|
|
|
(t/deftest language-specific-controls-require-an-explicit-matching-profile
|
|
(with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))]
|
|
(doseq [[language attr label] [[:japanese "ja" "workspace.options.text-options.line-adjustment"]
|
|
[:korean "ko" "workspace.options.text-options.korean-wrap"]]
|
|
profile [nil :multiple "zh-Hans" attr]]
|
|
(let [changes (atom [])
|
|
markup (rds/renderToStaticMarkup
|
|
(mf/element tll/language-layout-options*
|
|
#js {:language language
|
|
:values {:text-language profile}
|
|
:on-change #(swap! changes conj %)}))]
|
|
(t/is (= (= profile attr) (str/includes? markup label)))
|
|
(t/is (str/includes? markup "horizontal-tb-writing-mode"))
|
|
(t/is (empty? @changes) "Showing controls must not assign a profile")))))
|
|
|
|
|
|
(t/deftest chinese-decoration-controls-require-a-matching-profile
|
|
(with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))]
|
|
(doseq [profile [nil :multiple "ja" "ko" "zh-Hans-CN" "zh-Hant-TW"]]
|
|
(let [changes (atom [])
|
|
markup (rds/renderToStaticMarkup
|
|
(mf/element tll/language-layout-options*
|
|
(mf/props {:language :chinese
|
|
:values {:text-language profile}
|
|
:on-change #(swap! changes conj %)})))]
|
|
(doseq [label ["workspace.options.text-options.text-emphasis"
|
|
"workspace.options.text-options.emphasis-position"
|
|
"workspace.options.text-options.underline-position"]]
|
|
(t/is (= (tl/profile-matches? :chinese profile) (str/includes? markup label))))
|
|
(t/is (empty? @changes))))))
|
|
|
|
(t/deftest pinyin-reading-editor-requires-a-chinese-profile-and-text-selection
|
|
(with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))]
|
|
(doseq [profile [nil :multiple "ja" "ko" "zh-Hans" "zh-Hant-TW"]
|
|
selected [false true]]
|
|
(let [changes (atom [])
|
|
markup (rds/renderToStaticMarkup
|
|
(mf/element tll/language-layout-options*
|
|
(mf/props {:language :chinese
|
|
:values {:text-language profile :ruby "nǐ hǎo"}
|
|
:ruby-values {:ruby-hidden true}
|
|
:text-selection-active selected
|
|
:reading-selection-active selected
|
|
:on-change #(swap! changes conj %)
|
|
:on-ruby-presentation-change #(swap! changes conj %)})))
|
|
chinese (tl/profile-matches? :chinese profile)]
|
|
(t/is (= (and chinese selected)
|
|
(str/includes? markup "workspace.options.text-options.pinyin-placeholder")))
|
|
(t/is (= chinese
|
|
(str/includes? markup "workspace.options.text-options.pinyin-advanced-options")))
|
|
(t/is (not (str/includes? markup "workspace.options.text-options.ruby-placeholder")))
|
|
(t/is (empty? @changes) "Showing Pinyin controls must not edit text")))))
|
|
|
|
(t/deftest pinyin-reading-editor-rejects-a-selection-across-text-styles
|
|
(with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))]
|
|
(let [markup (rds/renderToStaticMarkup
|
|
(mf/element tll/language-layout-options*
|
|
(mf/props {:language :chinese
|
|
:values {:text-language "zh-Hans"}
|
|
:text-selection-active true
|
|
:reading-selection-active false
|
|
:on-change identity})))]
|
|
(t/is (str/includes? markup "workspace.options.text-options.pinyin-selection-hint"))
|
|
(t/is (not (str/includes? markup "workspace.options.text-options.pinyin-placeholder"))))))
|
|
|
|
(t/deftest pinyin-presentation-offers-guidance-without-a-text-selection
|
|
(with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))]
|
|
(let [markup (rds/renderToStaticMarkup
|
|
(mf/element tll/language-layout-options*
|
|
(mf/props {:language :chinese
|
|
:values {:text-language "zh-Hant-HK"}
|
|
:ruby-values {}
|
|
:text-selection-active false
|
|
:on-change identity
|
|
:on-ruby-presentation-change identity})))]
|
|
(t/is (str/includes? markup "workspace.options.text-options.pinyin-selection-hint"))
|
|
(t/is (str/includes? markup "ruby-presentation-options-toggle"))
|
|
(t/is (not (str/includes? markup "ruby-advanced-options-toggle"))))))
|
|
|
|
(t/deftest shared-writing-mode-controls-have-independent-radio-groups
|
|
(with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))]
|
|
(let [markup (rds/renderToStaticMarkup
|
|
(mf/html
|
|
[:div
|
|
[:> tll/language-layout-options* {:language :japanese :values {} :on-change identity}]
|
|
[:> tll/language-layout-options* {:language :chinese :values {} :on-change identity}]
|
|
[:> tll/language-layout-options* {:language :korean :values {} :on-change identity}]]))
|
|
inputs (re-seq #"<input[^>]+>" markup)
|
|
ids (map #(second (re-find #"id=\"([^\"]+)\"" %)) inputs)
|
|
names (map #(second (re-find #"name=\"([^\"]+)\"" %)) inputs)]
|
|
(t/is (= 6 (count (set ids))))
|
|
(t/is (= 3 (count (set names))))
|
|
(t/is (= 3 (count (filter #(str/includes? % "checked=\"\"") inputs)))))))
|
|
|
|
(t/deftest text-emphasis-select-reports-and-restores-canonical-values
|
|
(let [options (tjl/text-emphasis-options identity)
|
|
reported-id (:id (select/get-option options "filled-dot"))]
|
|
(t/is (= "filled-dot" reported-id))
|
|
(t/is (= "filled-dot"
|
|
(:id (select/get-option options reported-id))))))
|
|
|
|
(t/deftest annotation-clearance-select-reports-canonical-values
|
|
(let [options (tjl/annotation-clearance-options identity)]
|
|
(t/is (= ["none" "auto"] (mapv :id options)))
|
|
(t/is (= "auto" (:id (select/get-option options "auto"))))))
|
|
|
|
(t/deftest unrestricted-tcy-is-only-offered-for-a-text-selection
|
|
(t/is (= ["none" "digits"]
|
|
(mapv :value (tjl/text-combine-upright-options false identity))))
|
|
(t/is (= ["none" "all" "digits"]
|
|
(mapv :value (tjl/text-combine-upright-options true identity)))))
|
|
|
|
(t/deftest ruby-container-styles-preserve-customization
|
|
(let [style (text-styles/generate-ruby-container-styles
|
|
{:ruby-align "space-between"
|
|
:ruby-overhang "none"
|
|
:ruby-side "under"})]
|
|
(t/is (= "space-between" (.-rubyAlign style)))
|
|
(t/is (= "none" (.-rubyOverhang style)))
|
|
(t/is (= "under" (.-rubyPosition style)))))
|
|
|
|
(t/deftest advanced-furigana-options-are-collapsed-by-default
|
|
(let [markup (rds/renderToStaticMarkup
|
|
(mf/element tjl/ruby-presentation-options*
|
|
#js {:values {}
|
|
:on-change identity
|
|
:on-blur identity}))]
|
|
(t/is (str/includes? markup "data-testid=\"ruby-presentation-options-toggle\""))
|
|
(t/is (str/includes? markup "aria-expanded=\"false\""))
|
|
(t/is (str/includes? markup "workspace.options.text-options.ruby-advanced-options"))))
|
|
|
|
(t/deftest advanced-furigana-options-include-hide-toggle
|
|
(let [markup (rds/renderToStaticMarkup
|
|
(mf/element tjl/ruby-hidden-option*
|
|
#js {:values {:ruby-hidden true}
|
|
:on-change identity
|
|
:on-blur identity}))]
|
|
(t/is (str/includes? markup "workspace.options.text-options.ruby-hidden"))
|
|
(t/is (str/includes? markup "role=\"switch\""))
|
|
(t/is (str/includes? markup "aria-checked=\"true\""))))
|
|
|
|
(t/deftest hidden-furigana-renders-base-text-without-ruby-layout
|
|
(let [markup (rds/renderToStaticMarkup
|
|
(mf/element html-text/render-text*
|
|
#js {:node {:text "日"
|
|
:ruby "にち"
|
|
:ruby-hidden true}
|
|
:parent {}
|
|
:shape {}}))]
|
|
(t/is (str/includes? markup ">日</span>"))
|
|
(t/is (not (str/includes? markup "<ruby")))))
|
|
|
|
(t/deftest shape-level-furigana-presentation-only-updates-ruby-spans
|
|
(let [shape {:content
|
|
{:type "root"
|
|
:children
|
|
[{:type "paragraph-set"
|
|
:children
|
|
[{:type "paragraph"
|
|
:children
|
|
[{:text "日"
|
|
:ruby "にち"
|
|
:ruby-hidden false
|
|
:ruby-size "half"}
|
|
{:text "本"
|
|
:ruby ""
|
|
:ruby-hidden false
|
|
:ruby-size "quarter"}]}]}]}}
|
|
values (dwt/current-ruby-values {:shape shape
|
|
:attrs cjk/ruby-presentation-attrs})
|
|
updated (dwt/update-ruby-presentation-attrs
|
|
shape {:ruby-hidden true :ruby-size "third"})
|
|
spans (get-in updated [:content :children 0 :children 0 :children])]
|
|
(t/is (= "half" (:ruby-size values)))
|
|
(t/is (false? (:ruby-hidden values)))
|
|
(t/is (= "third" (:ruby-size (first spans))))
|
|
(t/is (true? (:ruby-hidden (first spans))))
|
|
(t/is (= "quarter" (:ruby-size (second spans))))
|
|
(t/is (false? (:ruby-hidden (second spans))))))
|
|
|
|
(t/deftest mixed-span-values-select-a-disabled-mixed-option
|
|
(let [options (tjl/with-mixed-span-option (tjl/text-emphasis-options identity) :multiple)]
|
|
(t/is (= "mixed" (tjl/span-select-value :multiple "none")))
|
|
(t/is (= "none" (tjl/span-select-value nil "none")))
|
|
(t/is (= "mixed" (:id (last options))))
|
|
(t/is (true? (:disabled (last options))))
|
|
(t/is (= options (tjl/with-mixed-span-option options "none"))
|
|
"an unmixed value adds no entry")))
|
|
|
|
(t/deftest mixed-radio-values-select-nothing
|
|
(t/is (= "" (radio-selected :multiple "none")))
|
|
(t/is (= "none" (radio-selected nil "none")))
|
|
(t/is (= "vertical-rl" (radio-selected :vertical-rl))))
|
|
|
|
(t/deftest whole-text-selection-reports-every-differing-span-value-as-mixed
|
|
(let [shape {:content
|
|
{:type "root"
|
|
:children
|
|
[{:type "paragraph-set"
|
|
:children
|
|
[{:type "paragraph"
|
|
:children
|
|
[{:text "日"
|
|
:text-combine-upright "digits2"
|
|
:text-emphasis "filled-dot"
|
|
:ruby "にち"
|
|
:ruby-hidden true
|
|
:ruby-size "third"
|
|
:ruby-align "center"
|
|
:ruby-overhang "none"
|
|
:ruby-side "under"
|
|
:warichu "warichu"
|
|
:font-features "palt"
|
|
:annotation-clearance "auto"}
|
|
{:text "本"
|
|
:text-combine-upright "none"
|
|
:text-emphasis "none"
|
|
:ruby ""
|
|
:ruby-hidden false
|
|
:ruby-size "half"
|
|
:ruby-align "space-around"
|
|
:ruby-overhang "auto"
|
|
:ruby-side "over"
|
|
:warichu "none"
|
|
:font-features "none"
|
|
:annotation-clearance "none"}]}]}]}}
|
|
values (dwt/current-text-values {:shape shape
|
|
:attrs japanese-span-attrs})]
|
|
(t/is (= (zipmap japanese-span-attrs (repeat :multiple)) values))))
|
|
|
|
(t/deftest wasm-editor-span-styles-preserve-persisted-vertical-paragraph-values
|
|
(let [shape {:content
|
|
{:type "root"
|
|
:children
|
|
[{:type "paragraph-set"
|
|
:children
|
|
[{:type "paragraph"
|
|
:writing-mode "vertical-rl"
|
|
:text-orientation "upright"
|
|
:children [{:text "12" :text-combine-upright "none"}]}]}]}}
|
|
editor-styles {:text-combine-upright "digits2"}
|
|
paragraph-values (dwt/current-paragraph-values
|
|
{:editor-styles editor-styles
|
|
:shape shape
|
|
:attrs [:writing-mode :text-orientation]})
|
|
text-values (dwt/current-text-values
|
|
{:editor-styles editor-styles
|
|
:shape shape
|
|
:attrs [:text-combine-upright]})]
|
|
(t/is (= "vertical-rl" (:writing-mode paragraph-values)))
|
|
(t/is (= "upright" (:text-orientation paragraph-values)))
|
|
(t/is (= "digits2" (:text-combine-upright text-values)))))
|
|
|
|
(t/deftest japanese-controls-distinguish-horizontal-and-vertical-modes
|
|
(t/is (false? (tjl/vertical-japanese-layout? {})))
|
|
(t/is (false? (tjl/vertical-japanese-layout?
|
|
{:writing-mode "horizontal-tb"})))
|
|
(t/is (true? (tjl/vertical-japanese-layout?
|
|
{:writing-mode "vertical-rl"})))
|
|
(t/is (= "palt"
|
|
(tjl/proportional-metrics-feature "horizontal-tb")))
|
|
(t/is (= "palt"
|
|
(tjl/proportional-metrics-feature nil)))
|
|
(t/is (= "vpal"
|
|
(tjl/proportional-metrics-feature "vertical-rl"))))
|
|
|
|
(t/deftest sidebar-options-offer-the-japanese-enum-values
|
|
(let [ids #(mapv :id %)
|
|
values #(mapv :value %)]
|
|
(t/is (= (:writing-mode cjk/enum-values) (values (tjl/writing-mode-options identity))))
|
|
(t/is (= (:text-orientation cjk/enum-values) (values (tjl/text-orientation-options identity))))
|
|
(t/is (= (:warichu cjk/enum-values) (values (tjl/warichu-options identity))))
|
|
(t/is (= (:text-emphasis cjk/enum-values) (ids (tjl/text-emphasis-options identity))))
|
|
(t/is (= (:annotation-clearance cjk/enum-values) (ids (tjl/annotation-clearance-options identity))))
|
|
(t/is (= (:ruby-size cjk/enum-values) (ids (tjl/ruby-size-options identity))))
|
|
(t/is (= (:ruby-align cjk/enum-values) (ids (tjl/ruby-align-options identity))))
|
|
(t/is (= (:ruby-overhang cjk/enum-values) (ids (tjl/ruby-overhang-options identity))))
|
|
(t/is (= (:ruby-side cjk/enum-values) (ids (tjl/ruby-side-options identity))))
|
|
(t/is (= (:line-adjustment cjk/enum-values) (ids (tjl/line-adjustment-options identity))))
|
|
(t/is (every? #(cjk/valid-enum-value? :text-combine-upright %)
|
|
(values (tjl/text-combine-upright-options true identity))))
|
|
(t/is (every? cjk/digit-combine? (ids (tjl/text-combine-upright-count-options identity))))))
|
|
|
|
(t/deftest html-text-drops-vertical-writing-under-a-horizontal-renderer
|
|
(let [shape {:id (random-uuid) :width 100 :height 100
|
|
:content {:type "root"
|
|
:children
|
|
[{:type "paragraph-set"
|
|
:children
|
|
[{:type "paragraph"
|
|
:writing-mode "vertical-rl"
|
|
:text-orientation "upright"
|
|
:children [{:text "縦"}]}]}]}}
|
|
markup #(rds/renderToStaticMarkup (mf/element html-text/text-shape* #js {:shape shape}))]
|
|
(with-redefs [wm/vertical-layout-active? (constantly false)]
|
|
(t/is (not (str/includes? (markup) "vertical-rl"))))
|
|
(with-redefs [wm/vertical-layout-active? (constantly true)]
|
|
(t/is (str/includes? (markup) "writing-mode:vertical-rl")))))
|
|
|
|
(defn- alignment-markup
|
|
([component writing-mode wasm-enabled]
|
|
(alignment-markup component writing-mode wasm-enabled {}))
|
|
([component writing-mode wasm-enabled values]
|
|
;; Server rendering has no DOM container for tooltip portals.
|
|
(with-redefs [wm/vertical-layout-active? (constantly wasm-enabled)
|
|
hooks/use-portal-container (fn ([] nil) ([_] nil))]
|
|
(rds/renderToStaticMarkup
|
|
(mf/element component #js {:values (assoc values :writing-mode writing-mode)
|
|
:on-change identity})))))
|
|
|
|
(defn- alignment-labels
|
|
[markup]
|
|
(mapv second (re-seq #"aria-label=\"([^\"]+)\"" markup)))
|
|
|
|
(defn- alignment-icons
|
|
[markup]
|
|
(mapv second (re-seq #"href=\"#icon-([^\"]+)\"" markup)))
|
|
|
|
(t/deftest vertical-paragraph-alignment-shows-top-middle-bottom-and-vertical-justify
|
|
(let [markup (alignment-markup text-menu/text-align-options* "vertical-rl" true)]
|
|
(t/is (= ["text-align-top" "text-align-middle" "text-align-bottom" "text-justify-vertical" "distribute-vertical-spacing"]
|
|
(alignment-icons markup)))
|
|
(t/is (= ["workspace.options.text-options.align-top"
|
|
"workspace.options.text-options.align-middle"
|
|
"workspace.options.text-options.align-bottom"
|
|
"workspace.options.text-options.text-align-justify"
|
|
"workspace.options.text-options.text-align-distribute"]
|
|
(alignment-labels markup)))
|
|
(t/is (= ["left" "center" "right" "justify" "distribute"]
|
|
(mapv second (re-seq #"value=\"([^\"]+)\"" markup))))))
|
|
|
|
(t/deftest vertical-column-block-alignment-shows-right-center-and-left
|
|
(let [markup (alignment-markup text-menu/vertical-align* "vertical-rl" true)]
|
|
(t/is (= ["text-right" "text-horizontal-center" "text-left"]
|
|
(alignment-icons markup)))
|
|
(t/is (= ["workspace.options.text-options.text-align-right"
|
|
"workspace.options.text-options.text-align-center"
|
|
"workspace.options.text-options.text-align-left"]
|
|
(alignment-labels markup)))
|
|
(t/is (= ["top" "center" "bottom"]
|
|
(mapv second (re-seq #"value=\"([^\"]+)\"" markup))))))
|
|
|
|
(t/deftest horizontal-and-mixed-writing-modes-use-horizontal-alignment-controls
|
|
(doseq [writing-mode [nil "horizontal-tb" :multiple]]
|
|
(let [paragraph (alignment-markup text-menu/text-align-options* writing-mode true)
|
|
block (alignment-markup text-menu/vertical-align* writing-mode true)]
|
|
(t/is (= ["text-align-left" "text-align-center" "text-align-right" "text-justify" "distribute-horizontally"]
|
|
(alignment-icons paragraph)))
|
|
(t/is (= ["text-top" "text-middle" "text-bottom"] (alignment-icons block))))))
|
|
|
|
(t/deftest svg-renderer-keeps-horizontal-alignment-for-stored-vertical-text
|
|
(let [paragraph (alignment-markup text-menu/text-align-options* "vertical-rl" false)
|
|
block (alignment-markup text-menu/vertical-align* "vertical-rl" false)]
|
|
(t/is (= ["text-align-left" "text-align-center" "text-align-right" "text-justify" "distribute-horizontally"]
|
|
(alignment-icons paragraph)))
|
|
(t/is (= ["text-top" "text-middle" "text-bottom"] (alignment-icons block)))
|
|
(t/is (= "workspace.options.text-options.text-align-left" (first (alignment-labels paragraph))))
|
|
(t/is (= "workspace.options.text-options.align-top" (first (alignment-labels block))))))
|
|
|
|
|
|
(t/deftest vertical-text-growth-controls-show-the-physical-growth-direction
|
|
(let [markup (alignment-markup text-menu/grow-options* "vertical-rl" true)]
|
|
(t/is (= ["text-fixed" "text-auto-width-vertical" "text-auto-height-vertical"]
|
|
(alignment-icons markup)))
|
|
(t/is (= ["workspace.options.text-options.grow-fixed"
|
|
"workspace.options.text-options.grow-auto-height"
|
|
"workspace.options.text-options.grow-auto-width"]
|
|
(alignment-labels markup)))
|
|
(t/is (= ["fixed" "auto-width" "auto-height"]
|
|
(mapv second (re-seq #"value=\"([^\"]+)\"" markup))))))
|
|
|
|
(t/deftest horizontal-and-svg-text-growth-controls-keep-horizontal-directions
|
|
(doseq [[writing-mode wasm-enabled] [[nil true]
|
|
["horizontal-tb" true]
|
|
[:multiple true]
|
|
["vertical-rl" false]]]
|
|
(let [markup (alignment-markup text-menu/grow-options* writing-mode wasm-enabled)]
|
|
(t/is (= ["text-fixed" "text-auto-width" "text-auto-height"]
|
|
(alignment-icons markup)))
|
|
(t/is (= ["workspace.options.text-options.grow-fixed"
|
|
"workspace.options.text-options.grow-auto-width"
|
|
"workspace.options.text-options.grow-auto-height"]
|
|
(alignment-labels markup))))))
|
|
|
|
|
|
(t/deftest japanese-layout-controls-include-line-adjustment
|
|
(let [markup (alignment-markup tjl/japanese-layout-options* "vertical-rl" true)]
|
|
(t/is (str/includes? markup "workspace.options.text-options.line-adjustment"))))
|
|
|
|
|
|
(t/deftest vertical-text-hides-ltr-and-rtl-controls
|
|
(t/is (empty? (alignment-markup text-menu/text-direction-options* "vertical-rl" true))))
|
|
|
|
(t/deftest horizontal-and-svg-text-show-ltr-and-rtl-controls
|
|
(doseq [[writing-mode wasm-enabled] [[nil true]
|
|
["horizontal-tb" true]
|
|
[:multiple true]
|
|
["vertical-rl" false]]]
|
|
(let [markup (alignment-markup text-menu/text-direction-options* writing-mode wasm-enabled)]
|
|
(t/is (= ["text-ltr" "text-rtl"] (alignment-icons markup))))))
|
|
|
|
(t/deftest hiding-direction-controls-preserves-the-stored-direction
|
|
(let [values {:text-direction "rtl"}
|
|
render #(alignment-markup text-menu/text-direction-options* % true values)]
|
|
(t/is (str/includes? (re-find #"<input[^>]*id=\"rtl-text-direction\"[^>]*>"
|
|
(render "horizontal-tb"))
|
|
"checked=\"\""))
|
|
(t/is (empty? (render "vertical-rl")))
|
|
(t/is (str/includes? (re-find #"<input[^>]*id=\"rtl-text-direction\"[^>]*>"
|
|
(render "horizontal-tb"))
|
|
"checked=\"\""))))
|
|
|
|
|
|
(t/deftest decoration-position-changes-refresh-the-text-options
|
|
(doseq [attr [:text-emphasis-position :text-underline-position]]
|
|
(let [before (mf/props {:values {attr "auto"}})
|
|
after (mf/props {:values {attr "under-left"}})]
|
|
(t/is (false? (@#'text-menu/check-props before after))))))
|
|
|
|
(t/deftest zhuyin-handoff-sets-symbols-upright-in-a-right-side-strip
|
|
(let [html (rds/renderToStaticMarkup
|
|
(mf/element html-text/render-text*
|
|
{:node {:text "中" :ruby "ㄋㄧˇ" :ruby-zhuyin true :font-size "40"}
|
|
:shape {} :parent {}}))]
|
|
(t/is (str/includes? html "width:1.6em"))
|
|
(t/is (str/includes? html "writing-mode:horizontal-tb"))
|
|
(t/is (str/includes? html "ㄋ"))
|
|
(t/is (str/includes? html "ㄧ"))
|
|
(t/is (str/includes? html "ˇ"))
|
|
(t/is (not (str/includes? html "<rt")))))
|