diff options
| -rw-r--r-- | docs/prototypes/gallery-widget.el | 25 | ||||
| -rw-r--r-- | tests/gallery-widgets/test-gallery-widget.el | 35 |
2 files changed, 55 insertions, 5 deletions
diff --git a/docs/prototypes/gallery-widget.el b/docs/prototypes/gallery-widget.el index 5d69148..5a80e59 100644 --- a/docs/prototypes/gallery-widget.el +++ b/docs/prototypes/gallery-widget.el @@ -24,9 +24,16 @@ (require 'svg) (require 'dom) +(require 'cl-lib) -(load (expand-file-name "gallery-tokens.el" - (file-name-directory (or load-file-name buffer-file-name))) +(defun gallery-widget--source-dir () + "Directory holding this module's source (and gallery-tokens.el). +Falls back to `default-directory' when evaluated outside a load and +outside a file buffer, where both location variables are nil." + (let ((src (or load-file-name buffer-file-name))) + (if src (file-name-directory src) default-directory))) + +(load (expand-file-name "gallery-tokens.el" (gallery-widget--source-dir)) nil t) (defun gallery-widget-token (name) @@ -43,11 +50,17 @@ a typo'd token must fail loudly, not render black." (defconst gallery-widget--gauge-max-deg 60.0 "Needle angle at value 100.") -(defun gallery-widget--needle-angle (value) - "Map VALUE (0-100, clamped) onto the gauge's needle angle in degrees." +(defun gallery-widget--clamp-value (value) + "Validate VALUE is a number and clamp it to the gauge's 0-100 range. +One clamp shared by the needle angle and the readout, so the two halves +of the widget can never disagree at an out-of-range input." (unless (numberp value) (error "Gauge value must be a number, got %S" value)) - (let ((v (min 100.0 (max 0.0 (float value))))) + (min 100.0 (max 0.0 (float value)))) + +(defun gallery-widget--needle-angle (value) + "Map VALUE (0-100, clamped) onto the gauge's needle angle in degrees." + (let ((v (gallery-widget--clamp-value value))) (+ gallery-widget--gauge-min-deg (* (/ v 100.0) (- gallery-widget--gauge-max-deg gallery-widget--gauge-min-deg))))) @@ -77,6 +90,8 @@ Positive angles swing right, matching the gauge's needle travel." Geometry mirrors the web card: 96px-wide semicircular dial, pivot at its bottom center, 40px needle sweeping -60..+60 degrees, readout below." (let* ((w 96) (h 64) (cx 48.0) (cy 48.0) + ;; clamp once; the angle and the readout render the same value + (value (gallery-widget--clamp-value value)) (angle (gallery-widget--needle-angle value)) (tip (gallery-widget--polar cx cy 40.0 angle)) (svg (svg-create w h :viewBox (format "0 0 %d %d" w h)))) diff --git a/tests/gallery-widgets/test-gallery-widget.el b/tests/gallery-widgets/test-gallery-widget.el index 166cd59..de0c165 100644 --- a/tests/gallery-widgets/test-gallery-widget.el +++ b/tests/gallery-widgets/test-gallery-widget.el @@ -110,5 +110,40 @@ "The readout shows a rounded integer percent, matching the web card." (should (string-match-p ">67%<" (test-gallery-widget--svg-string 66.6)))) +(ert-deftest gallery-widget-gauge-out-of-range-readout-clamps () + "Readout and needle agree at out-of-range values: both clamp. +Regression: the needle clamped to the dial ends while the readout rendered +the raw value, so a gauge fed 150 pinned the needle at +60 degrees under a +\"150%\" label." + (let ((over (test-gallery-widget--svg-string 150))) + (should (string-match-p ">100%<" over)) + (should-not (string-match-p ">150%<" over))) + (let ((under (test-gallery-widget--svg-string -5))) + (should (string-match-p ">0%<" under)) + (should-not (string-match-p ">-5%<" under)))) + +(ert-deftest gallery-widget-source-dir-load-and-fallback () + "The token-file directory resolves from load context, else default-directory. +Regression: `file-name-directory' received nil when the load form was re-evaluated +outside a load and outside a file buffer." + (let ((load-file-name "/tmp/proto/gallery-widget.el")) + (should (equal (gallery-widget--source-dir) "/tmp/proto/"))) + (with-temp-buffer + (let ((load-file-name nil) + (default-directory "/tmp/elsewhere/")) + (should (equal (gallery-widget--source-dir) "/tmp/elsewhere/"))))) + +(ert-deftest gallery-widget-requires-cl-lib-at-load-time () + "Loading the module alone brings in cl-lib, not just an autoload cookie. +A cold byte-compile or a changed autoload would otherwise break +`gallery-widget--node' with a void-function error on first call." + (with-temp-buffer + (let ((emacs (expand-file-name invocation-name invocation-directory)) + (widget (expand-file-name "docs/prototypes/gallery-widget.el" + test-gallery-widget--root))) + (call-process emacs nil t nil "--batch" "-l" widget + "--eval" "(princ (if (featurep 'cl-lib) \"cl-lib:yes\" \"cl-lib:no\"))") + (should (string-match-p "cl-lib:yes" (buffer-string)))))) + (provide 'test-gallery-widget) ;;; test-gallery-widget.el ends here |
