webwidgets 0.2.0__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- webwidgets-1.0.0/.gitignore +2 -0
- webwidgets-1.0.0/PKG-INFO +66 -0
- webwidgets-1.0.0/README.md +53 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/tests/compilation/test_css.py +144 -69
- {webwidgets-0.2.0 → webwidgets-1.0.0}/tests/compilation/test_html_node.py +44 -4
- {webwidgets-0.2.0 → webwidgets-1.0.0}/tests/compilation/test_html_tags.py +18 -4
- webwidgets-1.0.0/tests/utility/test_indentation.py +29 -0
- webwidgets-1.0.0/tests/utility/test_representation.py +118 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/tests/utility/test_validation.py +65 -1
- webwidgets-1.0.0/tests/website/__init__.py +11 -0
- webwidgets-1.0.0/tests/website/test_website.py +402 -0
- webwidgets-1.0.0/tests/widgets/__init__.py +11 -0
- webwidgets-1.0.0/tests/widgets/test_page.py +143 -0
- webwidgets-1.0.0/webwidgets/__init__.py +18 -0
- {webwidgets-0.2.0/webwidgets → webwidgets-1.0.0/webwidgets/compilation/css}/__init__.py +2 -3
- {webwidgets-0.2.0 → webwidgets-1.0.0}/webwidgets/compilation/css/css.py +90 -31
- {webwidgets-0.2.0 → webwidgets-1.0.0}/webwidgets/compilation/html/__init__.py +2 -1
- {webwidgets-0.2.0 → webwidgets-1.0.0}/webwidgets/compilation/html/html_node.py +45 -5
- {webwidgets-0.2.0 → webwidgets-1.0.0}/webwidgets/compilation/html/html_tags.py +38 -1
- {webwidgets-0.2.0 → webwidgets-1.0.0}/webwidgets/utility/__init__.py +2 -0
- webwidgets-1.0.0/webwidgets/utility/indentation.py +25 -0
- webwidgets-1.0.0/webwidgets/utility/representation.py +34 -0
- webwidgets-1.0.0/webwidgets/website/__init__.py +14 -0
- webwidgets-1.0.0/webwidgets/website/compiled_website.py +34 -0
- webwidgets-1.0.0/webwidgets/website/website.py +88 -0
- {webwidgets-0.2.0/webwidgets/compilation/css → webwidgets-1.0.0/webwidgets/widgets}/__init__.py +2 -1
- webwidgets-1.0.0/webwidgets/widgets/containers/__init__.py +14 -0
- webwidgets-1.0.0/webwidgets/widgets/containers/container.py +38 -0
- webwidgets-1.0.0/webwidgets/widgets/containers/page.py +59 -0
- webwidgets-1.0.0/webwidgets/widgets/widget.py +34 -0
- webwidgets-0.2.0/.gitignore +0 -1
- webwidgets-0.2.0/PKG-INFO +0 -18
- webwidgets-0.2.0/README.md +0 -5
- {webwidgets-0.2.0 → webwidgets-1.0.0}/.github/workflows/cd.yml +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/.github/workflows/ci-full.yml +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/.github/workflows/ci-quick.yml +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/LICENSE +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/pyproject.toml +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/tests/__init__.py +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/tests/compilation/__init__.py +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/tests/utility/__init__.py +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/tests/utility/test_sanitizing.py +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/webwidgets/compilation/__init__.py +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/webwidgets/utility/sanitizing.py +0 -0
- {webwidgets-0.2.0 → webwidgets-1.0.0}/webwidgets/utility/validation.py +0 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: webwidgets
|
3
|
+
Version: 1.0.0
|
4
|
+
Summary: A Python package for designing web UIs.
|
5
|
+
Project-URL: Source code, https://github.com/mlaasri/WebWidgets
|
6
|
+
Author: mlaasri
|
7
|
+
License-File: LICENSE
|
8
|
+
Keywords: design,webui
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Requires-Python: >=3.9
|
12
|
+
Description-Content-Type: text/markdown
|
13
|
+
|
14
|
+
# WebWidgets
|
15
|
+
|
16
|
+

