Skip to content

Commit 45ec084

Browse files
author
Jon M. Mease
committed
Added validator tests.
These are currently pytest-style tests. Install pytest and run with: ~> pytest _plotly_utils/tests/
1 parent 8a7b476 commit 45ec084

20 files changed

+1687
-0
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ js/node_modules/
3232

3333
# Compiled javascript
3434
plotlywidget/static/
35+
36+
.pytest_cache

Diff for: _plotly_utils/tests/resources/1x1-black.png

68 Bytes
Loading
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import pytest
2+
from _plotly_utils.basevalidators import AngleValidator
3+
import numpy as np
4+
5+
6+
# Fixtures
7+
# --------
8+
@pytest.fixture()
9+
def validator():
10+
return AngleValidator('prop', 'parent')
11+
12+
13+
# Tests
14+
# -----
15+
# ### Test acceptance ###
16+
@pytest.mark.parametrize('val', [0] + list(np.linspace(-180, 179.99)))
17+
def test_acceptance(val, validator):
18+
assert validator.validate_coerce(val) == val
19+
20+
21+
# ### Test coercion above 180 ###
22+
@pytest.mark.parametrize('val,expected', [
23+
(180, -180),
24+
(181, -179),
25+
(-180.25, 179.75),
26+
(540, -180),
27+
(-541, 179)
28+
])
29+
def test_coercion(val, expected, validator):
30+
assert validator.validate_coerce(val) == expected
31+
32+
33+
# ### Test rejection ###
34+
@pytest.mark.parametrize('val',
35+
['hello', (), [], [1, 2, 3], set(), '34'])
36+
def test_rejection(val, validator: AngleValidator):
37+
with pytest.raises(ValueError) as validation_failure:
38+
validator.validate_coerce(val)
39+
40+
assert 'Invalid value' in str(validation_failure.value)

