Skip to content

Commit db144c9

Browse files
test: add inf/null edge cases and docstrings to all test methods
1 parent eec4599 commit db144c9

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

tests/test_plots.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,90 +21,116 @@ def capture(msg, endpoint="events"):
2121
return sent
2222

2323
def test_y_1d_no_x(self):
24+
"""Basic 1D Y without X auto-generates x-axis."""
2425
sent = self._line(np.array([1.0, 2.0, 3.0]))
2526
self.assertIn("data", sent["payload"])
2627

2728
def test_y_0d_raises(self):
29+
"""Scalar Y raises before reaching scatter."""
2830
with self.assertRaises(AssertionError):
2931
self.viz.line(np.float64(1.0))
3032

3133
def test_y_3d_raises(self):
34+
"""3D Y raises on the ndim check."""
3235
with self.assertRaises(AssertionError):
3336
self.viz.line(np.ones((2, 3, 4)))
3437

3538
def test_y_empty_last_dim_raises(self):
39+
"""Zero-column Y raises on the empty check."""
3640
with self.assertRaises(AssertionError):
3741
self.viz.line(np.empty((5, 0)))
3842

3943
def test_x_3d_raises(self):
44+
"""3D X raises on the ndim check."""
4045
with self.assertRaises(AssertionError):
4146
self.viz.line(np.array([1.0, 2.0]), X=np.ones((2, 1, 1)))
4247

4348
def test_x_shape_mismatch_raises(self):
49+
"""X and Y with different lengths raise on the shape check."""
4450
with self.assertRaises(AssertionError):
4551
self.viz.line(np.array([1.0, 2.0, 3.0]), X=np.array([0.0, 1.0]))
4652

4753
def test_single_line_one_trace(self):
54+
"""1D Y produces exactly one trace."""
4855
sent = self._line(np.array([1.0, 2.0, 3.0]))
4956
self.assertEqual(len(sent["payload"]["data"]), 1)
5057

5158
def test_multi_line_2d_y(self):
59+
"""2D Y with M columns produces M traces."""
5260
Y = np.array([[1.0, 4.0], [2.0, 5.0], [3.0, 6.0]])
5361
sent = self._line(Y)
5462
self.assertEqual(len(sent["payload"]["data"]), 2)
5563

5664
def test_y_2d_single_col_one_trace(self):
65+
"""(N,1) Y is squeezed to 1D before building linedata."""
5766
sent = self._line(np.array([[1.0], [2.0], [3.0]]))
5867
self.assertEqual(len(sent["payload"]["data"]), 1)
5968

6069
def test_y_2d_x_1d_broadcasts(self):
70+
"""1D X is tiled to match the shape of 2D Y."""
6171
Y = np.array([[1.0, 4.0], [2.0, 5.0], [3.0, 6.0]])
6272
X = np.array([0.0, 1.0, 2.0])
6373
sent = self._line(Y, X=X)
6474
self.assertIn("data", sent["payload"])
6575

6676
def test_update_append_requires_x(self):
77+
"""update='append' without X raises before sending."""
6778
with self.assertRaises(AssertionError):
6879
self.viz.line(np.array([1.0, 2.0]), win="w", update="append")
6980

7081
def test_update_append_sets_append_true(self):
82+
"""Existing window with update='append' sets append=True in payload."""
7183
Y = np.array([1.0, 2.0])
7284
X = np.array([0.0, 1.0])
7385
with patch.object(self.viz, "win_exists", return_value=True):
7486
sent = self._line(Y, X=X, win="w", update="append")
7587
self.assertTrue(sent["payload"]["append"])
7688

7789
def test_update_replace_sets_append_false(self):
90+
"""update='replace' sets append=False in payload."""
7891
Y = np.array([1.0, 2.0])
7992
X = np.array([0.0, 1.0])
8093
sent = self._line(Y, X=X, win="w", update="replace")
8194
self.assertFalse(sent["payload"]["append"])
8295

8396
def test_update_replace_uses_update_endpoint(self):
97+
"""update='replace' routes to the update endpoint."""
8498
Y = np.array([1.0, 2.0])
8599
X = np.array([0.0, 1.0])
86100
sent = self._line(Y, X=X, win="w", update="replace")
87101
self.assertEqual(sent["endpoint"], "update")
88102

89103
def test_update_append_new_window_no_append_key(self):
104+
"""New window with update='append' falls back to creation, no append key."""
90105
Y = np.array([1.0, 2.0])
91106
X = np.array([0.0, 1.0])
92107
with patch.object(self.viz, "win_exists", return_value=False):
93108
sent = self._line(Y, X=X, win="w", update="append")
94109
self.assertNotIn("append", sent["payload"])
95110

