diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-wttrin--format-location-line.el | 35 | ||||
| -rw-r--r-- | tests/test-wttrin-geolocation-command.el | 161 | ||||
| -rw-r--r-- | tests/test-wttrin-geolocation-sentinel.el | 2 | ||||
| -rw-r--r-- | tests/test-wttrin-use-current-location.el | 73 |
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 |