Diff for: _plotly_utils/tests/validators/test_any_validator.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
from _plotly_utils.basevalidators import AnyValidator
3+
import numpy as np
4+
5+
6+
# Fixtures
7+
# --------
8+
@pytest.fixture()
9+
def validator():
10+
return AnyValidator('prop', 'parent')
11+
12+
13+
@pytest.fixture()
14+
def validator_aok():
15+
return AnyValidator('prop', 'parent', array_ok=True)
16+
17+
18+
# Tests
19+
# -----
20+
# ### Acceptance ###
21+
@pytest.mark.parametrize('val', [
22+
set(), 'Hello', 123, np.inf, np.nan, {}
23+
])
24+
def test_acceptance(val, validator: AnyValidator):
25+
assert validator.validate_coerce(val) is val
26+
27+
28+
# ### Acceptance of arrays ###
29+
@pytest.mark.parametrize('val', [
30+
[], np.array([]), ['Hello', 'World'], [np.pi, np.e, {}]
31+
])
32+
def test_acceptance_array(val, validator_aok: AnyValidator):
33+
coerce_val = validator_aok.validate_coerce(val)
34+
assert isinstance(coerce_val, np.ndarray)
35+
assert coerce_val.dtype == 'object'
36+
assert np.array_equal(coerce_val, val)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import pytest
2+
from _plotly_utils.basevalidators import BaseDataValidator
3+
from plotly.graph_objs import Scatter, Bar, Box
4+
5+
# Fixtures
6+
# --------
7+
@pytest.fixture()
8+
def validator():
9+
return BaseDataValidator(class_strs_map={'scatter': 'Scatter',
10+
'bar': 'Bar',
11+
'box': 'Box'},
12+
plotly_name='prop',
13+
parent_name='parent')
14+
15+
16+
# Tests
17+
# -----
18+
def test_acceptance(validator: BaseDataValidator):
19+
val = [Scatter(mode='lines'), Box(fillcolor='yellow')]
20+
res = validator.validate_coerce(val)
21+
22+
assert isinstance(res, tuple)
23+
assert isinstance(res[0], Scatter)
24+
assert res[0].type == 'scatter'
25+
assert res[0].mode == 'lines'
26+
27+
assert isinstance(res[1], Box)
28+
assert res[1].type == 'box'
29+
assert res[1].fillcolor == 'yellow'
30+
31+
# Make sure UIDs are actually unique
32+
assert res[0].uid != res[1].uid
33+
34+
35+
def test_acceptance_dict(validator: BaseDataValidator):
36+
val = (dict(type='scatter', mode='lines'),
37+
dict(type='box', fillcolor='yellow'))
38+
res = validator.validate_coerce(val)
39+
40+
assert isinstance(res, tuple)
41+
assert isinstance(res[0], Scatter)
42+
assert res[0].type == 'scatter'
43+
assert res[0].mode == 'lines'
44+
45+
assert isinstance(res[1], Box)
46+
assert res[1].type == 'box'
47+
assert res[1].fillcolor == 'yellow'
48+
49+
# Make sure UIDs are actually unique
50+
assert res[0].uid != res[1].uid
51+
52+
53+
def test_default_is_scatter(validator: BaseDataValidator):
54+
val = [dict(mode='lines')]
55+
res = validator.validate_coerce(val)
56+
57+
assert isinstance(res, tuple)
58+
assert isinstance(res[0], Scatter)
59+
assert res[0].type == 'scatter'
60+
assert res[0].mode == 'lines'
61+
62+
63+
def test_rejection_type(validator: BaseDataValidator):
64+
val = 37
65+
66+
with pytest.raises(ValueError) as validation_failure:
67+
validator.validate_coerce(val)
68+
69+
assert "Invalid value" in str(validation_failure.value)
70+
71+
72+
def test_rejection_element_type(validator: BaseDataValidator):
73+
val = [42]
74+
75+
with pytest.raises(ValueError) as validation_failure:
76+
validator.validate_coerce(val)
77+
78+
assert "Invalid element(s)" in str(validation_failure.value)
79+
80+
81+
def test_rejection_element_attr(validator: BaseDataValidator):
82+
val = [dict(type='scatter', bogus=99)]
83+
84+
with pytest.raises(ValueError) as validation_failure:
85+
validator.validate_coerce(val)
86+
87+
assert ("Invalid property specified for object of type " +
88+
"plotly.graph_objs.Scatter: 'bogus'" in
89+
str(validation_failure.value))
90+
91+
92+
def test_rejection_element_tracetype(validator: BaseDataValidator):
93+
val = [dict(type='bogus', a=4)]
94+
95+
with pytest.raises(ValueError) as validation_failure:
96+
validator.validate_coerce(val)
97+
98+
assert "Invalid element(s)" in str(validation_failure.value)
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pytest
2+
from _plotly_utils.basevalidators import BooleanValidator
3+
import numpy as np
4+
5+
6+
# Boolean Validator
7+
# =================
8+
# ### Fixtures ###
9+
@pytest.fixture(params=[True, False])
10+
def validator(request):
11+
return BooleanValidator('prop', 'parent', dflt=request.param)
12+
13+
14+
# ### Acceptance ###
15+
@pytest.mark.parametrize('val', [True, False])
16+
def test_acceptance(val, validator):
17+
assert val == validator.validate_coerce(val)
18+
19+
20+
# ### Rejection ###
21+
@pytest.mark.parametrize('val',
22+
[1.0, 0.0, 'True', 'False', [], 0, np.nan])
23+
def test_rejection(val, validator):
24+
with pytest.raises(ValueError) as validation_failure:
25+
validator.validate_coerce(val)
26+
27+
assert 'Invalid value' in str(validation_failure.value)
+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import pytest
2+
from _plotly_utils.basevalidators import ColorValidator
3+
import numpy as np
4+
5+
6+
# Fixtures
7+
# --------
8+
@pytest.fixture()
9+
def validator():
10+
return ColorValidator('prop', 'parent')
11+
12+
13+
@pytest.fixture()
14+
def validator_colorscale():
15+
return ColorValidator('prop', 'parent', colorscale_path='parent.colorscale')
16+
17+
18+
@pytest.fixture()
19+
def validator_aok():
20+
return ColorValidator('prop', 'parent', array_ok=True)
21+
22+
23+
@pytest.fixture()
24+
def validator_aok_colorscale():
25+
return ColorValidator('prop', 'parent', array_ok=True, colorscale_path='parent.colorscale')
26+
27+
28+
# Array not ok, numbers not ok
29+
# ----------------------------
30+
@pytest.mark.parametrize('val',
31+
['red', 'BLUE', 'rgb(255, 0, 0)', 'hsl(0, 100%, 50%)', 'hsla(0, 100%, 50%, 100%)',
32+
'hsv(0, 100%, 100%)', 'hsva(0, 100%, 100%, 50%)'])
33+
def test_acceptance(val, validator: ColorValidator):
34+
if isinstance(val, str):
35+
assert validator.validate_coerce(val) == str.replace(val.lower(), ' ', '')
36+
else:
37+
assert validator.validate_coerce(val) == val
38+
39+
40+
# ### Rejection by type ###
41+
@pytest.mark.parametrize('val',
42+
[set(), 23, 0.5, {}, ['red'], [12]])
43+
def test_rejection(val, validator: ColorValidator):
44+
with pytest.raises(ValueError) as validation_failure:
45+
validator.validate_coerce(val)
46+
47+
assert 'Invalid value' in str(validation_failure.value)
48+
49+
50+
# ### Rejection by value ###
51+
@pytest.mark.parametrize('val',
52+
['redd', 'rgbbb(255, 0, 0)', 'hsl(0, 1%0000%, 50%)'])
53+
def test_rejection(val, validator: ColorValidator):
54+
with pytest.raises(ValueError) as validation_failure:
55+
validator.validate_coerce(val)
56+
57+
assert 'Invalid value' in str(validation_failure.value)
58+
59+
60+
# Array not ok, numbers ok
61+
# ------------------------
62+
# ### Acceptance ###
63+
@pytest.mark.parametrize('val',
64+
['red', 'BLUE', 23, 15, 'rgb(255, 0, 0)', 'hsl(0, 100%, 50%)', 'hsla(0, 100%, 50%, 100%)',
65+
'hsv(0, 100%, 100%)', 'hsva(0, 100%, 100%, 50%)'])
66+
def test_acceptance_colorscale(val, validator_colorscale: ColorValidator):
67+
if isinstance(val, str):
68+
assert validator_colorscale.validate_coerce(val) == str.replace(val.lower(), ' ', '')
69+
else:
70+
assert validator_colorscale.validate_coerce(val) == val
71+
72+
73+
# ### Rejection by type ###
74+
@pytest.mark.parametrize('val',
75+
[set(), {}, ['red'], [12]])
76+
def test_rejection_colorscale(val, validator_colorscale: ColorValidator):
77+
with pytest.raises(ValueError) as validation_failure:
78+
validator_colorscale.validate_coerce(val)
79+
80+
assert 'Invalid value' in str(validation_failure.value)
81+
82+
83+
# ### Rejection by value ###
84+
@pytest.mark.parametrize('val',
85+
['redd', 'rgbbb(255, 0, 0)', 'hsl(0, 1%0000%, 50%)'])
86+
def test_rejection_colorscale(val, validator_colorscale: ColorValidator):
87+
with pytest.raises(ValueError) as validation_failure:
88+
validator_colorscale.validate_coerce(val)
89+
90+
assert 'Invalid value' in str(validation_failure.value)
91+
92+
93+
# Array ok, numbers not ok
94+
# ------------------------
95+
# ### Acceptance ###
96+
@pytest.mark.parametrize('val',
97+
['blue',
98+
['red', 'rgb(255, 0, 0)'],
99+
['hsl(0, 100%, 50%)', 'hsla(0, 100%, 50%, 100%)', 'hsv(0, 100%, 100%)'],
100+
['hsva(0, 100%, 100%, 50%)']])
101+
def test_acceptance_aok(val, validator_aok: ColorValidator):
102+
coerce_val = validator_aok.validate_coerce(val)
103+
if isinstance(val, (list, np.ndarray)):
104+
expected = np.array(
105+
[str.replace(v.lower(), ' ', '') if isinstance(v, str) else v for v in val],
106+
dtype=coerce_val.dtype)
107+
assert np.array_equal(coerce_val, expected)
108+
else:
109+
expected = str.replace(val.lower(), ' ', '') if isinstance(val, str) else val
110+
assert coerce_val == expected
111+
112+
113+
# ### Rejection ###
114+
@pytest.mark.parametrize('val',
115+
[[23], [0, 1, 2],
116+
['redd', 'rgb(255, 0, 0)'],
117+
['hsl(0, 100%, 50_00%)', 'hsla(0, 100%, 50%, 100%)', 'hsv(0, 100%, 100%)'],
118+
['hsva(0, 1%00%, 100%, 50%)']])
119+
def test_rejection_aok(val, validator_aok: ColorValidator):
120+
with pytest.raises(ValueError) as validation_failure:
121+
validator_aok.validate_coerce(val)
122+
123+
assert 'Invalid element(s)' in str(validation_failure.value)
124+
125+
126+
# Array ok, numbers ok
127+
# --------------------
128+
# ### Acceptance ###
129+
@pytest.mark.parametrize('val',
130+
['blue', 23, [0, 1, 2],
131+
['red', 0.5, 'rgb(255, 0, 0)'],
132+
['hsl(0, 100%, 50%)', 'hsla(0, 100%, 50%, 100%)', 'hsv(0, 100%, 100%)'],
133+
['hsva(0, 100%, 100%, 50%)']])
134+
def test_acceptance_aok_colorscale(val, validator_aok_colorscale: ColorValidator):
135+
coerce_val = validator_aok_colorscale.validate_coerce(val)
136+
if isinstance(val, (list, np.ndarray)):
137+
expected = np.array(
138+
[str.replace(v.lower(), ' ', '') if isinstance(v, str) else v for v in val],
139+
dtype=coerce_val.dtype)
140+
assert np.array_equal(coerce_val, expected)
141+
else:
142+
expected = str.replace(val.lower(), ' ', '') if isinstance(val, str) else val
143+
assert coerce_val == expected
144+
145+
146+
# ### Rejection ###
147+
@pytest.mark.parametrize('val',
148+
[['redd', 0.5, 'rgb(255, 0, 0)'],
149+
['hsl(0, 100%, 50_00%)', 'hsla(0, 100%, 50%, 100%)', 'hsv(0, 100%, 100%)'],
150+
['hsva(0, 1%00%, 100%, 50%)']])
151+
def test_rejection_aok_colorscale(val, validator_aok_colorscale: ColorValidator):
152+
with pytest.raises(ValueError) as validation_failure:
153+
validator_aok_colorscale.validate_coerce(val)
154+
155+
assert 'Invalid element(s)' in str(validation_failure.value)
156+
157+
158+
# Description
159+
# -----------
160+
# Test dynamic description logic
161+
def test_description(validator: ColorValidator):
162+
desc = validator.description()
163+
assert 'A number that will be interpreted as a color' not in desc
164+
assert 'A list or array of any of the above' not in desc
165+
166+
167+
def test_description_aok(validator_aok: ColorValidator):
168+
desc = validator_aok.description()
169+
assert 'A number that will be interpreted as a color' not in desc
170+
assert 'A list or array of any of the above' in desc
171+
172+
173+
def test_description_aok(validator_colorscale: ColorValidator):
174+
desc = validator_colorscale.description()
175+
assert 'A number that will be interpreted as a color' in desc
176+
assert 'A list or array of any of the above' not in desc
177+
178+
179+
def test_description_aok_colorscale(validator_aok_colorscale: ColorValidator):
180+
desc = validator_aok_colorscale.description()
181+
assert 'A number that will be interpreted as a color' in desc
182+
assert 'A list or array of any of the above' in desc

0 commit comments

Comments
 (0)