Skip to content

Commit 38b5b71

Browse files
committed
Added progress components CircularProgress, CircularProgressWithLabel,
`LinearProgress`, `LinearProgressWithLabel`: added more tests.
1 parent 6bff7d0 commit 38b5b71

File tree

13 files changed

+267
-49
lines changed

13 files changed

+267
-49
lines changed

chartlets.py/CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## Version 0.0.22 (in development)
22

3+
* Added progress components `CircularProgress`, `CircularProgressWithLabel`,
4+
`LinearProgress`, `LinearProgressWithLabel`.
5+
36
* Replacing components is now possible by using an
47
`Output` with `property` set to an empty string. (#38)
58

chartlets.py/chartlets/component.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ class Component(ABC):
3131
label: str | None = None
3232
"""HTML `label` attribute. Optional."""
3333

34+
color: str | None = None
35+
"""HTML `color` attribute. Optional."""
36+
3437
children: list[Union["Component", str]] | None = None
3538
"""Children used by many specific components. Optional."""
3639

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from .box import Box
22
from .button import Button
33
from .checkbox import Checkbox
4+
from .progress import CircularProgress
5+
from .progress import CircularProgressWithLabel
6+
from .progress import LinearProgress
7+
from .progress import LinearProgressWithLabel
48
from .plot import Plot
59
from .select import Select
610
from .typography import Typography
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from abc import ABC
2+
from dataclasses import dataclass, field
3+
from typing import Literal
4+
5+
from chartlets import Component
6+
7+
8+
@dataclass(frozen=True)
9+
class Progress(Component, ABC):
10+
"""Progress indicators commonly known as spinners,
11+
express an unspecified wait time or display the length of a process.
12+
"""
13+
14+
value: int | None = None
15+
"""Progress value in percent. Optional."""
16+
17+
variant: str | None = None
18+
"""Progress size. Example values: `"determinate"` or the default `"indeterminate"`."""
19+
20+
21+
@dataclass(frozen=True)
22+
class CircularProgress(Progress):
23+
"""Progress indicators commonly known as spinners,
24+
express an unspecified wait time or display the length of a process.
25+
"""
26+
27+
size: str | int | None = None
28+
"""Progress size. Example values: `"30px"`, `40`, `"3rem"`."""
29+
30+
31+
@dataclass(frozen=True)
32+
class CircularProgressWithLabel(CircularProgress):
33+
"""Progress indicators commonly known as spinners,
34+
express an unspecified wait time or display the length of a process.
35+
"""
36+
37+
38+
@dataclass(frozen=True)
39+
class LinearProgress(Progress):
40+
"""Progress indicators commonly known as spinners,
41+
express an unspecified wait time or display the length of a process.
42+
"""
43+
44+
45+
@dataclass(frozen=True)
46+
class LinearProgressWithLabel(LinearProgress):
47+
"""Progress indicators commonly known as spinners,
48+
express an unspecified wait time or display the length of a process.
49+
"""

chartlets.py/chartlets/components/typography.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,23 @@ class Typography(Component):
99
and efficiently as possible."""
1010

1111
text: str | None = None
12-
"""Text to be displayed. Optional"""
12+
"""Text to be displayed."""
13+
14+
align: str | None = None
15+
"""Set the text-align on the component."""
1316

1417
color: str | None = None
1518
"""The color of the component."""
1619

1720
variant: str | None = None
1821
"""Applies the theme typography styles."""
22+
23+
gutterBottom: bool | None = None
24+
"""If True, the text will have a bottom margin."""
25+
26+
noWrap: bool | None = None
27+
"""If true, the text will not wrap, but instead will
28+
truncate with a text overflow ellipsis."""
29+
30+
component: str | None = None
31+
"""The HTML element used for the root node."""
Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,47 @@
1-
import unittest
21
from dataclasses import dataclass
2+
import json
3+
import unittest
4+
from typing import Any
5+
36

47
from chartlets import Component
58

69

10+
def make_base(component_cls: type):
11+
class ComponentTestBase(unittest.TestCase):
12+
13+
cls = component_cls
14+
15+
def assert_is_json_serializable(
16+
self, component: Component, expected_dict: dict[str, Any]
17+
):
18+
self.assertIsInstance(component, Component)
19+
20+
actual_dict = component.to_dict()
21+
self.assertIsInstance(actual_dict, dict)
22+
23+
serialized = json.dumps(actual_dict)
24+
deserialized = json.loads(serialized)
25+
self.assertEqual(expected_dict, deserialized)
26+
27+
def test_type_is_class_name(self):
28+
component = self.cls()
29+
self.assertIsInstance(component, Component)
30+
self.assertEqual(self.cls.__name__, component.type)
31+
32+
return ComponentTestBase
33+
34+
735
@dataclass(frozen=True)
836
class Pin(Component):
937
text: str = ""
1038

1139

1240
# noinspection PyMethodMayBeStatic
13-
class ComponentTest(unittest.TestCase):
14-
pin = Pin(id="p12", text="hello!", style={"color": "red"})
15-
16-
def test_attributes(self):
17-
pin = self.pin
18-
self.assertEqual("Pin", pin.type)
19-
self.assertEqual("p12", pin.id)
20-
self.assertEqual("hello!", pin.text)
21-
self.assertEqual({"color": "red"}, pin.style)
22-
23-
def test_to_dict(self):
24-
pin = self.pin
25-
self.assertEqual(
41+
class ComponentTest(make_base(Pin)):
42+
43+
def test_is_json_serializable(self):
44+
self.assert_is_json_serializable(
45+
self.cls(id="p12", text="hello!", style={"color": "red"}),
2646
{"type": "Pin", "id": "p12", "text": "hello!", "style": {"color": "red"}},
27-
pin.to_dict(),
2847
)

chartlets.py/tests/components/box_test.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
import json
2-
import unittest
3-
41
from chartlets.components import Box
2+
from tests.component_test import make_base
53

64

7-
class BoxTest(unittest.TestCase):
5+
class BoxTest(make_base(Box)):
86

97
def test_is_json_serializable(self):
10-
box = Box(
11-
children=[
12-
Box(id="b1"),
13-
Box(id="b2"),
14-
]
8+
self.assert_is_json_serializable(
9+
self.cls(
10+
children=[
11+
Box(id="b1"),
12+
Box(id="b2"),
13+
],
14+
style={"color": "grey", "display": "flex"},
15+
),
16+
{
17+
"type": "Box",
18+
"children": [
19+
{"children": [], "id": "b1", "type": "Box"},
20+
{"children": [], "id": "b2", "type": "Box"},
21+
],
22+
"style": {"color": "grey", "display": "flex"},
23+
},
1524
)
16-
17-
d = box.to_dict()
18-
self.assertIsInstance(d, dict)
19-
self.assertIsInstance(d.get("children"), list)
20-
json_text = json.dumps(d)
21-
self.assertEqual("{", json_text[0])
22-
self.assertEqual("}", json_text[-1])
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from chartlets.components import Button
2+
from tests.component_test import make_base
3+
4+
5+
class ButtonTest(make_base(Button)):
6+
7+
def test_is_json_serializable(self):
8+
self.assert_is_json_serializable(
9+
self.cls(text="Update"),
10+
{"type": "Button", "text": "Update"},
11+
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from chartlets.components import Checkbox
2+
from tests.component_test import make_base
3+
4+
5+
class CheckboxTest(make_base(Checkbox)):
6+
7+
def test_is_json_serializable(self):
8+
self.assert_is_json_serializable(
9+
self.cls(value=True, label="Auto-safe"),
10+
{"type": "Checkbox", "value": True, "label": "Auto-safe"},
11+
)

chartlets.py/tests/components/plot_test.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,49 @@
77
from chartlets.components import Plot
88

99

10-
class PlotTest(unittest.TestCase):
11-
def setUp(self):
12-
dataset = pd.DataFrame(
10+
from tests.component_test import make_base
11+
12+
13+
class PlotTest(make_base(Plot)):
14+
15+
def test_is_json_serializable(self):
16+
source = pd.DataFrame(
1317
{"x": ["A", "B", "C", "D", "E"], "a": [28, 55, 43, 91, 81]}
1418
)
1519
self.chart = (
16-
alt.Chart(dataset)
20+
alt.Chart(source)
1721
.mark_bar()
1822
.encode(
1923
x=alt.X("x:N", title="x"),
2024
y=alt.Y("a:Q", title="a"),
2125
)
2226
)
2327

24-
def test_is_json_serializable(self):
25-
26-
plot = Plot(id="plot", chart=self.chart)
27-
p = plot.to_dict()
28-
29-
print(p)
30-
self.assertIsInstance(p, dict)
31-
self.assertEqual(p["type"], "Plot")
32-
self.assertIsInstance(p["chart"], dict)
33-
self.assertEqual(p["chart"]["mark"], {"type": "bar"})
34-
json_text = json.dumps(p)
35-
self.assertEqual("{", json_text[0])
36-
self.assertEqual("}", json_text[-1])
28+
self.assert_is_json_serializable(
29+
self.cls(id="plot", chart=self.chart),
30+
{
31+
"chart": {
32+
"$schema": "https://vega.github.io/schema/vega-lite/v5.20.1.json",
33+
"config": {
34+
"view": {"continuousHeight": 300, "continuousWidth": 300}
35+
},
36+
"data": {"name": "data-2780b27b376c14369bf3f449cf25f092"},
37+
"datasets": {
38+
"data-2780b27b376c14369bf3f449cf25f092": [
39+
{"a": 28, "x": "A"},
40+
{"a": 55, "x": "B"},
41+
{"a": 43, "x": "C"},
42+
{"a": 91, "x": "D"},
43+
{"a": 81, "x": "E"},
44+
]
45+
},
46+
"encoding": {
47+
"x": {"field": "x", "title": "x", "type": "nominal"},
48+
"y": {"field": "a", "title": "a", "type": "quantitative"},
49+
},
50+
"mark": {"type": "bar"},
51+
},
52+
"id": "plot",
53+
"type": "Plot",
54+
},
55+
)

0 commit comments

Comments
 (0)