|
17
|
+
|
18
|
+
A Python package for creating web UIs
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
You can install **WebWidgets** with `pip`. To install the latest stable version, run:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
pip install webwidgets
|
26
|
+
```
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
**WebWidgets** allows you to create custom widgets and build websites with them. For example:
|
31
|
+
|
32
|
+
```python
|
33
|
+
import webwidgets as ww
|
34
|
+
from webwidgets.compilation.html import HTMLNode, RawText
|
35
|
+
|
36
|
+
# A <div> element
|
37
|
+
class Div(HTMLNode):
|
38
|
+
pass
|
39
|
+
|
40
|
+
# A simple text widget
|
41
|
+
class Text(ww.Widget):
|
42
|
+
def build(self):
|
43
|
+
return Div([RawText("Hello, World!")])
|
44
|
+
|
45
|
+
# A website with one page containing a Text widget
|
46
|
+
page = ww.Page([Text()])
|
47
|
+
website = ww.Website([page])
|
48
|
+
|
49
|
+
# Compile the website into HTML code
|
50
|
+
compiled = website.compile()
|
51
|
+
print(compiled.html_content[0])
|
52
|
+
```
|
53
|
+
|
54
|
+
Prints the following result:
|
55
|
+
|
56
|
+
```console
|
57
|
+
<!DOCTYPE html>
|
58
|
+
<html>
|
59
|
+
<head></head>
|
60
|
+
<body>
|
61
|
+
<div>
|
62
|
+
Hello, World!
|
63
|
+
</div>
|
64
|
+
</body>
|
65
|
+
</html>
|
66
|
+
```
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# WebWidgets
|
2
|
+
|
3
|
+

|
4
|
+
|
5
|
+
A Python package for creating web UIs
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
You can install **WebWidgets** with `pip`. To install the latest stable version, run:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
pip install webwidgets
|
13
|
+
```
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
**WebWidgets** allows you to create custom widgets and build websites with them. For example:
|
18
|
+
|
19
|
+
```python
|
20
|
+
import webwidgets as ww
|
21
|
+
from webwidgets.compilation.html import HTMLNode, RawText
|
22
|
+
|
23
|
+
# A <div> element
|
24
|
+
class Div(HTMLNode):
|
25
|
+
pass
|
26
|
+
|
27
|
+
# A simple text widget
|
28
|
+
class Text(ww.Widget):
|
29
|
+
def build(self):
|
30
|
+
return Div([RawText("Hello, World!")])
|
31
|
+
|
32
|
+
# A website with one page containing a Text widget
|
33
|
+
page = ww.Page([Text()])
|
34
|
+
website = ww.Website([page])
|
35
|
+
|
36
|
+
# Compile the website into HTML code
|
37
|
+
compiled = website.compile()
|
38
|
+
print(compiled.html_content[0])
|
39
|
+
```
|
40
|
+
|
41
|
+
Prints the following result:
|
42
|
+
|
43
|
+
```console
|
44
|
+
<!DOCTYPE html>
|
45
|
+
<html>
|
46
|
+
<head></head>
|
47
|
+
<body>
|
48
|
+
<div>
|
49
|
+
Hello, World!
|
50
|
+
</div>
|
51
|
+
</body>
|
52
|
+
</html>
|
53
|
+
```
|
@@ -11,12 +11,38 @@
|
|
11
11
|
# =======================================================================
|
12
12
|
|
13
13
|
import pytest
|
14
|
+
from typing import Any, Dict, List
|
14
15
|
from webwidgets.compilation.html.html_node import HTMLNode
|
15
16
|
from webwidgets.compilation.html.html_tags import TextNode
|
16
|
-
from webwidgets.compilation.css.css import compile_css, CompiledCSS,
|
17
|
+
from webwidgets.compilation.css.css import compile_css, CSSRule, CompiledCSS, \
|
18
|
+
apply_css, default_rule_namer
|
17
19
|
|
18
20
|
|
19
21
|
class TestCompileCSS:
|
22
|
+
@staticmethod
|
23
|
+
def _serialize_rules(rules: List[CSSRule]) -> List[Dict[str, Any]]:
|
24
|
+
"""Utility function to convert a list of :py:class:`CSSRule` objects
|
25
|
+
into a dictionary that can be used in testing.
|
26
|
+
|
27
|
+
:param rules: List of :py:class:`CSSRule` objects.
|
28
|
+
:type rules: List[CSSRule]
|
29
|
+
:return: List of the member variables of each :py:class:`CSSRule`.
|
30
|
+
:rtype: Dict[int, Any]
|
31
|
+
"""
|
32
|
+
return [vars(rule) for rule in rules]
|
33
|
+
|
34
|
+
@staticmethod
|
35
|
+
def _serialize_mapping(mapping: Dict[int, List[CSSRule]]) -> Dict[int, List[str]]:
|
36
|
+
"""Utility function to convert a :py:attr:`CompiledCSS.mapping` object
|
37
|
+
into a dictionary that can be used in testing.
|
38
|
+
|
39
|
+
:param mapping: :py:attr:`CompiledCSS.mapping` object.
|
40
|
+
:type mapping: Dict[int, List[CSSRule]]
|
41
|
+
:return: Dictionary mapping each node ID to the name of the rules that
|
42
|
+
achieve the same style.
|
43
|
+
"""
|
44
|
+
return {i: [r.name for r in rules] for i, rules in mapping.items()}
|
45
|
+
|
20
46
|
def test_argument_type(self):
|
21
47
|
"""Compares compilation when given a node object versus a list of
|
22
48
|
nodes.
|
@@ -30,10 +56,10 @@ class TestCompileCSS:
|
|
30
56
|
)
|
31
57
|
|
32
58
|
# Define expected compilation results
|
33
|
-
expected_rules =
|
34
|
-
|
35
|
-
|
36
|
-
|
59
|
+
expected_rules = [
|
60
|
+
{"name": "r0", "declarations": {"a": "5"}},
|
61
|
+
{"name": "r1", "declarations": {"b": "4"}}
|
62
|
+
]
|
37
63
|
expected_mapping = {
|
38
64
|
id(tree): ['r0', 'r1'],
|
39
65
|
id(tree.children[0]): ['r0']
|
@@ -45,8 +71,10 @@ class TestCompileCSS:
|
|
45
71
|
# Check results of compilation
|
46
72
|
assert compiled_css.trees == [tree]
|
47
73
|
assert [id(t) for t in compiled_css.trees] == [id(tree)]
|
48
|
-
assert
|
49
|
-
|
74
|
+
assert TestCompileCSS._serialize_rules(
|
75
|
+
compiled_css.rules) == expected_rules
|
76
|
+
assert TestCompileCSS._serialize_mapping(
|
77
|
+
compiled_css.mapping) == expected_mapping
|
50
78
|
|
51
79
|
# Compile tree as list of one node
|
52
80
|
compiled_css2 = compile_css([tree])
|
@@ -54,8 +82,10 @@ class TestCompileCSS:
|
|
54
82
|
# Check results of compilation again (should be unchanged)
|
55
83
|
assert compiled_css2.trees == [tree]
|
56
84
|
assert [id(t) for t in compiled_css2.trees] == [id(tree)]
|
57
|
-
assert
|
58
|
-
|
85
|
+
assert TestCompileCSS._serialize_rules(
|
86
|
+
compiled_css2.rules) == expected_rules
|
87
|
+
assert TestCompileCSS._serialize_mapping(
|
88
|
+
compiled_css2.mapping) == expected_mapping
|
59
89
|
|
60
90
|
def test_basic_compilation(self):
|
61
91
|
# Create some HTML nodes with different styles
|
@@ -72,17 +102,19 @@ class TestCompileCSS:
|
|
72
102
|
id(node1), id(node2), id(node3)]
|
73
103
|
|
74
104
|
# Check that the rules are correctly generated
|
75
|
-
expected_rules =
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
assert
|
105
|
+
expected_rules = [
|
106
|
+
{"name": "r0", "declarations": {"color": "blue"}},
|
107
|
+
{"name": "r1", "declarations": {"margin": "0"}},
|
108
|
+
{"name": "r2", "declarations": {"padding": "0"}}
|
109
|
+
]
|
110
|
+
assert TestCompileCSS._serialize_rules(
|
111
|
+
compiled_css.rules) == expected_rules
|
81
112
|
|
82
113
|
# Check that the mapping is correctly generated
|
83
114
|
expected_mapping = {id(node1): ['r1', 'r2'], id(
|
84
115
|
node2): ['r0', 'r1'], id(node3): ['r1', 'r2']}
|
85
|
-
assert
|
116
|
+
assert TestCompileCSS._serialize_mapping(
|
117
|
+
compiled_css.mapping) == expected_mapping
|
86
118
|
|
87
119
|
def test_nested_compilation_one_tree(self):
|
88
120
|
# Create some nested HTML nodes
|
@@ -104,13 +136,14 @@ class TestCompileCSS:
|
|
104
136
|
assert [id(t) for t in compiled_css.trees] == [id(tree)]
|
105
137
|
|
106
138
|
# Check that the rules are correctly generated
|
107
|
-
expected_rules =
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
assert
|
139
|
+
expected_rules = [
|
140
|
+
{"name": "r0", "declarations": {"color": "blue"}},
|
141
|
+
{"name": "r1", "declarations": {"margin": "0"}},
|
142
|
+
{"name": "r2", "declarations": {"margin": "5"}},
|
143
|
+
{"name": "r3", "declarations": {"padding": "0"}}
|
144
|
+
]
|
145
|
+
assert TestCompileCSS._serialize_rules(
|
146
|
+
compiled_css.rules) == expected_rules
|
114
147
|
|
115
148
|
# Check that the mapping is correctly generated
|
116
149
|
expected_mapping = {
|
@@ -120,7 +153,8 @@ class TestCompileCSS:
|
|
120
153
|
id(tree.children[0].children[0]): [],
|
121
154
|
id(tree.children[1].children[0]): []
|
122
155
|
}
|
123
|
-
assert
|
156
|
+
assert TestCompileCSS._serialize_mapping(
|
157
|
+
compiled_css.mapping) == expected_mapping
|
124
158
|
|
125
159
|
def test_nested_compilation_two_trees(self):
|
126
160
|
# Create 2 trees
|
@@ -146,13 +180,14 @@ class TestCompileCSS:
|
|
146
180
|
id(tree1), id(tree2)]
|
147
181
|
|
148
182
|
# Check that the rules are correctly generated
|
149
|
-
expected_rules =
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
assert
|
183
|
+
expected_rules = [
|
184
|
+
{"name": "r0", "declarations": {"color": "red"}},
|
185
|
+
{"name": "r1", "declarations": {"margin": "10"}},
|
186
|
+
{"name": "r2", "declarations": {"margin": "5"}},
|
187
|
+
{"name": "r3", "declarations": {"padding": "0"}}
|
188
|
+
]
|
189
|
+
assert TestCompileCSS._serialize_rules(
|
190
|
+
compiled_css.rules) == expected_rules
|
156
191
|
|
157
192
|
# Check that the mapping is correctly generated
|
158
193
|
expected_mapping = {
|
@@ -161,7 +196,8 @@ class TestCompileCSS:
|
|
161
196
|
id(tree2): ['r2', 'r3'],
|
162
197
|
id(tree2.children[0]): ['r1']
|
163
198
|
}
|
164
|
-
assert
|
199
|
+
assert TestCompileCSS._serialize_mapping(
|
200
|
+
compiled_css.mapping) == expected_mapping
|
165
201
|
|
166
202
|
def test_rules_numbered_in_order(self):
|
167
203
|
"""Test that rules are numbered in lexicographical order"""
|
@@ -174,14 +210,15 @@ class TestCompileCSS:
|
|
174
210
|
]
|
175
211
|
)
|
176
212
|
compiled_css = compile_css(tree)
|
177
|
-
expected_rules =
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
assert
|
213
|
+
expected_rules = [
|
214
|
+
{"name": "r0", "declarations": {"a": "10"}},
|
215
|
+
{"name": "r1", "declarations": {"a": "5"}},
|
216
|
+
{"name": "r2", "declarations": {"b": "10"}},
|
217
|
+
{"name": "r3", "declarations": {"b": "4"}},
|
218
|
+
{"name": "r4", "declarations": {"c": "5"}},
|
219
|
+
]
|
220
|
+
assert TestCompileCSS._serialize_rules(
|
221
|
+
compiled_css.rules) == expected_rules
|
185
222
|
|
186
223
|
def test_duplicate_node(self):
|
187
224
|
"""Test that adding the same node twice does not impact compilation"""
|
@@ -193,11 +230,11 @@ class TestCompileCSS:
|
|
193
230
|
HTMLNode(style={"b": "10"}),
|
194
231
|
]
|
195
232
|
)
|
196
|
-
expected_rules =
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
233
|
+
expected_rules = [
|
234
|
+
{"name": "r0", "declarations": {"a": "5"}},
|
235
|
+
{"name": "r1", "declarations": {"b": "10"}},
|
236
|
+
{"name": "r2", "declarations": {"b": "4"}}
|
237
|
+
]
|
201
238
|
expected_mapping = {
|
202
239
|
id(tree): ['r0', 'r2'],
|
203
240
|
id(tree.children[0]): ['r0'],
|
@@ -206,8 +243,10 @@ class TestCompileCSS:
|
|
206
243
|
compiled_css = compile_css([tree])
|
207
244
|
assert compiled_css.trees == [tree]
|
208
245
|
assert [id(t) for t in compiled_css.trees] == [id(tree)]
|
209
|
-
assert
|
210
|
-
|
246
|
+
assert TestCompileCSS._serialize_rules(
|
247
|
+
compiled_css.rules) == expected_rules
|
248
|
+
assert TestCompileCSS._serialize_mapping(
|
249
|
+
compiled_css.mapping) == expected_mapping
|
211
250
|
|
212
251
|
# Compiling the tree and one of its children, which should already be
|
213
252
|
# included recursively from the tree itself and should not affect the
|
@@ -216,25 +255,41 @@ class TestCompileCSS:
|
|
216
255
|
assert compiled_css2.trees == [tree, tree.children[0]]
|
217
256
|
assert [id(t) for t in compiled_css2.trees] == [
|
218
257
|
id(tree), id(tree.children[0])]
|
219
|
-
assert
|
220
|
-
|
258
|
+
assert TestCompileCSS._serialize_rules(
|
259
|
+
compiled_css2.rules) == expected_rules
|
260
|
+
assert TestCompileCSS._serialize_mapping(
|
261
|
+
compiled_css2.mapping) == expected_mapping
|
262
|
+
|
263
|
+
@pytest.mark.parametrize("rule_namer, names", [
|
264
|
+
(lambda _, i: f"rule{i}", ["rule0", "rule1", "rule2"]),
|
265
|
+
(lambda _, i: f"rule-{i + 1}", ["rule-1", "rule-2", "rule-3"]),
|
266
|
+
(lambda r, i: f"{list(r[i].declarations.items())[0][0]}{i}", [
|
267
|
+
"az0", "bz1", "bz2"]),
|
268
|
+
(lambda r, i: f"{list(r[i].declarations.items())[0][0][0]}{i}", [
|
269
|
+
"a0", "b1", "b2"]),
|
270
|
+
(lambda r, i: f"r{list(r[i].declarations.items())[0][1]}-{i}", [
|
271
|
+
"r10-1", "r4-2", "r5-0"]),
|
272
|
+
])
|
273
|
+
def test_custom_rule_names(self, rule_namer, names):
|
274
|
+
tree = HTMLNode(
|
275
|
+
style={"az": "5", "bz": "4"},
|
276
|
+
children=[
|
277
|
+
HTMLNode(style={"az": "5"}),
|
278
|
+
HTMLNode(style={"bz": "10"}),
|
279
|
+
]
|
280
|
+
)
|
281
|
+
compiled_css = compile_css(tree, rule_namer=rule_namer)
|
282
|
+
assert [r.name for r in compiled_css.rules] == names
|
221
283
|
|
222
284
|
|
223
285
|
class TestCompiledCSS:
|
224
286
|
def test_export_custom_compiled_css(self):
|
225
|
-
rules =
|
226
|
-
"r0":
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
"color": "blue"
|
232
|
-
},
|
233
|
-
"r2": {
|
234
|
-
"background-color": "white",
|
235
|
-
"font-size": "16px"
|
236
|
-
}
|
237
|
-
}
|
287
|
+
rules = [
|
288
|
+
CSSRule(name="r0", declarations={"margin": "0", "padding": "0"}),
|
289
|
+
CSSRule(name="r1", declarations={"color": "blue"}),
|
290
|
+
CSSRule(name="r2", declarations={
|
291
|
+
"background-color": "white", "font-size": "16px"})
|
292
|
+
]
|
238
293
|
compiled_css = CompiledCSS(trees=None,
|
239
294
|
rules=rules,
|
240
295
|
mapping=None)
|
@@ -364,12 +419,12 @@ class TestApplyCSS:
|
|
364
419
|
|
365
420
|
# Compiling and applying CSS to the tree
|
366
421
|
compiled_css = compile_css(tree)
|
367
|
-
assert compiled_css.rules ==
|
368
|
-
"r0": {"color": "blue"},
|
369
|
-
"r1": {"color": "green"},
|
370
|
-
"r2": {"margin": "0"},
|
371
|
-
"r3": {"padding": "0"}
|
372
|
-
|
422
|
+
assert TestCompileCSS._serialize_rules(compiled_css.rules) == [
|
423
|
+
{"name": "r0", "declarations": {"color": "blue"}},
|
424
|
+
{"name": "r1", "declarations": {"color": "green"}},
|
425
|
+
{"name": "r2", "declarations": {"margin": "0"}},
|
426
|
+
{"name": "r3", "declarations": {"padding": "0"}}
|
427
|
+
]
|
373
428
|
apply_css(compiled_css, tree)
|
374
429
|
|
375
430
|
# Checking the tree's new classes
|
@@ -398,7 +453,7 @@ class TestApplyCSS:
|
|
398
453
|
)
|
399
454
|
html_before = tree.to_html()
|
400
455
|
compiled_css = compile_css(tree)
|
401
|
-
assert compiled_css.rules ==
|
456
|
+
assert compiled_css.rules == []
|
402
457
|
apply_css(compiled_css, tree)
|
403
458
|
html_after = tree.to_html()
|
404
459
|
|
@@ -475,3 +530,23 @@ class TestApplyCSS:
|
|
475
530
|
apply_css(compiled_css, tree)
|
476
531
|
assert "class" not in tree.attributes
|
477
532
|
assert tree.to_html() == '<htmlnode></htmlnode>'
|
533
|
+
|
534
|
+
|
535
|
+
class TestDefaultRuleNamer:
|
536
|
+
def test_default_rule_namer(self):
|
537
|
+
rules = [CSSRule(None, {"color": "red"}),
|
538
|
+
CSSRule(None, {"margin": "0"})]
|
539
|
+
for i, rule in enumerate(rules):
|
540
|
+
rule.name = default_rule_namer(rules=rules, index=i)
|
541
|
+
assert rules[0].name == "r0"
|
542
|
+
assert rules[1].name == "r1"
|
543
|
+
|
544
|
+
def test_default_rule_namer_override(self):
|
545
|
+
rules = [CSSRule("first", {"color": "red"}),
|
546
|
+
CSSRule("second", {"margin": "0"})]
|
547
|
+
assert rules[0].name == "first"
|
548
|
+
assert rules[1].name == "second"
|
549
|
+
for i, rule in enumerate(rules):
|
550
|
+
rule.name = default_rule_namer(rules=rules, index=i)
|
551
|
+
assert rules[0].name == "r0"
|
552
|
+
assert rules[1].name == "r1"
|
@@ -12,7 +12,7 @@
|
|
12
12
|
|
13
13
|
import pytest
|
14
14
|
from webwidgets.compilation.html.html_node import HTMLNode, no_start_tag, \
|
15
|
-
no_end_tag, one_line, RawText
|
15
|
+
no_end_tag, one_line, RawText, RootNode
|
16
16
|
|
17
17
|
|
18
18
|
class TestHTMLNode:
|
@@ -63,9 +63,14 @@ class TestHTMLNode:
|
|
63
63
|
|
64
64
|
def test_attributes(self):
|
65
65
|
node = HTMLNode(attributes={'id': 'test-id', 'class': 'test-class'})
|
66
|
-
assert node.start_tag == '<htmlnode
|
66
|
+
assert node.start_tag == '<htmlnode class="test-class" id="test-id">'
|
67
67
|
assert node.end_tag == '</htmlnode>'
|
68
|
-
assert node.to_html() == '<htmlnode
|
68
|
+
assert node.to_html() == '<htmlnode class="test-class" id="test-id"></htmlnode>'
|
69
|
+
|
70
|
+
def test_attributes_order(self):
|
71
|
+
node = HTMLNode(attributes={'d': '0', 'a': '1', 'c': '2', 'b': '3'})
|
72
|
+
assert node._render_attributes() == 'a="1" b="3" c="2" d="0"'
|
73
|
+
assert node.to_html() == '<htmlnode a="1" b="3" c="2" d="0"></htmlnode>'
|
69
74
|
|
70
75
|
def test_no_start_tag(self):
|
71
76
|
node = TestHTMLNode.NoStartNode()
|
@@ -230,7 +235,7 @@ class TestHTMLNode:
|
|
230
235
|
assert node.to_html() == expected_html
|
231
236
|
|
232
237
|
@pytest.mark.parametrize("indent_level", [0, 1, 2])
|
233
|
-
@pytest.mark.parametrize("indent_size", [3, 4, 8])
|
238
|
+
@pytest.mark.parametrize("indent_size", [2, 3, 4, 8])
|
234
239
|
def test_indentation(self, indent_level: int, indent_size: int):
|
235
240
|
"""Test the to_html method with different indentation parameters."""
|
236
241
|
|
@@ -270,6 +275,28 @@ class TestHTMLNode:
|
|
270
275
|
indent_size=indent_size, indent_level=indent_level)
|
271
276
|
assert actual_html == expected_html
|
272
277
|
|
278
|
+
@pytest.mark.parametrize("indent_level", [-3, -2, -1])
|
279
|
+
def test_negative_indent_level(self, indent_level: int):
|
280
|
+
node = HTMLNode(children=[
|
281
|
+
RawText('child1'),
|
282
|
+
RawText('child2'),
|
283
|
+
HTMLNode(children=[
|
284
|
+
RawText('grandchild1'),
|
285
|
+
RawText('grandchild2')
|
286
|
+
])
|
287
|
+
])
|
288
|
+
expected_html = "\n".join([
|
289
|
+
"<htmlnode>",
|
290
|
+
"child1",
|
291
|
+
"child2",
|
292
|
+
"<htmlnode>",
|
293
|
+
f"{' ' if indent_level == -1 else ''}grandchild1",
|
294
|
+
f"{' ' if indent_level == -1 else ''}grandchild2",
|
295
|
+
"</htmlnode>",
|
296
|
+
"</htmlnode>"
|
297
|
+
])
|
298
|
+
assert node.to_html(indent_level=indent_level) == expected_html
|
299
|
+
|
273
300
|
def test_collapse_empty(self):
|
274
301
|
node = HTMLNode(children=[
|
275
302
|
TestHTMLNode.CustomNode(),
|
@@ -441,3 +468,16 @@ class TestHTMLNode:
|
|
441
468
|
copied_node.children.append(child)
|
442
469
|
assert len(node.children) == 1
|
443
470
|
assert id(node.children[0]) == id(child)
|
471
|
+
|
472
|
+
def test_empty_root_node(self):
|
473
|
+
node = RootNode()
|
474
|
+
assert node.to_html() == ""
|
475
|
+
|
476
|
+
@pytest.mark.parametrize("n", [1, 2, 3])
|
477
|
+
def test_root_node_with_children(self, n):
|
478
|
+
node = RootNode(
|
479
|
+
children=[HTMLNode()] * n
|
480
|
+
)
|
481
|
+
expected_html = "\n".join(["<htmlnode></htmlnode>"] * n)
|
482
|
+
print(expected_html)
|
483
|
+
assert node.to_html() == expected_html
|
@@ -21,7 +21,21 @@ class TestHTMLTags:
|
|
21
21
|
def test_text_node_with_attributes(self):
|
22
22
|
text_node = TextNode("Hello, World!",
|
23
23
|
attributes={"class": "my-class", "id": "my-id"})
|
24
|
-
assert text_node.to_html()
|
25
|
-
'<textnode class="my-class" id="my-id">Hello, World!</textnode>'
|
26
|
-
|
27
|
-
|
24
|
+
assert text_node.to_html() == \
|
25
|
+
'<textnode class="my-class" id="my-id">Hello, World!</textnode>'
|
26
|
+
|
27
|
+
def test_body(self):
|
28
|
+
body = Body()
|
29
|
+
assert body.to_html() == "<body></body>"
|
30
|
+
|
31
|
+
def test_doctype(self):
|
32
|
+
doctype = Doctype()
|
33
|
+
assert doctype.to_html() == "<!DOCTYPE html>"
|
34
|
+
|
35
|
+
def test_head(self):
|
36
|
+
head = Head()
|
37
|
+
assert head.to_html() == "<head></head>"
|
38
|
+
|
39
|
+
def test_html(self):
|
40
|
+
html = Html()
|
41
|
+
assert html.to_html() == "<html></html>"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# =======================================================================
|
2
|
+
#
|
3
|
+
# This file is part of WebWidgets, a Python package for designing web
|
4
|
+
# UIs.
|
5
|
+
#
|
6
|
+
# You should have received a copy of the MIT License along with
|
7
|
+
# WebWidgets. If not, see <https://opensource.org/license/mit>.
|
8
|
+
#
|
9
|
+
# Copyright(C) 2025, mlaasri
|
10
|
+
#
|
11
|
+
# =======================================================================
|
12
|
+
|
13
|
+
import pytest
|
14
|
+
from webwidgets.utility.indentation import get_indentation
|
15
|
+
|
16
|
+
|
17
|
+
class TestIndentation:
|
18
|
+
@pytest.mark.parametrize("indent_level", [0, 1, 2])
|
19
|
+
@pytest.mark.parametrize("indent_size", [3, 4, 8])
|
20
|
+
def test_get_indentation(self, indent_level: int, indent_size: int):
|
21
|
+
"""Tests get_indentation with different indentation levels and sizes."""
|
22
|
+
expected_indentation = ' ' * indent_size * indent_level
|
23
|
+
assert get_indentation(
|
24
|
+
indent_level, indent_size) == expected_indentation
|
25
|
+
|
26
|
+
@pytest.mark.parametrize("indent_level", [-2, -1, 0])
|
27
|
+
@pytest.mark.parametrize("indent_size", [3, 4, 8])
|
28
|
+
def test_get_indentation_for_negative_levels(self, indent_level, indent_size):
|
29
|
+
assert get_indentation(indent_level, indent_size) == ''
|