From 28b7e4cecadce207d532b8d42346c3823450a35f Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Tue, 17 Feb 2026 19:14:14 -0600 Subject: refactor: tests: standardize naming, consolidate files, and expand testutil - Expand testutil-wttrin.el with shared fixtures and macros (testutil-wttrin-with-clean-weather-buffer, testutil-wttrin-mock-http-response, sample ANSI/weather constants) - Consolidate cache tests: port unique tests from cleanup-cache-constants and cleanup-cache-refactored into cleanup-cache-if-needed, delete obsolete files - Extract startup-delay tests into dedicated file - Add setup/teardown and (require 'testutil-wttrin) to all test files - Rename all 160 tests to follow test---- convention - Rename integration test file and add detailed docstrings - Update Makefile glob to discover new integration test naming pattern Co-Authored-By: Claude Opus 4.6 --- tests/README-DEBUG-TESTS.md | 82 ------ tests/test-integration-debug.el | 292 +++++++++++++++++++ tests/test-wttrin--add-buffer-instructions.el | 31 +- tests/test-wttrin--build-url.el | 33 ++- tests/test-wttrin--cleanup-cache-constants.el | 146 ---------- tests/test-wttrin--cleanup-cache-if-needed.el | 77 +++++ tests/test-wttrin--cleanup-cache-refactored.el | 182 ------------ tests/test-wttrin--display-weather.el | 21 +- tests/test-wttrin--extract-response-body.el | 37 ++- tests/test-wttrin--fetch-url.el | 11 + tests/test-wttrin--get-cache-entries-by-age.el | 218 ++++++++------ tests/test-wttrin--handle-fetch-callback.el | 37 ++- tests/test-wttrin--make-cache-key.el | 11 + tests/test-wttrin--mode-line-map.el | 27 +- tests/test-wttrin--process-weather-content.el | 35 ++- tests/test-wttrin--validate-weather-data.el | 35 ++- tests/test-wttrin-additional-url-params.el | 11 + tests/test-wttrin-ansi-color-rendering.el | 388 ++++++++++--------------- tests/test-wttrin-integration-with-debug.el | 244 ---------------- tests/test-wttrin-mode-initialization-order.el | 69 +++-- tests/test-wttrin-mode-line-startup-delay.el | 56 ++++ tests/test-wttrin-smoke.el | 11 + tests/testutil-wttrin.el | 45 +++ 23 files changed, 1000 insertions(+), 1099 deletions(-) delete mode 100644 tests/README-DEBUG-TESTS.md create mode 100644 tests/test-integration-debug.el delete mode 100644 tests/test-wttrin--cleanup-cache-constants.el delete mode 100644 tests/test-wttrin--cleanup-cache-refactored.el delete mode 100644 tests/test-wttrin-integration-with-debug.el create mode 100644 tests/test-wttrin-mode-line-startup-delay.el (limited to 'tests') 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-integration-debug.el b/tests/test-integration-debug.el new file mode 100644 index 0000000..db2706b --- /dev/null +++ b/tests/test-integration-debug.el @@ -0,0 +1,292 @@ +;;; test-integration-debug.el --- Integration test with debug enabled -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Craig Jennings + +;;; Commentary: +;; Comprehensive integration test that: +;; 1. Enables debug mode +;; 2. Mocks weather fetch to avoid network calls +;; 3. Tests mode-line display with real weather data +;; 4. Verifies debug log captures key events + +;;; Code: + +(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.") + +;;; Setup and Teardown + +(defun test-integration-debug-setup () + "Set up test environment with debug enabled." + ;; Enable debug mode + (setq wttrin-debug t) + ;; Load debug module if not already loaded + (unless (featurep 'wttrin-debug) + (require 'wttrin-debug)) + ;; Clear any existing debug log + (wttrin-debug-clear-log) + ;; Clear cache + (wttrin-clear-cache) + ;; Set test configuration + (setq wttrin-favorite-location "Berkeley, CA") + (setq wttrin-mode-line-startup-delay 1) ; Minimum valid value + (setq wttrin-unit-system "m")) + +(defun test-integration-debug-teardown () + "Clean up after tests." + (when (boundp 'wttrin-mode-line-mode) + (wttrin-mode-line-mode -1)) + (wttrin-clear-cache) + (wttrin-debug-clear-log) + (setq wttrin-mode-line-string nil) + (setq wttrin--mode-line-tooltip-data nil)) + +;;; Mock URL Fetching + +(defvar test-wttrin--original-fetch-url nil + "Original wttrin--fetch-url function for restoration after test.") + +(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) + (wttrin--debug-log "MOCK-FETCH: Called with URL: %s" url)) + ;; Call callback directly (synchronous) since run-at-time doesn't work well in batch mode + (when (featurep 'wttrin-debug) + (wttrin--debug-log "MOCK-FETCH: Calling callback with mock data")) + (funcall callback test-wttrin-sample-weather-data)) + +(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-integration-debug-mock-fetch) + ,@body) + (fset 'wttrin--fetch-url test-wttrin--original-fetch-url)))) + +;;; Integration Tests + +(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 + (test-integration-debug-with-mocked-fetch + ;; Clear debug log + (wttrin-debug-clear-log) + + ;; Fetch weather for mode-line + (wttrin--mode-line-fetch-weather) + + ;; Wait for async callback to complete (mocked, so should be fast) + (sleep-for 0.1) + + ;; Verify mode-line string was set + (should wttrin-mode-line-string) + (should (stringp wttrin-mode-line-string)) + (should (string-match-p "☀" wttrin-mode-line-string)) ; Should contain emoji + + ;; Verify tooltip data was set + (should wttrin--mode-line-tooltip-data) + (should (string= test-wttrin-sample-weather-data wttrin--mode-line-tooltip-data)) + + ;; Verify debug log captured key events + (let ((log-messages (mapcar #'cdr wttrin--debug-log))) + ;; Should have logged the fetch start + (should (seq-some (lambda (msg) (string-match-p "mode-line-fetch: Starting" msg)) + log-messages)) + ;; Should have logged receiving data + (should (seq-some (lambda (msg) (string-match-p "mode-line-fetch: Received data" msg)) + log-messages)) + ;; Should have logged display update + (should (seq-some (lambda (msg) (string-match-p "mode-line-display:" msg)) + log-messages)) + ;; Should have logged emoji extraction + (should (seq-some (lambda (msg) (string-match-p "Extracted emoji" msg)) + log-messages)))) + (test-integration-debug-teardown))) + +(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 + (wttrin-debug-clear-log) + + ;; Mock full weather fetch (synchronous for batch mode) + (cl-letf (((symbol-function 'wttrin--fetch-url) + (lambda (url callback) + (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 testutil-wttrin-sample-full-weather)))) + + ;; Start the query (now synchronous with mocked fetch) + (wttrin-query "Berkeley, CA") + + ;; Verify buffer was created + (should (get-buffer "*wttr.in*")) + + ;; Verify debug log shows mock was called + (let ((log-messages (mapcar #'cdr wttrin--debug-log))) + ;; Should have logged the mock fetch + (should (seq-some (lambda (msg) (string-match-p "MOCK-FETCH: Full weather query" msg)) + log-messages))))) + ;; Cleanup + (when (get-buffer "*wttr.in*") + (kill-buffer "*wttr.in*")) + (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. + +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 + (test-integration-debug-with-mocked-fetch + ;; Clear debug log + (wttrin-debug-clear-log) + + ;; Enable mode-line mode + (wttrin-mode-line-mode 1) + (should wttrin-mode-line-mode) + + ;; Manually trigger the initial fetch instead of waiting for timer + ;; (timers don't process well in batch mode) + (wttrin--mode-line-fetch-weather) + + ;; Verify mode-line string is set + (should wttrin-mode-line-string) + + ;; Verify global-mode-string contains our widget + (should (member 'wttrin-mode-line-string global-mode-string)) + + ;; Disable mode-line mode + (wttrin-mode-line-mode -1) + (should-not wttrin-mode-line-mode) + + ;; Verify mode-line string is cleared + (should-not wttrin-mode-line-string) + + ;; Verify removed from global-mode-string + (should-not (member 'wttrin-mode-line-string global-mode-string))) + (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. + +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 + (wttrin-debug-clear-log) + + ;; Mock fetch that returns nil (simulating network error) + (cl-letf (((symbol-function 'wttrin--fetch-url) + (lambda (url callback) + (when (featurep 'wttrin-debug) + (wttrin--debug-log "MOCK-FETCH: Simulating error for URL: %s" url)) + (run-at-time 0 nil (lambda () (funcall callback nil)))))) + + ;; Try to fetch (should handle error gracefully) + (wttrin--mode-line-fetch-weather) + + ;; Wait for async callback + (sleep-for 0.1) + + ;; Verify error was logged + (let ((log-messages (mapcar #'cdr wttrin--debug-log))) + (should (seq-some (lambda (msg) (string-match-p "No data received" msg)) + log-messages))))) + (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) + +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 + (wttrin-debug-clear-log) + (wttrin--debug-log "Test message 1") + (wttrin--debug-log "Test message 2 with arg: %s" "value") + + ;; Verify log structure + (should (= 2 (length wttrin--debug-log))) + (should (consp (car wttrin--debug-log))) ; Each entry is (timestamp . message) + (should (stringp (caar wttrin--debug-log))) ; Timestamp is string + (should (stringp (cdar wttrin--debug-log))) ; Message is string + + ;; Verify messages + (let ((messages (mapcar #'cdr wttrin--debug-log))) + (should (member "Test message 1" messages)) + (should (seq-some (lambda (msg) (string-match-p "Test message 2.*value" msg)) + messages))) + + ;; Clear log + (wttrin-debug-clear-log) + (should (= 0 (length wttrin--debug-log)))) + (test-integration-debug-teardown))) + +(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-integration-with-debug.el b/tests/test-wttrin-integration-with-debug.el deleted file mode 100644 index 0330bb3..0000000 --- a/tests/test-wttrin-integration-with-debug.el +++ /dev/null @@ -1,244 +0,0 @@ -;;; test-wttrin-integration-with-debug.el --- Integration test with debug enabled -*- lexical-binding: t; -*- - -;; Copyright (C) 2024 Craig Jennings - -;;; Commentary: -;; Comprehensive integration test that: -;; 1. Enables debug mode -;; 2. Mocks weather fetch to avoid network calls -;; 3. Tests mode-line display with real weather data -;; 4. Verifies debug log captures key events - -;;; Code: - -(require 'ert) -(require '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 () - "Set up test environment with debug enabled." - ;; Enable debug mode - (setq wttrin-debug t) - ;; Load debug module if not already loaded - (unless (featurep 'wttrin-debug) - (require 'wttrin-debug)) - ;; Clear any existing debug log - (wttrin-debug-clear-log) - ;; Clear cache - (wttrin-clear-cache) - ;; Set test configuration - (setq wttrin-favorite-location "Berkeley, CA") - (setq wttrin-mode-line-startup-delay 1) ; Minimum valid value - (setq wttrin-unit-system "m")) - -(defun test-wttrin-teardown () - "Clean up after tests." - (when (boundp 'wttrin-mode-line-mode) - (wttrin-mode-line-mode -1)) - (wttrin-clear-cache) - (wttrin-debug-clear-log) - (setq wttrin-mode-line-string nil) - (setq wttrin--mode-line-tooltip-data nil)) - -;;; Mock URL Fetching - -(defvar test-wttrin--original-fetch-url nil - "Original wttrin--fetch-url function for restoration after test.") - -(defun test-wttrin-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) - (wttrin--debug-log "MOCK-FETCH: Called with URL: %s" url)) - ;; Call callback directly (synchronous) since run-at-time doesn't work well in batch mode - (when (featurep 'wttrin-debug) - (wttrin--debug-log "MOCK-FETCH: Calling callback with mock data")) - (funcall callback test-wttrin-sample-weather-data)) - -(defmacro 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) - ,@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) - (unwind-protect - (with-mocked-fetch - ;; Clear debug log - (wttrin-debug-clear-log) - - ;; Fetch weather for mode-line - (wttrin--mode-line-fetch-weather) - - ;; Wait for async callback to complete (mocked, so should be fast) - (sleep-for 0.1) - - ;; Verify mode-line string was set - (should wttrin-mode-line-string) - (should (stringp wttrin-mode-line-string)) - (should (string-match-p "☀" wttrin-mode-line-string)) ; Should contain emoji - - ;; Verify tooltip data was set - (should wttrin--mode-line-tooltip-data) - (should (string= test-wttrin-sample-weather-data wttrin--mode-line-tooltip-data)) - - ;; Verify debug log captured key events - (let ((log-messages (mapcar #'cdr wttrin--debug-log))) - ;; Should have logged the fetch start - (should (seq-some (lambda (msg) (string-match-p "mode-line-fetch: Starting" msg)) - log-messages)) - ;; Should have logged receiving data - (should (seq-some (lambda (msg) (string-match-p "mode-line-fetch: Received data" msg)) - log-messages)) - ;; Should have logged display update - (should (seq-some (lambda (msg) (string-match-p "mode-line-display:" msg)) - log-messages)) - ;; Should have logged emoji extraction - (should (seq-some (lambda (msg) (string-match-p "Extracted emoji" msg)) - log-messages)))) - (test-wttrin-teardown))) - -(ert-deftest test-wttrin-debug-integration-full-weather-query () - "Integration test: Query full weather and verify debug logging." - (test-wttrin-setup) - (unwind-protect - (progn - ;; Clear debug log - (wttrin-debug-clear-log) - - ;; Mock full weather fetch (synchronous for batch mode) - (cl-letf (((symbol-function 'wttrin--fetch-url) - (lambda (url callback) - (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)))) - - ;; Start the query (now synchronous with mocked fetch) - (wttrin-query "Berkeley, CA") - - ;; Verify buffer was created - (should (get-buffer "*wttr.in*")) - - ;; Verify debug log shows mock was called - (let ((log-messages (mapcar #'cdr wttrin--debug-log))) - ;; Should have logged the mock fetch - (should (seq-some (lambda (msg) (string-match-p "MOCK-FETCH: Full weather query" msg)) - log-messages))))) - ;; Cleanup - (when (get-buffer "*wttr.in*") - (kill-buffer "*wttr.in*")) - (test-wttrin-teardown))) - -(ert-deftest test-wttrin-debug-integration-mode-line-mode-toggle () - "Integration test: Toggle mode-line mode and verify debug logging." - (test-wttrin-setup) - (unwind-protect - (with-mocked-fetch - ;; Clear debug log - (wttrin-debug-clear-log) - - ;; Enable mode-line mode - (wttrin-mode-line-mode 1) - (should wttrin-mode-line-mode) - - ;; Manually trigger the initial fetch instead of waiting for timer - ;; (timers don't process well in batch mode) - (wttrin--mode-line-fetch-weather) - - ;; Verify mode-line string is set - (should wttrin-mode-line-string) - - ;; Verify global-mode-string contains our widget - (should (member 'wttrin-mode-line-string global-mode-string)) - - ;; Disable mode-line mode - (wttrin-mode-line-mode -1) - (should-not wttrin-mode-line-mode) - - ;; Verify mode-line string is cleared - (should-not wttrin-mode-line-string) - - ;; Verify removed from global-mode-string - (should-not (member 'wttrin-mode-line-string global-mode-string))) - (test-wttrin-teardown))) - -(ert-deftest test-wttrin-debug-integration-error-handling () - "Integration test: Verify debug logging captures errors correctly." - (test-wttrin-setup) - (unwind-protect - (progn - ;; Clear debug log - (wttrin-debug-clear-log) - - ;; Mock fetch that returns nil (simulating network error) - (cl-letf (((symbol-function 'wttrin--fetch-url) - (lambda (url callback) - (when (featurep 'wttrin-debug) - (wttrin--debug-log "MOCK-FETCH: Simulating error for URL: %s" url)) - (run-at-time 0 nil (lambda () (funcall callback nil)))))) - - ;; Try to fetch (should handle error gracefully) - (wttrin--mode-line-fetch-weather) - - ;; Wait for async callback - (sleep-for 0.1) - - ;; Verify error was logged - (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))) - -(ert-deftest test-wttrin-debug-integration-log-inspection () - "Integration test: Verify debug log can be inspected programmatically." - (test-wttrin-setup) - (unwind-protect - (progn - ;; Clear and add some test log entries - (wttrin-debug-clear-log) - (wttrin--debug-log "Test message 1") - (wttrin--debug-log "Test message 2 with arg: %s" "value") - - ;; Verify log structure - (should (= 2 (length wttrin--debug-log))) - (should (consp (car wttrin--debug-log))) ; Each entry is (timestamp . message) - (should (stringp (caar wttrin--debug-log))) ; Timestamp is string - (should (stringp (cdar wttrin--debug-log))) ; Message is string - - ;; Verify messages - (let ((messages (mapcar #'cdr wttrin--debug-log))) - (should (member "Test message 1" messages)) - (should (seq-some (lambda (msg) (string-match-p "Test message 2.*value" msg)) - messages))) - - ;; Clear log - (wttrin-debug-clear-log) - (should (= 0 (length wttrin--debug-log)))) - (test-wttrin-teardown))) - -(provide 'test-wttrin-integration-with-debug) -;;; test-wttrin-integration-with-debug.el ends here 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 () -- cgit v1.2.3