aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/README-DEBUG-TESTS.md82
-rw-r--r--tests/test-integration-debug.el (renamed from tests/test-wttrin-integration-with-debug.el)130
-rw-r--r--tests/test-wttrin--add-buffer-instructions.el31
-rw-r--r--tests/test-wttrin--build-url.el33
-rw-r--r--tests/test-wttrin--cleanup-cache-constants.el146
-rw-r--r--tests/test-wttrin--cleanup-cache-if-needed.el77
-rw-r--r--tests/test-wttrin--cleanup-cache-refactored.el182
-rw-r--r--tests/test-wttrin--display-weather.el21
-rw-r--r--tests/test-wttrin--extract-response-body.el37
-rw-r--r--tests/test-wttrin--fetch-url.el11
-rw-r--r--tests/test-wttrin--get-cache-entries-by-age.el218
-rw-r--r--tests/test-wttrin--handle-fetch-callback.el37
-rw-r--r--tests/test-wttrin--make-cache-key.el11
-rw-r--r--tests/test-wttrin--mode-line-map.el27
-rw-r--r--tests/test-wttrin--process-weather-content.el35
-rw-r--r--tests/test-wttrin--validate-weather-data.el35
-rw-r--r--tests/test-wttrin-additional-url-params.el11
-rw-r--r--tests/test-wttrin-ansi-color-rendering.el388
-rw-r--r--tests/test-wttrin-mode-initialization-order.el69
-rw-r--r--tests/test-wttrin-mode-line-startup-delay.el56
-rw-r--r--tests/test-wttrin-smoke.el11
-rw-r--r--tests/testutil-wttrin.el45
22 files changed, 797 insertions, 896 deletions
diff --git a/tests/README-DEBUG-TESTS.md b/tests/README-DEBUG-TESTS.md
deleted file mode 100644
index c1abd52..0000000
--- a/tests/README-DEBUG-TESTS.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Wttrin Debug Integration Tests
-
-This directory contains comprehensive integration tests with debug logging enabled.
-
-## Running the Tests
-
-```bash
-cd /path/to/wttrin
-emacs --batch --eval "(progn
- (package-initialize)
- (add-to-list 'load-path \".\")
- (setq wttrin-debug t)
- (load-file \"wttrin.el\")
- (load-file \"tests/test-wttrin-integration-with-debug.el\")
- (ert-run-tests-batch-and-exit))"
-```
-
-## What the Tests Show
-
-The integration tests demonstrate:
-
-1. **Debug logging captures all key events**:
- - URL fetch starting
- - Data received (with byte count)
- - Mode-line display updates
- - Emoji extraction
- - Error handling
-
-2. **Example debug output from a successful fetch**:
-```
-[wttrin-debug 11:51:46.490] mode-line-fetch: Starting fetch for Berkeley, CA
-[wttrin-debug 11:51:46.490] mode-line-fetch: URL = https://wttr.in/Berkeley%2C%20CA?m&format=%l:+%c+%t+%C
-[wttrin-debug 11:51:46.490] mode-line-fetch: Received data = "Berkeley, CA: ☀️ +62°F Clear"
-[wttrin-debug 11:51:46.490] mode-line-display: Updating display with: "Berkeley, CA: ☀️ +62°F Clear"
-[wttrin-debug 11:51:46.490] mode-line-display: Extracted emoji = "☀", font = Noto Color Emoji
-[wttrin-debug 11:51:46.490] mode-line-display: Complete. mode-line-string set = YES
-```
-
-## Using Debug Mode in Your Configuration
-
-### Enable Debug Before Loading
-
-```emacs-lisp
-;; In your init.el, BEFORE (require 'wttrin):
-(setq wttrin-debug t)
-(require 'wttrin)
-```
-
-### Or Enable Later
-
-```emacs-lisp
-M-x debug-wttrin-enable
-```
-
-### View Debug Log
-
-```emacs-lisp
-M-x wttrin-debug-show-log
-```
-
-This opens a buffer showing all debug events with timestamps.
-
-### Clear Debug Log
-
-```emacs-lisp
-M-x wttrin-debug-clear-log
-```
-
-## Test Fixtures
-
-- `fixtures/test-init.el` - Minimal init file with debug enabled for manual testing
-
-## Troubleshooting
-
-If wttrin isn't loading in your configuration:
-
-1. **Enable debug mode** (set `wttrin-debug` to `t` before loading)
-2. **Check dependencies**: Run `M-x package-list-packages` and ensure `xterm-color` is installed
-3. **View debug log**: Run `M-x wttrin-debug-show-log` after trying to use wttrin
-4. **Check for errors**: Look in `*Messages*` buffer for any error messages
-
-The debug log will show you exactly where the process stops or fails.
diff --git a/tests/test-wttrin-integration-with-debug.el b/tests/test-integration-debug.el
index 0330bb3..db2706b 100644
--- a/tests/test-wttrin-integration-with-debug.el
+++ b/tests/test-integration-debug.el
@@ -1,4 +1,4 @@
-;;; test-wttrin-integration-with-debug.el --- Integration test with debug enabled -*- lexical-binding: t; -*-
+;;; test-integration-debug.el --- Integration test with debug enabled -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Craig Jennings
@@ -13,25 +13,16 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
;; Sample weather data from wttr.in custom format API
(defconst test-wttrin-sample-weather-data
"Berkeley, CA: ☀️ +62°F Clear"
"Sample weather data in wttr.in custom format.")
-(defconst test-wttrin-sample-full-weather
- "Weather for Berkeley, CA
-
- \\ / Clear
- .-. 62 °F
- ― ( ) ― ↑ 5 mph
- `-' 10 mi
- / \\ 0.0 in"
- "Sample full weather display data.")
-
;;; Setup and Teardown
-(defun test-wttrin-setup ()
+(defun test-integration-debug-setup ()
"Set up test environment with debug enabled."
;; Enable debug mode
(setq wttrin-debug t)
@@ -47,7 +38,7 @@
(setq wttrin-mode-line-startup-delay 1) ; Minimum valid value
(setq wttrin-unit-system "m"))
-(defun test-wttrin-teardown ()
+(defun test-integration-debug-teardown ()
"Clean up after tests."
(when (boundp 'wttrin-mode-line-mode)
(wttrin-mode-line-mode -1))
@@ -61,7 +52,7 @@
(defvar test-wttrin--original-fetch-url nil
"Original wttrin--fetch-url function for restoration after test.")
-(defun test-wttrin-mock-fetch (url callback)
+(defun test-integration-debug-mock-fetch (url callback)
"Mock version of wttrin--fetch-url that returns fake weather data.
URL is ignored. CALLBACK is called with mock data."
(when (featurep 'wttrin-debug)
@@ -71,22 +62,34 @@ URL is ignored. CALLBACK is called with mock data."
(wttrin--debug-log "MOCK-FETCH: Calling callback with mock data"))
(funcall callback test-wttrin-sample-weather-data))
-(defmacro with-mocked-fetch (&rest body)
+(defmacro test-integration-debug-with-mocked-fetch (&rest body)
"Execute BODY with wttrin--fetch-url mocked to return test data."
`(let ((test-wttrin--original-fetch-url (symbol-function 'wttrin--fetch-url)))
(unwind-protect
(progn
- (fset 'wttrin--fetch-url #'test-wttrin-mock-fetch)
+ (fset 'wttrin--fetch-url #'test-integration-debug-mock-fetch)
,@body)
(fset 'wttrin--fetch-url test-wttrin--original-fetch-url))))
;;; Integration Tests
-(ert-deftest test-wttrin-debug-integration-mode-line-fetch-and-display ()
- "Integration test: Fetch weather and verify mode-line display with debug logging."
- (test-wttrin-setup)
+(ert-deftest test-integration-debug-mode-line-fetch-and-display-logs-events ()
+ "Test the mode-line weather fetch and display pipeline with debug logging.
+
+Context: The mode-line weather feature fetches weather data asynchronously
+and updates the mode-line string with an emoji icon and tooltip.
+
+Components integrated:
+- `wttrin--mode-line-fetch-weather' (async fetch trigger)
+- `wttrin--mode-line-update-display' (mode-line string formatting)
+- `wttrin--debug-log' (debug event capture)
+
+Validates that a successful fetch sets the mode-line string with an emoji,
+stores tooltip data, and logs fetch-start, data-received, display-update,
+and emoji-extraction events to the debug log."
+ (test-integration-debug-setup)
(unwind-protect
- (with-mocked-fetch
+ (test-integration-debug-with-mocked-fetch
;; Clear debug log
(wttrin-debug-clear-log)
@@ -119,11 +122,23 @@ URL is ignored. CALLBACK is called with mock data."
;; Should have logged emoji extraction
(should (seq-some (lambda (msg) (string-match-p "Extracted emoji" msg))
log-messages))))
- (test-wttrin-teardown)))
+ (test-integration-debug-teardown)))
-(ert-deftest test-wttrin-debug-integration-full-weather-query ()
- "Integration test: Query full weather and verify debug logging."
- (test-wttrin-setup)
+(ert-deftest test-integration-debug-full-weather-query-creates-buffer ()
+ "Test the full weather query pipeline from fetch through buffer display.
+
+Context: When a user calls `wttrin-query', the system fetches weather data,
+processes it through ANSI filtering, and displays it in a dedicated buffer.
+
+Components integrated:
+- `wttrin-query' (user-facing entry point)
+- `wttrin--fetch-url' (HTTP fetch, mocked)
+- `wttrin--display-weather' (buffer creation and content rendering)
+- `wttrin--debug-log' (debug event capture)
+
+Validates that a full weather query creates the *wttr.in* buffer and
+that the debug log records the mock fetch call."
+ (test-integration-debug-setup)
(unwind-protect
(progn
;; Clear debug log
@@ -135,7 +150,7 @@ URL is ignored. CALLBACK is called with mock data."
(when (featurep 'wttrin-debug)
(wttrin--debug-log "MOCK-FETCH: Full weather query for URL: %s" url))
;; Call directly instead of using run-at-time (doesn't work in batch)
- (funcall callback test-wttrin-sample-full-weather))))
+ (funcall callback testutil-wttrin-sample-full-weather))))
;; Start the query (now synchronous with mocked fetch)
(wttrin-query "Berkeley, CA")
@@ -151,13 +166,24 @@ URL is ignored. CALLBACK is called with mock data."
;; Cleanup
(when (get-buffer "*wttr.in*")
(kill-buffer "*wttr.in*"))
- (test-wttrin-teardown)))
+ (test-integration-debug-teardown)))
+
+(ert-deftest test-integration-debug-mode-line-mode-toggle-updates-global-string ()
+ "Test enabling and disabling wttrin-mode-line-mode updates global state.
-(ert-deftest test-wttrin-debug-integration-mode-line-mode-toggle ()
- "Integration test: Toggle mode-line mode and verify debug logging."
- (test-wttrin-setup)
+Context: `wttrin-mode-line-mode' is a global minor mode that adds a weather
+widget to the Emacs mode-line. Toggling it should cleanly add/remove state.
+
+Components integrated:
+- `wttrin-mode-line-mode' (global minor mode toggle)
+- `wttrin--mode-line-fetch-weather' (initial data fetch)
+- `global-mode-string' (Emacs mode-line display list)
+
+Validates that enabling the mode sets `wttrin-mode-line-string' and adds it
+to `global-mode-string', and that disabling clears both."
+ (test-integration-debug-setup)
(unwind-protect
- (with-mocked-fetch
+ (test-integration-debug-with-mocked-fetch
;; Clear debug log
(wttrin-debug-clear-log)
@@ -184,11 +210,22 @@ URL is ignored. CALLBACK is called with mock data."
;; Verify removed from global-mode-string
(should-not (member 'wttrin-mode-line-string global-mode-string)))
- (test-wttrin-teardown)))
+ (test-integration-debug-teardown)))
+
+(ert-deftest test-integration-debug-error-handling-logs-no-data-received ()
+ "Test that network errors are handled gracefully and logged to debug.
+
+Context: When wttr.in is unreachable or returns an error, the fetch callback
+receives nil. The mode-line handler should not crash and should log the error.
-(ert-deftest test-wttrin-debug-integration-error-handling ()
- "Integration test: Verify debug logging captures errors correctly."
- (test-wttrin-setup)
+Components integrated:
+- `wttrin--mode-line-fetch-weather' (fetch trigger)
+- `wttrin--fetch-url' (HTTP fetch, mocked to return nil)
+- `wttrin--debug-log' (error event capture)
+
+Validates that a nil fetch response does not crash and that a
+\"No data received\" message is recorded in the debug log."
+ (test-integration-debug-setup)
(unwind-protect
(progn
;; Clear debug log
@@ -211,11 +248,22 @@ URL is ignored. CALLBACK is called with mock data."
(let ((log-messages (mapcar #'cdr wttrin--debug-log)))
(should (seq-some (lambda (msg) (string-match-p "No data received" msg))
log-messages)))))
- (test-wttrin-teardown)))
+ (test-integration-debug-teardown)))
+
+(ert-deftest test-integration-debug-log-inspection-stores-timestamped-entries ()
+ "Test that the debug log stores structured entries that can be inspected.
+
+Context: The debug log is an alist of (timestamp . message) pairs used for
+diagnosing issues. It should support programmatic inspection and clearing.
+
+Components integrated:
+- `wttrin--debug-log' (log writing)
+- `wttrin-debug-clear-log' (log clearing)
+- `wttrin--debug-log' variable (log storage as alist)
-(ert-deftest test-wttrin-debug-integration-log-inspection ()
- "Integration test: Verify debug log can be inspected programmatically."
- (test-wttrin-setup)
+Validates that log entries are (string . string) cons cells, that formatted
+messages are stored correctly, and that clearing empties the log."
+ (test-integration-debug-setup)
(unwind-protect
(progn
;; Clear and add some test log entries
@@ -238,7 +286,7 @@ URL is ignored. CALLBACK is called with mock data."
;; Clear log
(wttrin-debug-clear-log)
(should (= 0 (length wttrin--debug-log))))
- (test-wttrin-teardown)))
+ (test-integration-debug-teardown)))
-(provide 'test-wttrin-integration-with-debug)
-;;; test-wttrin-integration-with-debug.el ends here
+(provide 'test-integration-debug)
+;;; test-integration-debug.el ends here
diff --git a/tests/test-wttrin--add-buffer-instructions.el b/tests/test-wttrin--add-buffer-instructions.el
index d42d3a0..f32e045 100644
--- a/tests/test-wttrin--add-buffer-instructions.el
+++ b/tests/test-wttrin--add-buffer-instructions.el
@@ -10,17 +10,28 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--add-buffer-instructions-setup ()
+ "Setup for add-buffer-instructions tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--add-buffer-instructions-teardown ()
+ "Teardown for add-buffer-instructions tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
-(ert-deftest test-wttrin--add-buffer-instructions-normal-empty-buffer ()
+(ert-deftest test-wttrin--add-buffer-instructions-normal-empty-buffer-adds-instructions ()
"Test adding instructions to empty buffer."
(with-temp-buffer
(wttrin--add-buffer-instructions)
(should (string= "\n\nPress: [a] for another location [g] to refresh [q] to quit"
(buffer-string)))))
-(ert-deftest test-wttrin--add-buffer-instructions-normal-with-existing-content ()
+(ert-deftest test-wttrin--add-buffer-instructions-normal-with-existing-content-appends-instructions ()
"Test adding instructions to buffer with existing content."
(with-temp-buffer
(insert "Weather: Sunny\nTemperature: 20°C")
@@ -28,7 +39,7 @@
(should (string= "Weather: Sunny\nTemperature: 20°C\n\nPress: [a] for another location [g] to refresh [q] to quit"
(buffer-string)))))
-(ert-deftest test-wttrin--add-buffer-instructions-normal-preserves-point ()
+(ert-deftest test-wttrin--add-buffer-instructions-normal-preserves-point-moves-to-end ()
"Test that point is moved to end after adding instructions."
(with-temp-buffer
(insert "Some content")
@@ -36,7 +47,7 @@
(wttrin--add-buffer-instructions)
(should (= (point) (point-max)))))
-(ert-deftest test-wttrin--add-buffer-instructions-normal-idempotent-check ()
+(ert-deftest test-wttrin--add-buffer-instructions-normal-called-twice-adds-instructions-twice ()
"Test that calling function twice adds instructions twice (not idempotent)."
(with-temp-buffer
(insert "Weather")
@@ -64,7 +75,7 @@
;;; Boundary Cases
-(ert-deftest test-wttrin--add-buffer-instructions-boundary-point-at-beginning ()
+(ert-deftest test-wttrin--add-buffer-instructions-boundary-point-at-beginning-appends-at-end ()
"Test adding instructions when point is at beginning of buffer."
(with-temp-buffer
(insert "Weather data here")
@@ -73,7 +84,7 @@
(should (string-suffix-p "Press: [a] for another location [g] to refresh [q] to quit"
(buffer-string)))))
-(ert-deftest test-wttrin--add-buffer-instructions-boundary-point-in-middle ()
+(ert-deftest test-wttrin--add-buffer-instructions-boundary-point-in-middle-appends-at-end ()
"Test adding instructions when point is in middle of buffer."
(with-temp-buffer
(insert "Line 1\nLine 2\nLine 3")
@@ -83,7 +94,7 @@
(should (string-suffix-p "Press: [a] for another location [g] to refresh [q] to quit"
(buffer-string)))))
-(ert-deftest test-wttrin--add-buffer-instructions-boundary-buffer-with-trailing-newlines ()
+(ert-deftest test-wttrin--add-buffer-instructions-boundary-trailing-newlines-preserves-newlines ()
"Test adding instructions to buffer that already ends with newlines."
(with-temp-buffer
(insert "Weather\n\n\n")
@@ -91,7 +102,7 @@
(should (string= "Weather\n\n\n\n\nPress: [a] for another location [g] to refresh [q] to quit"
(buffer-string)))))
-(ert-deftest test-wttrin--add-buffer-instructions-boundary-very-large-buffer ()
+(ert-deftest test-wttrin--add-buffer-instructions-boundary-very-large-buffer-appends-at-end ()
"Test adding instructions to large buffer."
(with-temp-buffer
(insert (make-string 10000 ?x))
@@ -101,7 +112,7 @@
;;; Error Cases
-(ert-deftest test-wttrin--add-buffer-instructions-error-read-only-buffer ()
+(ert-deftest test-wttrin--add-buffer-instructions-error-read-only-buffer-signals-error ()
"Test that function signals error when buffer is read-only."
(with-temp-buffer
(insert "Some content")
@@ -109,7 +120,7 @@
(should-error (wttrin--add-buffer-instructions)
:type 'buffer-read-only)))
-(ert-deftest test-wttrin--add-buffer-instructions-error-narrowed-buffer ()
+(ert-deftest test-wttrin--add-buffer-instructions-error-narrowed-buffer-adds-at-narrowed-end ()
"Test adding instructions to narrowed buffer adds at narrowed end."
(with-temp-buffer
(insert "Line 1\nLine 2\nLine 3\nLine 4")
diff --git a/tests/test-wttrin--build-url.el b/tests/test-wttrin--build-url.el
index 87a5f92..46760bc 100644
--- a/tests/test-wttrin--build-url.el
+++ b/tests/test-wttrin--build-url.el
@@ -10,28 +10,39 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--build-url-setup ()
+ "Setup for build-url tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--build-url-teardown ()
+ "Teardown for build-url tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
-(ert-deftest test-wttrin--build-url-normal-simple-city ()
+(ert-deftest test-wttrin--build-url-normal-simple-city-returns-correct-url ()
"Test URL building with simple city name."
(let ((wttrin-unit-system nil))
(should (string= "https://wttr.in/Paris?A"
(wttrin--build-url "Paris")))))
-(ert-deftest test-wttrin--build-url-normal-city-with-country ()
+(ert-deftest test-wttrin--build-url-normal-city-with-country-returns-encoded-url ()
"Test URL building with city and country code."
(let ((wttrin-unit-system nil))
(should (string= "https://wttr.in/London%2C%20GB?A"
(wttrin--build-url "London, GB")))))
-(ert-deftest test-wttrin--build-url-normal-metric-system ()
+(ert-deftest test-wttrin--build-url-normal-metric-system-includes-m-flag ()
"Test URL building with metric unit system."
(let ((wttrin-unit-system "m"))
(should (string= "https://wttr.in/Tokyo?mA"
(wttrin--build-url "Tokyo")))))
-(ert-deftest test-wttrin--build-url-normal-imperial-system ()
+(ert-deftest test-wttrin--build-url-normal-imperial-system-includes-u-flag ()
"Test URL building with USCS/imperial unit system."
(let ((wttrin-unit-system "u"))
(should (string= "https://wttr.in/NewYork?uA"
@@ -39,7 +50,7 @@
;;; Boundary Cases
-(ert-deftest test-wttrin--build-url-boundary-unicode-city-name ()
+(ert-deftest test-wttrin--build-url-boundary-unicode-city-name-returns-encoded-url ()
"Test URL building with Unicode characters in city name."
(let ((wttrin-unit-system nil))
(let ((url (wttrin--build-url "北京"))) ; Beijing in Chinese
@@ -47,32 +58,32 @@
(should (string-prefix-p "https://wttr.in/" url))
(should (string-suffix-p "?A" url)))))
-(ert-deftest test-wttrin--build-url-boundary-special-characters ()
+(ert-deftest test-wttrin--build-url-boundary-special-characters-returns-encoded-url ()
"Test URL building with special characters requiring encoding."
(let ((wttrin-unit-system nil))
(let ((url (wttrin--build-url "São Paulo")))
(should (string-match-p "https://wttr.in/S%C3%A3o%20Paulo\\?A" url)))))
-(ert-deftest test-wttrin--build-url-boundary-spaces-encoded ()
+(ert-deftest test-wttrin--build-url-boundary-spaces-returns-percent-encoded ()
"Test that spaces in query are properly URL-encoded."
(let ((wttrin-unit-system nil))
(let ((url (wttrin--build-url "New York")))
(should (string= "https://wttr.in/New%20York?A" url))
(should-not (string-match-p " " url)))))
-(ert-deftest test-wttrin--build-url-boundary-airport-code ()
+(ert-deftest test-wttrin--build-url-boundary-airport-code-returns-correct-url ()
"Test URL building with 3-letter airport code."
(let ((wttrin-unit-system nil))
(should (string= "https://wttr.in/SFO?A"
(wttrin--build-url "SFO")))))
-(ert-deftest test-wttrin--build-url-boundary-gps-coordinates ()
+(ert-deftest test-wttrin--build-url-boundary-gps-coordinates-returns-correct-url ()
"Test URL building with GPS coordinates."
(let ((wttrin-unit-system nil))
(let ((url (wttrin--build-url "-78.46,106.79")))
(should (string-match-p "https://wttr.in/-78\\.46.*106\\.79\\?A" url)))))
-(ert-deftest test-wttrin--build-url-boundary-domain-name ()
+(ert-deftest test-wttrin--build-url-boundary-domain-name-returns-encoded-url ()
"Test URL building with domain name (wttr.in supports @domain).
The @ symbol should be URL-encoded as %40."
(let ((wttrin-unit-system nil))
@@ -88,7 +99,7 @@ The @ symbol should be URL-encoded as %40."
(should-error (wttrin--build-url nil)
:type 'error)))
-(ert-deftest test-wttrin--build-url-error-nil-query-has-message ()
+(ert-deftest test-wttrin--build-url-error-nil-query-has-descriptive-message ()
"Test that nil query error has descriptive message."
(let ((wttrin-unit-system nil))
(condition-case err
diff --git a/tests/test-wttrin--cleanup-cache-constants.el b/tests/test-wttrin--cleanup-cache-constants.el
deleted file mode 100644
index dd2e4bb..0000000
--- a/tests/test-wttrin--cleanup-cache-constants.el
+++ /dev/null
@@ -1,146 +0,0 @@
-;;; test-wttrin--cleanup-cache-constants.el --- Tests for cache cleanup constants -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2024 Craig Jennings
-
-;;; Commentary:
-;; Unit tests verifying cache cleanup behavior with named constants.
-;; These tests verify the behavior before and after refactoring magic numbers.
-
-;;; Code:
-
-(require 'ert)
-(require 'wttrin)
-
-;;; Normal Cases - Cache Cleanup Behavior
-
-(ert-deftest test-wttrin--cleanup-cache-removes-oldest-entries ()
- "Test that cleanup removes oldest entries when cache exceeds max."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 5)
- (now (float-time)))
- ;; Add 10 entries with sequential timestamps
- (dotimes (i 10)
- (puthash (format "location-%d" i)
- (cons (+ now i) (format "data-%d" i))
- wttrin--cache))
- ;; Cache should have 10 entries
- (should (= 10 (hash-table-count wttrin--cache)))
-
- ;; Trigger cleanup
- (wttrin--cleanup-cache-if-needed)
-
- ;; After cleanup, cache should have fewer entries
- ;; With 10 entries and max of 5, should remove 20% = 2 entries
- ;; Leaving 8 entries
- (should (= 8 (hash-table-count wttrin--cache)))
-
- ;; Oldest entries (location-0, location-1) should be removed
- (should-not (gethash "location-0" wttrin--cache))
- (should-not (gethash "location-1" wttrin--cache))
-
- ;; Newer entries should remain
- (should (gethash "location-9" wttrin--cache))
- (should (gethash "location-8" wttrin--cache))))
-
-(ert-deftest test-wttrin--cleanup-cache-removes-approximately-20-percent ()
- "Test that cleanup removes approximately 20% of entries."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 10)
- (now (float-time)))
- ;; Add 20 entries (twice the max)
- (dotimes (i 20)
- (puthash (format "loc-%d" i)
- (cons (+ now i) (format "data-%d" i))
- wttrin--cache))
-
- (should (= 20 (hash-table-count wttrin--cache)))
-
- ;; Trigger cleanup - should remove 20% of 20 = 4 entries
- (wttrin--cleanup-cache-if-needed)
-
- ;; Should have 16 entries remaining (20 - 4)
- (should (= 16 (hash-table-count wttrin--cache)))))
-
-(ert-deftest test-wttrin--cleanup-cache-no-action-when-under-max ()
- "Test that cleanup does nothing when cache is under max."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 10)
- (now (float-time)))
- ;; Add 5 entries (under max of 10)
- (dotimes (i 5)
- (puthash (format "location-%d" i)
- (cons (+ now i) (format "data-%d" i))
- wttrin--cache))
-
- (should (= 5 (hash-table-count wttrin--cache)))
-
- ;; Trigger cleanup
- (wttrin--cleanup-cache-if-needed)
-
- ;; Should still have 5 entries (no cleanup needed)
- (should (= 5 (hash-table-count wttrin--cache)))))
-
-;;; Boundary Cases
-
-(ert-deftest test-wttrin--cleanup-cache-exactly-at-max ()
- "Test that cleanup does nothing when cache is exactly at max."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 5)
- (now (float-time)))
- ;; Add exactly 5 entries (at max)
- (dotimes (i 5)
- (puthash (format "location-%d" i)
- (cons (+ now i) (format "data-%d" i))
- wttrin--cache))
-
- (should (= 5 (hash-table-count wttrin--cache)))
-
- ;; Trigger cleanup - should do nothing since not > max
- (wttrin--cleanup-cache-if-needed)
-
- ;; Should still have 5 entries
- (should (= 5 (hash-table-count wttrin--cache)))))
-
-(ert-deftest test-wttrin--cleanup-cache-one-over-max ()
- "Test cleanup when cache has just one entry over max."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 5)
- (now (float-time)))
- ;; Add 6 entries (1 over max)
- (dotimes (i 6)
- (puthash (format "location-%d" i)
- (cons (+ now i) (format "data-%d" i))
- wttrin--cache))
-
- (should (= 6 (hash-table-count wttrin--cache)))
-
- ;; Trigger cleanup - should remove 20% of 6 = 1.2 = 1 entry (floor)
- (wttrin--cleanup-cache-if-needed)
-
- ;; Should have 5 entries remaining
- (should (= 5 (hash-table-count wttrin--cache)))
-
- ;; Oldest entry (location-0) should be removed
- (should-not (gethash "location-0" wttrin--cache))))
-
-;;; Tests for Mode-Line Startup Delay (after refactoring)
-
-(ert-deftest test-wttrin-mode-line-startup-delay-exists ()
- "Test that wttrin-mode-line-startup-delay defcustom exists after refactoring."
- ;; This test will fail initially, then pass after refactoring
- (should (boundp 'wttrin-mode-line-startup-delay)))
-
-(ert-deftest test-wttrin-mode-line-startup-delay-is-number ()
- "Test that startup delay is a number."
- (should (numberp wttrin-mode-line-startup-delay)))
-
-(ert-deftest test-wttrin-mode-line-startup-delay-reasonable-range ()
- "Test that startup delay default value is in reasonable range (1-10 seconds)."
- ;; Check the defcustom's standard value, not current runtime value
- ;; (other tests may set it to 0 for faster testing)
- (let ((default-value (eval (car (get 'wttrin-mode-line-startup-delay 'standard-value)))))
- (should (>= default-value 1))
- (should (<= default-value 10))))
-
-(provide 'test-wttrin--cleanup-cache-constants)
-;;; test-wttrin--cleanup-cache-constants.el ends here
diff --git a/tests/test-wttrin--cleanup-cache-if-needed.el b/tests/test-wttrin--cleanup-cache-if-needed.el
index c280723..32e5f8d 100644
--- a/tests/test-wttrin--cleanup-cache-if-needed.el
+++ b/tests/test-wttrin--cleanup-cache-if-needed.el
@@ -120,5 +120,82 @@
(should (= 2 (testutil-wttrin-cache-size))))
(test-wttrin--cleanup-cache-if-needed-teardown)))
+(ert-deftest test-wttrin--cleanup-cache-if-needed-boundary-one-over-max-removes-oldest ()
+ "Test cleanup when cache has just one entry over max."
+ (test-wttrin--cleanup-cache-if-needed-setup)
+ (unwind-protect
+ (testutil-wttrin-with-cache-max 5
+ ;; Add 6 entries (1 over max)
+ (dotimes (i 6)
+ (testutil-wttrin-add-to-cache (format "loc%d" i) (format "data-%d" i) (* (- 6 i) 100)))
+ (should (= 6 (testutil-wttrin-cache-size)))
+ (wttrin--cleanup-cache-if-needed)
+ ;; Should remove 20% of 6 = 1.2 = 1 entry (floor)
+ (should (= 5 (testutil-wttrin-cache-size))))
+ (test-wttrin--cleanup-cache-if-needed-teardown)))
+
+(ert-deftest test-wttrin--cleanup-cache-if-needed-normal-large-dataset-removes-20-percent ()
+ "Test that cleanup removes approximately 20% of entries with larger dataset."
+ (test-wttrin--cleanup-cache-if-needed-setup)
+ (unwind-protect
+ (testutil-wttrin-with-cache-max 10
+ ;; Add 20 entries (twice the max)
+ (dotimes (i 20)
+ (testutil-wttrin-add-to-cache (format "loc%d" i) (format "data-%d" i) (* (- 20 i) 100)))
+ (should (= 20 (testutil-wttrin-cache-size)))
+ ;; Trigger cleanup - should remove 20% of 20 = 4 entries
+ (wttrin--cleanup-cache-if-needed)
+ ;; Should have 16 entries remaining (20 - 4)
+ (should (= 16 (testutil-wttrin-cache-size))))
+ (test-wttrin--cleanup-cache-if-needed-teardown)))
+
+(ert-deftest test-wttrin--cleanup-cache-if-needed-boundary-custom-cleanup-percentage-removes-expected ()
+ "Test that custom cleanup percentage is respected."
+ (test-wttrin--cleanup-cache-if-needed-setup)
+ (unwind-protect
+ (let ((wttrin-cache-max-entries 100)
+ (wttrin--cache-cleanup-percentage 0.30)) ; 30% cleanup
+ ;; Add 101 entries
+ (dotimes (i 101)
+ (testutil-wttrin-add-to-cache (format "loc%d" i) "data" (* (- 101 i) 10)))
+ (wttrin--cleanup-cache-if-needed)
+ ;; Should remove floor(101 * 0.30) = 30 oldest entries, leaving 71
+ (should (= 71 (testutil-wttrin-cache-size))))
+ (test-wttrin--cleanup-cache-if-needed-teardown)))
+
+(ert-deftest test-wttrin--cleanup-cache-if-needed-boundary-small-percentage-removes-minimum ()
+ "Test that small cleanup percentage (10%) removes correct number."
+ (test-wttrin--cleanup-cache-if-needed-setup)
+ (unwind-protect
+ (let ((wttrin-cache-max-entries 10)
+ (wttrin--cache-cleanup-percentage 0.10)) ; Only 10%
+ ;; Add 11 entries
+ (dotimes (i 11)
+ (testutil-wttrin-add-to-cache (format "loc%d" i) "data" (* (- 11 i) 100)))
+ (wttrin--cleanup-cache-if-needed)
+ ;; Should remove floor(11 * 0.10) = 1 oldest entry
+ (should (= 10 (testutil-wttrin-cache-size))))
+ (test-wttrin--cleanup-cache-if-needed-teardown)))
+
+(ert-deftest test-wttrin--cleanup-cache-if-needed-normal-multiple-cleanups-work-correctly ()
+ "Test that multiple cleanup cycles work correctly."
+ (test-wttrin--cleanup-cache-if-needed-setup)
+ (unwind-protect
+ (let ((wttrin-cache-max-entries 50)
+ (wttrin--cache-cleanup-percentage 0.20))
+ ;; First batch: 51 entries
+ (dotimes (i 51)
+ (testutil-wttrin-add-to-cache (format "batch1-%d" i) "data" (* (- 51 i) 10)))
+ (wttrin--cleanup-cache-if-needed)
+ (should (= 41 (testutil-wttrin-cache-size)))
+
+ ;; Second batch: add 10 more (now 51 again)
+ (dotimes (i 10)
+ (testutil-wttrin-add-to-cache (format "batch2-%d" i) "data"))
+ (wttrin--cleanup-cache-if-needed)
+ ;; Should cleanup again: floor(51 * 0.20) = 10 removed, leaving 41
+ (should (= 41 (testutil-wttrin-cache-size))))
+ (test-wttrin--cleanup-cache-if-needed-teardown)))
+
(provide 'test-wttrin--cleanup-cache-if-needed)
;;; test-wttrin--cleanup-cache-if-needed.el ends here
diff --git a/tests/test-wttrin--cleanup-cache-refactored.el b/tests/test-wttrin--cleanup-cache-refactored.el
deleted file mode 100644
index 80d1da0..0000000
--- a/tests/test-wttrin--cleanup-cache-refactored.el
+++ /dev/null
@@ -1,182 +0,0 @@
-;;; test-wttrin--cleanup-cache-refactored.el --- Tests for refactored cache cleanup -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2024 Craig Jennings
-
-;;; Commentary:
-;; Tests for refactored wttrin--cleanup-cache-if-needed function.
-;; Verifies that the cleanup logic works correctly when using
-;; the extracted wttrin--get-cache-entries-by-age helper.
-
-;;; Code:
-
-(require 'ert)
-(require 'wttrin)
-
-;;; Normal Cases
-
-(ert-deftest test-wttrin--cleanup-cache-refactored-does-nothing-when-under-limit ()
- "Should not remove entries when cache is below max."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 50))
- ;; Add 40 entries (under the limit of 50)
- (dotimes (i 40)
- (puthash (format "Loc%d|m" i) (cons (float i) "data") wttrin--cache))
-
- (wttrin--cleanup-cache-if-needed)
-
- ;; Should still have all 40 entries
- (should (= 40 (hash-table-count wttrin--cache)))))
-
-(ert-deftest test-wttrin--cleanup-cache-refactored-removes-oldest-entries ()
- "Should remove oldest 20% when cache exceeds max."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 50)
- (wttrin--cache-cleanup-percentage 0.20))
- ;; Add 51 entries (1 over the limit)
- ;; Timestamps: 1000.0, 1001.0, ..., 1050.0
- (dotimes (i 51)
- (puthash (format "Loc%d|m" i)
- (cons (+ 1000.0 i) "data")
- wttrin--cache))
-
- (wttrin--cleanup-cache-if-needed)
-
- ;; Should remove floor(51 * 0.20) = 10 oldest entries
- (should (= 41 (hash-table-count wttrin--cache)))
-
- ;; Oldest entries (Loc0 through Loc9) should be gone
- (should-not (gethash "Loc0|m" wttrin--cache))
- (should-not (gethash "Loc9|m" wttrin--cache))
-
- ;; Newer entries (Loc10 and up) should remain
- (should (gethash "Loc10|m" wttrin--cache))
- (should (gethash "Loc50|m" wttrin--cache))))
-
-(ert-deftest test-wttrin--cleanup-cache-refactored-uses-helper-function ()
- "Should use wttrin--get-cache-entries-by-age for sorting."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 10)
- (wttrin--cache-cleanup-percentage 0.20)
- (helper-called nil))
-
- ;; Add entries in random timestamp order
- (puthash "New|m" (cons 3000.0 "data") wttrin--cache)
- (puthash "Old|m" (cons 1000.0 "data") wttrin--cache)
- (puthash "Mid|m" (cons 2000.0 "data") wttrin--cache)
- (dotimes (i 8)
- (puthash (format "Extra%d|m" i) (cons (+ 1500.0 i) "data") wttrin--cache))
-
- ;; Mock the helper to verify it's called
- (cl-letf (((symbol-function 'wttrin--get-cache-entries-by-age)
- (lambda ()
- (setq helper-called t)
- ;; Return actual sorted entries
- (let ((entries nil))
- (maphash (lambda (k v) (push (cons k (car v)) entries))
- wttrin--cache)
- (sort entries (lambda (a b) (< (cdr a) (cdr b))))))))
-
- (wttrin--cleanup-cache-if-needed)
-
- ;; Verify helper was called (after refactoring)
- ;; Note: This test will pass even before refactoring since we mock it
- ;; The real verification is that cleanup still works correctly
- (should (= 9 (hash-table-count wttrin--cache)))
-
- ;; Oldest entry should be removed
- (should-not (gethash "Old|m" wttrin--cache))
-
- ;; Newer entries should remain
- (should (gethash "Mid|m" wttrin--cache))
- (should (gethash "New|m" wttrin--cache)))))
-
-;;; Boundary Cases
-
-(ert-deftest test-wttrin--cleanup-cache-refactored-exactly-at-max ()
- "Should not cleanup when exactly at max entries."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 50))
- ;; Add exactly 50 entries
- (dotimes (i 50)
- (puthash (format "Loc%d|m" i) (cons (float i) "data") wttrin--cache))
-
- (wttrin--cleanup-cache-if-needed)
-
- ;; Should still have all 50 entries
- (should (= 50 (hash-table-count wttrin--cache)))))
-
-(ert-deftest test-wttrin--cleanup-cache-refactored-respects-cleanup-percentage ()
- "Should use wttrin--cache-cleanup-percentage constant."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 100)
- (wttrin--cache-cleanup-percentage 0.30)) ; 30% cleanup
- ;; Add 101 entries
- (dotimes (i 101)
- (puthash (format "Loc%d|m" i) (cons (float i) "data") wttrin--cache))
-
- (wttrin--cleanup-cache-if-needed)
-
- ;; Should remove floor(101 * 0.30) = 30 oldest entries
- ;; Leaving 71 entries
- (should (= 71 (hash-table-count wttrin--cache)))
-
- ;; First 30 should be gone
- (should-not (gethash "Loc0|m" wttrin--cache))
- (should-not (gethash "Loc29|m" wttrin--cache))
-
- ;; Entry 30 and beyond should remain
- (should (gethash "Loc30|m" wttrin--cache))
- (should (gethash "Loc100|m" wttrin--cache))))
-
-(ert-deftest test-wttrin--cleanup-cache-refactored-small-percentage ()
- "Should handle small cleanup percentages correctly."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 10)
- (wttrin--cache-cleanup-percentage 0.10)) ; Only 10%
- ;; Add 11 entries
- (dotimes (i 11)
- (puthash (format "Loc%d|m" i) (cons (float i) "data") wttrin--cache))
-
- (wttrin--cleanup-cache-if-needed)
-
- ;; Should remove floor(11 * 0.10) = 1 oldest entry
- (should (= 10 (hash-table-count wttrin--cache)))
-
- ;; Oldest should be gone
- (should-not (gethash "Loc0|m" wttrin--cache))
-
- ;; Rest should remain
- (should (gethash "Loc1|m" wttrin--cache))
- (should (gethash "Loc10|m" wttrin--cache))))
-
-;;; Integration Tests
-
-(ert-deftest test-wttrin--cleanup-cache-refactored-multiple-cleanups ()
- "Should handle multiple cleanup cycles correctly."
- (let ((wttrin--cache (make-hash-table :test 'equal))
- (wttrin-cache-max-entries 50)
- (wttrin--cache-cleanup-percentage 0.20))
-
- ;; First batch: 51 entries
- (dotimes (i 51)
- (puthash (format "Batch1-%d|m" i) (cons (+ 1000.0 i) "data") wttrin--cache))
-
- (wttrin--cleanup-cache-if-needed)
- (should (= 41 (hash-table-count wttrin--cache)))
-
- ;; Second batch: add 10 more (now 51 again)
- (dotimes (i 10)
- (puthash (format "Batch2-%d|m" i) (cons (+ 2000.0 i) "data") wttrin--cache))
-
- (wttrin--cleanup-cache-if-needed)
- ;; Should cleanup again: floor(51 * 0.20) = 10 removed, leaving 41
- (should (= 41 (hash-table-count wttrin--cache)))
-
- ;; Oldest from first batch should be gone
- (should-not (gethash "Batch1-0|m" wttrin--cache))
-
- ;; Newest from second batch should remain
- (should (gethash "Batch2-9|m" wttrin--cache))))
-
-(provide 'test-wttrin--cleanup-cache-refactored)
-;;; test-wttrin--cleanup-cache-refactored.el ends here
diff --git a/tests/test-wttrin--display-weather.el b/tests/test-wttrin--display-weather.el
index 4b2a90d..54de04f 100644
--- a/tests/test-wttrin--display-weather.el
+++ b/tests/test-wttrin--display-weather.el
@@ -39,14 +39,12 @@ Weather report: Paris, France
(defun test-wttrin--display-weather-setup ()
"Setup for display weather tests."
(testutil-wttrin-setup)
- ;; Kill any existing weather buffer
(when (get-buffer "*wttr.in*")
(kill-buffer "*wttr.in*")))
(defun test-wttrin--display-weather-teardown ()
"Teardown for display weather tests."
(testutil-wttrin-teardown)
- ;; Clean up weather buffer
(when (get-buffer "*wttr.in*")
(kill-buffer "*wttr.in*")))
@@ -56,7 +54,7 @@ Weather report: Paris, France
"Test that valid weather data creates and displays buffer correctly."
(test-wttrin--display-weather-setup)
(unwind-protect
- (progn
+ (testutil-wttrin-with-clean-weather-buffer
(wttrin--display-weather "Paris, France" test-wttrin--display-weather-sample-raw-data)
;; Buffer should exist
@@ -80,7 +78,7 @@ Weather report: Paris, France
"Test that keybindings are properly set up in weather buffer."
(test-wttrin--display-weather-setup)
(unwind-protect
- (progn
+ (testutil-wttrin-with-clean-weather-buffer
(wttrin--display-weather "London" test-wttrin--display-weather-sample-raw-data)
(with-current-buffer "*wttr.in*"
@@ -95,7 +93,7 @@ Weather report: Paris, France
"Test that help instructions are displayed at bottom of buffer."
(test-wttrin--display-weather-setup)
(unwind-protect
- (progn
+ (testutil-wttrin-with-clean-weather-buffer
(wttrin--display-weather "Tokyo" test-wttrin--display-weather-sample-raw-data)
(with-current-buffer "*wttr.in*"
@@ -114,7 +112,7 @@ Weather report: Paris, France
"Test that empty location name still creates buffer."
(test-wttrin--display-weather-setup)
(unwind-protect
- (progn
+ (testutil-wttrin-with-clean-weather-buffer
(wttrin--display-weather "" test-wttrin--display-weather-sample-raw-data)
;; Buffer should still be created
@@ -129,7 +127,7 @@ Weather report: Paris, France
"Test that location with special characters creates buffer."
(test-wttrin--display-weather-setup)
(unwind-protect
- (progn
+ (testutil-wttrin-with-clean-weather-buffer
(wttrin--display-weather "São Paulo, BR 🌆" test-wttrin--display-weather-sample-raw-data)
(should (get-buffer "*wttr.in*"))
@@ -144,15 +142,13 @@ Weather report: Paris, France
Empty string does not match ERROR pattern, so it's processed as data."
(test-wttrin--display-weather-setup)
(unwind-protect
- (progn
+ (testutil-wttrin-with-clean-weather-buffer
(wttrin--display-weather "Paris" "")
;; Empty string is not treated as error, buffer is created
(should (get-buffer "*wttr.in*"))
(with-current-buffer "*wttr.in*"
- ;; Buffer exists but will have minimal/broken content
- ;; Just verify it was created and made read-only
(should buffer-read-only)))
(test-wttrin--display-weather-teardown)))
@@ -163,7 +159,6 @@ Empty string does not match ERROR pattern, so it's processed as data."
(test-wttrin--display-weather-setup)
(unwind-protect
(progn
- ;; Capture message output
(let ((message-log-max t)
(message-displayed nil))
(cl-letf (((symbol-function 'message)
@@ -198,13 +193,9 @@ Empty string does not match ERROR pattern, so it's processed as data."
(test-wttrin--display-weather-setup)
(unwind-protect
(progn
- ;; Suppress message output
(cl-letf (((symbol-function 'message) (lambda (&rest _) nil)))
(wttrin--display-weather "InvalidCity" nil)
- ;; Buffer should not be created for error case
- ;; (or if it exists from before, it shouldn't be switched to)
- ;; This is testing the error path doesn't create/switch to buffer
(should-not (string-match-p "wttr.in"
(buffer-name (current-buffer))))))
(test-wttrin--display-weather-teardown)))
diff --git a/tests/test-wttrin--extract-response-body.el b/tests/test-wttrin--extract-response-body.el
index 41c3752..5269c8f 100644
--- a/tests/test-wttrin--extract-response-body.el
+++ b/tests/test-wttrin--extract-response-body.el
@@ -10,10 +10,21 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--extract-response-body-setup ()
+ "Setup for extract-response-body tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--extract-response-body-teardown ()
+ "Teardown for extract-response-body tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
-(ert-deftest test-wttrin--extract-response-body-normal-simple-response ()
+(ert-deftest test-wttrin--extract-response-body-normal-simple-response-returns-body ()
"Test extracting body from simple HTTP response."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\r\n")
@@ -23,7 +34,7 @@
(let ((result (wttrin--extract-response-body)))
(should (string= "Weather data" result)))))
-(ert-deftest test-wttrin--extract-response-body-normal-utf8-content ()
+(ert-deftest test-wttrin--extract-response-body-normal-utf8-content-preserves-encoding ()
"Test extracting UTF-8 encoded body with emoji and international characters."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\r\n\r\n")
@@ -33,7 +44,7 @@
(should (string-match-p "中文" result))
(should (string-match-p "مرحبا" result)))))
-(ert-deftest test-wttrin--extract-response-body-normal-multiline-body ()
+(ert-deftest test-wttrin--extract-response-body-normal-multiline-body-returns-all-lines ()
"Test extracting multi-line response body."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\r\n\r\n")
@@ -47,7 +58,7 @@
;;; Boundary Cases
-(ert-deftest test-wttrin--extract-response-body-boundary-empty-body ()
+(ert-deftest test-wttrin--extract-response-body-boundary-empty-body-returns-empty-string ()
"Test extracting empty response body."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\r\n\r\n")
@@ -55,7 +66,7 @@
(let ((result (wttrin--extract-response-body)))
(should (string= "" result)))))
-(ert-deftest test-wttrin--extract-response-body-boundary-large-body ()
+(ert-deftest test-wttrin--extract-response-body-boundary-large-body-returns-full-content ()
"Test extracting large response body."
(let ((large-content (make-string 50000 ?x)))
(with-temp-buffer
@@ -65,7 +76,7 @@
(should (= 50000 (length result)))
(should (string= large-content result))))))
-(ert-deftest test-wttrin--extract-response-body-boundary-unix-line-endings ()
+(ert-deftest test-wttrin--extract-response-body-boundary-unix-line-endings-returns-body ()
"Test extracting body with Unix-style LF line endings."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\n")
@@ -75,7 +86,7 @@
(let ((result (wttrin--extract-response-body)))
(should (string= "Body content" result)))))
-(ert-deftest test-wttrin--extract-response-body-boundary-windows-line-endings ()
+(ert-deftest test-wttrin--extract-response-body-boundary-windows-line-endings-returns-body ()
"Test extracting body with Windows-style CRLF line endings."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\r\n")
@@ -85,7 +96,7 @@
(let ((result (wttrin--extract-response-body)))
(should (string= "Body content" result)))))
-(ert-deftest test-wttrin--extract-response-body-boundary-mixed-line-endings ()
+(ert-deftest test-wttrin--extract-response-body-boundary-mixed-line-endings-returns-body ()
"Test extracting body with mixed LF/CRLF line endings in headers."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\r\n")
@@ -96,7 +107,7 @@
(let ((result (wttrin--extract-response-body)))
(should (string= "Body content" result)))))
-(ert-deftest test-wttrin--extract-response-body-boundary-many-headers ()
+(ert-deftest test-wttrin--extract-response-body-boundary-many-headers-strips-all-headers ()
"Test extracting body with many response headers."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\r\n")
@@ -109,7 +120,7 @@
;; Headers should not be in result
(should-not (string-match-p "Header-" result)))))
-(ert-deftest test-wttrin--extract-response-body-boundary-body-looks-like-headers ()
+(ert-deftest test-wttrin--extract-response-body-boundary-body-looks-like-headers-preserves-body ()
"Test extracting body that contains text resembling HTTP headers."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\r\n\r\n")
@@ -121,7 +132,7 @@
;;; Error Cases
-(ert-deftest test-wttrin--extract-response-body-error-no-header-separator ()
+(ert-deftest test-wttrin--extract-response-body-error-no-header-separator-returns-result ()
"Test handling of response with no header/body separator."
(with-temp-buffer
(insert "HTTP/1.1 200 OK\r\n")
@@ -132,7 +143,7 @@
;; Should return whatever comes after attempting to find separator
(should result))))
-(ert-deftest test-wttrin--extract-response-body-error-empty-buffer ()
+(ert-deftest test-wttrin--extract-response-body-error-empty-buffer-returns-nil-or-empty ()
"Test handling of completely empty buffer."
(with-temp-buffer
;; Empty buffer
@@ -140,7 +151,7 @@
;; Should return empty string or nil without crashing
(should (or (null result) (string= "" result))))))
-(ert-deftest test-wttrin--extract-response-body-error-buffer-kills-cleanly ()
+(ert-deftest test-wttrin--extract-response-body-error-buffer-kills-cleanly-after-extraction ()
"Test that buffer is killed even when processing succeeds."
(let ((buffers-before (buffer-list))
result)
diff --git a/tests/test-wttrin--fetch-url.el b/tests/test-wttrin--fetch-url.el
index e16f787..8c4cdbd 100644
--- a/tests/test-wttrin--fetch-url.el
+++ b/tests/test-wttrin--fetch-url.el
@@ -10,6 +10,17 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--fetch-url-setup ()
+ "Setup for fetch-url tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--fetch-url-teardown ()
+ "Teardown for fetch-url tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
diff --git a/tests/test-wttrin--get-cache-entries-by-age.el b/tests/test-wttrin--get-cache-entries-by-age.el
index c840ac2..e847f88 100644
--- a/tests/test-wttrin--get-cache-entries-by-age.el
+++ b/tests/test-wttrin--get-cache-entries-by-age.el
@@ -10,116 +10,150 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--get-cache-entries-by-age-setup ()
+ "Setup for get-cache-entries-by-age tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--get-cache-entries-by-age-teardown ()
+ "Teardown for get-cache-entries-by-age tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
-(ert-deftest test-wttrin--get-cache-entries-by-age-empty-cache ()
+(ert-deftest test-wttrin--get-cache-entries-by-age-normal-empty-cache-returns-empty-list ()
"Should return empty list when cache is empty."
- (let ((wttrin--cache (make-hash-table :test 'equal)))
- (should (equal '() (wttrin--get-cache-entries-by-age)))))
+ (test-wttrin--get-cache-entries-by-age-setup)
+ (unwind-protect
+ (should (equal '() (wttrin--get-cache-entries-by-age)))
+ (test-wttrin--get-cache-entries-by-age-teardown)))
-(ert-deftest test-wttrin--get-cache-entries-by-age-single-entry ()
+(ert-deftest test-wttrin--get-cache-entries-by-age-normal-single-entry-returns-one-element ()
"Should return single entry in list."
- (let ((wttrin--cache (make-hash-table :test 'equal)))
- (puthash "Paris|m" (cons 1000.0 "weather data") wttrin--cache)
- (let ((result (wttrin--get-cache-entries-by-age)))
- (should (= 1 (length result)))
- (should (equal "Paris|m" (car (car result))))
- (should (= 1000.0 (cdr (car result)))))))
-
-(ert-deftest test-wttrin--get-cache-entries-by-age-sorted-oldest-first ()
+ (test-wttrin--get-cache-entries-by-age-setup)
+ (unwind-protect
+ (progn
+ (puthash "Paris|m" (cons 1000.0 "weather data") wttrin--cache)
+ (let ((result (wttrin--get-cache-entries-by-age)))
+ (should (= 1 (length result)))
+ (should (equal "Paris|m" (car (car result))))
+ (should (= 1000.0 (cdr (car result))))))
+ (test-wttrin--get-cache-entries-by-age-teardown)))
+
+(ert-deftest test-wttrin--get-cache-entries-by-age-normal-multiple-entries-returns-sorted-oldest-first ()
"Should return entries sorted by timestamp, oldest first."
- (let ((wttrin--cache (make-hash-table :test 'equal)))
- ;; Add entries with different timestamps (not in sorted order)
- (puthash "New York|u" (cons 3000.0 "newest data") wttrin--cache)
- (puthash "Paris|m" (cons 1000.0 "oldest data") wttrin--cache)
- (puthash "Tokyo|m" (cons 2000.0 "middle data") wttrin--cache)
-
- (let ((result (wttrin--get-cache-entries-by-age)))
- (should (= 3 (length result)))
- ;; Check order: oldest to newest
- (should (equal "Paris|m" (car (nth 0 result))))
- (should (= 1000.0 (cdr (nth 0 result))))
- (should (equal "Tokyo|m" (car (nth 1 result))))
- (should (= 2000.0 (cdr (nth 1 result))))
- (should (equal "New York|u" (car (nth 2 result))))
- (should (= 3000.0 (cdr (nth 2 result)))))))
-
-(ert-deftest test-wttrin--get-cache-entries-by-age-returns-key-timestamp-pairs ()
+ (test-wttrin--get-cache-entries-by-age-setup)
+ (unwind-protect
+ (progn
+ ;; Add entries with different timestamps (not in sorted order)
+ (puthash "New York|u" (cons 3000.0 "newest data") wttrin--cache)
+ (puthash "Paris|m" (cons 1000.0 "oldest data") wttrin--cache)
+ (puthash "Tokyo|m" (cons 2000.0 "middle data") wttrin--cache)
+
+ (let ((result (wttrin--get-cache-entries-by-age)))
+ (should (= 3 (length result)))
+ ;; Check order: oldest to newest
+ (should (equal "Paris|m" (car (nth 0 result))))
+ (should (= 1000.0 (cdr (nth 0 result))))
+ (should (equal "Tokyo|m" (car (nth 1 result))))
+ (should (= 2000.0 (cdr (nth 1 result))))
+ (should (equal "New York|u" (car (nth 2 result))))
+ (should (= 3000.0 (cdr (nth 2 result))))))
+ (test-wttrin--get-cache-entries-by-age-teardown)))
+
+(ert-deftest test-wttrin--get-cache-entries-by-age-normal-entry-format-returns-key-timestamp-pairs ()
"Should return (key . timestamp) pairs, not full cache values."
- (let ((wttrin--cache (make-hash-table :test 'equal)))
- ;; Cache value is (timestamp . data)
- (puthash "London|m" (cons 1500.0 "complete weather string with lots of data") wttrin--cache)
-
- (let ((result (wttrin--get-cache-entries-by-age)))
- (should (= 1 (length result)))
- (let ((entry (car result)))
- ;; Entry should be (key . timestamp), not (key . (timestamp . data))
- (should (equal "London|m" (car entry)))
- (should (= 1500.0 (cdr entry)))
- ;; Should NOT contain the weather data string
- (should-not (stringp (cdr entry)))))))
+ (test-wttrin--get-cache-entries-by-age-setup)
+ (unwind-protect
+ (progn
+ ;; Cache value is (timestamp . data)
+ (puthash "London|m" (cons 1500.0 "complete weather string with lots of data") wttrin--cache)
+
+ (let ((result (wttrin--get-cache-entries-by-age)))
+ (should (= 1 (length result)))
+ (let ((entry (car result)))
+ ;; Entry should be (key . timestamp), not (key . (timestamp . data))
+ (should (equal "London|m" (car entry)))
+ (should (= 1500.0 (cdr entry)))
+ ;; Should NOT contain the weather data string
+ (should-not (stringp (cdr entry))))))
+ (test-wttrin--get-cache-entries-by-age-teardown)))
;;; Boundary Cases
-(ert-deftest test-wttrin--get-cache-entries-by-age-same-timestamp ()
+(ert-deftest test-wttrin--get-cache-entries-by-age-boundary-same-timestamp-returns-all-entries ()
"Should handle entries with identical timestamps."
- (let ((wttrin--cache (make-hash-table :test 'equal)))
- (puthash "Location1|m" (cons 1000.0 "data1") wttrin--cache)
- (puthash "Location2|m" (cons 1000.0 "data2") wttrin--cache)
- (puthash "Location3|m" (cons 1000.0 "data3") wttrin--cache)
-
- (let ((result (wttrin--get-cache-entries-by-age)))
- (should (= 3 (length result)))
- ;; All should have same timestamp
- (should (= 1000.0 (cdr (nth 0 result))))
- (should (= 1000.0 (cdr (nth 1 result))))
- (should (= 1000.0 (cdr (nth 2 result)))))))
-
-(ert-deftest test-wttrin--get-cache-entries-by-age-float-timestamps ()
+ (test-wttrin--get-cache-entries-by-age-setup)
+ (unwind-protect
+ (progn
+ (puthash "Location1|m" (cons 1000.0 "data1") wttrin--cache)
+ (puthash "Location2|m" (cons 1000.0 "data2") wttrin--cache)
+ (puthash "Location3|m" (cons 1000.0 "data3") wttrin--cache)
+
+ (let ((result (wttrin--get-cache-entries-by-age)))
+ (should (= 3 (length result)))
+ ;; All should have same timestamp
+ (should (= 1000.0 (cdr (nth 0 result))))
+ (should (= 1000.0 (cdr (nth 1 result))))
+ (should (= 1000.0 (cdr (nth 2 result))))))
+ (test-wttrin--get-cache-entries-by-age-teardown)))
+
+(ert-deftest test-wttrin--get-cache-entries-by-age-boundary-float-timestamps-sorts-correctly ()
"Should correctly sort float timestamps with fractional seconds."
- (let ((wttrin--cache (make-hash-table :test 'equal)))
- (puthash "A|m" (cons 1000.123 "data") wttrin--cache)
- (puthash "B|m" (cons 1000.789 "data") wttrin--cache)
- (puthash "C|m" (cons 1000.456 "data") wttrin--cache)
-
- (let ((result (wttrin--get-cache-entries-by-age)))
- ;; Should be sorted: 1000.123 < 1000.456 < 1000.789
- (should (equal "A|m" (car (nth 0 result))))
- (should (equal "C|m" (car (nth 1 result))))
- (should (equal "B|m" (car (nth 2 result)))))))
-
-(ert-deftest test-wttrin--get-cache-entries-by-age-many-entries ()
+ (test-wttrin--get-cache-entries-by-age-setup)
+ (unwind-protect
+ (progn
+ (puthash "A|m" (cons 1000.123 "data") wttrin--cache)
+ (puthash "B|m" (cons 1000.789 "data") wttrin--cache)
+ (puthash "C|m" (cons 1000.456 "data") wttrin--cache)
+
+ (let ((result (wttrin--get-cache-entries-by-age)))
+ ;; Should be sorted: 1000.123 < 1000.456 < 1000.789
+ (should (equal "A|m" (car (nth 0 result))))
+ (should (equal "C|m" (car (nth 1 result))))
+ (should (equal "B|m" (car (nth 2 result))))))
+ (test-wttrin--get-cache-entries-by-age-teardown)))
+
+(ert-deftest test-wttrin--get-cache-entries-by-age-boundary-fifty-entries-returns-sorted ()
"Should handle cache at max capacity (50 entries)."
- (let ((wttrin--cache (make-hash-table :test 'equal)))
- ;; Add 50 entries with timestamps 1000, 1001, 1002, ..., 1049
- (dotimes (i 50)
- (puthash (format "Location%d|m" i)
- (cons (+ 1000.0 i) "data")
- wttrin--cache))
-
- (let ((result (wttrin--get-cache-entries-by-age)))
- (should (= 50 (length result)))
- ;; First should be oldest (1000.0)
- (should (= 1000.0 (cdr (car result))))
- ;; Last should be newest (1049.0)
- (should (= 1049.0 (cdr (car (last result))))))))
+ (test-wttrin--get-cache-entries-by-age-setup)
+ (unwind-protect
+ (progn
+ ;; Add 50 entries with timestamps 1000, 1001, 1002, ..., 1049
+ (dotimes (i 50)
+ (puthash (format "Location%d|m" i)
+ (cons (+ 1000.0 i) "data")
+ wttrin--cache))
+
+ (let ((result (wttrin--get-cache-entries-by-age)))
+ (should (= 50 (length result)))
+ ;; First should be oldest (1000.0)
+ (should (= 1000.0 (cdr (car result))))
+ ;; Last should be newest (1049.0)
+ (should (= 1049.0 (cdr (car (last result)))))))
+ (test-wttrin--get-cache-entries-by-age-teardown)))
;;; Error Cases
-(ert-deftest test-wttrin--get-cache-entries-by-age-preserves-original-cache ()
+(ert-deftest test-wttrin--get-cache-entries-by-age-error-call-preserves-original-cache ()
"Should not modify the original cache hash table."
- (let ((wttrin--cache (make-hash-table :test 'equal)))
- (puthash "Paris|m" (cons 1000.0 "data") wttrin--cache)
- (puthash "Tokyo|m" (cons 2000.0 "data") wttrin--cache)
-
- (let ((original-count (hash-table-count wttrin--cache)))
- (wttrin--get-cache-entries-by-age)
- ;; Cache should still have same number of entries
- (should (= original-count (hash-table-count wttrin--cache)))
- ;; Original entries should still be present
- (should (gethash "Paris|m" wttrin--cache))
- (should (gethash "Tokyo|m" wttrin--cache)))))
+ (test-wttrin--get-cache-entries-by-age-setup)
+ (unwind-protect
+ (progn
+ (puthash "Paris|m" (cons 1000.0 "data") wttrin--cache)
+ (puthash "Tokyo|m" (cons 2000.0 "data") wttrin--cache)
+
+ (let ((original-count (hash-table-count wttrin--cache)))
+ (wttrin--get-cache-entries-by-age)
+ ;; Cache should still have same number of entries
+ (should (= original-count (hash-table-count wttrin--cache)))
+ ;; Original entries should still be present
+ (should (gethash "Paris|m" wttrin--cache))
+ (should (gethash "Tokyo|m" wttrin--cache))))
+ (test-wttrin--get-cache-entries-by-age-teardown)))
(provide 'test-wttrin--get-cache-entries-by-age)
;;; test-wttrin--get-cache-entries-by-age.el ends here
diff --git a/tests/test-wttrin--handle-fetch-callback.el b/tests/test-wttrin--handle-fetch-callback.el
index 203a232..ccd455e 100644
--- a/tests/test-wttrin--handle-fetch-callback.el
+++ b/tests/test-wttrin--handle-fetch-callback.el
@@ -10,10 +10,21 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--handle-fetch-callback-setup ()
+ "Setup for handle-fetch-callback tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--handle-fetch-callback-teardown ()
+ "Teardown for handle-fetch-callback tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
-(ert-deftest test-wttrin--handle-fetch-callback-normal-successful-response ()
+(ert-deftest test-wttrin--handle-fetch-callback-normal-successful-response-calls-callback-with-data ()
"Test handling successful response with callback invocation."
(let ((callback-called nil)
(callback-data nil))
@@ -29,7 +40,7 @@
(should callback-called)
(should (string= "Weather: ☀️ Sunny" callback-data)))))
-(ert-deftest test-wttrin--handle-fetch-callback-normal-empty-response ()
+(ert-deftest test-wttrin--handle-fetch-callback-normal-empty-response-calls-callback-with-empty ()
"Test handling empty but successful response."
(let ((callback-called nil)
(callback-data 'not-nil))
@@ -44,7 +55,7 @@
(should callback-called)
(should (string= "" callback-data)))))
-(ert-deftest test-wttrin--handle-fetch-callback-normal-large-response ()
+(ert-deftest test-wttrin--handle-fetch-callback-normal-large-response-calls-callback-with-full-data ()
"Test handling large response data."
(let ((callback-called nil)
(callback-data nil)
@@ -63,7 +74,7 @@
;;; Boundary Cases
-(ert-deftest test-wttrin--handle-fetch-callback-boundary-nil-status ()
+(ert-deftest test-wttrin--handle-fetch-callback-boundary-nil-status-calls-callback ()
"Test handling nil status (successful response)."
(let ((callback-called nil))
(cl-letf (((symbol-function 'wttrin--extract-response-body)
@@ -75,7 +86,7 @@
(should callback-called))))
-(ert-deftest test-wttrin--handle-fetch-callback-boundary-empty-status-plist ()
+(ert-deftest test-wttrin--handle-fetch-callback-boundary-empty-status-plist-calls-callback ()
"Test handling empty status plist."
(let ((callback-called nil)
(callback-data nil))
@@ -90,7 +101,7 @@
(should callback-called)
(should (string= "data" callback-data)))))
-(ert-deftest test-wttrin--handle-fetch-callback-boundary-status-with-other-keys ()
+(ert-deftest test-wttrin--handle-fetch-callback-boundary-status-with-other-keys-calls-callback ()
"Test handling status with various keys but no error."
(let ((callback-called nil))
(cl-letf (((symbol-function 'wttrin--extract-response-body)
@@ -104,7 +115,7 @@
;;; Error Cases
-(ert-deftest test-wttrin--handle-fetch-callback-error-network-error ()
+(ert-deftest test-wttrin--handle-fetch-callback-error-network-error-calls-callback-with-nil ()
"Test handling network error in status."
(let ((callback-called nil)
(callback-data 'not-nil))
@@ -117,7 +128,7 @@
(should callback-called)
(should (null callback-data))))
-(ert-deftest test-wttrin--handle-fetch-callback-error-http-404 ()
+(ert-deftest test-wttrin--handle-fetch-callback-error-http-404-calls-callback-with-nil ()
"Test handling HTTP error status."
(let ((callback-called nil)
(callback-data 'not-nil))
@@ -130,7 +141,7 @@
(should callback-called)
(should (null callback-data))))
-(ert-deftest test-wttrin--handle-fetch-callback-error-timeout ()
+(ert-deftest test-wttrin--handle-fetch-callback-error-timeout-calls-callback-with-nil ()
"Test handling timeout error."
(let ((callback-called nil)
(callback-data 'not-nil))
@@ -143,7 +154,7 @@
(should callback-called)
(should (null callback-data))))
-(ert-deftest test-wttrin--handle-fetch-callback-error-callback-throws ()
+(ert-deftest test-wttrin--handle-fetch-callback-error-callback-throws-does-not-propagate ()
"Test handling errors thrown by user callback."
(let ((callback-called nil)
(error-caught nil))
@@ -163,7 +174,7 @@
;; Error should be caught and handled, not propagated
(should-not error-caught))))
-(ert-deftest test-wttrin--handle-fetch-callback-error-extract-body-throws ()
+(ert-deftest test-wttrin--handle-fetch-callback-error-extract-body-throws-propagates-error ()
"Test that errors during body extraction are not caught and propagate."
(let ((callback-called nil)
(error-caught nil))
@@ -181,7 +192,7 @@
(should-not callback-called)
(should error-caught))))
-(ert-deftest test-wttrin--handle-fetch-callback-error-nil-data-from-extract ()
+(ert-deftest test-wttrin--handle-fetch-callback-error-nil-data-from-extract-calls-callback-with-nil ()
"Test handling nil data returned from extraction."
(let ((callback-called nil)
(callback-data 'not-nil))
@@ -196,7 +207,7 @@
(should callback-called)
(should (null callback-data)))))
-(ert-deftest test-wttrin--handle-fetch-callback-error-multiple-error-keys ()
+(ert-deftest test-wttrin--handle-fetch-callback-error-multiple-error-keys-calls-callback-with-nil ()
"Test handling status with multiple error indicators."
(let ((callback-called nil)
(callback-data 'not-nil))
diff --git a/tests/test-wttrin--make-cache-key.el b/tests/test-wttrin--make-cache-key.el
index ad2238d..96dfbaf 100644
--- a/tests/test-wttrin--make-cache-key.el
+++ b/tests/test-wttrin--make-cache-key.el
@@ -10,6 +10,17 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--make-cache-key-setup ()
+ "Setup for make-cache-key tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--make-cache-key-teardown ()
+ "Teardown for make-cache-key tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
diff --git a/tests/test-wttrin--mode-line-map.el b/tests/test-wttrin--mode-line-map.el
index 7a1052e..2328300 100644
--- a/tests/test-wttrin--mode-line-map.el
+++ b/tests/test-wttrin--mode-line-map.el
@@ -10,43 +10,54 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--mode-line-map-setup ()
+ "Setup for mode-line-map tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--mode-line-map-teardown ()
+ "Teardown for mode-line-map tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases - Keymap Existence and Structure
-(ert-deftest test-wttrin--mode-line-map-exists ()
+(ert-deftest test-wttrin--mode-line-map-normal-defvar-exists-returns-true ()
"Test that wttrin--mode-line-map defvar exists after refactoring."
(should (boundp 'wttrin--mode-line-map)))
-(ert-deftest test-wttrin--mode-line-map-is-keymap ()
+(ert-deftest test-wttrin--mode-line-map-normal-value-is-keymap-returns-true ()
"Test that wttrin--mode-line-map is actually a keymap."
(should (keymapp wttrin--mode-line-map)))
-(ert-deftest test-wttrin--mode-line-map-is-sparse-keymap ()
+(ert-deftest test-wttrin--mode-line-map-normal-structure-is-sparse-keymap ()
"Test that wttrin--mode-line-map is a sparse keymap."
;; Sparse keymaps start with 'keymap symbol
(should (eq 'keymap (car wttrin--mode-line-map))))
;;; Keybinding Tests
-(ert-deftest test-wttrin--mode-line-map-has-mouse-1-binding ()
+(ert-deftest test-wttrin--mode-line-map-normal-mouse-1-bound-to-click ()
"Test that left-click (mouse-1) is bound to wttrin-mode-line-click."
(let ((binding (lookup-key wttrin--mode-line-map [mode-line mouse-1])))
(should (eq binding 'wttrin-mode-line-click))))
-(ert-deftest test-wttrin--mode-line-map-has-mouse-3-binding ()
+(ert-deftest test-wttrin--mode-line-map-normal-mouse-3-bound-to-force-refresh ()
"Test that right-click (mouse-3) is bound to wttrin-mode-line-force-refresh."
(let ((binding (lookup-key wttrin--mode-line-map [mode-line mouse-3])))
(should (eq binding 'wttrin-mode-line-force-refresh))))
;;; Boundary Cases - Verify No Unexpected Bindings
-(ert-deftest test-wttrin--mode-line-map-no-mouse-2-binding ()
+(ert-deftest test-wttrin--mode-line-map-boundary-mouse-2-has-no-binding ()
"Test that middle-click (mouse-2) has no binding."
(let ((binding (lookup-key wttrin--mode-line-map [mode-line mouse-2])))
;; Should return nil (unbound) or a prefix keymap
(should-not (and binding (symbolp binding)))))
-(ert-deftest test-wttrin--mode-line-map-minimal-bindings ()
+(ert-deftest test-wttrin--mode-line-map-boundary-keymap-has-expected-binding-count ()
"Test that keymap has the expected bindings."
;; Count non-nil entries in keymap
(let ((count 0))
@@ -60,7 +71,7 @@
;;; Integration Test - Verify Mode-Line Uses the Keymap
-(ert-deftest test-wttrin-mode-line-display-uses-shared-keymap ()
+(ert-deftest test-wttrin--mode-line-map-normal-display-uses-shared-keymap ()
"Test that mode-line display uses wttrin--mode-line-map after refactoring.
This test verifies the refactoring eliminated inline keymap construction."
;; Set up minimal mode-line state
diff --git a/tests/test-wttrin--process-weather-content.el b/tests/test-wttrin--process-weather-content.el
index a372f4c..eb845af 100644
--- a/tests/test-wttrin--process-weather-content.el
+++ b/tests/test-wttrin--process-weather-content.el
@@ -11,27 +11,38 @@
(require 'ert)
(require 'wttrin)
(require 'xterm-color)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--process-weather-content-setup ()
+ "Setup for process-weather-content tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--process-weather-content-teardown ()
+ "Teardown for process-weather-content tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
-(ert-deftest test-wttrin--process-weather-content-normal-plain-text ()
+(ert-deftest test-wttrin--process-weather-content-normal-plain-text-returns-unchanged ()
"Test processing plain text without ANSI codes or Location lines."
(let ((input "Weather: Sunny\nTemperature: 20°C"))
(should (string= input (wttrin--process-weather-content input)))))
-(ert-deftest test-wttrin--process-weather-content-normal-removes-location-line ()
+(ert-deftest test-wttrin--process-weather-content-normal-location-line-is-removed ()
"Test that Location line with coordinates is removed."
(let ((input "Weather: Sunny\n Location: Paris [48.8566, 2.3522]\nTemperature: 20°C")
(expected "Weather: Sunny\nTemperature: 20°C"))
(should (string= expected (wttrin--process-weather-content input)))))
-(ert-deftest test-wttrin--process-weather-content-normal-multiple-location-lines ()
+(ert-deftest test-wttrin--process-weather-content-normal-multiple-location-lines-all-removed ()
"Test that multiple Location lines are all removed."
(let ((input "Location: Paris [48.8566, 2.3522]\nWeather: Sunny\n Location: Test [0, 0]\nTemp: 20°C")
(expected "Weather: Sunny\nTemp: 20°C"))
(should (string= expected (wttrin--process-weather-content input)))))
-(ert-deftest test-wttrin--process-weather-content-normal-with-ansi-codes ()
+(ert-deftest test-wttrin--process-weather-content-normal-ansi-codes-are-filtered ()
"Test that ANSI color codes are filtered by xterm-color-filter."
;; xterm-color-filter should process ANSI codes
;; We'll test that the function calls xterm-color-filter by checking
@@ -44,50 +55,50 @@
;;; Boundary Cases
-(ert-deftest test-wttrin--process-weather-content-boundary-empty-string ()
+(ert-deftest test-wttrin--process-weather-content-boundary-empty-string-returns-empty ()
"Test processing empty string."
(should (string= "" (wttrin--process-weather-content ""))))
-(ert-deftest test-wttrin--process-weather-content-boundary-only-location-line ()
+(ert-deftest test-wttrin--process-weather-content-boundary-only-location-line-returns-empty ()
"Test processing string with only a Location line removes it."
(let ((input " Location: Test [0, 0]\n")
(expected ""))
;; Input must have newline for delete-region to work correctly
(should (string= expected (wttrin--process-weather-content input)))))
-(ert-deftest test-wttrin--process-weather-content-boundary-location-without-brackets ()
+(ert-deftest test-wttrin--process-weather-content-boundary-location-without-brackets-preserved ()
"Test that Location line without brackets is not removed (doesn't match pattern)."
(let ((input "Location: Paris\nWeather: Sunny"))
;; Pattern requires [coordinates], so this line should remain
(should (string-match-p "Location: Paris" (wttrin--process-weather-content input)))))
-(ert-deftest test-wttrin--process-weather-content-boundary-location-case-insensitive ()
+(ert-deftest test-wttrin--process-weather-content-boundary-location-case-insensitive-removed ()
"Test that 'location' (lowercase) IS removed due to case-insensitive regex."
(let ((input "location: test [0, 0]\nWeather: Sunny"))
;; re-search-forward uses case-fold-search (defaults to t)
;; so lowercase 'location' matches 'Location' pattern
(should-not (string-match-p "location:" (wttrin--process-weather-content input)))))
-(ert-deftest test-wttrin--process-weather-content-boundary-whitespace-variations ()
+(ert-deftest test-wttrin--process-weather-content-boundary-whitespace-variations-removed ()
"Test Location line with various whitespace patterns."
(let ((input " Location: Test [1, 2] \nWeather: Sunny")
(expected "Weather: Sunny"))
(should (string= expected (wttrin--process-weather-content input)))))
-(ert-deftest test-wttrin--process-weather-content-boundary-preserves-non-location-brackets ()
+(ert-deftest test-wttrin--process-weather-content-boundary-non-location-brackets-preserved ()
"Test that lines with brackets but not Location pattern are preserved."
(let ((input "Weather: [Sunny] 20°C\nWind: [Strong]"))
(should (string= input (wttrin--process-weather-content input)))))
;;; Error Cases
-(ert-deftest test-wttrin--process-weather-content-error-handles-malformed-ansi ()
+(ert-deftest test-wttrin--process-weather-content-error-malformed-ansi-returns-string ()
"Test that function handles malformed ANSI codes gracefully."
;; xterm-color-filter should handle this, but we verify no errors occur
(let ((input "\x1b[9999mInvalid ANSI\nNormal text"))
(should (stringp (wttrin--process-weather-content input)))))
-(ert-deftest test-wttrin--process-weather-content-error-very-long-line ()
+(ert-deftest test-wttrin--process-weather-content-error-very-long-line-returns-unchanged ()
"Test processing very long line without errors."
(let ((long-line (make-string 10000 ?x)))
(should (string= long-line (wttrin--process-weather-content long-line)))))
diff --git a/tests/test-wttrin--validate-weather-data.el b/tests/test-wttrin--validate-weather-data.el
index 651fb5c..168c8ef 100644
--- a/tests/test-wttrin--validate-weather-data.el
+++ b/tests/test-wttrin--validate-weather-data.el
@@ -10,62 +10,73 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin--validate-weather-data-setup ()
+ "Setup for validate-weather-data tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin--validate-weather-data-teardown ()
+ "Teardown for validate-weather-data tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
-(ert-deftest test-wttrin--validate-weather-data-normal-valid-weather-string ()
+(ert-deftest test-wttrin--validate-weather-data-normal-valid-weather-string-returns-true ()
"Test that valid weather data string is accepted."
(let ((valid-weather "Weather: 20°C\nCondition: Sunny\nWind: 10 km/h"))
(should (wttrin--validate-weather-data valid-weather))))
-(ert-deftest test-wttrin--validate-weather-data-normal-multiline-weather ()
+(ert-deftest test-wttrin--validate-weather-data-normal-multiline-weather-returns-true ()
"Test that multiline weather data is accepted."
(let ((weather "┌─────────────┐\n│ Weather │\n│ 20°C │\n└─────────────┘"))
(should (wttrin--validate-weather-data weather))))
-(ert-deftest test-wttrin--validate-weather-data-normal-weather-with-unicode ()
+(ert-deftest test-wttrin--validate-weather-data-normal-weather-with-unicode-returns-true ()
"Test that weather data with Unicode characters is accepted."
(let ((weather "Temperature: 20°C ☀️\nWind: 15 km/h 💨"))
(should (wttrin--validate-weather-data weather))))
;;; Boundary Cases
-(ert-deftest test-wttrin--validate-weather-data-boundary-empty-string ()
+(ert-deftest test-wttrin--validate-weather-data-boundary-empty-string-returns-true ()
"Test that empty string is considered valid (though unusual)."
(should (wttrin--validate-weather-data "")))
-(ert-deftest test-wttrin--validate-weather-data-boundary-whitespace-only ()
+(ert-deftest test-wttrin--validate-weather-data-boundary-whitespace-only-returns-true ()
"Test that whitespace-only string is considered valid."
(should (wttrin--validate-weather-data " \n \t ")))
-(ert-deftest test-wttrin--validate-weather-data-boundary-single-character ()
+(ert-deftest test-wttrin--validate-weather-data-boundary-single-character-returns-true ()
"Test that single character string is valid."
(should (wttrin--validate-weather-data "x")))
-(ert-deftest test-wttrin--validate-weather-data-boundary-error-lowercase ()
+(ert-deftest test-wttrin--validate-weather-data-boundary-error-lowercase-returns-nil ()
"Test that lowercase 'error' is rejected (case-insensitive matching)."
;; string-match uses case-fold-search which defaults to t in Emacs
(should-not (wttrin--validate-weather-data "error: connection failed")))
-(ert-deftest test-wttrin--validate-weather-data-boundary-error-in-middle ()
+(ert-deftest test-wttrin--validate-weather-data-boundary-error-in-middle-returns-nil ()
"Test that 'ERROR' anywhere in string causes rejection."
(should-not (wttrin--validate-weather-data "Weather: ERROR occurred while fetching")))
;;; Error Cases
-(ert-deftest test-wttrin--validate-weather-data-error-nil-string ()
+(ert-deftest test-wttrin--validate-weather-data-error-nil-string-returns-nil ()
"Test that nil string is rejected."
(should-not (wttrin--validate-weather-data nil)))
-(ert-deftest test-wttrin--validate-weather-data-error-uppercase-error ()
+(ert-deftest test-wttrin--validate-weather-data-error-uppercase-error-returns-nil ()
"Test that string containing 'ERROR' is rejected."
(should-not (wttrin--validate-weather-data "ERROR: Unable to fetch weather")))
-(ert-deftest test-wttrin--validate-weather-data-error-error-at-start ()
+(ert-deftest test-wttrin--validate-weather-data-error-error-at-start-returns-nil ()
"Test that 'ERROR' at start of string causes rejection."
(should-not (wttrin--validate-weather-data "ERROR 404")))
-(ert-deftest test-wttrin--validate-weather-data-error-error-at-end ()
+(ert-deftest test-wttrin--validate-weather-data-error-error-at-end-returns-nil ()
"Test that 'ERROR' at end of string causes rejection."
(should-not (wttrin--validate-weather-data "Network ERROR")))
diff --git a/tests/test-wttrin-additional-url-params.el b/tests/test-wttrin-additional-url-params.el
index d3cb369..63f87a5 100644
--- a/tests/test-wttrin-additional-url-params.el
+++ b/tests/test-wttrin-additional-url-params.el
@@ -10,6 +10,17 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin-additional-url-params-setup ()
+ "Setup for additional-url-params tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin-additional-url-params-teardown ()
+ "Teardown for additional-url-params tests."
+ (testutil-wttrin-teardown))
;;; Normal Cases
diff --git a/tests/test-wttrin-ansi-color-rendering.el b/tests/test-wttrin-ansi-color-rendering.el
index 656971b..30c5a4d 100644
--- a/tests/test-wttrin-ansi-color-rendering.el
+++ b/tests/test-wttrin-ansi-color-rendering.el
@@ -14,17 +14,6 @@
(require 'xterm-color)
(require 'testutil-wttrin)
-;;; Test Data - Realistic wttr.in response with ANSI codes
-
-(defconst test-wttrin-ansi-sample-with-colors
- "Weather report: Paris
-
-\x1b[38;5;226m \\ /\x1b[0m Partly cloudy
-\x1b[38;5;226m _ /\"\"\x1b[38;5;250m.-.\x1b[0m \x1b[38;5;118m+13\x1b[0m(\x1b[38;5;082m12\x1b[0m) °C
-\x1b[38;5;226m \\_\x1b[38;5;250m( ). \x1b[0m ↑ \x1b[38;5;190m12\x1b[0m km/h
-"
- "Sample weather data with ANSI color codes (escape sequences).")
-
;;; Test Setup and Teardown
(defun test-wttrin-ansi-setup ()
@@ -64,28 +53,28 @@ Returns cons (colored-chars . total-chars)."
;;; Tests
-(ert-deftest test-wttrin-ansi-xterm-color-filter-processes-codes ()
+(ert-deftest test-wttrin-ansi-normal-xterm-color-filter-removes-escape-codes ()
"Test that xterm-color-filter properly processes ANSI codes."
(test-wttrin-ansi-setup)
(unwind-protect
- (let ((filtered (xterm-color-filter test-wttrin-ansi-sample-with-colors)))
+ (let ((filtered (xterm-color-filter testutil-wttrin-sample-ansi-response)))
;; After filtering, ANSI escape codes should be removed
(should-not (test-wttrin-ansi--has-ansi-escape-codes filtered))
;; The filtered text should be shorter (escape codes removed)
- (should (< (length filtered) (length test-wttrin-ansi-sample-with-colors)))
+ (should (< (length filtered) (length testutil-wttrin-sample-ansi-response)))
;; Text should still contain the actual weather content
(should (string-match-p "Paris" filtered))
(should (string-match-p "Partly cloudy" filtered)))
(test-wttrin-ansi-teardown)))
-(ert-deftest test-wttrin-ansi-display-weather-renders-colors ()
+(ert-deftest test-wttrin-ansi-normal-display-weather-renders-colored-text ()
"Test that display-weather properly renders ANSI colors in buffer."
(test-wttrin-ansi-setup)
(unwind-protect
- (progn
- (wttrin--display-weather "Paris" test-wttrin-ansi-sample-with-colors)
+ (testutil-wttrin-with-clean-weather-buffer
+ (wttrin--display-weather "Paris" testutil-wttrin-sample-ansi-response)
(should (get-buffer "*wttr.in*"))
@@ -104,91 +93,85 @@ Returns cons (colored-chars . total-chars)."
(colored (car counts))
(total (cdr counts)))
;; At least some characters should have color properties
- ;; (not all white text)
(should (> colored 0))
-
;; Colored text should be a reasonable portion (not just 2 chars)
- ;; With ANSI codes, we expect at least 10% of text to be colored
(should (> colored (/ total 10))))))
(test-wttrin-ansi-teardown)))
-(ert-deftest test-wttrin-ansi-mode-line-click-renders-colors ()
+(ert-deftest test-wttrin-ansi-normal-mode-line-click-renders-colored-text ()
"Test that clicking mode-line icon renders colors properly.
This reproduces the bug where mode-line click shows white text."
(test-wttrin-ansi-setup)
(unwind-protect
- (let* ((location "Paris")
- (cache-key (wttrin--make-cache-key location))
- (now 1000.0)
- (wttrin-favorite-location location))
-
- ;; Mock the async fetch to return ANSI-coded data
- (cl-letf (((symbol-function 'float-time)
- (lambda () now))
- ((symbol-function 'wttrin-fetch-raw-string)
- (lambda (_location callback)
- (funcall callback test-wttrin-ansi-sample-with-colors)))
- ((symbol-function 'wttrin--cleanup-cache-if-needed)
- (lambda () nil)))
-
- ;; Simulate mode-line click by calling wttrin
- ;; (wttrin-mode-line-click just calls this)
- (wttrin location)
-
- ;; Give async callback time to execute
- ;; (In real execution this is async, but our mock is synchronous)
- (should (get-buffer "*wttr.in*"))
-
- (with-current-buffer "*wttr.in*"
- ;; Buffer should not contain raw ANSI codes
- (let ((buffer-text (buffer-substring-no-properties (point-min) (point-max))))
- (should-not (test-wttrin-ansi--has-ansi-escape-codes buffer-text)))
-
- ;; Check that text has color properties (not all white)
- (let* ((counts (test-wttrin-ansi--count-colored-text))
- (colored (car counts)))
- ;; Should have colored text (proving colors are rendered)
- (should (> colored 0))))))
+ (testutil-wttrin-with-clean-weather-buffer
+ (let* ((location "Paris")
+ (cache-key (wttrin--make-cache-key location))
+ (now 1000.0)
+ (wttrin-favorite-location location))
+
+ ;; Mock the async fetch to return ANSI-coded data
+ (cl-letf (((symbol-function 'float-time)
+ (lambda () now))
+ ((symbol-function 'wttrin-fetch-raw-string)
+ (lambda (_location callback)
+ (funcall callback testutil-wttrin-sample-ansi-response)))
+ ((symbol-function 'wttrin--cleanup-cache-if-needed)
+ (lambda () nil)))
+
+ (wttrin location)
+
+ (should (get-buffer "*wttr.in*"))
+
+ (with-current-buffer "*wttr.in*"
+ ;; Buffer should not contain raw ANSI codes
+ (let ((buffer-text (buffer-substring-no-properties (point-min) (point-max))))
+ (should-not (test-wttrin-ansi--has-ansi-escape-codes buffer-text)))
+
+ ;; Check that text has color properties (not all white)
+ (let* ((counts (test-wttrin-ansi--count-colored-text))
+ (colored (car counts)))
+ (should (> colored 0)))))))
(test-wttrin-ansi-teardown)))
-(ert-deftest test-wttrin-ansi-cached-data-preserves-colors ()
+(ert-deftest test-wttrin-ansi-normal-cached-data-preserves-color-properties ()
"Test that cached weather data preserves color information.
Verifies that cache doesn't strip ANSI codes or color properties."
(test-wttrin-ansi-setup)
(unwind-protect
- (let* ((location "Berlin")
- (cache-key (wttrin--make-cache-key location))
- (now 1000.0))
-
- ;; Pre-populate cache with ANSI-coded data
- (puthash cache-key (cons now test-wttrin-ansi-sample-with-colors)
- wttrin--cache)
-
- (cl-letf (((symbol-function 'float-time)
- (lambda () (+ now 100.0)))) ; Within TTL
-
- ;; Call wttrin - should use cached data
- (wttrin location)
-
- (should (get-buffer "*wttr.in*"))
-
- (with-current-buffer "*wttr.in*"
- ;; Even with cached data, colors should be rendered
- (let* ((counts (test-wttrin-ansi--count-colored-text))
- (colored (car counts))
- (total (cdr counts)))
- (should (> colored 0))
- ;; Should have reasonable color coverage
- (should (> colored (/ total 10)))))))
+ (testutil-wttrin-with-clean-weather-buffer
+ (let* ((location "Berlin")
+ (cache-key (wttrin--make-cache-key location))
+ (now 1000.0))
+
+ ;; Pre-populate cache with ANSI-coded data
+ (puthash cache-key (cons now testutil-wttrin-sample-ansi-response)
+ wttrin--cache)
+
+ (cl-letf (((symbol-function 'float-time)
+ (lambda () (+ now 100.0)))) ; Within TTL
+
+ ;; Call wttrin - should use cached data
+ (wttrin location)
+
+ (should (get-buffer "*wttr.in*"))
+
+ (with-current-buffer "*wttr.in*"
+ ;; Even with cached data, colors should be rendered
+ (let* ((counts (test-wttrin-ansi--count-colored-text))
+ (colored (car counts))
+ (total (cdr counts)))
+ (should (> colored 0))
+ ;; Should have reasonable color coverage
+ (should (> colored (/ total 10))))))))
(test-wttrin-ansi-teardown)))
-(ert-deftest test-wttrin-ansi-buffer-face-mode-doesnt-strip-colors ()
+(ert-deftest test-wttrin-ansi-boundary-buffer-face-mode-preserves-colors ()
"Test that buffer-face-mode doesn't strip xterm-color face properties.
This reproduces the bug where weather buffer shows mostly white text."
(test-wttrin-ansi-setup)
(unwind-protect
- (progn
- (wttrin--display-weather "Paris" test-wttrin-ansi-sample-with-colors)
+ (testutil-wttrin-with-clean-weather-buffer
+ (wttrin--display-weather "Paris" testutil-wttrin-sample-ansi-response)
(should (get-buffer "*wttr.in*"))
@@ -204,151 +187,122 @@ This reproduces the bug where weather buffer shows mostly white text."
(* 100.0 (/ (float colored) total))
0)))
;; Should have substantial colored text (>10%)
- ;; If this fails with ~5% or less, buffer-face-mode is interfering
(should (> percentage 10.0))
-
- ;; With face-remap-add-relative fix, we get ~30-40 colored chars
- ;; (our test data is small, so absolute count is low)
- ;; The key is the percentage, not absolute count
(should (> colored 20)))))
(test-wttrin-ansi-teardown)))
-(ert-deftest test-wttrin-ansi-mode-line-doesnt-pollute-main-cache ()
+(ert-deftest test-wttrin-ansi-boundary-mode-line-plain-text-cache-shows-content ()
"Test that mode-line weather fetch doesn't pollute main cache with plain text.
This reproduces the bug where clicking mode-line shows white text."
(test-wttrin-ansi-setup)
(unwind-protect
- (let* ((location "Berlin")
- (cache-key (wttrin--make-cache-key location))
- (now 1000.0)
- (plain-text-weather "Berlin: ☀️ +20°C Clear") ; No ANSI codes
- (ansi-weather test-wttrin-ansi-sample-with-colors)) ; Has ANSI codes
-
- ;; Simulate mode-line storing plain-text in cache (the bug)
- ;; This shouldn't happen, but let's verify the system is resilient
- (puthash cache-key (cons now plain-text-weather) wttrin--cache)
-
- (cl-letf (((symbol-function 'float-time)
- (lambda () (+ now 100.0))) ; Within TTL, will use cache
- ((symbol-function 'wttrin-fetch-raw-string)
- (lambda (_location callback)
- ;; Should never be called since cache is fresh
- (error "Should not fetch when cache is fresh"))))
-
- ;; Call wttrin - it will use cached data
- (wttrin location)
-
- (should (get-buffer "*wttr.in*"))
-
- (with-current-buffer "*wttr.in*"
- ;; Even if cache has plain text, should we handle it gracefully?
- ;; At minimum, buffer should exist and not error
- (should (> (buffer-size) 0))
-
- ;; The real fix: mode-line should use separate cache or namespace
- ;; For now, document that plain-text cache = no colors
- (let* ((buffer-text (buffer-substring-no-properties (point-min) (point-max))))
- ;; Should contain the weather data (even if not colored)
- (should (string-match-p "Berlin" buffer-text))))))
+ (testutil-wttrin-with-clean-weather-buffer
+ (let* ((location "Berlin")
+ (cache-key (wttrin--make-cache-key location))
+ (now 1000.0)
+ (plain-text-weather "Berlin: ☀️ +20°C Clear")) ; No ANSI codes
+
+ ;; Simulate mode-line storing plain-text in cache (the bug)
+ (puthash cache-key (cons now plain-text-weather) wttrin--cache)
+
+ (cl-letf (((symbol-function 'float-time)
+ (lambda () (+ now 100.0))) ; Within TTL, will use cache
+ ((symbol-function 'wttrin-fetch-raw-string)
+ (lambda (_location callback)
+ (error "Should not fetch when cache is fresh"))))
+
+ (wttrin location)
+
+ (should (get-buffer "*wttr.in*"))
+
+ (with-current-buffer "*wttr.in*"
+ (should (> (buffer-size) 0))
+ (let* ((buffer-text (buffer-substring-no-properties (point-min) (point-max))))
+ (should (string-match-p "Berlin" buffer-text)))))))
(test-wttrin-ansi-teardown)))
-(ert-deftest test-wttrin-ansi-full-scenario-mode-line-then-click ()
+(ert-deftest test-wttrin-ansi-normal-full-scenario-mode-line-then-click-has-colors ()
"Test full scenario: mode-line fetch, then user clicks to open buffer.
This is the exact user workflow that exposes the bug."
(test-wttrin-ansi-setup)
(unwind-protect
- (let* ((location "Tokyo")
- (now 1000.0)
- (wttrin-favorite-location location)
- (mode-line-fetch-count 0)
- (main-fetch-count 0))
-
- (cl-letf (((symbol-function 'float-time)
- (lambda () now))
- ((symbol-function 'url-retrieve)
- (lambda (url callback)
- ;; Mode-line uses url-retrieve directly
- (setq mode-line-fetch-count (1+ mode-line-fetch-count))
- ;; Simulate async callback with plain-text format
- (with-temp-buffer
- (insert "HTTP/1.1 200 OK\n\n")
- (insert "Tokyo: ☀️ +25°C Clear") ; Plain text, no ANSI
- (funcall callback nil))))
- ((symbol-function 'wttrin-fetch-raw-string)
- (lambda (_location callback)
- ;; Main buffer fetch should get ANSI codes
- (setq main-fetch-count (1+ main-fetch-count))
- (funcall callback test-wttrin-ansi-sample-with-colors)))
- ((symbol-function 'wttrin--cleanup-cache-if-needed)
- (lambda () nil)))
-
- ;; Step 1: Mode-line fetches weather (simulated)
- (wttrin--mode-line-fetch-weather)
- ;; Mode-line should have fetched
- (should (= mode-line-fetch-count 1))
-
- ;; Step 2: User clicks mode-line icon (calls wttrin)
- (wttrin location)
- ;; Main fetch should have happened (cache miss)
- (should (= main-fetch-count 1))
-
- ;; Step 3: Verify buffer has colors
- (should (get-buffer "*wttr.in*"))
-
- (with-current-buffer "*wttr.in*"
- (let* ((counts (test-wttrin-ansi--count-colored-text))
- (colored (car counts))
- (total (cdr counts)))
- ;; Should have colored text
- (should (> colored 0))
- ;; Should be substantial (>10%)
- (should (> colored (/ total 10)))))))
+ (testutil-wttrin-with-clean-weather-buffer
+ (let* ((location "Tokyo")
+ (now 1000.0)
+ (wttrin-favorite-location location)
+ (mode-line-fetch-count 0)
+ (main-fetch-count 0))
+
+ (cl-letf (((symbol-function 'float-time)
+ (lambda () now))
+ ((symbol-function 'url-retrieve)
+ (lambda (url callback)
+ (setq mode-line-fetch-count (1+ mode-line-fetch-count))
+ (with-temp-buffer
+ (insert "HTTP/1.1 200 OK\n\n")
+ (insert "Tokyo: ☀️ +25°C Clear")
+ (funcall callback nil))))
+ ((symbol-function 'wttrin-fetch-raw-string)
+ (lambda (_location callback)
+ (setq main-fetch-count (1+ main-fetch-count))
+ (funcall callback testutil-wttrin-sample-ansi-response)))
+ ((symbol-function 'wttrin--cleanup-cache-if-needed)
+ (lambda () nil)))
+
+ ;; Step 1: Mode-line fetches weather (simulated)
+ (wttrin--mode-line-fetch-weather)
+ (should (= mode-line-fetch-count 1))
+
+ ;; Step 2: User clicks mode-line icon (calls wttrin)
+ (wttrin location)
+ (should (= main-fetch-count 1))
+
+ ;; Step 3: Verify buffer has colors
+ (should (get-buffer "*wttr.in*"))
+
+ (with-current-buffer "*wttr.in*"
+ (let* ((counts (test-wttrin-ansi--count-colored-text))
+ (colored (car counts))
+ (total (cdr counts)))
+ (should (> colored 0))
+ (should (> colored (/ total 10))))))))
(test-wttrin-ansi-teardown)))
-(ert-deftest test-wttrin-ansi-cache-stores-ansi-codes ()
+(ert-deftest test-wttrin-ansi-normal-cache-stores-raw-ansi-codes ()
"Test that cache stores raw ANSI codes, not filtered text.
This verifies the cache workflow preserves ANSI codes for later filtering."
(test-wttrin-ansi-setup)
(unwind-protect
- (let* ((location "Vienna")
- (cache-key (wttrin--make-cache-key location))
- (now 1000.0)
- (fetch-count 0))
-
- (cl-letf (((symbol-function 'float-time)
- (lambda () now))
- ((symbol-function 'wttrin-fetch-raw-string)
- (lambda (_location callback)
- (setq fetch-count (1+ fetch-count))
- ;; Simulate fetch returning ANSI codes
- (funcall callback test-wttrin-ansi-sample-with-colors)))
- ((symbol-function 'wttrin--cleanup-cache-if-needed)
- (lambda () nil)))
-
- ;; Fetch weather (will cache the result)
- (wttrin location)
-
- ;; Verify fetch was called
- (should (= fetch-count 1))
-
- ;; Check what's in the cache
- (let* ((cached (gethash cache-key wttrin--cache))
- (cached-data (cdr cached)))
-
- ;; Cache should have data
- (should cached)
- (should cached-data)
-
- ;; CRITICAL: Cache should store RAW ANSI codes
- ;; NOT filtered text with face properties
- (should (stringp cached-data))
- (should (string-match-p "\x1b\\[" cached-data))
-
- ;; Verify it's the original ANSI string
- (should (string-match-p "Partly cloudy" cached-data)))))
+ (testutil-wttrin-with-clean-weather-buffer
+ (let* ((location "Vienna")
+ (cache-key (wttrin--make-cache-key location))
+ (now 1000.0)
+ (fetch-count 0))
+
+ (cl-letf (((symbol-function 'float-time)
+ (lambda () now))
+ ((symbol-function 'wttrin-fetch-raw-string)
+ (lambda (_location callback)
+ (setq fetch-count (1+ fetch-count))
+ (funcall callback testutil-wttrin-sample-ansi-response)))
+ ((symbol-function 'wttrin--cleanup-cache-if-needed)
+ (lambda () nil)))
+
+ (wttrin location)
+
+ (should (= fetch-count 1))
+
+ (let* ((cached (gethash cache-key wttrin--cache))
+ (cached-data (cdr cached)))
+ (should cached)
+ (should cached-data)
+ ;; CRITICAL: Cache should store RAW ANSI codes
+ (should (stringp cached-data))
+ (should (string-match-p "\x1b\\[" cached-data))
+ (should (string-match-p "Partly cloudy" cached-data))))))
(test-wttrin-ansi-teardown)))
-(ert-deftest test-wttrin-ansi-fetch-returns-ansi-codes ()
+(ert-deftest test-wttrin-ansi-normal-fetch-returns-raw-ansi-codes ()
"Test that wttrin-fetch-raw-string returns data with ANSI codes.
This verifies the fetch function returns unfiltered data from wttr.in."
(test-wttrin-ansi-setup)
@@ -356,38 +310,21 @@ This verifies the fetch function returns unfiltered data from wttr.in."
(let ((location "Prague")
(callback-data nil)
(url-retrieve-called nil)
- (wttrin-unit-system "u")) ; Set unit system for URL generation
+ (wttrin-unit-system "u"))
;; Mock url-retrieve to simulate wttr.in response
- (cl-letf (((symbol-function 'url-retrieve)
- (lambda (url callback)
- (setq url-retrieve-called t)
- ;; Verify URL has ANSI format flag
- ;; wttr.in uses ?mA, ?uA, or ?A for ANSI colored output
- (should (or (string-match-p "\\?mA" url)
- (string-match-p "\\?uA" url)
- (string-match-p "\\?A" url)))
- ;; Simulate HTTP response with ANSI codes
- (with-temp-buffer
- (insert "HTTP/1.1 200 OK\n\n")
- (insert test-wttrin-ansi-sample-with-colors)
- (funcall callback nil)))))
-
- ;; Call fetch
+ (testutil-wttrin-mock-http-response testutil-wttrin-sample-ansi-response
(wttrin-fetch-raw-string
location
(lambda (data)
(setq callback-data data)))
- ;; Verify url-retrieve was called
- (should url-retrieve-called)
-
;; Verify callback received ANSI codes
(should callback-data)
(should (string-match-p "\x1b\\[" callback-data))))
(test-wttrin-ansi-teardown)))
-(ert-deftest test-wttrin-ansi-build-url-includes-ansi-flag ()
+(ert-deftest test-wttrin-ansi-normal-build-url-includes-ansi-flag ()
"Test that wttrin--build-url includes ANSI color flag in URL.
The 'A' flag tells wttr.in to include ANSI color codes in the response."
(test-wttrin-ansi-setup)
@@ -396,19 +333,16 @@ The 'A' flag tells wttr.in to include ANSI color codes in the response."
;; Test with metric system
(let ((wttrin-unit-system "m"))
(let ((url (wttrin--build-url "Berlin")))
- ;; Should have ?mA flag (metric + ANSI)
(should (string-match-p "\\?mA" url))))
;; Test with USCS system
(let ((wttrin-unit-system "u"))
(let ((url (wttrin--build-url "London")))
- ;; Should have ?uA flag (USCS + ANSI)
(should (string-match-p "\\?uA" url))))
;; Test with no unit system
(let ((wttrin-unit-system nil))
(let ((url (wttrin--build-url "Paris")))
- ;; Should have just ?A flag (ANSI)
(should (string-match-p "\\?A" url)))))
(test-wttrin-ansi-teardown)))
diff --git a/tests/test-wttrin-mode-initialization-order.el b/tests/test-wttrin-mode-initialization-order.el
index 20adf06..5ecd4bc 100644
--- a/tests/test-wttrin-mode-initialization-order.el
+++ b/tests/test-wttrin-mode-initialization-order.el
@@ -18,6 +18,8 @@
(require 'wttrin)
(require 'testutil-wttrin)
+;;; Setup and Teardown
+
(defun test-wttrin-mode-init-setup ()
"Setup for mode initialization tests."
(testutil-wttrin-setup)
@@ -30,7 +32,9 @@
(when (get-buffer "*wttr.in*")
(kill-buffer "*wttr.in*")))
-(ert-deftest test-wttrin-mode-initialization-order-mode-before-buffer-local-vars ()
+;;; Tests
+
+(ert-deftest test-wttrin-mode-initialization-order-normal-mode-before-buffer-local-vars-calls-mode-first ()
"Test that wttrin-mode is activated before setting buffer-local variables.
This test verifies the fix for the color rendering bug where xterm-color--state
@@ -43,45 +47,46 @@ The test strategy:
4. Verify wttrin-mode was called BEFORE any buffer-local vars were set"
(test-wttrin-mode-init-setup)
(unwind-protect
- (let ((mode-called-at nil)
- (first-setq-local-at nil)
- (call-counter 0))
-
- ;; Advise to track when wttrin-mode is called
- (cl-letf (((symbol-function 'wttrin-mode)
- (let ((orig-fn (symbol-function 'wttrin-mode)))
- (lambda ()
- (setq mode-called-at (cl-incf call-counter))
- (funcall orig-fn))))
-
- ;; Advise to track first buffer-local variable set
- ((symbol-function 'set)
- (let ((orig-fn (symbol-function 'set)))
- (lambda (symbol value)
- ;; Track xterm-color--state specifically
- (when (and (eq symbol 'xterm-color--state)
- (null first-setq-local-at))
- (setq first-setq-local-at (cl-incf call-counter)))
- (funcall orig-fn symbol value)))))
-
- (wttrin--display-weather "Paris" "Test weather data")
-
- ;; Verify mode was called
- (should mode-called-at)
- ;; Verify buffer-local var was set
- (should first-setq-local-at)
- ;; Critical: mode must be called BEFORE buffer-local var
- (should (< mode-called-at first-setq-local-at))))
+ (testutil-wttrin-with-clean-weather-buffer
+ (let ((mode-called-at nil)
+ (first-setq-local-at nil)
+ (call-counter 0))
+
+ ;; Advise to track when wttrin-mode is called
+ (cl-letf (((symbol-function 'wttrin-mode)
+ (let ((orig-fn (symbol-function 'wttrin-mode)))
+ (lambda ()
+ (setq mode-called-at (cl-incf call-counter))
+ (funcall orig-fn))))
+
+ ;; Advise to track first buffer-local variable set
+ ((symbol-function 'set)
+ (let ((orig-fn (symbol-function 'set)))
+ (lambda (symbol value)
+ ;; Track xterm-color--state specifically
+ (when (and (eq symbol 'xterm-color--state)
+ (null first-setq-local-at))
+ (setq first-setq-local-at (cl-incf call-counter)))
+ (funcall orig-fn symbol value)))))
+
+ (wttrin--display-weather "Paris" "Test weather data")
+
+ ;; Verify mode was called
+ (should mode-called-at)
+ ;; Verify buffer-local var was set
+ (should first-setq-local-at)
+ ;; Critical: mode must be called BEFORE buffer-local var
+ (should (< mode-called-at first-setq-local-at)))))
(test-wttrin-mode-init-teardown)))
-(ert-deftest test-wttrin-mode-initialization-order-xterm-color-state-survives ()
+(ert-deftest test-wttrin-mode-initialization-order-normal-xterm-color-state-survives-mode-init ()
"Test that xterm-color--state remains buffer-local after wttrin--display-weather.
This verifies that the state isn't wiped by kill-all-local-variables."
(test-wttrin-mode-init-setup)
(unwind-protect
- (progn
+ (testutil-wttrin-with-clean-weather-buffer
(wttrin--display-weather "London" "Test data")
(should (get-buffer "*wttr.in*"))
diff --git a/tests/test-wttrin-mode-line-startup-delay.el b/tests/test-wttrin-mode-line-startup-delay.el
new file mode 100644
index 0000000..af98b20
--- /dev/null
+++ b/tests/test-wttrin-mode-line-startup-delay.el
@@ -0,0 +1,56 @@
+;;; test-wttrin-mode-line-startup-delay.el --- Tests for wttrin-mode-line-startup-delay -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025 Craig Jennings
+
+;;; Commentary:
+
+;; Unit tests for wttrin-mode-line-startup-delay defcustom.
+;; Tests that the startup delay variable exists and has reasonable defaults.
+
+;;; Code:
+
+(require 'ert)
+(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin-mode-line-startup-delay-setup ()
+ "Setup for startup delay tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin-mode-line-startup-delay-teardown ()
+ "Teardown for startup delay tests."
+ (testutil-wttrin-teardown))
+
+;;; Normal Cases
+
+(ert-deftest test-wttrin-mode-line-startup-delay-normal-exists ()
+ "Test that wttrin-mode-line-startup-delay defcustom exists."
+ (test-wttrin-mode-line-startup-delay-setup)
+ (unwind-protect
+ (should (boundp 'wttrin-mode-line-startup-delay))
+ (test-wttrin-mode-line-startup-delay-teardown)))
+
+(ert-deftest test-wttrin-mode-line-startup-delay-normal-is-number ()
+ "Test that startup delay is a number."
+ (test-wttrin-mode-line-startup-delay-setup)
+ (unwind-protect
+ (should (numberp wttrin-mode-line-startup-delay))
+ (test-wttrin-mode-line-startup-delay-teardown)))
+
+;;; Boundary Cases
+
+(ert-deftest test-wttrin-mode-line-startup-delay-boundary-reasonable-range ()
+ "Test that startup delay default value is in reasonable range (1-10 seconds)."
+ (test-wttrin-mode-line-startup-delay-setup)
+ (unwind-protect
+ ;; Check the defcustom's standard value, not current runtime value
+ ;; (other tests may set it to 0 for faster testing)
+ (let ((default-value (eval (car (get 'wttrin-mode-line-startup-delay 'standard-value)))))
+ (should (>= default-value 1))
+ (should (<= default-value 10)))
+ (test-wttrin-mode-line-startup-delay-teardown)))
+
+(provide 'test-wttrin-mode-line-startup-delay)
+;;; test-wttrin-mode-line-startup-delay.el ends here
diff --git a/tests/test-wttrin-smoke.el b/tests/test-wttrin-smoke.el
index b3c7eee..2a2d828 100644
--- a/tests/test-wttrin-smoke.el
+++ b/tests/test-wttrin-smoke.el
@@ -18,6 +18,17 @@
(require 'ert)
(require 'wttrin)
+(require 'testutil-wttrin)
+
+;;; Setup and Teardown
+
+(defun test-wttrin-smoke-setup ()
+ "Setup for smoke tests."
+ (testutil-wttrin-setup))
+
+(defun test-wttrin-smoke-teardown ()
+ "Teardown for smoke tests."
+ (testutil-wttrin-teardown))
;;; Package Loading Tests
diff --git a/tests/testutil-wttrin.el b/tests/testutil-wttrin.el
index 0e109ad..685cc09 100644
--- a/tests/testutil-wttrin.el
+++ b/tests/testutil-wttrin.el
@@ -27,6 +27,25 @@
"ERROR: Unknown location; please try ~curl wttr.in/:help"
"Sample error response from wttr.in service.")
+(defconst testutil-wttrin-sample-ansi-response
+ "Weather report: Paris
+
+\x1b[38;5;226m \\ /\x1b[0m Partly cloudy
+\x1b[38;5;226m _ /\"\"\x1b[38;5;250m.-.\x1b[0m \x1b[38;5;118m+13\x1b[0m(\x1b[38;5;082m12\x1b[0m) °C
+\x1b[38;5;226m \\_\x1b[38;5;250m( ). \x1b[0m ↑ \x1b[38;5;190m12\x1b[0m km/h
+"
+ "Sample weather data with ANSI color codes for testing rendering.")
+
+(defconst testutil-wttrin-sample-full-weather
+ "Weather for Berkeley, CA
+
+ \\ / Clear
+ .-. 62 °F
+ ― ( ) ― ↑ 5 mph
+ `-' 10 mi
+ / \\ 0.0 in"
+ "Sample full weather display data for integration tests.")
+
;;; Cache Testing Helpers
(defun testutil-wttrin-clear-cache ()
@@ -65,6 +84,32 @@
`(let ((wttrin-cache-max-entries ,max-entries))
,@body))
+;;; Buffer Management
+
+(defmacro testutil-wttrin-with-clean-weather-buffer (&rest body)
+ "Execute BODY with clean *wttr.in* buffer setup/teardown."
+ (declare (indent 0))
+ `(progn
+ (when (get-buffer "*wttr.in*")
+ (kill-buffer "*wttr.in*"))
+ (unwind-protect
+ (progn ,@body)
+ (when (get-buffer "*wttr.in*")
+ (kill-buffer "*wttr.in*")))))
+
+;;; HTTP Mock Helpers
+
+(defmacro testutil-wttrin-mock-http-response (response-body &rest body)
+ "Mock url-retrieve to return HTTP 200 with RESPONSE-BODY, execute BODY."
+ (declare (indent 1))
+ `(cl-letf (((symbol-function 'url-retrieve)
+ (lambda (url callback)
+ (with-temp-buffer
+ (insert "HTTP/1.1 200 OK\n\n")
+ (insert ,response-body)
+ (funcall callback nil)))))
+ ,@body))
+
;;; Test Setup and Teardown
(defun testutil-wttrin-setup ()