aboutsummaryrefslogtreecommitdiff
path: root/tests/test-wttrin--mode-line-update-display.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-02-21 07:06:50 -0600
committerCraig Jennings <c@cjennings.net>2026-02-21 07:11:03 -0600
commitb74b98f177d92d50ddbede900ba41212e07c5f63 (patch)
tree459b1630dcc7d1c941f850565acdc16332831948 /tests/test-wttrin--mode-line-update-display.el
parentec8130cfe1a7390e9939b311c8db39907a3f7f44 (diff)
downloademacs-wttrin-b74b98f177d92d50ddbede900ba41212e07c5f63.tar.gz
emacs-wttrin-b74b98f177d92d50ddbede900ba41212e07c5f63.zip
feat: unified cache and staleness handling for mode-line and buffer
Replace TTL-based cache invalidation with proactive scheduled refresh. Both mode-line and buffer systems now follow: timer refreshes cache, display reads from cache, staleness indicated when data is old. Phase 1 - Mode-line cache formalization + staleness display: - Replace wttrin--mode-line-tooltip-data with wttrin--mode-line-cache as (timestamp . data) cons cell matching buffer cache pattern - Add wttrin--format-age helper for human-readable age strings - Rewrite wttrin--mode-line-update-display to take no arguments, read from cache, compute staleness (age > 2x refresh interval), dim emoji gray when stale, show staleness info in tooltip - Rewrite wttrin--mode-line-fetch-weather to write cache on success, show stale display on failure with cache, error placeholder without - Add wttrin--mode-line-update-placeholder-error for first-launch failure Phase 2 - Remove TTL, add proactive buffer refresh: - Rename wttrin-cache-ttl to wttrin-refresh-interval (default 3600s) with define-obsolete-variable-alias for backward compatibility - Change wttrin-mode-line-refresh-interval default from 900 to 3600 - Remove TTL check from wttrin--get-cached-or-fetch; serve cached data regardless of age, background timer keeps it fresh - Add buffer refresh timer (wttrin--buffer-cache-refresh) Phase 3 - Buffer staleness display: - Add wttrin--format-staleness-header for buffer age display - Insert staleness line in wttrin--display-weather before instructions Phase 4 - Cleanup: - Remove all references to wttrin--mode-line-tooltip-data - Update README.org cache settings and mode-line documentation - Update tests for new API (198 tests across 21 files, all passing)
Diffstat (limited to 'tests/test-wttrin--mode-line-update-display.el')
-rw-r--r--tests/test-wttrin--mode-line-update-display.el275
1 files changed, 198 insertions, 77 deletions
diff --git a/tests/test-wttrin--mode-line-update-display.el b/tests/test-wttrin--mode-line-update-display.el
index 07ab73f..57a5823 100644
--- a/tests/test-wttrin--mode-line-update-display.el
+++ b/tests/test-wttrin--mode-line-update-display.el
@@ -3,8 +3,9 @@
;; Copyright (C) 2025 Craig Jennings
;;; Commentary:
-;; Unit tests for wttrin--mode-line-update-display and
-;; wttrin--mode-line-valid-response-p.
+;; Unit tests for wttrin--mode-line-update-display,
+;; wttrin--mode-line-valid-response-p, wttrin--mode-line-fetch-weather,
+;; wttrin--mode-line-set-placeholder, and wttrin--mode-line-update-placeholder-error.
;;; Code:
@@ -18,13 +19,13 @@
"Setup for mode-line update display tests."
(testutil-wttrin-setup)
(setq wttrin-mode-line-string nil)
- (setq wttrin--mode-line-tooltip-data nil))
+ (setq wttrin--mode-line-cache nil))
(defun test-wttrin--mode-line-update-display-teardown ()
"Teardown for mode-line update display tests."
(testutil-wttrin-teardown)
(setq wttrin-mode-line-string nil)
- (setq wttrin--mode-line-tooltip-data nil))
+ (setq wttrin--mode-line-cache nil))
;;; --------------------------------------------------------------------------
;;; wttrin--mode-line-valid-response-p
@@ -65,45 +66,65 @@
(should-not (wttrin--mode-line-valid-response-p 42)))
;;; --------------------------------------------------------------------------
+;;; wttrin--format-age
+;;; --------------------------------------------------------------------------
+
+(ert-deftest test-wttrin--format-age-just-now ()
+ "Seconds under 60 returns just now."
+ (should (equal (wttrin--format-age 0) "just now"))
+ (should (equal (wttrin--format-age 59) "just now")))
+
+(ert-deftest test-wttrin--format-age-minutes ()
+ "Seconds in the minutes range."
+ (should (equal (wttrin--format-age 60) "1 minute ago"))
+ (should (equal (wttrin--format-age 300) "5 minutes ago"))
+ (should (equal (wttrin--format-age 3599) "59 minutes ago")))
+
+(ert-deftest test-wttrin--format-age-hours ()
+ "Seconds in the hours range."
+ (should (equal (wttrin--format-age 3600) "1 hour ago"))
+ (should (equal (wttrin--format-age 7200) "2 hours ago"))
+ (should (equal (wttrin--format-age 86399) "23 hours ago")))
+
+(ert-deftest test-wttrin--format-age-days ()
+ "Seconds in the days range."
+ (should (equal (wttrin--format-age 86400) "1 day ago"))
+ (should (equal (wttrin--format-age 172800) "2 days ago")))
+
+;;; --------------------------------------------------------------------------
;;; wttrin--mode-line-update-display
;;; --------------------------------------------------------------------------
;;; Normal Cases
(ert-deftest test-wttrin--mode-line-update-display-normal-sets-mode-line-string ()
- "Display update sets wttrin-mode-line-string to non-nil."
+ "Display update from cache sets wttrin-mode-line-string to non-nil."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
- (progn
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
+ (cl-letf (((symbol-function 'float-time) (lambda () 1000.0)))
+ (setq wttrin--mode-line-cache (cons 1000.0 "Paris: ☀️ +61°F Clear"))
+ (wttrin--mode-line-update-display)
(should wttrin-mode-line-string))
(test-wttrin--mode-line-update-display-teardown)))
-(ert-deftest test-wttrin--mode-line-update-display-normal-stores-tooltip-data ()
- "Display update stores weather string as tooltip data."
- (test-wttrin--mode-line-update-display-setup)
- (unwind-protect
- (progn
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
- (should (equal wttrin--mode-line-tooltip-data "Paris: ☀️ +61°F Clear")))
- (test-wttrin--mode-line-update-display-teardown)))
-
(ert-deftest test-wttrin--mode-line-update-display-normal-extracts-emoji ()
"Display update extracts emoji character into mode-line string."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
(let ((wttrin-mode-line-emoji-font nil))
- (wttrin--mode-line-update-display "Paris: X +61°F Clear")
- ;; Mode-line string should contain the extracted character
- (should (string-match-p "X" (substring-no-properties wttrin-mode-line-string))))
+ (cl-letf (((symbol-function 'float-time) (lambda () 1000.0)))
+ (setq wttrin--mode-line-cache (cons 1000.0 "Paris: X +61°F Clear"))
+ (wttrin--mode-line-update-display)
+ (should (string-match-p "X" (substring-no-properties wttrin-mode-line-string)))))
(test-wttrin--mode-line-update-display-teardown)))
(ert-deftest test-wttrin--mode-line-update-display-normal-has-help-echo ()
"Display update sets help-echo property for tooltip."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
- (progn
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
+ (cl-letf (((symbol-function 'float-time) (lambda () 1000.0)))
+ (setq wttrin--mode-line-cache (cons 1000.0 "Paris: ☀️ +61°F Clear"))
+ (wttrin--mode-line-update-display)
(should (get-text-property 0 'help-echo wttrin-mode-line-string)))
(test-wttrin--mode-line-update-display-teardown)))
@@ -111,91 +132,124 @@
"Display update sets local-map property for mouse interaction."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
- (progn
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
+ (cl-letf (((symbol-function 'float-time) (lambda () 1000.0)))
+ (setq wttrin--mode-line-cache (cons 1000.0 "Paris: ☀️ +61°F Clear"))
+ (wttrin--mode-line-update-display)
(should (eq (get-text-property 0 'local-map wttrin-mode-line-string)
wttrin--mode-line-map)))
(test-wttrin--mode-line-update-display-teardown)))
-;;; Boundary Cases
-
-(ert-deftest test-wttrin--mode-line-update-display-boundary-no-emoji-match-uses-fallback ()
- "When emoji regex doesn't match, fallback character '?' is used."
+(ert-deftest test-wttrin--mode-line-update-display-normal-fresh-tooltip-shows-updated ()
+ "Fresh data tooltip shows weather data and 'Updated' age."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
- (let ((wttrin-mode-line-emoji-font nil))
- (wttrin--mode-line-update-display "no colon here")
- (should (string-match-p "\\?" (substring-no-properties wttrin-mode-line-string))))
+ (cl-letf (((symbol-function 'float-time) (lambda () 1300.0)))
+ (setq wttrin--mode-line-cache (cons 1000.0 "Paris: ☀️ +61°F Clear"))
+ (wttrin--mode-line-update-display)
+ (let ((tooltip (get-text-property 0 'help-echo wttrin-mode-line-string)))
+ (should (string-match-p "Paris" tooltip))
+ (should (string-match-p "Updated 5 minutes ago" tooltip))))
(test-wttrin--mode-line-update-display-teardown)))
-;;; Tooltip Lambda Tests
+;;; Stale Cases
-(ert-deftest test-wttrin--mode-line-update-display-normal-tooltip-returns-weather-data ()
- "Tooltip lambda returns weather data when available."
+(ert-deftest test-wttrin--mode-line-update-display-stale-tooltip-shows-stale ()
+ "Stale data tooltip indicates staleness and retry info."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
- (progn
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
- (let ((tooltip-fn (get-text-property 0 'help-echo wttrin-mode-line-string)))
- (should (equal (funcall tooltip-fn nil nil nil) "Paris: ☀️ +61°F Clear"))))
+ (let ((wttrin-mode-line-refresh-interval 900))
+ (cl-letf (((symbol-function 'float-time) (lambda () 3000.0)))
+ ;; Data is 2000 seconds old, threshold is 2*900=1800 -> stale
+ (setq wttrin--mode-line-cache (cons 1000.0 "Paris: ☀️ +61°F Clear"))
+ (wttrin--mode-line-update-display)
+ (let ((tooltip (get-text-property 0 'help-echo wttrin-mode-line-string)))
+ (should (string-match-p "Stale" tooltip))
+ (should (string-match-p "fetch failed" tooltip)))))
(test-wttrin--mode-line-update-display-teardown)))
-(ert-deftest test-wttrin--mode-line-update-display-boundary-tooltip-empty-string-uses-fallback ()
- "Tooltip lambda falls back when tooltip data is empty string."
+(ert-deftest test-wttrin--mode-line-update-display-stale-emoji-dimmed ()
+ "Stale data dims the emoji with gray foreground."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
- (let ((wttrin-favorite-location "Paris"))
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
- ;; Simulate empty tooltip data (as would happen with bad response)
- (setq wttrin--mode-line-tooltip-data "")
- (let ((tooltip-fn (get-text-property 0 'help-echo wttrin-mode-line-string)))
- (should (string-match-p "Weather for Paris" (funcall tooltip-fn nil nil nil)))))
+ (let ((wttrin-mode-line-refresh-interval 900)
+ (wttrin-mode-line-emoji-font nil))
+ (cl-letf (((symbol-function 'float-time) (lambda () 3000.0)))
+ (setq wttrin--mode-line-cache (cons 1000.0 "Paris: X +61°F Clear"))
+ (wttrin--mode-line-update-display)
+ ;; The emoji character should have a gray face
+ (let* ((str wttrin-mode-line-string)
+ ;; Find the emoji position (after the space)
+ (emoji-pos 1)
+ (face (get-text-property emoji-pos 'face str)))
+ (should face)
+ (should (equal (plist-get face :foreground) "gray60")))))
(test-wttrin--mode-line-update-display-teardown)))
-(ert-deftest test-wttrin--mode-line-update-display-boundary-tooltip-nil-uses-fallback ()
- "Tooltip lambda falls back when tooltip data is nil."
+;;; Boundary Cases
+
+(ert-deftest test-wttrin--mode-line-update-display-boundary-no-emoji-match-uses-fallback ()
+ "When emoji regex doesn't match, fallback character '?' is used."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
- (let ((wttrin-favorite-location "Paris"))
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
- (setq wttrin--mode-line-tooltip-data nil)
- (let ((tooltip-fn (get-text-property 0 'help-echo wttrin-mode-line-string)))
- (should (string-match-p "Weather for Paris" (funcall tooltip-fn nil nil nil)))))
+ (let ((wttrin-mode-line-emoji-font nil))
+ (cl-letf (((symbol-function 'float-time) (lambda () 1000.0)))
+ (setq wttrin--mode-line-cache (cons 1000.0 "no colon here"))
+ (wttrin--mode-line-update-display)
+ (should (string-match-p "\\?" (substring-no-properties wttrin-mode-line-string)))))
+ (test-wttrin--mode-line-update-display-teardown)))
+
+(ert-deftest test-wttrin--mode-line-update-display-boundary-nil-cache-does-nothing ()
+ "When cache is nil, update-display does not set mode-line-string."
+ (test-wttrin--mode-line-update-display-setup)
+ (unwind-protect
+ (progn
+ (setq wttrin--mode-line-cache nil)
+ (wttrin--mode-line-update-display)
+ (should-not wttrin-mode-line-string))
(test-wttrin--mode-line-update-display-teardown)))
;;; --------------------------------------------------------------------------
-;;; wttrin--mode-line-fetch-weather (validation integration)
+;;; wttrin--mode-line-fetch-weather
;;; --------------------------------------------------------------------------
+(ert-deftest test-wttrin--mode-line-fetch-weather-normal-valid-response-updates-cache ()
+ "Valid API response populates cache and updates display."
+ (test-wttrin--mode-line-update-display-setup)
+ (unwind-protect
+ (let ((wttrin-favorite-location "Paris"))
+ (testutil-wttrin-mock-http-response "Paris: ☀️ +61°F Clear"
+ (wttrin--mode-line-fetch-weather)
+ (should wttrin--mode-line-cache)
+ (should (equal (cdr wttrin--mode-line-cache) "Paris: ☀️ +61°F Clear"))
+ (should wttrin-mode-line-string)))
+ (test-wttrin--mode-line-update-display-teardown)))
+
(ert-deftest test-wttrin--mode-line-fetch-weather-error-empty-response-keeps-previous ()
- "Empty API response does not overwrite previous valid display."
+ "Empty API response does not overwrite previous valid cache."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
(let ((wttrin-favorite-location "Paris"))
- ;; Set up a valid prior state
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
- (let ((previous-string wttrin-mode-line-string)
- (previous-tooltip wttrin--mode-line-tooltip-data))
+ ;; Set up a valid prior cache
+ (setq wttrin--mode-line-cache (cons (float-time) "Paris: ☀️ +61°F Clear"))
+ (wttrin--mode-line-update-display)
+ (let ((previous-cache wttrin--mode-line-cache))
;; Simulate fetch returning empty response
(testutil-wttrin-mock-http-response ""
(wttrin--mode-line-fetch-weather)
- ;; Previous state should be preserved
- (should (equal wttrin-mode-line-string previous-string))
- (should (equal wttrin--mode-line-tooltip-data previous-tooltip)))))
+ ;; Cache should be preserved
+ (should (equal wttrin--mode-line-cache previous-cache)))))
(test-wttrin--mode-line-update-display-teardown)))
(ert-deftest test-wttrin--mode-line-fetch-weather-error-no-colon-response-keeps-previous ()
- "Malformed API response without colon does not overwrite previous valid display."
+ "Malformed API response without colon does not overwrite previous valid cache."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
(let ((wttrin-favorite-location "Paris"))
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
- (let ((previous-string wttrin-mode-line-string)
- (previous-tooltip wttrin--mode-line-tooltip-data))
+ (setq wttrin--mode-line-cache (cons (float-time) "Paris: ☀️ +61°F Clear"))
+ (let ((previous-cache wttrin--mode-line-cache))
(testutil-wttrin-mock-http-response "Unknown location"
(wttrin--mode-line-fetch-weather)
- (should (equal wttrin-mode-line-string previous-string))
- (should (equal wttrin--mode-line-tooltip-data previous-tooltip)))))
+ (should (equal wttrin--mode-line-cache previous-cache)))))
(test-wttrin--mode-line-update-display-teardown)))
(ert-deftest test-wttrin--mode-line-fetch-weather-error-nil-location-does-not-fetch ()
@@ -210,15 +264,43 @@
(should-not fetch-called)))
(test-wttrin--mode-line-update-display-teardown)))
-(ert-deftest test-wttrin--mode-line-fetch-weather-normal-valid-response-updates-display ()
- "Valid API response updates the mode-line display."
+(ert-deftest test-wttrin--mode-line-fetch-weather-error-network-fail-with-cache-shows-stale ()
+ "Network failure with existing cache triggers stale display."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
- (let ((wttrin-favorite-location "Paris"))
- (testutil-wttrin-mock-http-response "Paris: ☀️ +61°F Clear"
+ (let ((wttrin-favorite-location "Paris")
+ (wttrin-mode-line-refresh-interval 900))
+ ;; Set up old cache data
+ (setq wttrin--mode-line-cache (cons (- (float-time) 2000) "Paris: ☀️ +61°F Clear"))
+ ;; Simulate network failure (nil data)
+ (cl-letf (((symbol-function 'wttrin--fetch-url)
+ (lambda (_url callback) (funcall callback nil))))
(wttrin--mode-line-fetch-weather)
+ ;; Cache should still exist
+ (should wttrin--mode-line-cache)
+ ;; Mode-line should be updated (stale display)
+ (should wttrin-mode-line-string)))
+ (test-wttrin--mode-line-update-display-teardown)))
+
+(ert-deftest test-wttrin--mode-line-fetch-weather-error-network-fail-no-cache-shows-error ()
+ "Network failure with no cache shows error placeholder."
+ (test-wttrin--mode-line-update-display-setup)
+ (unwind-protect
+ (let ((wttrin-favorite-location "Paris")
+ (wttrin-mode-line-emoji-font nil))
+ ;; No cache
+ (should-not wttrin--mode-line-cache)
+ ;; Simulate network failure
+ (cl-letf (((symbol-function 'wttrin--fetch-url)
+ (lambda (_url callback) (funcall callback nil))))
+ (wttrin--mode-line-fetch-weather)
+ ;; Should show error placeholder with hourglass
(should wttrin-mode-line-string)
- (should (equal wttrin--mode-line-tooltip-data "Paris: ☀️ +61°F Clear"))))
+ (should (string-match-p "⏳" (substring-no-properties wttrin-mode-line-string)))
+ ;; Tooltip should mention failure
+ (let ((tooltip (get-text-property 0 'help-echo wttrin-mode-line-string)))
+ (should (string-match-p "failed" tooltip))
+ (should (string-match-p "Paris" tooltip)))))
(test-wttrin--mode-line-update-display-teardown)))
;;; --------------------------------------------------------------------------
@@ -268,26 +350,65 @@
(test-wttrin--mode-line-update-display-teardown)))
(ert-deftest test-wttrin--mode-line-set-placeholder-normal-replaced-by-real-data ()
- "Placeholder is replaced when real weather data arrives."
+ "Placeholder is replaced when real weather data arrives via cache."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
(let ((wttrin-favorite-location "Paris")
(wttrin-mode-line-emoji-font nil))
(wttrin--mode-line-set-placeholder)
(should (string-match-p "⏳" (substring-no-properties wttrin-mode-line-string)))
- (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear")
+ (cl-letf (((symbol-function 'float-time) (lambda () 1000.0)))
+ (setq wttrin--mode-line-cache (cons 1000.0 "Paris: ☀️ +61°F Clear"))
+ (wttrin--mode-line-update-display))
(should-not (string-match-p "⏳" (substring-no-properties wttrin-mode-line-string))))
(test-wttrin--mode-line-update-display-teardown)))
;;; Boundary Cases
-(ert-deftest test-wttrin--mode-line-set-placeholder-boundary-does-not-set-tooltip-data ()
- "Placeholder does not contaminate tooltip data variable."
+(ert-deftest test-wttrin--mode-line-set-placeholder-boundary-does-not-set-cache ()
+ "Placeholder does not contaminate cache variable."
(test-wttrin--mode-line-update-display-setup)
(unwind-protect
(let ((wttrin-favorite-location "Paris"))
(wttrin--mode-line-set-placeholder)
- (should-not wttrin--mode-line-tooltip-data))
+ (should-not wttrin--mode-line-cache))
+ (test-wttrin--mode-line-update-display-teardown)))
+
+;;; --------------------------------------------------------------------------
+;;; wttrin--mode-line-update-placeholder-error
+;;; --------------------------------------------------------------------------
+
+(ert-deftest test-wttrin--mode-line-update-placeholder-error-sets-mode-line-string ()
+ "Error placeholder sets wttrin-mode-line-string to non-nil."
+ (test-wttrin--mode-line-update-display-setup)
+ (unwind-protect
+ (let ((wttrin-favorite-location "Paris"))
+ (wttrin--mode-line-update-placeholder-error)
+ (should wttrin-mode-line-string))
+ (test-wttrin--mode-line-update-display-teardown)))
+
+(ert-deftest test-wttrin--mode-line-update-placeholder-error-contains-hourglass ()
+ "Error placeholder displays hourglass emoji."
+ (test-wttrin--mode-line-update-display-setup)
+ (unwind-protect
+ (let ((wttrin-favorite-location "Paris")
+ (wttrin-mode-line-emoji-font nil))
+ (wttrin--mode-line-update-placeholder-error)
+ (should (string-match-p "⏳" (substring-no-properties wttrin-mode-line-string))))
+ (test-wttrin--mode-line-update-display-teardown)))
+
+(ert-deftest test-wttrin--mode-line-update-placeholder-error-tooltip-mentions-failure ()
+ "Error placeholder tooltip mentions failure and retry."
+ (test-wttrin--mode-line-update-display-setup)
+ (unwind-protect
+ (let ((wttrin-favorite-location "Paris")
+ (wttrin-mode-line-refresh-interval 900))
+ (wttrin--mode-line-update-placeholder-error)
+ (let ((tooltip (get-text-property 0 'help-echo wttrin-mode-line-string)))
+ (should (string-match-p "failed" tooltip))
+ (should (string-match-p "Paris" tooltip))
+ (should (string-match-p "retry" tooltip))
+ (should (string-match-p "15 minutes" tooltip))))
(test-wttrin--mode-line-update-display-teardown)))
(provide 'test-wttrin--mode-line-update-display)