aboutsummaryrefslogtreecommitdiff
path: root/tests/test-wttrin--mode-line-forecast.el
blob: c371f945fa0c7eea122187d45093d407186be5aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
;;; test-wttrin--mode-line-forecast.el --- Tests for the tooltip forecast -*- lexical-binding: t; -*-

;; Copyright (C) 2026 Craig Jennings

;;; Commentary:

;; Unit tests for the mode-line tooltip forecast: parsing wttr.in's j1 JSON
;; into a day list, formatting up to `wttrin-mode-line-tooltip-forecast-days'
;; lines, and appending the block to `wttrin--mode-line-tooltip' output.

;;; Code:

(require 'ert)
(require 'wttrin)
(require 'testutil-wttrin)

;;; Fixture

(defconst test-wttrin--forecast-j1
  "{\"weather\": [
     {\"date\": \"2026-07-01\", \"mintempC\": \"22\", \"maxtempC\": \"31\",
      \"mintempF\": \"71\", \"maxtempF\": \"88\",
      \"hourly\": [{\"time\": \"0\", \"weatherDesc\": [{\"value\": \"Clear\"}]},
                   {\"time\": \"1200\", \"weatherDesc\": [{\"value\": \"Partly cloudy\"}]},
                   {\"time\": \"2100\", \"weatherDesc\": [{\"value\": \"Clear\"}]}]},
     {\"date\": \"2026-07-02\", \"mintempC\": \"20\", \"maxtempC\": \"26\",
      \"mintempF\": \"68\", \"maxtempF\": \"79\",
      \"hourly\": [{\"time\": \"1200\", \"weatherDesc\": [{\"value\": \"Light rain\"}]}]},
     {\"date\": \"2026-07-03\", \"mintempC\": \"21\", \"maxtempC\": \"31\",
      \"mintempF\": \"70\", \"maxtempF\": \"88\",
      \"hourly\": [{\"time\": \"1200\", \"weatherDesc\": [{\"value\": \"Sunny\"}]}]}]}"
  "Trimmed wttr.in ?format=j1 response: three days, the keys the code reads.")

(defun test-wttrin--forecast-days ()
  "Return the parsed day list from the fixture."
  (wttrin--forecast-parse test-wttrin--forecast-j1))

;;; --------------------------------------------------------------------------
;;; wttrin--forecast-parse
;;; --------------------------------------------------------------------------

(ert-deftest test-wttrin--forecast-parse-normal-three-days ()
  "Normal: the fixture parses into a list of three day alists."
  (let ((days (test-wttrin--forecast-days)))
    (should (= 3 (length days)))
    (should (equal "2026-07-01" (alist-get 'date (car days))))))

(ert-deftest test-wttrin--forecast-parse-error-malformed-json ()
  "Error: malformed JSON returns nil instead of signaling."
  (should-not (wttrin--forecast-parse "{not json")))

(ert-deftest test-wttrin--forecast-parse-error-missing-weather-key ()
  "Error: JSON without a weather array returns nil."
  (should-not (wttrin--forecast-parse "{\"nearest_area\": []}")))

(ert-deftest test-wttrin--forecast-parse-boundary-nil-and-empty ()
  "Boundary: nil and empty-string input return nil."
  (should-not (wttrin--forecast-parse nil))
  (should-not (wttrin--forecast-parse "")))

;;; --------------------------------------------------------------------------
;;; wttrin--forecast-format
;;; --------------------------------------------------------------------------

(ert-deftest test-wttrin--forecast-format-normal-three-days-fahrenheit ()
  "Normal: unit system \"u\" renders three labeled lines with °F ranges."
  (let* ((wttrin-unit-system "u")
         (text (wttrin--forecast-format (test-wttrin--forecast-days) 3))
         (lines (split-string text "\n")))
    (should (= 3 (length lines)))
    (should (string-match-p "\\`Today 71-88°F Partly cloudy\\'" (nth 0 lines)))
    (should (string-match-p "\\`Tomorrow 68-79°F Light rain\\'" (nth 1 lines)))
    ;; 2026-07-03 is a Friday; the third day is labeled by weekday.
    (should (string-match-p "\\`Fri 70-88°F Sunny\\'" (nth 2 lines)))))

(ert-deftest test-wttrin--forecast-format-normal-metric ()
  "Normal: a non-\"u\" unit system renders °C ranges."
  (let* ((wttrin-unit-system "m")
         (text (wttrin--forecast-format (test-wttrin--forecast-days) 2)))
    (should (string-match-p "Today 22-31°C" text))
    (should (string-match-p "Tomorrow 20-26°C" text))))

(ert-deftest test-wttrin--forecast-format-boundary-day-counts ()
  "Boundary: 0 days is nil; 1 day is one line; >3 caps at the data."
  (let ((wttrin-unit-system "u")
        (days (test-wttrin--forecast-days)))
    (should-not (wttrin--forecast-format days 0))
    (should (= 1 (length (split-string (wttrin--forecast-format days 1) "\n"))))
    (should (= 3 (length (split-string (wttrin--forecast-format days 5) "\n"))))))

(ert-deftest test-wttrin--forecast-format-boundary-missing-hourly-desc ()
  "Boundary: a day with no hourly data renders the temps without a description."
  (let* ((wttrin-unit-system "u")
         (day '((date . "2026-07-01") (mintempF . "71") (maxtempF . "88")
                (mintempC . "22") (maxtempC . "31") (hourly . ())))
         (text (wttrin--forecast-format (list day) 1)))
    (should (equal "Today 71-88°F" text))))

(ert-deftest test-wttrin--forecast-format-error-nil-days ()
  "Error: nil day list returns nil."
  (should-not (wttrin--forecast-format nil 3)))

;;; --------------------------------------------------------------------------
;;; tooltip integration
;;; --------------------------------------------------------------------------

(defun test-wttrin--forecast-tooltip-fixture (option-days)
  "Return the tooltip with fresh current + forecast caches and OPTION-DAYS."
  (let ((wttrin-unit-system "u")
        (wttrin-mode-line-tooltip-forecast-days option-days)
        (wttrin--mode-line-cache (cons (float-time) "Paris: ☀️ +61°F Clear"))
        (wttrin--mode-line-forecast-cache
         (cons (float-time) (test-wttrin--forecast-days))))
    (wttrin--mode-line-tooltip)))

(ert-deftest test-wttrin--forecast-tooltip-normal-appends-block ()
  "Normal: with the option at 3, the tooltip carries the forecast lines."
  (testutil-wttrin-setup)
  (unwind-protect
      (let ((tooltip (test-wttrin--forecast-tooltip-fixture 3)))
        (should (string-match-p "Paris" tooltip))
        (should (string-match-p "Today 71-88°F Partly cloudy" tooltip))
        (should (string-match-p "Fri 70-88°F Sunny" tooltip))
        (should (string-match-p "Updated" tooltip)))
    (testutil-wttrin-teardown)))

(ert-deftest test-wttrin--forecast-tooltip-boundary-option-zero-unchanged ()
  "Boundary: with the option at 0 (default), the tooltip has no forecast."
  (testutil-wttrin-setup)
  (unwind-protect
      (let ((tooltip (test-wttrin--forecast-tooltip-fixture 0)))
        (should (string-match-p "Paris" tooltip))
        (should-not (string-match-p "Today" tooltip)))
    (testutil-wttrin-teardown)))

(ert-deftest test-wttrin--forecast-tooltip-boundary-empty-forecast-cache ()
  "Boundary: option on but no forecast cache yet leaves the tooltip as before."
  (testutil-wttrin-setup)
  (unwind-protect
      (let ((wttrin-mode-line-tooltip-forecast-days 3)
            (wttrin--mode-line-cache (cons (float-time) "Paris: ☀️ +61°F Clear"))
            (wttrin--mode-line-forecast-cache nil))
        (let ((tooltip (wttrin--mode-line-tooltip)))
          (should (string-match-p "Paris" tooltip))
          (should-not (string-match-p "Today" tooltip))))
    (testutil-wttrin-teardown)))

(provide 'test-wttrin--mode-line-forecast)
;;; test-wttrin--mode-line-forecast.el ends here