aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-wttrin--format-location-line.el35
-rw-r--r--tests/test-wttrin-geolocation-command.el161
-rw-r--r--tests/test-wttrin-geolocation-sentinel.el2
-rw-r--r--tests/test-wttrin-use-current-location.el73
4 files changed, 270 insertions, 1 deletions
diff --git a/tests/test-wttrin--format-location-line.el b/tests/test-wttrin--format-location-line.el
new file mode 100644
index 0000000..28a9af8
--- /dev/null
+++ b/tests/test-wttrin--format-location-line.el
@@ -0,0 +1,35 @@
+;;; test-wttrin--format-location-line.el --- Tests for the buffer Location line -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024-2026 Craig Jennings
+
+;;; Commentary:
+;; Unit tests for `wttrin--format-location-line', the "Location: ADDRESS" line
+;; shown in the weather buffer when a geolocation command supplies an address.
+
+;;; Code:
+
+(require 'ert)
+(require 'wttrin)
+
+(ert-deftest test-wttrin--format-location-line-normal-builds-line ()
+ "Normal: a non-empty address becomes a \"Location: ...\" line."
+ (let ((line (wttrin--format-location-line "Westerly, Rhode Island, USA")))
+ (should (stringp line))
+ (should (string-prefix-p "Location: Westerly, Rhode Island, USA" line))))
+
+(ert-deftest test-wttrin--format-location-line-normal-uses-face ()
+ "Normal: the line carries the staleness-header face."
+ (let ((line (wttrin--format-location-line "Westerly, RI")))
+ (should (eq 'wttrin-staleness-header
+ (get-text-property 0 'face line)))))
+
+(ert-deftest test-wttrin--format-location-line-boundary-nil-returns-nil ()
+ "Boundary: a nil address yields no line."
+ (should (null (wttrin--format-location-line nil))))
+
+(ert-deftest test-wttrin--format-location-line-boundary-empty-returns-nil ()
+ "Boundary: an empty address yields no line."
+ (should (null (wttrin--format-location-line ""))))
+
+(provide 'test-wttrin--format-location-line)
+;;; test-wttrin--format-location-line.el ends here
diff --git a/tests/test-wttrin-geolocation-command.el b/tests/test-wttrin-geolocation-command.el
new file mode 100644
index 0000000..40ea278
--- /dev/null
+++ b/tests/test-wttrin-geolocation-command.el
@@ -0,0 +1,161 @@
+;;; test-wttrin-geolocation-command.el --- Tests for the external-command geolocation provider -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024-2026 Craig Jennings
+
+;;; Commentary:
+;; Unit tests for the generic external-command geolocation provider:
+;; `wttrin-geolocation--parse-coordinates' (pure JSON -> "LAT,LNG"), the
+;; routing/fallback in `wttrin-geolocation-detect' when
+;; `wttrin-geolocation-command' is set, and an end-to-end run of
+;; `wttrin-geolocation--detect-via-command' against a real shell command.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'wttrin)
+(require 'wttrin-geolocation)
+
+;;; wttrin-geolocation--parse-coordinates
+
+(ert-deftest test-wttrin-geolocation-command-normal-parse-coordinates ()
+ "Normal: numeric lat/lng become a \"LAT,LNG\" string."
+ (should (equal "41.32,-71.81"
+ (wttrin-geolocation--parse-coordinates
+ "{\"lat\": 41.32, \"lng\": -71.81}"))))
+
+(ert-deftest test-wttrin-geolocation-command-boundary-parse-ignores-extra-keys ()
+ "Boundary: extra keys (accuracy, address) are ignored."
+ (should (equal "1.5,2.5"
+ (wttrin-geolocation--parse-coordinates
+ "{\"lat\":1.5,\"lng\":2.5,\"accuracy_m\":11.5,\"address\":\"x\"}"))))
+
+(ert-deftest test-wttrin-geolocation-command-error-parse-malformed-json ()
+ "Error: malformed JSON returns nil."
+ (should (null (wttrin-geolocation--parse-coordinates "not json")))
+ (should (null (wttrin-geolocation--parse-coordinates "")))
+ (should (null (wttrin-geolocation--parse-coordinates nil))))
+
+(ert-deftest test-wttrin-geolocation-command-error-parse-missing-or-nonnumeric ()
+ "Error: missing or non-numeric coordinates return nil."
+ (should (null (wttrin-geolocation--parse-coordinates "{\"lat\":1.0}")))
+ (should (null (wttrin-geolocation--parse-coordinates
+ "{\"lat\":\"x\",\"lng\":\"y\"}"))))
+
+;;; wttrin-geolocation--parse-address
+
+(ert-deftest test-wttrin-geolocation-command-normal-parse-address ()
+ "Normal: the address key is returned verbatim."
+ (should (equal "Westerly, Rhode Island, USA"
+ (wttrin-geolocation--parse-address
+ "{\"lat\":1.0,\"lng\":2.0,\"address\":\"Westerly, Rhode Island, USA\"}"))))
+
+(ert-deftest test-wttrin-geolocation-command-boundary-parse-address-label-synonym ()
+ "Boundary: a `label' key is accepted when `address' is absent."
+ (should (equal "Westerly, RI"
+ (wttrin-geolocation--parse-address
+ "{\"lat\":1.0,\"lng\":2.0,\"label\":\"Westerly, RI\"}"))))
+
+(ert-deftest test-wttrin-geolocation-command-error-parse-address-absent ()
+ "Error: no address or label, malformed JSON, or empty string returns nil."
+ (should (null (wttrin-geolocation--parse-address "{\"lat\":1.0,\"lng\":2.0}")))
+ (should (null (wttrin-geolocation--parse-address "not json")))
+ (should (null (wttrin-geolocation--parse-address
+ "{\"lat\":1.0,\"lng\":2.0,\"address\":\"\"}"))))
+
+;;; wttrin-geolocation-detect — routing and fallback
+
+(ert-deftest test-wttrin-geolocation-command-normal-detect-uses-command ()
+ "Normal: with a command set that succeeds, its coordinates are used."
+ (let ((got 'none)
+ (ip-called nil)
+ (wttrin-geolocation-command "ignored-in-mock"))
+ (cl-letf (((symbol-function 'wttrin-geolocation--detect-via-command)
+ (lambda (cb) (funcall cb "1.0,2.0")))
+ ((symbol-function 'wttrin-geolocation--detect-via-ip)
+ (lambda (cb) (setq ip-called t) (funcall cb "City, ST"))))
+ (wttrin-geolocation-detect (lambda (r &optional _a) (setq got r))))
+ (should (equal "1.0,2.0" got))
+ (should-not ip-called)))
+
+(ert-deftest test-wttrin-geolocation-command-boundary-detect-falls-back-to-ip ()
+ "Boundary: with a command that fails (nil), detection falls back to IP."
+ (let ((got 'none)
+ (wttrin-geolocation-command "ignored-in-mock"))
+ (cl-letf (((symbol-function 'wttrin-geolocation--detect-via-command)
+ (lambda (cb) (funcall cb nil)))
+ ((symbol-function 'wttrin-geolocation--detect-via-ip)
+ (lambda (cb) (funcall cb "City, ST"))))
+ (wttrin-geolocation-detect (lambda (r &optional _a) (setq got r))))
+ (should (equal "City, ST" got))))
+
+(ert-deftest test-wttrin-geolocation-command-boundary-detect-no-command-uses-ip ()
+ "Boundary: with no command set, the IP path runs directly."
+ (let ((got 'none)
+ (cmd-called nil)
+ (wttrin-geolocation-command nil))
+ (cl-letf (((symbol-function 'wttrin-geolocation--detect-via-command)
+ (lambda (cb) (setq cmd-called t) (funcall cb "9.0,9.0")))
+ ((symbol-function 'wttrin-geolocation--detect-via-ip)
+ (lambda (cb) (funcall cb "City, ST"))))
+ (wttrin-geolocation-detect (lambda (r &optional _a) (setq got r))))
+ (should (equal "City, ST" got))
+ (should-not cmd-called)))
+
+;;; wttrin-geolocation--detect-via-command — real process
+
+(defun test-wttrin-geolocation-command--run-sync (command)
+ "Run `wttrin-geolocation--detect-via-command' with COMMAND, wait, return coords.
+The address (second callback argument) is ignored here; a separate test covers it."
+ (let ((result 'pending)
+ (wttrin-geolocation-command command))
+ (wttrin-geolocation--detect-via-command
+ (lambda (r &optional _a) (setq result r)))
+ (with-timeout (5 (error "detect-via-command timed out"))
+ (while (eq result 'pending)
+ (accept-process-output nil 0.05)))
+ result))
+
+(ert-deftest test-wttrin-geolocation-command-integration-real-command-success ()
+ "Integration: a command printing lat/lng JSON resolves to \"LAT,LNG\".
+Components: wttrin-geolocation--detect-via-command (real make-process),
+the shell (real), wttrin-geolocation--parse-coordinates (real)."
+ (should (equal "1.5,2.5"
+ (test-wttrin-geolocation-command--run-sync
+ "echo '{\"lat\":1.5,\"lng\":2.5}'"))))
+
+(ert-deftest test-wttrin-geolocation-command-integration-real-command-passes-address ()
+ "Integration: a command printing lat/lng plus an address passes both to the callback.
+Components: detect-via-command (real make-process), the shell, the coord and
+address parsers (real)."
+ (let ((coords 'pending)
+ (address 'pending)
+ (wttrin-geolocation-command
+ "echo '{\"lat\":1.5,\"lng\":2.5,\"address\":\"Westerly, RI\"}'"))
+ (wttrin-geolocation--detect-via-command
+ (lambda (c &optional a) (setq coords c address a)))
+ (with-timeout (5 (error "detect-via-command timed out"))
+ (while (eq coords 'pending)
+ (accept-process-output nil 0.05)))
+ (should (equal "1.5,2.5" coords))
+ (should (equal "Westerly, RI" address))))
+
+(ert-deftest test-wttrin-geolocation-command-integration-real-command-nonzero-exit ()
+ "Integration: a command exiting non-zero yields nil (caller falls back to IP)."
+ (should (null (test-wttrin-geolocation-command--run-sync "exit 1"))))
+
+(ert-deftest test-wttrin-geolocation-command-integration-real-command-bad-output ()
+ "Integration: a zero-exit command printing non-JSON yields nil."
+ (should (null (test-wttrin-geolocation-command--run-sync "echo not-json"))))
+
+(ert-deftest test-wttrin-geolocation-command-error-spawn-failure-yields-nil ()
+ "Error: when the process cannot spawn, the callback receives nil."
+ (let ((result 'pending)
+ (wttrin-geolocation-command "whatever"))
+ (cl-letf (((symbol-function 'make-process)
+ (lambda (&rest _) (error "spawn failed"))))
+ (wttrin-geolocation--detect-via-command (lambda (r) (setq result r))))
+ (should (null result))))
+
+(provide 'test-wttrin-geolocation-command)
+;;; test-wttrin-geolocation-command.el ends here
diff --git a/tests/test-wttrin-geolocation-sentinel.el b/tests/test-wttrin-geolocation-sentinel.el
index 0536173..61d8997 100644
--- a/tests/test-wttrin-geolocation-sentinel.el
+++ b/tests/test-wttrin-geolocation-sentinel.el
@@ -115,7 +115,7 @@ through `wttrin--query-selection' (smoke test of the entry wrapper)."
(cl-letf (((symbol-function 'wttrin-geolocation-detect)
(lambda (callback) (funcall callback "Austin, TX")))
((symbol-function 'wttrin-query)
- (lambda (loc) (setq captured loc)))
+ (lambda (loc &optional _address) (setq captured loc)))
((symbol-function 'message) (lambda (&rest _) nil)))
(wttrin--query-selection wttrin--geolocation-sentinel))
(should (equal "Austin, TX" captured)))
diff --git a/tests/test-wttrin-use-current-location.el b/tests/test-wttrin-use-current-location.el
new file mode 100644
index 0000000..2b08448
--- /dev/null
+++ b/tests/test-wttrin-use-current-location.el
@@ -0,0 +1,73 @@
+;;; test-wttrin-use-current-location.el --- Tests for wttrin-use-current-location -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024-2026 Craig Jennings
+
+;;; Commentary:
+;; Unit tests for the `wttrin-use-current-location' command, the labeled way to
+;; set `wttrin-favorite-location' to t (always auto-detect) instead of typing
+;; the bare symbol. Mocks `yes-or-no-p' and `message'; touches no network.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'wttrin)
+
+;;; Setup and Teardown
+
+(defvar test-wttrin-use-current-location--saved nil
+ "Snapshot of `wttrin-favorite-location' restored in teardown.")
+
+(defun test-wttrin-use-current-location-setup ()
+ "Snapshot `wttrin-favorite-location' and clear it."
+ (setq test-wttrin-use-current-location--saved wttrin-favorite-location)
+ (setq wttrin-favorite-location nil))
+
+(defun test-wttrin-use-current-location-teardown ()
+ "Restore `wttrin-favorite-location'."
+ (setq wttrin-favorite-location test-wttrin-use-current-location--saved))
+
+;;; Normal Cases
+
+(ert-deftest test-wttrin-use-current-location-normal-confirm-sets-t ()
+ "Normal: confirming sets the favorite to t (auto-detect)."
+ (test-wttrin-use-current-location-setup)
+ (unwind-protect
+ (let ((wttrin-geolocation-enabled t))
+ (cl-letf (((symbol-function 'yes-or-no-p) (lambda (&rest _) t))
+ ((symbol-function 'message) (lambda (&rest _) nil)))
+ (wttrin-use-current-location))
+ (should (eq t wttrin-favorite-location)))
+ (test-wttrin-use-current-location-teardown)))
+
+(ert-deftest test-wttrin-use-current-location-normal-decline-leaves-unchanged ()
+ "Normal: declining leaves the favorite untouched."
+ (test-wttrin-use-current-location-setup)
+ (setq wttrin-favorite-location "Berkeley, CA")
+ (unwind-protect
+ (let ((wttrin-geolocation-enabled t))
+ (cl-letf (((symbol-function 'yes-or-no-p) (lambda (&rest _) nil))
+ ((symbol-function 'message) (lambda (&rest _) nil)))
+ (wttrin-use-current-location))
+ (should (equal "Berkeley, CA" wttrin-favorite-location)))
+ (test-wttrin-use-current-location-teardown)))
+
+;;; Boundary / Error Cases
+
+(ert-deftest test-wttrin-use-current-location-boundary-disabled-no-prompt-no-set ()
+ "Boundary: with geolocation disabled, no prompt and the favorite is unchanged."
+ (test-wttrin-use-current-location-setup)
+ (setq wttrin-favorite-location "Berkeley, CA")
+ (unwind-protect
+ (let ((prompted nil)
+ (wttrin-geolocation-enabled nil))
+ (cl-letf (((symbol-function 'yes-or-no-p)
+ (lambda (&rest _) (setq prompted t) t))
+ ((symbol-function 'message) (lambda (&rest _) nil)))
+ (wttrin-use-current-location))
+ (should-not prompted)
+ (should (equal "Berkeley, CA" wttrin-favorite-location)))
+ (test-wttrin-use-current-location-teardown)))
+
+(provide 'test-wttrin-use-current-location)
+;;; test-wttrin-use-current-location.el ends here