Normalize size units and intrinsic sizing across Elisp and native layout. Add help, pointer, hover-style and keymap support with reusable interaction adapters. Keep content updates local, preserve scroll caches and hover borders, and avoid rebuilding retained plans and ownership metadata for stable geometry. Validation: make check and native-rust-tests passed; targeted native interaction and scroll publication regressions passed.
224 lines
11 KiB
EmacsLisp
224 lines
11 KiB
EmacsLisp
;;; ebox-size-style-tests.el --- CSS size style boundary tests -*- lexical-binding: t; -*-
|
|
|
|
;;; Code:
|
|
|
|
(require 'ert)
|
|
(require 'ebox-style)
|
|
|
|
(ert-deftest ebox-size-style-canonical-values ()
|
|
"Each size property accepts explicit lengths in its own rendering axis."
|
|
(dolist (case '(((:width :min-width :max-width)
|
|
(px 12) (% 50) (vw 33.5) (ch 80)
|
|
(calc (- (vw 100) (ch 1)))
|
|
(min (% 100) (ch 80))
|
|
(clamp (px 1) (% 50) (ch 80)))
|
|
((:height :min-height :max-height)
|
|
(lh 3) (vh 100) (% 50)
|
|
(calc (- (vh 100) (lh 1)))
|
|
(min (% 100) (lh 8))
|
|
(clamp (lh 1) (% 50) (vh 80)))))
|
|
(dolist (property (car case))
|
|
(dolist (value (cdr case))
|
|
(should (ebox-style-compile-form 'box (list property value)))))))
|
|
|
|
(ert-deftest ebox-size-style-rejects-legacy-values ()
|
|
"Legacy implicit lengths fail at every author dimension boundary."
|
|
(dolist (value '(12 0 (12) (0) viewport (viewport) viewport-height
|
|
(viewport-height) contain (fit-content (ch 80))))
|
|
(dolist (property '(:width :height :min-width :min-height
|
|
:max-width :max-height))
|
|
(should-error (ebox-style-compile-form 'box (list property value))))))
|
|
|
|
(ert-deftest ebox-size-style-keyword-applicability ()
|
|
"Maximum sizes accept none and reject auto on either axis."
|
|
(dolist (property '(:max-width :max-height))
|
|
(should-error (ebox-style-compile-form 'box (list property 'auto)))
|
|
(dolist (keyword '(none min-content max-content fit-content stretch))
|
|
(should (ebox-style-compile-form 'box (list property keyword)))))
|
|
(dolist (property '(:width :height :min-width :min-height))
|
|
(should-error (ebox-style-compile-form 'box (list property 'none)))))
|
|
|
|
(ert-deftest ebox-size-style-shorthands-preserve-atomic-expressions ()
|
|
"Shorthands distinguish one size expression from several edge values."
|
|
(let ((declarations (ebox-style-compile-form
|
|
'box '(:padding ((lh 1) (ch 2))
|
|
:margin ((lh 1) (ch 2))
|
|
:border ((px 1) solid "red")))))
|
|
(should (equal (plist-get declarations 'ebox/padding-block-start) '(lh 1)))
|
|
(should (equal (plist-get declarations 'ebox/padding-inline-end) '(ch 2)))
|
|
(should (equal (plist-get declarations 'ebox/margin-block-start) '(lh 1)))
|
|
(should (equal (plist-get declarations 'ebox/margin-inline-end) '(ch 2)))
|
|
(should (equal (plist-get declarations 'ebox/border-top-width) '(px 1)))))
|
|
|
|
(ert-deftest ebox-size-style-trbl-shorthand-arity ()
|
|
"Four-side shorthands keep arity rules and each target edge's unit domain."
|
|
(dolist (property '(:padding :margin :border-width))
|
|
(let ((unit (if (eq property :border-width) 'px '%)))
|
|
(dolist (amounts '((1) (1 2) (1 2 3) (1 2 3 4)))
|
|
(should (ebox-style-compile-form
|
|
'box (list property (mapcar (lambda (n) (list unit n))
|
|
amounts)))))))
|
|
(let ((declarations
|
|
(ebox-style-compile-form
|
|
'box '(:padding ((lh 1) (px 2) (lh 3) (px 4))))))
|
|
(should
|
|
(equal declarations
|
|
'(ebox/padding-block-start (lh 1)
|
|
ebox/padding-inline-end (px 2)
|
|
ebox/padding-block-end (lh 3)
|
|
ebox/padding-inline-start (px 4)))))
|
|
(dolist (property '(:padding :margin :border-width))
|
|
(dolist (values '(nil ((px 1) (px 2) (px 3) (px 4) (px 5))))
|
|
(should-error (ebox-style-compile-form 'box (list property values))))))
|
|
|
|
(ert-deftest ebox-size-style-pair-shorthand-arity ()
|
|
"Axis and gap shorthands preserve atomic expressions and target axes."
|
|
(dolist (case '((:padding-inline px) (:padding-block lh)
|
|
(:margin-inline px) (:margin-block lh) (:gap %)))
|
|
(dolist (amounts '((1) (1 2)))
|
|
(should (ebox-style-compile-form
|
|
'flex (list (car case)
|
|
(mapcar (lambda (n) (list (cadr case) n))
|
|
amounts))))))
|
|
(should
|
|
(equal (ebox-style-compile-form 'flex '(:gap ((lh 1) (ch 2))))
|
|
'(ebox/row-gap (lh 1) ebox/column-gap (ch 2))))
|
|
(let ((expression '(min (% 1) (% 2) (% 3) (% 4) (% 5))))
|
|
(should
|
|
(equal (ebox-style-compile-form 'flex (list :gap expression))
|
|
(list 'ebox/row-gap expression 'ebox/column-gap expression))))
|
|
(dolist (property '(:padding-inline :padding-block
|
|
:margin-inline :margin-block :gap))
|
|
(dolist (values '(nil ((px 1) (px 2) (px 3))))
|
|
(should-error (ebox-style-compile-form 'flex (list property values))))))
|
|
|
|
(ert-deftest ebox-size-style-property-ranges ()
|
|
"Each property enforces its supported size and percentage ranges."
|
|
(dolist (property '(:padding :gap :border-width :border-top-width))
|
|
(should-error (ebox-style-compile-form 'flex (list property 1))))
|
|
(should-error (ebox-style-compile-form 'box '(:padding (px -1))))
|
|
(should-error (ebox-style-compile-form 'box '(:margin (px -1))))
|
|
(should-error (ebox-style-compile-form 'box '(:border-width (% 2))))
|
|
(should-error (ebox-style-compile-form
|
|
'box '(:border-width (calc (+ (px 1) (% 2)))))))
|
|
|
|
(ert-deftest ebox-size-style-projection-remains-unresolved ()
|
|
"Computed projection preserves contextual sizes until used layout."
|
|
(let* ((expression '(calc (- (vh 100) (lh 1))))
|
|
(projected (ebox-style--expand-engine-plist
|
|
(list :height expression :width '(ch 80)
|
|
:padding-top '(lh 1) :margin-left '(ch 2)
|
|
:border-top-width '(px 1)
|
|
:border-top-style 'solid))))
|
|
(should (equal (plist-get projected :height) expression))
|
|
(should (equal (plist-get projected :width) '(ch 80)))
|
|
(should (equal (plist-get projected :padding-top-height) '(lh 1)))
|
|
(should (equal (plist-get projected :margin-left-pixel) '(ch 2)))
|
|
(should (equal (plist-get projected :border-top-pixel) '(px 1)))))
|
|
|
|
(ert-deftest ebox-size-style-generated-defaults ()
|
|
"Schema and generated declarations use canonical dimensional defaults."
|
|
(should (eq (plist-get (ebox-style-property :min-width) :initial) 'auto))
|
|
(should (eq (plist-get (ebox-style-property :min-height) :initial) 'auto))
|
|
(should (equal (plist-get (ebox-style-compile-form 'box '(:flex 1))
|
|
'ebox/flex-basis) '(% 0)))
|
|
(should (equal (plist-get (ebox-style-compile-form 'box '(:border "red"))
|
|
'ebox/border-top-width) '(px 1)))
|
|
(let ((declarations (ebox-style-compile-form 'box '(:border none))))
|
|
(should (equal (plist-get declarations 'ebox/border-top-width) '(px 1)))
|
|
(should (eq (plist-get declarations 'ebox/border-top-style) 'none)))
|
|
(should (equal (ebox-style--convert 0 'pixel) 0)))
|
|
|
|
(ert-deftest ebox-size-style-axis-matrix-is-enforced-during-build ()
|
|
"Dimensions, physical/logical edges, and gaps reject wrong-axis units early."
|
|
(require 'ebox)
|
|
(dolist (case
|
|
'((box inline
|
|
:width :min-width :max-width
|
|
:padding-right :padding-left :padding-inline-start :padding-inline-end
|
|
:margin-right :margin-left :margin-inline-start :margin-inline-end)
|
|
(box block
|
|
:height :min-height :max-height
|
|
:padding-top :padding-bottom :padding-block-start :padding-block-end
|
|
:margin-top :margin-bottom :margin-block-start :margin-block-end)
|
|
(flex inline :column-gap)
|
|
(flex block :row-gap)
|
|
(row inline :item-gap)
|
|
(column block :item-gap)))
|
|
(dolist (property (cddr case))
|
|
(dolist (unit '(px ch vw lh vh %))
|
|
(let ((form (list (car case) property (list unit 2))))
|
|
(ert-info ((format "initial build %S" form))
|
|
(if (memq unit (cdr (assq (cadr case) ebox-size-axis-units)))
|
|
(should (ebox-build form))
|
|
(let ((message
|
|
(condition-case condition
|
|
(progn (ebox-build form) nil)
|
|
(error (error-message-string condition)))))
|
|
(should (string-match-p (regexp-quote (symbol-name property))
|
|
message))
|
|
(should (string-match-p "allowed units:" message))))))))))
|
|
|
|
(ert-deftest ebox-size-style-recursive-axis-errors-reject-shorthands-and-tracks ()
|
|
"Expressions cannot hide forbidden units in shorthand or Grid wrappers."
|
|
(require 'ebox)
|
|
(dolist (form
|
|
'((box :height (calc (* 0 (px 1))))
|
|
(box :max-width (clamp (px 1) (lh 1) (ch 80)))
|
|
(box :padding ((calc (+ (lh 1) (px 0))) (ch 1)))
|
|
(box :margin-inline (min (ch 1) (vh 1)))
|
|
(box :padding (lh 1))
|
|
(box :margin (px 1))
|
|
(flex :gap (px 1))
|
|
(grid :grid-template-rows ((minmax (lh 1) (px 2))))
|
|
(grid :grid-template-columns ((repeat 2 (minmax (lh 1) (fr 1)))))))
|
|
(should-error (ebox-build form)))
|
|
(dolist (form
|
|
'((grid :grid-template-rows ((repeat 2 (minmax (lh 1) (fr 1)))))
|
|
(grid :grid-template-columns ((minmax (ch 1) (vw 20))))
|
|
(grid :grid-auto-rows (max (lh 1) (vh 20)))
|
|
(grid :grid-auto-columns (calc (+ (ch 1) (% 20))))))
|
|
(should (ebox-build form))))
|
|
|
|
(ert-deftest ebox-size-style-stroke-units-are-independent-of-edge-orientation ()
|
|
"All border edges retain their non-percentage paint-thickness domain."
|
|
(dolist (property '(:border-top-width :border-right-width
|
|
:border-bottom-width :border-left-width))
|
|
(dolist (unit '(px ch lh vw vh))
|
|
(should (ebox-style-compile-form 'box (list property (list unit 1)))))
|
|
(should-error (ebox-style-compile-form 'box (list property '(% 1))))))
|
|
|
|
(ert-deftest ebox-size-style-malformed-shorthand-functions-keep-unit-diagnostics ()
|
|
"Malformed atomic expressions report the receiving property's unit domain."
|
|
(dolist (case '((:gap (calc (lh 1) (lh 2) (lh 3)) ":row-gap")
|
|
(:padding (clamp (lh 1)) ":padding-block-start")
|
|
(:border ((calc (px 1) (px 2)) solid "red")
|
|
":border-top-width")))
|
|
(let ((message
|
|
(condition-case condition
|
|
(progn (ebox-style-compile-form 'flex (list (car case) (cadr case)))
|
|
nil)
|
|
(error (error-message-string condition)))))
|
|
(should (string-match-p (nth 2 case) message))
|
|
(should (string-match-p "allowed units:" message)))))
|
|
|
|
(ert-deftest ebox-size-style-simple-configs-enforce-axis-before-render ()
|
|
"Both direct LayoutConfig constructors enforce the same item-gap rules."
|
|
(require 'ebox-layout-config)
|
|
(should (equal (plist-get (ebox-layout-config-props
|
|
(ebox-row-layout-create)) :item-gap) '(px 0)))
|
|
(should (equal (plist-get (ebox-layout-config-props
|
|
(ebox-column-layout-create)) :item-gap) '(lh 0)))
|
|
(should-error (ebox-row-layout-create :item-gap '(lh 1))
|
|
:type 'ebox-size-error)
|
|
(should-error (ebox-column-layout-create :item-gap '(px 0))
|
|
:type 'ebox-size-error)
|
|
(should-error
|
|
(ebox-layout-config-with-props
|
|
(ebox-column-layout-create)
|
|
'(:item-gap (calc (* 0 (px 1))) :cross-align stretch))
|
|
:type 'ebox-size-error))
|
|
|
|
(provide 'ebox-size-style-tests)
|
|
;;; ebox-size-style-tests.el ends here
|