webwidgets 0.2.1__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.1 → webwidgets-1.0.0}/tests/compilation/test_html_node.py +44 -4
- {webwidgets-0.2.1 → 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/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-0.2.1 → webwidgets-1.0.0}/webwidgets/__init__.py +4 -1
- {webwidgets-0.2.1 → webwidgets-1.0.0}/webwidgets/compilation/css/css.py +4 -3
- {webwidgets-0.2.1 → webwidgets-1.0.0}/webwidgets/compilation/html/__init__.py +2 -1
- {webwidgets-0.2.1 → webwidgets-1.0.0}/webwidgets/compilation/html/html_node.py +42 -4
- {webwidgets-0.2.1 → webwidgets-1.0.0}/webwidgets/compilation/html/html_tags.py +38 -1
- {webwidgets-0.2.1 → webwidgets-1.0.0}/webwidgets/utility/__init__.py +1 -0
- webwidgets-1.0.0/webwidgets/utility/indentation.py +25 -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-1.0.0/webwidgets/widgets/__init__.py +14 -0
- 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.1/.gitignore +0 -1
- webwidgets-0.2.1/PKG-INFO +0 -18
- webwidgets-0.2.1/README.md +0 -5
- {webwidgets-0.2.1 → webwidgets-1.0.0}/.github/workflows/cd.yml +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/.github/workflows/ci-full.yml +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/.github/workflows/ci-quick.yml +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/LICENSE +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/pyproject.toml +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/tests/__init__.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/tests/compilation/__init__.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/tests/compilation/test_css.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/tests/utility/__init__.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/tests/utility/test_representation.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/tests/utility/test_sanitizing.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/tests/utility/test_validation.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/webwidgets/compilation/__init__.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/webwidgets/compilation/css/__init__.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/webwidgets/utility/representation.py +0 -0
- {webwidgets-0.2.1 → webwidgets-1.0.0}/webwidgets/utility/sanitizing.py +0 -0
- {webwidgets-0.2.1 → 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
|
+
```
|
@@ -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) == ''
|
@@ -0,0 +1,11 @@
|
|
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
|
+
# =======================================================================
|
@@ -0,0 +1,402 @@
|
|
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 typing import Dict
|
15
|
+
import webwidgets as ww
|
16
|
+
from webwidgets.compilation.html.html_node import HTMLNode, RawText
|
17
|
+
|
18
|
+
|
19
|
+
class TestWebsite:
|
20
|
+
class Empty(ww.Widget):
|
21
|
+
def build(self):
|
22
|
+
return HTMLNode()
|
23
|
+
|
24
|
+
class Text(ww.Widget):
|
25
|
+
def __init__(self, text: str, style: Dict[str, str] = None):
|
26
|
+
super().__init__()
|
27
|
+
self.text = text
|
28
|
+
self.style = style
|
29
|
+
|
30
|
+
def build(self):
|
31
|
+
return HTMLNode(children=[RawText(self.text)], style=self.style)
|
32
|
+
|
33
|
+
class SimpleWebsite(ww.Website):
|
34
|
+
def __init__(self):
|
35
|
+
page = ww.Page([
|
36
|
+
TestWebsite.Text("Text!", {"padding": "0"}),
|
37
|
+
TestWebsite.Text("Another Text!", {"margin": "0"}),
|
38
|
+
])
|
39
|
+
super().__init__([page])
|
40
|
+
|
41
|
+
@pytest.mark.parametrize("num_pages", [1, 2, 3])
|
42
|
+
@pytest.mark.parametrize("num_widgets", [1, 2, 3])
|
43
|
+
@pytest.mark.parametrize("text", ["a", "b", "c"])
|
44
|
+
def test_compile_website_without_css(self, num_pages, num_widgets, text):
|
45
|
+
# Create a new website object
|
46
|
+
website = ww.Website()
|
47
|
+
for _ in range(num_pages):
|
48
|
+
website.add(ww.Page([TestWebsite.Text(text)] * num_widgets))
|
49
|
+
|
50
|
+
# Compile the website to HTML
|
51
|
+
compiled = website.compile()
|
52
|
+
|
53
|
+
# Check if the compiled HTML contains the expected code
|
54
|
+
expected_html = "\n".join([
|
55
|
+
"<!DOCTYPE html>",
|
56
|
+
"<html>",
|
57
|
+
" <head></head>",
|
58
|
+
" <body>"
|
59
|
+
]) + "\n" + "\n".join([
|
60
|
+
" <htmlnode>",
|
61
|
+
f" {text}",
|
62
|
+
" </htmlnode>",
|
63
|
+
] * num_widgets) + "\n" + "\n".join([
|
64
|
+
" </body>",
|
65
|
+
"</html>"
|
66
|
+
])
|
67
|
+
assert len(compiled.html_content) == num_pages
|
68
|
+
assert all(c == expected_html for c in compiled.html_content)
|
69
|
+
|
70
|
+
# Check if the compiled CSS contains the expected code
|
71
|
+
assert compiled.css_content == "" # No CSS in this case
|
72
|
+
|
73
|
+
@pytest.mark.parametrize("num_pages", [1, 2, 3, 4, 5, 6])
|
74
|
+
@pytest.mark.parametrize("num_widgets", [1, 2, 3])
|
75
|
+
@pytest.mark.parametrize("text", ["a", "b", "c"])
|
76
|
+
def test_compile_website_with_css(self, num_pages, num_widgets, text):
|
77
|
+
# Defining a set of styles to pick from
|
78
|
+
styles = [{"margin": "0"}, {"color": "blue"}, {"font-size": "16px"}]
|
79
|
+
|
80
|
+
# Compile expected rule names based on number of pages involved
|
81
|
+
rule_names = {
|
82
|
+
1: ["r0"],
|
83
|
+
2: ["r1", "r0"],
|
84
|
+
3: ["r2", "r0", "r1"]
|
85
|
+
}
|
86
|
+
for k in range(4, num_pages + 1):
|
87
|
+
rule_names[k] = [rule_names[3][i % len(styles)] for i in range(k)]
|
88
|
+
|
89
|
+
# Create a new website object
|
90
|
+
website = ww.Website()
|
91
|
+
for i in range(num_pages):
|
92
|
+
website.add(
|
93
|
+
ww.Page([
|
94
|
+
TestWebsite.Text(text, styles[i % len(styles)])
|
95
|
+
] * num_widgets))
|
96
|
+
|
97
|
+
# Compile the website to HTML
|
98
|
+
compiled = website.compile()
|
99
|
+
|
100
|
+
# Check if the compiled HTML contains the expected code
|
101
|
+
expected_html = [(
|
102
|
+
"\n".join([
|
103
|
+
"<!DOCTYPE html>",
|
104
|
+
"<html>",
|
105
|
+
" <head>",
|
106
|
+
' <link href="styles.css" rel="stylesheet">',
|
107
|
+
" </head>",
|
108
|
+
" <body>"
|
109
|
+
]) + "\n" + "\n".join([
|
110
|
+
f' <htmlnode class="{rule_names[num_pages][i % len(rule_names)]}">',
|
111
|
+
f" {text}",
|
112
|
+
" </htmlnode>",
|
113
|
+
] * num_widgets) + "\n" + "\n".join([
|
114
|
+
" </body>",
|
115
|
+
"</html>"
|
116
|
+
])) for i in range(num_pages)]
|
117
|
+
assert compiled.html_content == expected_html
|
118
|
+
|
119
|
+
# Check if the compiled CSS contains the expected code
|
120
|
+
sorted_rules = sorted(list(set(
|
121
|
+
zip(rule_names[num_pages],
|
122
|
+
[list(styles[i % len(styles)].items())[0]
|
123
|
+
for i in range(num_pages)]))), key=lambda x: x[0])
|
124
|
+
expected_css = "\n\n".join([
|
125
|
+
'\n'.join([
|
126
|
+
f".{name} " + "{",
|
127
|
+
f" {p}: {v};",
|
128
|
+
"}"
|
129
|
+
]) for name, (p, v) in sorted_rules
|
130
|
+
])
|
131
|
+
assert compiled.css_content == expected_css
|
132
|
+
|
133
|
+
def test_compile_collapse_empty(self):
|
134
|
+
website = ww.Website([ww.Page([TestWebsite.Empty()])])
|
135
|
+
|
136
|
+
# Collapse empty elements
|
137
|
+
compiled_true = website.compile(collapse_empty=True)
|
138
|
+
expected_html_true = "\n".join([
|
139
|
+
"<!DOCTYPE html>",
|
140
|
+
"<html>",
|
141
|
+
" <head></head>",
|
142
|
+
" <body>",
|
143
|
+
" <htmlnode></htmlnode>",
|
144
|
+
" </body>",
|
145
|
+
"</html>"
|
146
|
+
])
|
147
|
+
assert len(compiled_true.html_content) == 1
|
148
|
+
assert compiled_true.html_content[0] == expected_html_true
|
149
|
+
assert compiled_true.css_content == ""
|
150
|
+
|
151
|
+
# Don't collapse empty elements
|
152
|
+
compiled_false = website.compile(collapse_empty=False)
|
153
|
+
expected_html_false = "\n".join([
|
154
|
+
"<!DOCTYPE html>",
|
155
|
+
"<html>",
|
156
|
+
" <head>",
|
157
|
+
" </head>",
|
158
|
+
" <body>",
|
159
|
+
" <htmlnode>",
|
160
|
+
" </htmlnode>",
|
161
|
+
" </body>",
|
162
|
+
"</html>"
|
163
|
+
])
|
164
|
+
assert len(compiled_false.html_content) == 1
|
165
|
+
assert compiled_false.html_content[0] == expected_html_false
|
166
|
+
assert compiled_false.css_content == ""
|
167
|
+
|
168
|
+
@pytest.mark.parametrize("css_file_name",
|
169
|
+
["style.css", "s.css", "css.css"])
|
170
|
+
def test_compile_css_file_name(self, css_file_name):
|
171
|
+
website = TestWebsite.SimpleWebsite()
|
172
|
+
compiled = website.compile(css_file_name=css_file_name)
|
173
|
+
expected_html = "\n".join([
|
174
|
+
"<!DOCTYPE html>",
|
175
|
+
"<html>",
|
176
|
+
" <head>",
|
177
|
+
f' <link href="{css_file_name}" rel="stylesheet">',
|
178
|
+
" </head>",
|
179
|
+
" <body>",
|
180
|
+
' <htmlnode class="r1">',
|
181
|
+
" Text!",
|
182
|
+
" </htmlnode>",
|
183
|
+
' <htmlnode class="r0">',
|
184
|
+
" Another Text!",
|
185
|
+
" </htmlnode>",
|
186
|
+
" </body>",
|
187
|
+
"</html>"
|
188
|
+
])
|
189
|
+
expected_css = "\n".join([
|
190
|
+
".r0 {",
|
191
|
+
" margin: 0;",
|
192
|
+
"}",
|
193
|
+
"",
|
194
|
+
".r1 {",
|
195
|
+
" padding: 0;",
|
196
|
+
"}"
|
197
|
+
])
|
198
|
+
assert len(compiled.html_content) == 1
|
199
|
+
assert compiled.html_content[0] == expected_html
|
200
|
+
assert compiled.css_content == expected_css
|
201
|
+
|
202
|
+
def test_compile_force_one_line(self):
|
203
|
+
website = TestWebsite.SimpleWebsite()
|
204
|
+
expected_css = "\n".join([
|
205
|
+
".r0 {",
|
206
|
+
" margin: 0;",
|
207
|
+
"}",
|
208
|
+
"",
|
209
|
+
".r1 {",
|
210
|
+
" padding: 0;",
|
211
|
+
"}"
|
212
|
+
])
|
213
|
+
|
214
|
+
# Force one line HTML
|
215
|
+
compiled_true = website.compile(force_one_line=True)
|
216
|
+
expected_html_true = ''.join([
|
217
|
+
"<!DOCTYPE html>",
|
218
|
+
"<html>",
|
219
|
+
"<head>",
|
220
|
+
'<link href="styles.css" rel="stylesheet">',
|
221
|
+
"</head>",
|
222
|
+
"<body>",
|
223
|
+
'<htmlnode class="r1">',
|
224
|
+
"Text!",
|
225
|
+
"</htmlnode>",
|
226
|
+
'<htmlnode class="r0">',
|
227
|
+
"Another Text!",
|
228
|
+
"</htmlnode>",
|
229
|
+
"</body>",
|
230
|
+
"</html>"
|
231
|
+
])
|
232
|
+
assert len(compiled_true.html_content) == 1
|
233
|
+
assert compiled_true.html_content[0] == expected_html_true
|
234
|
+
assert compiled_true.css_content == expected_css
|
235
|
+
|
236
|
+
# Don't force one line HTML
|
237
|
+
compiled_false = website.compile(force_one_line=False)
|
238
|
+
expected_html_false = "\n".join([
|
239
|
+
"<!DOCTYPE html>",
|
240
|
+
"<html>",
|
241
|
+
" <head>",
|
242
|
+
' <link href="styles.css" rel="stylesheet">',
|
243
|
+
" </head>",
|
244
|
+
" <body>",
|
245
|
+
' <htmlnode class="r1">',
|
246
|
+
" Text!",
|
247
|
+
" </htmlnode>",
|
248
|
+
' <htmlnode class="r0">',
|
249
|
+
" Another Text!",
|
250
|
+
" </htmlnode>",
|
251
|
+
" </body>",
|
252
|
+
"</html>"
|
253
|
+
])
|
254
|
+
assert len(compiled_false.html_content) == 1
|
255
|
+
assert compiled_false.html_content[0] == expected_html_false
|
256
|
+
assert compiled_false.css_content == expected_css
|
257
|
+
|
258
|
+
@pytest.mark.parametrize("indent_level", [0, 1, 2])
|
259
|
+
@pytest.mark.parametrize("indent_size", [2, 3, 4, 8])
|
260
|
+
def test_compile_indentation(self, indent_level: int, indent_size: int):
|
261
|
+
"""Test the `compile` method with custom indentation levels and sizes."""
|
262
|
+
website = TestWebsite.SimpleWebsite()
|
263
|
+
compiled = website.compile(
|
264
|
+
indent_level=indent_level,
|
265
|
+
indent_size=indent_size
|
266
|
+
)
|
267
|
+
|
268
|
+
# Check the results
|
269
|
+
expected_html = "\n".join([
|
270
|
+
f"{' ' * indent_size * indent_level}<!DOCTYPE html>",
|
271
|
+
f"{' ' * indent_size * indent_level}<html>",
|
272
|
+
f"{' ' * indent_size * (indent_level + 1)}<head>",
|
273
|
+
f'{" " * indent_size * (indent_level + 2)}<link href="styles.css" rel="stylesheet">',
|
274
|
+
f"{' ' * indent_size * (indent_level + 1)}</head>",
|
275
|
+
f"{' ' * indent_size * (indent_level + 1)}<body>",
|
276
|
+
f'{" " * indent_size * (indent_level + 2)}<htmlnode class="r1">',
|
277
|
+
f"{' ' * indent_size * (indent_level + 3)}Text!",
|
278
|
+
f"{' ' * indent_size * (indent_level + 2)}</htmlnode>",
|
279
|
+
f'{" " * indent_size * (indent_level + 2)}<htmlnode class="r0">',
|
280
|
+
f"{' ' * indent_size * (indent_level + 3)}Another Text!",
|
281
|
+
f"{' ' * indent_size * (indent_level + 2)}</htmlnode>",
|
282
|
+
f"{' ' * indent_size * (indent_level + 1)}</body>",
|
283
|
+
f"{' ' * indent_size * indent_level}</html>"
|
284
|
+
])
|
285
|
+
assert len(compiled.html_content) == 1
|
286
|
+
assert compiled.html_content[0] == expected_html
|
287
|
+
expected_css = "\n".join([
|
288
|
+
".r0 {",
|
289
|
+
f"{' ' * indent_size}margin: 0;",
|
290
|
+
"}",
|
291
|
+
"",
|
292
|
+
".r1 {",
|
293
|
+
f"{' ' * indent_size}padding: 0;",
|
294
|
+
"}"
|
295
|
+
])
|
296
|
+
assert compiled.css_content == expected_css
|
297
|
+
|
298
|
+
@pytest.mark.parametrize("indent_level", [-2, -1])
|
299
|
+
def test_compile_negative_indent_levels(self, indent_level: int):
|
300
|
+
website = TestWebsite.SimpleWebsite()
|
301
|
+
compiled = website.compile(indent_level=indent_level)
|
302
|
+
indent = " " if indent_level == -1 else ""
|
303
|
+
expected_html = "\n".join([
|
304
|
+
"<!DOCTYPE html>",
|
305
|
+
"<html>",
|
306
|
+
"<head>",
|
307
|
+
f'{indent}<link href="styles.css" rel="stylesheet">',
|
308
|
+
"</head>",
|
309
|
+
"<body>",
|
310
|
+
f'{indent}<htmlnode class="r1">',
|
311
|
+
f"{indent} Text!",
|
312
|
+
f"{indent}</htmlnode>",
|
313
|
+
f'{indent}<htmlnode class="r0">',
|
314
|
+
f"{indent} Another Text!",
|
315
|
+
f"{indent}</htmlnode>",
|
316
|
+
"</body>",
|
317
|
+
"</html>"
|
318
|
+
])
|
319
|
+
expected_css = "\n".join([
|
320
|
+
".r0 {",
|
321
|
+
" margin: 0;",
|
322
|
+
"}",
|
323
|
+
"",
|
324
|
+
".r1 {",
|
325
|
+
" padding: 0;",
|
326
|
+
"}"
|
327
|
+
])
|
328
|
+
assert len(compiled.html_content) == 1
|
329
|
+
assert compiled.html_content[0] == expected_html
|
330
|
+
assert compiled.css_content == expected_css
|
331
|
+
|
332
|
+
def test_compile_rule_namer(self):
|
333
|
+
"""Test the `compile` method with a custom rule namer function."""
|
334
|
+
# Define a custom rule namer function
|
335
|
+
def custom_rule_namer(rules, index):
|
336
|
+
return f"custom_{index}_{list(rules[index].declarations.keys())[0]}"
|
337
|
+
|
338
|
+
# Compile a simple website with the custom rule namer
|
339
|
+
website = TestWebsite.SimpleWebsite()
|
340
|
+
compiled = website.compile(rule_namer=custom_rule_namer)
|
341
|
+
|
342
|
+
# Check the results
|
343
|
+
expected_html = "\n".join([
|
344
|
+
"<!DOCTYPE html>",
|
345
|
+
"<html>",
|
346
|
+
" <head>",
|
347
|
+
' <link href="styles.css" rel="stylesheet">',
|
348
|
+
" </head>",
|
349
|
+
" <body>",
|
350
|
+
' <htmlnode class="custom_1_padding">',
|
351
|
+
" Text!",
|
352
|
+
" </htmlnode>",
|
353
|
+
' <htmlnode class="custom_0_margin">',
|
354
|
+
" Another Text!",
|
355
|
+
" </htmlnode>",
|
356
|
+
" </body>",
|
357
|
+
"</html>"
|
358
|
+
])
|
359
|
+
assert len(compiled.html_content) == 1
|
360
|
+
assert compiled.html_content[0] == expected_html
|
361
|
+
expected_css = "\n".join([
|
362
|
+
".custom_0_margin {",
|
363
|
+
" margin: 0;",
|
364
|
+
"}",
|
365
|
+
"",
|
366
|
+
".custom_1_padding {",
|
367
|
+
" padding: 0;",
|
368
|
+
"}"
|
369
|
+
])
|
370
|
+
assert compiled.css_content == expected_css
|
371
|
+
|
372
|
+
def test_compile_kwargs(self):
|
373
|
+
website = TestWebsite.SimpleWebsite()
|
374
|
+
compiled = website.compile(replace_all_entities=True)
|
375
|
+
expected_html = "\n".join([
|
376
|
+
"<!DOCTYPE html>",
|
377
|
+
"<html>",
|
378
|
+
" <head>",
|
379
|
+
' <link href="styles.css" rel="stylesheet">',
|
380
|
+
" </head>",
|
381
|
+
" <body>",
|
382
|
+
' <htmlnode class="r1">',
|
383
|
+
" Text!",
|
384
|
+
" </htmlnode>",
|
385
|
+
' <htmlnode class="r0">',
|
386
|
+
" Another Text!",
|
387
|
+
" </htmlnode>",
|
388
|
+
" </body>",
|
389
|
+
"</html>"
|
390
|
+
])
|
391
|
+
expected_css = "\n".join([
|
392
|
+
".r0 {",
|
393
|
+
" margin: 0;",
|
394
|
+
"}",
|
395
|
+
"",
|
396
|
+
".r1 {",
|
397
|
+
" padding: 0;",
|
398
|
+
"}"
|
399
|
+
])
|
400
|
+
assert len(compiled.html_content) == 1
|
401
|
+
assert compiled.html_content[0] == expected_html
|
402
|
+
assert compiled.css_content == expected_css
|
@@ -0,0 +1,11 @@
|
|
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
|
+
# =======================================================================
|