diff options
| -rw-r--r-- | tests/test-wttrin--mode-line-update-display.el | 225 | ||||
| -rw-r--r-- | wttrin.el | 23 |
2 files changed, 244 insertions, 4 deletions
diff --git a/tests/test-wttrin--mode-line-update-display.el b/tests/test-wttrin--mode-line-update-display.el new file mode 100644 index 0000000..0bcb154 --- /dev/null +++ b/tests/test-wttrin--mode-line-update-display.el @@ -0,0 +1,225 @@ +;;; test-wttrin--mode-line-update-display.el --- Tests for mode-line display update -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Craig Jennings + +;;; Commentary: +;; Unit tests for wttrin--mode-line-update-display and +;; wttrin--mode-line-valid-response-p. + +;;; Code: + +(require 'ert) +(require 'wttrin) +(require 'testutil-wttrin) + +;;; Setup and Teardown + +(defun test-wttrin--mode-line-update-display-setup () + "Setup for mode-line update display tests." + (testutil-wttrin-setup) + (setq wttrin-mode-line-string nil) + (setq wttrin--mode-line-tooltip-data 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)) + +;;; -------------------------------------------------------------------------- +;;; wttrin--mode-line-valid-response-p +;;; -------------------------------------------------------------------------- + +;;; Normal Cases + +(ert-deftest test-wttrin--mode-line-valid-response-p-normal-standard-response () + "Valid response with location, emoji, temp, and conditions." + (should (wttrin--mode-line-valid-response-p "Paris: ☀️ +61°F Clear"))) + +(ert-deftest test-wttrin--mode-line-valid-response-p-normal-minimal-with-colon () + "Minimal valid response containing a colon." + (should (wttrin--mode-line-valid-response-p "X: Y"))) + +;;; Boundary Cases + +(ert-deftest test-wttrin--mode-line-valid-response-p-boundary-colon-only () + "Response that is just a colon is technically valid (has expected delimiter)." + (should (wttrin--mode-line-valid-response-p ":"))) + +;;; Error Cases + +(ert-deftest test-wttrin--mode-line-valid-response-p-error-nil () + "Nil input is invalid." + (should-not (wttrin--mode-line-valid-response-p nil))) + +(ert-deftest test-wttrin--mode-line-valid-response-p-error-empty-string () + "Empty string is invalid." + (should-not (wttrin--mode-line-valid-response-p ""))) + +(ert-deftest test-wttrin--mode-line-valid-response-p-error-no-colon () + "String without colon is invalid (doesn't match expected format)." + (should-not (wttrin--mode-line-valid-response-p "no colon here"))) + +(ert-deftest test-wttrin--mode-line-valid-response-p-error-not-a-string () + "Non-string input is invalid." + (should-not (wttrin--mode-line-valid-response-p 42))) + +;;; -------------------------------------------------------------------------- +;;; 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." + (test-wttrin--mode-line-update-display-setup) + (unwind-protect + (progn + (wttrin--mode-line-update-display "Paris: ☀️ +61°F Clear") + (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)))) + (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") + (should (get-text-property 0 'help-echo wttrin-mode-line-string))) + (test-wttrin--mode-line-update-display-teardown))) + +(ert-deftest test-wttrin--mode-line-update-display-normal-has-local-map () + "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") + (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." + (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)))) + (test-wttrin--mode-line-update-display-teardown))) + +;;; Tooltip Lambda Tests + +(ert-deftest test-wttrin--mode-line-update-display-normal-tooltip-returns-weather-data () + "Tooltip lambda returns weather data when available." + (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")))) + (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." + (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))))) + (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." + (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))))) + (test-wttrin--mode-line-update-display-teardown))) + +;;; -------------------------------------------------------------------------- +;;; wttrin--mode-line-fetch-weather (validation integration) +;;; -------------------------------------------------------------------------- + +(ert-deftest test-wttrin--mode-line-fetch-weather-error-empty-response-keeps-previous () + "Empty API response does not overwrite previous valid display." + (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)) + ;; 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))))) + (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." + (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)) + (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))))) + (test-wttrin--mode-line-update-display-teardown))) + +(ert-deftest test-wttrin--mode-line-fetch-weather-error-nil-location-does-not-fetch () + "No fetch is attempted when wttrin-favorite-location is nil." + (test-wttrin--mode-line-update-display-setup) + (unwind-protect + (let ((wttrin-favorite-location nil) + (fetch-called nil)) + (cl-letf (((symbol-function 'wttrin--fetch-url) + (lambda (_url _callback) (setq fetch-called t)))) + (wttrin--mode-line-fetch-weather) + (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." + (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-string) + (should (equal wttrin--mode-line-tooltip-data "Paris: ☀️ +61°F Clear")))) + (test-wttrin--mode-line-update-display-teardown))) + +(provide 'test-wttrin--mode-line-update-display) +;;; test-wttrin--mode-line-update-display.el ends here @@ -476,12 +476,22 @@ This creates headroom to avoid frequent cleanups." ;;; Mode-line weather display +(defun wttrin--mode-line-valid-response-p (weather-string) + "Return non-nil if WEATHER-STRING looks like a valid mode-line response. +Expected format: \"Location: emoji temp conditions\", +e.g., \"Paris: ☀️ +61°F Clear\"." + (and (stringp weather-string) + (not (string-empty-p weather-string)) + (string-match-p ":" weather-string))) + (defun wttrin--mode-line-fetch-weather () "Fetch weather for favorite location and update mode-line display. Uses wttr.in custom format for concise weather with emoji." (when (featurep 'wttrin-debug) (wttrin--debug-log "mode-line-fetch: Starting fetch for %s" wttrin-favorite-location)) - (when wttrin-favorite-location + (if (not wttrin-favorite-location) + (when (featurep 'wttrin-debug) + (wttrin--debug-log "mode-line-fetch: No favorite location set, skipping")) (let* ((location wttrin-favorite-location) ;; Custom format: location + emoji + temp + conditions ;; %l=location, %c=weather emoji, %t=temp, %C=conditions @@ -501,7 +511,10 @@ Uses wttr.in custom format for concise weather with emoji." (let ((trimmed-data (string-trim data))) (when (featurep 'wttrin-debug) (wttrin--debug-log "mode-line-fetch: Received data = %S" trimmed-data)) - (wttrin--mode-line-update-display trimmed-data)) + (if (wttrin--mode-line-valid-response-p trimmed-data) + (wttrin--mode-line-update-display trimmed-data) + (when (featurep 'wttrin-debug) + (wttrin--debug-log "mode-line-fetch: Invalid response, keeping previous display")))) (when (featurep 'wttrin-debug) (wttrin--debug-log "mode-line-fetch: No data received (network error)")))))))) @@ -532,9 +545,11 @@ e.g., \"Paris: ☀️ +61°F Clear\"." (setq wttrin-mode-line-string (propertize (concat " " emoji-with-font) 'help-echo (lambda (_window _object _pos) - (or wttrin--mode-line-tooltip-data + (let ((tip wttrin--mode-line-tooltip-data)) + (if (and tip (not (string-empty-p tip))) + tip (format "Weather for %s\nClick to refresh" - wttrin-favorite-location))) + wttrin-favorite-location)))) 'mouse-face 'mode-line-highlight 'local-map wttrin--mode-line-map))) (force-mode-line-update t) |
