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
|
-- Checks for lib/weather.lua.
-- Run from the repo root: lua test/test_weather.lua
-- The domain tables are what silently misreport when a boundary is off by one,
-- so every range boundary gets an assertion. The card itself is verified by
-- screenshot.
package.path = './?.lua;' .. package.path
local weather = require 'lib.weather'
-- === Condition id to icon =================================================
-- OWM ids group by hundreds, but the boundaries are not round numbers: the
-- ranges come from the polybar script that ran against this API for years.
-- Each assertion pairs the last id in a range with the first id of the next,
-- which is where an off-by-one would hide.
-- Sunrise 1500, sunset 1900, so DAY must sit INSIDE that window and NIGHT
-- outside it. Naming them without checking them against the window is how the
-- first draft of this test made every timestamp evaluate as night.
local DAY, NIGHT = 1700, 2000
local function icon(id, now)
return weather.icon(id, now, 1500, 1900) -- sunrise 1500, sunset 1900
end
-- Thunderstorm: everything up to 232.
assert(icon(200, DAY) == weather.ICON.thunder_day, 'id 200 day')
assert(icon(232, DAY) == weather.ICON.thunder_day, 'id 232 is still thunder')
assert(icon(232, NIGHT) == weather.ICON.thunder_night, 'id 232 night')
-- Light drizzle: 233..311.
assert(icon(233, DAY) == weather.ICON.drizzle_day, 'id 233 leaves thunder')
assert(icon(311, DAY) == weather.ICON.drizzle_day, 'id 311 is still light drizzle')
-- Heavy drizzle: 312..321.
assert(icon(312, DAY) == weather.ICON.drizzle_heavy_day, 'id 312 is heavy drizzle')
assert(icon(321, DAY) == weather.ICON.drizzle_heavy_day, 'id 321 is still heavy drizzle')
-- Rain: 322..531.
assert(icon(322, DAY) == weather.ICON.rain_day, 'id 322 is rain')
assert(icon(531, DAY) == weather.ICON.rain_day, 'id 531 is still rain')
-- Snow: 532..622. One icon, no day/night variant.
assert(icon(600, DAY) == weather.ICON.snow, 'id 600 is snow')
assert(icon(622, NIGHT) == weather.ICON.snow, 'snow is the same at night')
-- Fog: 623..771.
assert(icon(741, DAY) == weather.ICON.fog, 'id 741 is fog')
assert(icon(771, DAY) == weather.ICON.fog, 'id 771 is still fog')
-- Tornado is a single id, not a range.
assert(icon(781, DAY) == weather.ICON.tornado, 'id 781 is tornado')
-- Clear and few clouds each have a day and a night face.
assert(icon(800, DAY) == weather.ICON.clear_day, 'id 800 day is the sun')
assert(icon(800, NIGHT) == weather.ICON.clear_night, 'id 800 night is the moon')
assert(icon(801, DAY) == weather.ICON.few_day, 'id 801 day')
assert(icon(801, NIGHT) == weather.ICON.few_night, 'id 801 night')
-- Overcast: 802..804, no night variant.
assert(icon(804, DAY) == weather.ICON.overcast, 'id 804 is overcast')
-- Anything outside the known ids must be visibly wrong, not silently sunny.
assert(icon(999, DAY) == weather.ICON.unknown, 'unknown id gets the error glyph')
assert(weather.icon(nil, DAY, 1500, 1900) == weather.ICON.unknown, 'nil id')
-- Nothing may raise. A missing timestamp with a known sun, or a known
-- timestamp with no sun, both have to return a glyph rather than an error,
-- because an error here is a blank dashboard with no message.
assert(weather.icon(800, nil, 1500, 1900) == weather.ICON.clear_day,
'a nil now must not raise, and falls back to the day face')
assert(weather.icon(800, DAY, nil, nil) == weather.ICON.clear_day,
'no sun times must not raise')
-- The codepoint VALUES are deliberately not asserted. A test comparing
-- M.ICON.snow against a literal would only prove the same escape was typed
-- twice; whether E31A actually draws a snow cloud in the target font is not
-- something an assertion can see. That check is the glyph sheet rendered in
-- the plan's Step 5, and it is how a wrong-but-present codepoint was caught
-- before this file existed.
-- === Wind =================================================================
-- OWM metric gives m/s; the card shows km/h. The conversion is where a wrong
-- factor would look plausible but read 3.6x off.
assert(weather.kmh(10) == 36, '10 m/s is 36 km/h')
assert(weather.kmh(0) == 0, 'calm')
assert(weather.kmh(nil) == nil, 'missing wind speed stays missing')
-- Beaufort thresholds, in km/h, from the polybar script. Each assertion sits
-- on a boundary and just past it, which is where an inclusive/exclusive
-- mistake hides.
assert(weather.beaufort(0) == 0, 'calm is force 0')
assert(weather.beaufort(1) == 0, '1 km/h is still force 0')
assert(weather.beaufort(2) == 1, 'just over 1 is force 1')
assert(weather.beaufort(5) == 1, '5 is still force 1')
assert(weather.beaufort(6) == 2, 'just over 5 is force 2')
assert(weather.beaufort(11) == 2, '11 is still force 2')
assert(weather.beaufort(12) == 3, 'just over 11 is force 3')
assert(weather.beaufort(19) == 3, '19 is still force 3')
assert(weather.beaufort(28) == 4, '28 is force 4')
assert(weather.beaufort(38) == 5, '38 is force 5')
assert(weather.beaufort(49) == 6, '49 is force 6')
assert(weather.beaufort(61) == 7, '61 is force 7')
assert(weather.beaufort(74) == 8, '74 is force 8')
assert(weather.beaufort(88) == 9, '88 is force 9')
assert(weather.beaufort(102) == 10, '102 is force 10')
assert(weather.beaufort(117) == 11, '117 is force 11')
assert(weather.beaufort(118) == 12, 'over 117 is hurricane force')
-- Compass: eight points, each spanning 45 degrees, offset by half a step so
-- north straddles 0 rather than starting at it. The wraparound is the case
-- that a naive floor(deg/45) gets wrong.
assert(weather.compass(0) == 'N', '0 is north')
assert(weather.compass(360) == 'N', '360 wraps to north')
assert(weather.compass(11) == 'N', 'just under the NE boundary')
assert(weather.compass(349) == 'N', 'just over the NW boundary wraps to north')
assert(weather.compass(45) == 'NE', '45 is northeast')
assert(weather.compass(90) == 'E', '90 is east')
assert(weather.compass(135) == 'SE', '135 is southeast')
assert(weather.compass(180) == 'S', '180 is south')
assert(weather.compass(225) == 'SW', '225 is southwest')
assert(weather.compass(270) == 'W', '270 is west')
assert(weather.compass(315) == 'NW', '315 is northwest')
assert(weather.compass(nil) == nil, 'missing direction stays missing')
-- The arrow points where the wind is going, and OWM reports where it comes
-- from, so a north wind draws a downward arrow.
assert(weather.arrow(0) == '\u{2193}', 'a north wind blows southward')
assert(weather.arrow(180) == '\u{2191}', 'a south wind blows northward')
assert(weather.arrow(nil) == '', 'no direction draws nothing')
print('test_weather: all assertions passed')
|