96111
def test_update_remove_sends_delete(self):
112+
"""update='remove' sends delete=True without touching Y."""
97113
sent = self._line(None, win="w", name="trace1", update="remove")
98114
self.assertTrue(sent["payload"]["delete"])
99115

100116
def test_nan_y_passes_through(self):
117+
"""All-NaN Y values survive into the payload for use as update mask."""
101118
Y = np.array([np.nan, np.nan, np.nan])
102119
X = np.array([0.0, 1.0, 2.0])
103120
with patch.object(self.viz, "win_exists", return_value=True):
104121
sent = self._line(Y, X=X, win="w", update="append")
105122
y_vals = sent["payload"]["data"][0]["y"]
106123
self.assertTrue(all(np.isnan(v) for v in y_vals))
107124

125+
def test_inf_y_passes_through(self):
126+
"""Inf Y values pass through without raising."""
127+
Y = np.array([np.inf, 1.0, -np.inf])
128+
X = np.array([0.0, 1.0, 2.0])
129+
sent = self._line(Y, X=X)
130+
y_vals = sent["payload"]["data"][0]["y"]
131+
self.assertTrue(np.isinf(y_vals[0]))
132+
self.assertTrue(np.isinf(y_vals[2]))
133+
108134

109135
class TestScatter(unittest.TestCase):
110136
def setUp(self):
@@ -123,77 +149,99 @@ def capture(msg, endpoint="events"):
123149
return sent
124150

125151
def test_nx2_input(self):
152+
"""Nx2 X produces a scatter trace."""
126153
X = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])
127154
sent = self._scatter(X)
128155
self.assertEqual(sent["payload"]["data"][0]["type"], "scatter")
129156

130157
def test_nx3_input_produces_scatter3d(self):
158+
"""Nx3 X produces a scatter3d trace."""
131159
X = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
132160
sent = self._scatter(X)
133161
self.assertEqual(sent["payload"]["data"][0]["type"], "scatter3d")
134162

135163
def test_x_1d_raises(self):
164+
"""1D X raises on the ndim check."""
136165
with self.assertRaises(AssertionError):
137166
self.viz.scatter(np.array([1.0, 2.0, 3.0]))
138167

139168
def test_x_wrong_cols_raises(self):
169+
"""X with column count other than 2 or 3 raises."""
140170
with self.assertRaises(AssertionError):
141171
self.viz.scatter(np.ones((3, 4)))
142172

143173
def test_y_size_mismatch_raises(self):
174+
"""Y length not matching X row count raises."""
144175
X = np.array([[1.0, 2.0], [3.0, 4.0]])
145176
Y = np.array([1, 2, 3])
146177
with self.assertRaises(AssertionError):
147178
self.viz.scatter(X, Y=Y)
148179

149180
def test_nan_label_raises(self):
181+
"""NaN in Y labels raises via the isfinite check in _normalize_labels."""
150182
X = np.array([[1.0, 2.0], [3.0, 4.0]])
151183
Y = np.array([1.0, np.nan])
152184
with self.assertRaises(AssertionError):
153185
self.viz.scatter(X, Y=Y)
154186

187+
def test_inf_label_raises(self):
188+
"""Inf in Y labels raises via the same isfinite check as NaN."""
189+
X = np.array([[1.0, 2.0], [3.0, 4.0]])
190+
Y = np.array([1.0, np.inf])
191+
with self.assertRaises(AssertionError):
192+
self.viz.scatter(X, Y=Y)
193+
155194
def test_multiple_labels_produce_multiple_traces(self):
195+
"""Distinct Y label values produce one trace each."""
156196
X = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])
157197
Y = np.array([1, 1, 2])
158198
sent = self._scatter(X, Y=Y)
159199
self.assertEqual(len(sent["payload"]["data"]), 2)
160200

161201
def test_name_with_multiple_labels_raises(self):
202+
"""name= combined with multiple label groups raises."""
162203
X = np.array([[1.0, 2.0], [3.0, 4.0]])
163204
Y = np.array([1, 2])
164205
with self.assertRaises(AssertionError):
165206
self.viz.scatter(X, Y=Y, name="trace1")
166207

167208
def test_store_history_with_update_raises(self):
209+
"""store_history=True combined with update raises ValueError."""
168210
X = np.array([[1.0, 2.0], [3.0, 4.0]])
169211
with self.assertRaises(ValueError):
170212
self.viz.scatter(X, opts={"store_history": True}, update="append", win="w")
171213

