diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-wttrin-geolocation--parse-ipapi.el | 83 | ||||
| -rw-r--r-- | tests/test-wttrin-geolocation--parse-ipinfo.el | 61 | ||||
| -rw-r--r-- | tests/test-wttrin-geolocation--parse-ipwhois.el | 63 | ||||
| -rw-r--r-- | tests/test-wttrin-geolocation-detect.el | 179 | ||||
| -rw-r--r-- | tests/test-wttrin-set-location-from-geolocation.el | 122 |
5 files changed, 508 insertions, 0 deletions
diff --git a/tests/test-wttrin-geolocation--parse-ipapi.el b/tests/test-wttrin-geolocation--parse-ipapi.el new file mode 100644 index 0000000..26d67bc --- /dev/null +++ b/tests/test-wttrin-geolocation--parse-ipapi.el @@ -0,0 +1,83 @@ +;;; test-wttrin-geolocation--parse-ipapi.el --- Tests for ipapi.co response parser -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Craig Jennings + +;;; Commentary: +;; Unit tests for `wttrin-geolocation--parse-ipapi'. Pure function — no +;; network, no async. Covers normal, boundary, and error cases per the +;; Normal/Boundary/Error discipline. + +;;; Code: + +(require 'ert) +(require 'wttrin-geolocation) + +;;; Setup and Teardown + +(defun test-wttrin-geolocation--parse-ipapi-setup () + "Setup for ipapi parser tests." + nil) + +(defun test-wttrin-geolocation--parse-ipapi-teardown () + "Teardown for ipapi parser tests." + nil) + +;;; Normal Cases + +(ert-deftest test-wttrin-geolocation--parse-ipapi-normal-full-response-returns-formatted-string () + "A full ipapi.co response yields \"City, Region\"." + (let ((json "{\"ip\":\"1.2.3.4\",\"city\":\"Berkeley\",\"region\":\"California\",\"country_name\":\"United States\",\"latitude\":37.87,\"longitude\":-122.27}")) + (should (string= "Berkeley, California" + (wttrin-geolocation--parse-ipapi json))))) + +(ert-deftest test-wttrin-geolocation--parse-ipapi-normal-unicode-preserved () + "Unicode city and region names round-trip through the parser." + (let ((json "{\"city\":\"São Paulo\",\"region\":\"São Paulo\"}")) + (should (string= "São Paulo, São Paulo" + (wttrin-geolocation--parse-ipapi json))))) + +;;; Boundary Cases + +(ert-deftest test-wttrin-geolocation--parse-ipapi-boundary-extra-fields-ignored () + "Unknown fields in the response do not affect the parsed result." + (let ((json "{\"city\":\"Paris\",\"region\":\"Ile-de-France\",\"currency\":\"EUR\",\"asn\":\"AS1234\"}")) + (should (string= "Paris, Ile-de-France" + (wttrin-geolocation--parse-ipapi json))))) + +(ert-deftest test-wttrin-geolocation--parse-ipapi-boundary-multi-word-city-preserved () + "Multi-word city names are preserved verbatim." + (let ((json "{\"city\":\"New Orleans\",\"region\":\"Louisiana\"}")) + (should (string= "New Orleans, Louisiana" + (wttrin-geolocation--parse-ipapi json))))) + +;;; Error Cases + +(ert-deftest test-wttrin-geolocation--parse-ipapi-error-nil-input-returns-nil () + "A nil input string returns nil." + (should-not (wttrin-geolocation--parse-ipapi nil))) + +(ert-deftest test-wttrin-geolocation--parse-ipapi-error-empty-string-returns-nil () + "An empty input string returns nil." + (should-not (wttrin-geolocation--parse-ipapi ""))) + +(ert-deftest test-wttrin-geolocation--parse-ipapi-error-malformed-json-returns-nil () + "Malformed JSON returns nil rather than signalling." + (should-not (wttrin-geolocation--parse-ipapi "{not valid json"))) + +(ert-deftest test-wttrin-geolocation--parse-ipapi-error-missing-city-returns-nil () + "A response without a city field returns nil." + (let ((json "{\"region\":\"California\"}")) + (should-not (wttrin-geolocation--parse-ipapi json)))) + +(ert-deftest test-wttrin-geolocation--parse-ipapi-error-missing-region-returns-nil () + "A response without a region field returns nil." + (let ((json "{\"city\":\"Berkeley\"}")) + (should-not (wttrin-geolocation--parse-ipapi json)))) + +(ert-deftest test-wttrin-geolocation--parse-ipapi-error-api-error-flag-returns-nil () + "An ipapi rate-limit / error response returns nil even if city/region are absent." + (let ((json "{\"error\":true,\"reason\":\"RateLimited\",\"message\":\"wait\"}")) + (should-not (wttrin-geolocation--parse-ipapi json)))) + +(provide 'test-wttrin-geolocation--parse-ipapi) +;;; test-wttrin-geolocation--parse-ipapi.el ends here diff --git a/tests/test-wttrin-geolocation--parse-ipinfo.el b/tests/test-wttrin-geolocation--parse-ipinfo.el new file mode 100644 index 0000000..4543e32 --- /dev/null +++ b/tests/test-wttrin-geolocation--parse-ipinfo.el @@ -0,0 +1,61 @@ +;;; test-wttrin-geolocation--parse-ipinfo.el --- Tests for ipinfo.io response parser -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Craig Jennings + +;;; Commentary: +;; Unit tests for `wttrin-geolocation--parse-ipinfo'. Pure function — no +;; network, no async. + +;;; Code: + +(require 'ert) +(require 'wttrin-geolocation) + +;;; Setup and Teardown + +(defun test-wttrin-geolocation--parse-ipinfo-setup () + "Setup for ipinfo parser tests." + nil) + +(defun test-wttrin-geolocation--parse-ipinfo-teardown () + "Teardown for ipinfo parser tests." + nil) + +;;; Normal Cases + +(ert-deftest test-wttrin-geolocation--parse-ipinfo-normal-full-response-returns-formatted-string () + "A full ipinfo.io response yields \"City, Region\"." + (let ((json "{\"ip\":\"8.8.8.8\",\"city\":\"Mountain View\",\"region\":\"California\",\"country\":\"US\",\"loc\":\"37.3860,-122.0838\",\"org\":\"AS15169 Google LLC\",\"postal\":\"94035\",\"timezone\":\"America/Los_Angeles\"}")) + (should (string= "Mountain View, California" + (wttrin-geolocation--parse-ipinfo json))))) + +;;; Boundary Cases + +(ert-deftest test-wttrin-geolocation--parse-ipinfo-boundary-minimal-fields-works () + "A response with only city and region parses correctly." + (let ((json "{\"city\":\"Berlin\",\"region\":\"Berlin\"}")) + (should (string= "Berlin, Berlin" + (wttrin-geolocation--parse-ipinfo json))))) + +;;; Error Cases + +(ert-deftest test-wttrin-geolocation--parse-ipinfo-error-nil-input-returns-nil () + "A nil input string returns nil." + (should-not (wttrin-geolocation--parse-ipinfo nil))) + +(ert-deftest test-wttrin-geolocation--parse-ipinfo-error-malformed-json-returns-nil () + "Malformed JSON returns nil rather than signalling." + (should-not (wttrin-geolocation--parse-ipinfo "not json at all"))) + +(ert-deftest test-wttrin-geolocation--parse-ipinfo-error-missing-city-returns-nil () + "A response without a city field returns nil." + (let ((json "{\"region\":\"California\",\"country\":\"US\"}")) + (should-not (wttrin-geolocation--parse-ipinfo json)))) + +(ert-deftest test-wttrin-geolocation--parse-ipinfo-error-missing-region-returns-nil () + "A response without a region field returns nil." + (let ((json "{\"city\":\"Mountain View\",\"country\":\"US\"}")) + (should-not (wttrin-geolocation--parse-ipinfo json)))) + +(provide 'test-wttrin-geolocation--parse-ipinfo) +;;; test-wttrin-geolocation--parse-ipinfo.el ends here diff --git a/tests/test-wttrin-geolocation--parse-ipwhois.el b/tests/test-wttrin-geolocation--parse-ipwhois.el new file mode 100644 index 0000000..14c8119 --- /dev/null +++ b/tests/test-wttrin-geolocation--parse-ipwhois.el @@ -0,0 +1,63 @@ +;;; test-wttrin-geolocation--parse-ipwhois.el --- Tests for ipwho.is response parser -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Craig Jennings + +;;; Commentary: +;; Unit tests for `wttrin-geolocation--parse-ipwhois'. Pure function — no +;; network, no async. The ipwho.is response carries a `success' flag which +;; is false on rate-limit / lookup failure even when the response is +;; well-formed JSON. + +;;; Code: + +(require 'ert) +(require 'wttrin-geolocation) + +;;; Setup and Teardown + +(defun test-wttrin-geolocation--parse-ipwhois-setup () + "Setup for ipwho.is parser tests." + nil) + +(defun test-wttrin-geolocation--parse-ipwhois-teardown () + "Teardown for ipwho.is parser tests." + nil) + +;;; Normal Cases + +(ert-deftest test-wttrin-geolocation--parse-ipwhois-normal-success-response-returns-formatted-string () + "A successful ipwho.is response yields \"City, Region\"." + (let ((json "{\"ip\":\"8.8.8.8\",\"success\":true,\"type\":\"IPv4\",\"country\":\"United States\",\"country_code\":\"US\",\"region\":\"California\",\"region_code\":\"CA\",\"city\":\"Mountain View\",\"latitude\":37.386,\"longitude\":-122.0838}")) + (should (string= "Mountain View, California" + (wttrin-geolocation--parse-ipwhois json))))) + +;;; Boundary Cases + +(ert-deftest test-wttrin-geolocation--parse-ipwhois-boundary-unicode-preserved () + "Unicode city and region names round-trip through the parser." + (let ((json "{\"success\":true,\"city\":\"München\",\"region\":\"Bayern\"}")) + (should (string= "München, Bayern" + (wttrin-geolocation--parse-ipwhois json))))) + +;;; Error Cases + +(ert-deftest test-wttrin-geolocation--parse-ipwhois-error-nil-input-returns-nil () + "A nil input string returns nil." + (should-not (wttrin-geolocation--parse-ipwhois nil))) + +(ert-deftest test-wttrin-geolocation--parse-ipwhois-error-success-false-returns-nil () + "A response with `success: false' returns nil even if city/region are present." + (let ((json "{\"success\":false,\"message\":\"rate limit reached\",\"city\":\"Anywhere\",\"region\":\"Anywhere\"}")) + (should-not (wttrin-geolocation--parse-ipwhois json)))) + +(ert-deftest test-wttrin-geolocation--parse-ipwhois-error-missing-city-returns-nil () + "A success response without a city field returns nil." + (let ((json "{\"success\":true,\"region\":\"California\"}")) + (should-not (wttrin-geolocation--parse-ipwhois json)))) + +(ert-deftest test-wttrin-geolocation--parse-ipwhois-error-malformed-json-returns-nil () + "Malformed JSON returns nil rather than signalling." + (should-not (wttrin-geolocation--parse-ipwhois "{\"success\":true,\"city\":"))) + +(provide 'test-wttrin-geolocation--parse-ipwhois) +;;; test-wttrin-geolocation--parse-ipwhois.el ends here diff --git a/tests/test-wttrin-geolocation-detect.el b/tests/test-wttrin-geolocation-detect.el new file mode 100644 index 0000000..ccd113f --- /dev/null +++ b/tests/test-wttrin-geolocation-detect.el @@ -0,0 +1,179 @@ +;;; test-wttrin-geolocation-detect.el --- Tests for wttrin-geolocation-detect -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Craig Jennings + +;;; Commentary: +;; Unit tests for `wttrin-geolocation-detect'. Mocks `url-retrieve' at +;; the boundary — exercises the real extract/parse logic via the selected +;; provider. No network, no async timing. + +;;; Code: + +(require 'ert) +(require 'cl-lib) +(require 'wttrin-geolocation) + +;;; Setup and Teardown + +(defun test-wttrin-geolocation-detect-setup () + "Setup for detect tests — pin provider to ipapi for determinism." + (setq wttrin-geolocation-provider 'ipapi)) + +(defun test-wttrin-geolocation-detect-teardown () + "Teardown for detect tests — restore default provider." + (setq wttrin-geolocation-provider 'ipapi)) + +;;; Helpers + +(defmacro test-wttrin-geolocation-detect--with-http (status body-string &rest body) + "Run BODY with `url-retrieve' mocked to return HTTP STATUS and BODY-STRING. +The mock writes a full HTTP response into a temp buffer and invokes the +retrieval callback with a nil status plist (success)." + (declare (indent 2)) + `(cl-letf (((symbol-function 'url-retrieve) + (lambda (_url callback) + (with-temp-buffer + (insert (format "HTTP/1.1 %d OK\r\n" ,status)) + (insert "Content-Type: application/json\r\n\r\n") + (insert ,body-string) + (funcall callback nil))))) + ,@body)) + +(defmacro test-wttrin-geolocation-detect--with-network-error (&rest body) + "Run BODY with `url-retrieve' mocked to report a network-level error." + (declare (indent 0)) + `(cl-letf (((symbol-function 'url-retrieve) + (lambda (_url callback) + (with-temp-buffer + (funcall callback '(:error (error "Network unreachable"))))))) + ,@body)) + +;;; Normal Cases + +(ert-deftest test-wttrin-geolocation-detect-normal-ipapi-success-callback-receives-location () + "A successful ipapi response leads to the callback receiving \"City, Region\"." + (test-wttrin-geolocation-detect-setup) + (unwind-protect + (let ((result 'unset)) + (test-wttrin-geolocation-detect--with-http 200 + "{\"city\":\"Berkeley\",\"region\":\"California\"}" + (wttrin-geolocation-detect (lambda (loc) (setq result loc)))) + (should (string= "Berkeley, California" result))) + (test-wttrin-geolocation-detect-teardown))) + +(ert-deftest test-wttrin-geolocation-detect-normal-selected-provider-used () + "Switching `wttrin-geolocation-provider' routes through that provider's URL and parser." + (let ((wttrin-geolocation-provider 'ipinfo) + (requested-url nil) + (result nil)) + (cl-letf (((symbol-function 'url-retrieve) + (lambda (url callback) + (setq requested-url url) + (with-temp-buffer + (insert "HTTP/1.1 200 OK\r\n\r\n") + (insert "{\"city\":\"Mountain View\",\"region\":\"California\",\"loc\":\"37.4,-122.0\"}") + (funcall callback nil))))) + (wttrin-geolocation-detect (lambda (loc) (setq result loc)))) + (should (string= "https://ipinfo.io/json" requested-url)) + (should (string= "Mountain View, California" result)))) + +;;; Boundary Cases + +(ert-deftest test-wttrin-geolocation-detect-boundary-parser-returns-nil-callback-gets-nil () + "If the parser rejects the response (missing fields), the callback receives nil." + (test-wttrin-geolocation-detect-setup) + (unwind-protect + (let ((result 'unset)) + (test-wttrin-geolocation-detect--with-http 200 + "{\"city\":\"Berkeley\"}" + (wttrin-geolocation-detect (lambda (loc) (setq result loc)))) + (should-not result)) + (test-wttrin-geolocation-detect-teardown))) + +(ert-deftest test-wttrin-geolocation-detect-boundary-ipapi-rate-limit-error-flag-returns-nil () + "An ipapi rate-limit response (HTTP 200 with error flag) yields nil." + (test-wttrin-geolocation-detect-setup) + (unwind-protect + (let ((result 'unset)) + (test-wttrin-geolocation-detect--with-http 200 + "{\"error\":true,\"reason\":\"RateLimited\"}" + (wttrin-geolocation-detect (lambda (loc) (setq result loc)))) + (should-not result)) + (test-wttrin-geolocation-detect-teardown))) + +;;; Error Cases + +(ert-deftest test-wttrin-geolocation-detect-error-network-failure-calls-callback-with-nil () + "A network-level error surfaces as nil to the callback." + (test-wttrin-geolocation-detect-setup) + (unwind-protect + (let ((called nil) + (result 'unset)) + (test-wttrin-geolocation-detect--with-network-error + (wttrin-geolocation-detect (lambda (loc) + (setq called t) + (setq result loc)))) + (should called) + (should-not result)) + (test-wttrin-geolocation-detect-teardown))) + +(ert-deftest test-wttrin-geolocation-detect-error-http-429-returns-nil () + "An HTTP 429 rate-limit response yields nil." + (test-wttrin-geolocation-detect-setup) + (unwind-protect + (let ((result 'unset)) + (test-wttrin-geolocation-detect--with-http 429 + "{\"error\":\"rate limit\"}" + (wttrin-geolocation-detect (lambda (loc) (setq result loc)))) + (should-not result)) + (test-wttrin-geolocation-detect-teardown))) + +(ert-deftest test-wttrin-geolocation-detect-error-http-500-returns-nil () + "An HTTP 500 server error yields nil." + (test-wttrin-geolocation-detect-setup) + (unwind-protect + (let ((result 'unset)) + (test-wttrin-geolocation-detect--with-http 500 + "Internal Server Error" + (wttrin-geolocation-detect (lambda (loc) (setq result loc)))) + (should-not result)) + (test-wttrin-geolocation-detect-teardown))) + +(ert-deftest test-wttrin-geolocation-detect-error-empty-body-returns-nil () + "An empty response body yields nil." + (test-wttrin-geolocation-detect-setup) + (unwind-protect + (let ((result 'unset)) + (test-wttrin-geolocation-detect--with-http 200 "" + (wttrin-geolocation-detect (lambda (loc) (setq result loc)))) + (should-not result)) + (test-wttrin-geolocation-detect-teardown))) + +(ert-deftest test-wttrin-geolocation-detect-error-malformed-json-returns-nil () + "Malformed JSON in the response body yields nil." + (test-wttrin-geolocation-detect-setup) + (unwind-protect + (let ((result 'unset)) + (test-wttrin-geolocation-detect--with-http 200 "{not valid json" + (wttrin-geolocation-detect (lambda (loc) (setq result loc)))) + (should-not result)) + (test-wttrin-geolocation-detect-teardown))) + +(ert-deftest test-wttrin-geolocation-detect-error-buffer-cleanup-after-success () + "The response buffer is killed after a successful fetch (no leaks)." + (test-wttrin-geolocation-detect-setup) + (unwind-protect + (let ((buffers-before (length (buffer-list)))) + (test-wttrin-geolocation-detect--with-http 200 + "{\"city\":\"Paris\",\"region\":\"IDF\"}" + (wttrin-geolocation-detect #'ignore)) + (should (= buffers-before (length (buffer-list))))) + (test-wttrin-geolocation-detect-teardown))) + +(ert-deftest test-wttrin-geolocation-detect-error-unknown-provider-signals-error () + "Selecting an unknown provider signals a clear error." + (let ((wttrin-geolocation-provider 'nonexistent-provider)) + (should-error (wttrin-geolocation-detect #'ignore) :type 'error))) + +(provide 'test-wttrin-geolocation-detect) +;;; test-wttrin-geolocation-detect.el ends here diff --git a/tests/test-wttrin-set-location-from-geolocation.el b/tests/test-wttrin-set-location-from-geolocation.el new file mode 100644 index 0000000..170d0fb --- /dev/null +++ b/tests/test-wttrin-set-location-from-geolocation.el @@ -0,0 +1,122 @@ +;;; test-wttrin-set-location-from-geolocation.el --- Tests for wttrin-set-location-from-geolocation -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Craig Jennings + +;;; Commentary: +;; Unit tests for the interactive `wttrin-set-location-from-geolocation' +;; command. Mocks `wttrin-geolocation-detect' (to invoke the callback +;; synchronously with a chosen value) and `yes-or-no-p' (to simulate +;; user consent). Does not hit the network and does not prompt. + +;;; Code: + +(require 'ert) +(require 'cl-lib) +(require 'wttrin) +(require 'wttrin-geolocation) + +;;; Setup and Teardown + +(defvar test-wttrin-set-location-from-geolocation--saved-favorite nil + "Snapshot of `wttrin-favorite-location' restored in teardown.") + +(defun test-wttrin-set-location-from-geolocation-setup () + "Snapshot `wttrin-favorite-location' and clear it for the test." + (setq test-wttrin-set-location-from-geolocation--saved-favorite + wttrin-favorite-location) + (setq wttrin-favorite-location nil)) + +(defun test-wttrin-set-location-from-geolocation-teardown () + "Restore `wttrin-favorite-location' to its pre-test value." + (setq wttrin-favorite-location + test-wttrin-set-location-from-geolocation--saved-favorite)) + +;;; Helpers + +(defmacro test-wttrin-set-location--with-detected (location confirm &rest body) + "Run BODY with `wttrin-geolocation-detect' returning LOCATION and `yes-or-no-p' returning CONFIRM." + (declare (indent 2)) + `(cl-letf (((symbol-function 'wttrin-geolocation-detect) + (lambda (callback) (funcall callback ,location))) + ((symbol-function 'yes-or-no-p) + (lambda (&rest _) ,confirm))) + ,@body)) + +;;; Normal Cases + +(ert-deftest test-wttrin-set-location-from-geolocation-normal-confirm-sets-variable () + "Successful detection followed by user confirmation sets the favorite." + (test-wttrin-set-location-from-geolocation-setup) + (unwind-protect + (progn + (test-wttrin-set-location--with-detected "Berkeley, California" t + (wttrin-set-location-from-geolocation)) + (should (string= "Berkeley, California" wttrin-favorite-location))) + (test-wttrin-set-location-from-geolocation-teardown))) + +(ert-deftest test-wttrin-set-location-from-geolocation-normal-decline-leaves-variable-unchanged () + "Successful detection followed by user declining leaves the favorite untouched." + (test-wttrin-set-location-from-geolocation-setup) + (setq wttrin-favorite-location "Pre-existing, Place") + (unwind-protect + (progn + (test-wttrin-set-location--with-detected "Berkeley, California" nil + (wttrin-set-location-from-geolocation)) + (should (string= "Pre-existing, Place" wttrin-favorite-location))) + (test-wttrin-set-location-from-geolocation-teardown))) + +;;; Boundary Cases + +(ert-deftest test-wttrin-set-location-from-geolocation-boundary-unicode-location () + "A Unicode location string round-trips into the favorite variable." + (test-wttrin-set-location-from-geolocation-setup) + (unwind-protect + (progn + (test-wttrin-set-location--with-detected "München, Bayern" t + (wttrin-set-location-from-geolocation)) + (should (string= "München, Bayern" wttrin-favorite-location))) + (test-wttrin-set-location-from-geolocation-teardown))) + +;;; Error Cases + +(ert-deftest test-wttrin-set-location-from-geolocation-error-nil-detection-leaves-variable-unchanged () + "When detection returns nil, the favorite variable is not modified." + (test-wttrin-set-location-from-geolocation-setup) + (setq wttrin-favorite-location "Pre-existing, Place") + (unwind-protect + (progn + (test-wttrin-set-location--with-detected nil t + (wttrin-set-location-from-geolocation)) + (should (string= "Pre-existing, Place" wttrin-favorite-location))) + (test-wttrin-set-location-from-geolocation-teardown))) + +(ert-deftest test-wttrin-set-location-from-geolocation-error-nil-detection-does-not-prompt () + "When detection returns nil, the user is not prompted for confirmation." + (test-wttrin-set-location-from-geolocation-setup) + (unwind-protect + (let ((prompt-called nil)) + (cl-letf (((symbol-function 'wttrin-geolocation-detect) + (lambda (callback) (funcall callback nil))) + ((symbol-function 'yes-or-no-p) + (lambda (&rest _) (setq prompt-called t) t))) + (wttrin-set-location-from-geolocation)) + (should-not prompt-called)) + (test-wttrin-set-location-from-geolocation-teardown))) + +(ert-deftest test-wttrin-set-location-from-geolocation-error-detection-failure-shows-message () + "When detection returns nil, the user sees a diagnostic message." + (test-wttrin-set-location-from-geolocation-setup) + (unwind-protect + (let ((messages nil)) + (cl-letf (((symbol-function 'wttrin-geolocation-detect) + (lambda (callback) (funcall callback nil))) + ((symbol-function 'message) + (lambda (fmt &rest args) + (push (apply #'format fmt args) messages)))) + (wttrin-set-location-from-geolocation)) + (should (cl-some (lambda (m) (string-match-p "[Cc]ould not detect" m)) + messages))) + (test-wttrin-set-location-from-geolocation-teardown))) + +(provide 'test-wttrin-set-location-from-geolocation) +;;; test-wttrin-set-location-from-geolocation.el ends here |
