ebox/tests/ebox-size-tests.el
Kinneyzhang 79f5bc23d1 feat: add CSS sizing and native text interaction capabilities
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.
2026-09-09 22:25:18 +08:00

129 lines
6.1 KiB
EmacsLisp

;;; ebox-size-tests.el --- Pure size expression contracts -*- lexical-binding: t; -*-
;;; Code:
(require 'ert)
(require 'ebox-size)
(ert-deftest ebox-size-explicit-units-resolve-against-their-reference ()
(let ((context '(:percent-base 300 :viewport-width 853 :viewport-height 701
:ch 8.5 :lh 19)))
(dolist (case '(((px 12.5) . 12.5) ((% 50) . 150.0)
((vw 33) . 281.49) ((vh 50) . 350.5)
((ch 80) . 680.0) ((lh 3) . 57)))
(should (ebox-size-value-p (car case)))
(should (< (abs (- (ebox-size-resolve (car case) context)
(cdr case)))
0.000001)))))
(ert-deftest ebox-size-fractions-survive-until-the-renderer ()
(should (= (ebox-size-resolve '(calc (- (vh 50) (lh 1)))
'(:viewport-height 931 :lh 19))
446.5))
(should (= (ebox-size-resolve '(calc (/ (px 3) 2)) nil) 1.5))
(should (= (ebox-size-resolve '(calc (* (/ 1 2) (lh 3))) '(:lh 19))
28.5)))
(ert-deftest ebox-size-math-preserves-dimensions-and-css-clamp-precedence ()
(let ((context '(:percent-base 300 :ch 8)))
(should (= (ebox-size-resolve '(min (% 100) (ch 80)) context) 300))
(should (= (ebox-size-resolve '(max (px 320) (% 50)) context) 320))
(should (= (ebox-size-resolve '(clamp (ch 20) (% 50) (ch 80)) context)
160))
;; CSS gives the minimum precedence when bounds are reversed.
(should (= (ebox-size-resolve '(clamp (px 80) (px 40) (px 20)) nil)
80))
(should (= (ebox-size-resolve '(calc (+ (px 10) (- (px 4)) (px -2)))
nil)
4))
(should (= (ebox-size-resolve '(calc (* (px 6) (min 2 3))) nil) 12))))
(ert-deftest ebox-size-missing-reference-defers-but-zero-resolves ()
(dolist (unit '(% vw vh ch lh))
(should-not (ebox-size-resolve (list unit 3) nil)))
(should-not (ebox-size-resolve '(min (px 20) (% 50)) nil))
(should-not (ebox-size-resolve '(calc (+ (vh 100) (px 1))) nil))
(should (= (ebox-size-resolve '(% 50) '(:percent-base 0)) 0))
(should (= (ebox-size-resolve '(calc (+ (vh 100) (px 1)))
'(:viewport-height 0))
1)))
(ert-deftest ebox-size-dependencies-are-recursive-distinct-and-stable ()
(let ((expression '(clamp (ch 20)
(calc (+ (% 30) (vw 4) (ch 2) (vh 1)))
(max (lh 8) (% 100)))))
(should (equal (ebox-size-dependencies expression)
'(ch percent viewport-width viewport-height lh)))
(should (equal (ebox-size-dependencies expression)
'(ch percent viewport-width viewport-height lh)))
(should-not (ebox-size-dependencies '(calc (+ (px 1) (px 2)))))))
(ert-deftest ebox-size-property-restrictions-apply-to-literals-and-dependencies ()
(should-not (ebox-size-value-p '(px -1)))
(should (ebox-size-value-p '(lh -1) t))
(should (ebox-size-value-p '(calc (- (px 1) (px 2)))))
(should (= (ebox-size-resolve '(calc (- (px 1) (px 2))) nil) -1))
(should (ebox-size-value-p '(% 50)))
(should-not (ebox-size-value-p '(% 50) t nil))
(should-not (ebox-size-value-p '(min (px 20) (calc (* 2 (% 10)))) t nil))
(should (ebox-size-value-p '(calc (- (vw 100) (px 1))) nil nil)))
(ert-deftest ebox-size-rejects-legacy-values-malformed-functions-and-unit-algebra ()
(dolist (value '(0 3 (240) "50%" auto viewport (viewport-height)
(fit-content (ch 80)) (em 2) (px) (px 1 2) (px "2")
(px . 2) (+ (px 1) (px 2))
(calc) (calc (px 1) (px 2)) (min) (max)
(clamp (px 1) (px 2)) (calc (+))
(calc (* (px 1) (px 2))) (calc (/ (px 1) (lh 2)))
(calc (+ (px 1) 1)) (min (px 1) 2)
(calc 2) (calc (/ 2 3))
(calc (/ (% 50) 0)) (calc (/ (px 1) (- 3 3)))
(calc (/ (px 1) 2 3))))
(should-not (ebox-size-value-p value t))
(should-error (ebox-size-resolve value nil) :type 'ebox-size-error)))
(ert-deftest ebox-size-rejects-nonfinite-input-context-and-overflow ()
(dolist (number (list 0.0e+NaN 1.0e+INF -1.0e+INF))
(should-not (ebox-size-value-p (list 'px number) t))
(should-error (ebox-size-resolve '(ch 1) (list :ch number))
:type 'ebox-size-error))
(should-error (ebox-size-resolve '(vh 1) '(:viewport-height -1))
:type 'ebox-size-error)
(should-not (ebox-size-value-p '(calc (* (px 1.0e+308) 10))))
(should-error (ebox-size-resolve '(ch 1.0e+308) '(:ch 100))
:type 'ebox-size-error))
(ert-deftest ebox-size-axis-contract-inspects-every-math-operand ()
"Property domains constrain every literal, even inside zero-valued math."
(dolist (case '((inline (px ch vw %) (lh vh))
(block (lh vh %) (px ch vw))
(stroke (px ch lh vw vh) (%))))
(dolist (unit (cadr case))
(should (ebox-size-value-for-axis-p (list unit 2) (car case))))
(dolist (unit (caddr case))
(let ((value (list unit 0)))
(dolist (expression
(list value (list 'min value value)
(list 'clamp value value value)
(list 'calc (list '* 0 value))
(list 'calc (list '- value value))))
(should (ebox-size-value-p expression))
(should-not (ebox-size-value-for-axis-p expression (car case)))
(should-error
(ebox-size-validate-for-axis expression :example (car case))
:type 'ebox-size-error))))))
(ert-deftest ebox-size-axis-errors-describe-property-and-allowed-units ()
"Malformed functions and unknown units give the same actionable context."
(dolist (value '((px 1) (rh 1) (calc (+ (lh 1) (px 0)))
(clamp (lh 1)) (lh -1) (lh "1")))
(let ((message
(condition-case condition
(progn (ebox-size-validate-for-axis value :height 'block) nil)
(ebox-size-error (error-message-string condition)))))
(should (string-match-p ":height" message))
(should (string-match-p "allowed units: (lh vh %)" message)))))
(provide 'ebox-size-tests)
;;; ebox-size-tests.el ends here