172214
def test_update_without_win_raises(self):
215+
"""update without a win raises ValueError."""
173216
X = np.array([[1.0, 2.0], [3.0, 4.0]])
174217
with self.assertRaises(ValueError):
175218
self.viz.scatter(X, update="replace")
176219

177220
def test_update_remove_requires_name(self):
221+
"""update='remove' without name raises."""
178222
with self.assertRaises(AssertionError):
179223
self.viz.scatter(None, win="w", update="remove")
180224

181225
def test_update_remove_sends_delete(self):
226+
"""update='remove' sends delete=True in payload."""
182227
sent = self._scatter(None, win="w", name="trace1", update="remove")
183228
self.assertTrue(sent["payload"]["delete"])
184229

185230
def test_update_append_sets_append_true(self):
231+
"""Existing window with update='append' sets append=True."""
186232
X = np.array([[1.0, 2.0], [3.0, 4.0]])
187233
with patch.object(self.viz, "win_exists", return_value=True):
188234
sent = self._scatter(X, win="w", update="append")
189235
self.assertTrue(sent["payload"]["append"])
190236

191237
def test_update_replace_sets_append_false(self):
238+
"""update='replace' sets append=False in payload."""
192239
X = np.array([[1.0, 2.0], [3.0, 4.0]])
193240
sent = self._scatter(X, win="w", update="replace")
194241
self.assertFalse(sent["payload"]["append"])
195242

196243
def test_name_based_update_1d_x_1d_y(self):
244+
"""Name-based update stacks 1D X and Y into Nx2 before sending."""
197245
X = np.array([1.0, 2.0, 3.0])
198246
Y = np.array([4.0, 5.0, 6.0])
199247
with patch.object(self.viz, "win_exists", return_value=True):
@@ -220,42 +268,57 @@ def capture(msg, endpoint="events"):
220268
return sent
221269

222270
def test_nx_m_input(self):
271+
"""NxM X produces a heatmap trace."""
223272
X = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])
224273
sent = self._heatmap(X)
225274
self.assertEqual(sent["payload"]["data"][0]["type"], "heatmap")
226275

227276
def test_x_not_2d_raises(self):
277+
"""1D X raises on the 2D check."""
228278
with self.assertRaises(AssertionError):
229279
self.viz.heatmap(np.array([1.0, 2.0, 3.0]))
230280

231281
def test_invalid_update_raises(self):
282+
"""Unknown update value raises before building data."""
232283
X = np.ones((3, 3))
233284
with self.assertRaises(AssertionError):
234285
self.viz.heatmap(X, update="badvalue")
235286

236287
def test_colormap_defaults_to_viridis(self):
288+
"""colormap defaults to Viridis when not specified."""
237289
X = np.ones((2, 2))
238290
sent = self._heatmap(X)
239291
self.assertEqual(sent["payload"]["opts"]["colormap"], "Viridis")
240292

241293
def test_append_row_sets_update_dir(self):
294+
"""appendRow sets updateDir and append=True on the update endpoint."""
242295
X = np.ones((2, 2))
243296
sent = self._heatmap(X, update="appendRow", win="w")
244297
self.assertEqual(sent["payload"]["updateDir"], "appendRow")
245298
self.assertTrue(sent["payload"]["append"])
246299
self.assertEqual(sent["endpoint"], "update")
247300

248301
def test_append_column_sets_update_dir(self):
302+
"""appendColumn sets updateDir and append=True."""
249303
X = np.ones((2, 2))
250304
sent = self._heatmap(X, update="appendColumn", win="w")
251305
self.assertEqual(sent["payload"]["updateDir"], "appendColumn")
252306
self.assertTrue(sent["payload"]["append"])
253307

254308
def test_replace_sets_append_false(self):
309+
"""replace sets append=False on the update endpoint."""
255310
X = np.ones((2, 2))
256311
sent = self._heatmap(X, update="replace", win="w")
257312
self.assertFalse(sent["payload"]["append"])
258313

314+
def test_nan_values_pass_through(self):
315+
"""NaN values in X pass through to the payload without raising."""
316+
X = np.array([[1.0, np.nan], [np.nan, 4.0]])
317+
sent = self._heatmap(X)
318+
z = sent["payload"]["data"][0]["z"]
319+
self.assertTrue(np.isnan(z[0][1]))
320+
self.assertTrue(np.isnan(z[1][0]))
321+
259322

260323
if __name__ == "__main__":
261324
unittest.main()

0 commit comments

Comments
 (0)