webwidgets 1.0.0__tar.gz → 1.1.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.
Files changed (57) hide show
  1. {webwidgets-1.0.0 → webwidgets-1.1.0}/.github/workflows/ci-full.yml +2 -3
  2. {webwidgets-1.0.0 → webwidgets-1.1.0}/.github/workflows/ci-quick.yml +2 -3
  3. {webwidgets-1.0.0 → webwidgets-1.1.0}/PKG-INFO +6 -1
  4. {webwidgets-1.0.0 → webwidgets-1.1.0}/pyproject.toml +8 -0
  5. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/compilation/test_css.py +144 -137
  6. webwidgets-1.1.0/tests/compilation/test_css_rule.py +108 -0
  7. webwidgets-1.1.0/tests/compilation/test_css_sections.py +191 -0
  8. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/compilation/test_html_tags.py +4 -0
  9. webwidgets-1.1.0/tests/conftest.py +20 -0
  10. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/utility/test_validation.py +92 -27
  11. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/website/test_website.py +68 -62
  12. webwidgets-1.1.0/tests/widgets/conftest.py +48 -0
  13. webwidgets-1.1.0/tests/widgets/containers/__init__.py +11 -0
  14. webwidgets-1.1.0/tests/widgets/containers/test_box.py +110 -0
  15. {webwidgets-1.0.0/tests/widgets → webwidgets-1.1.0/tests/widgets/containers}/test_page.py +1 -1
  16. webwidgets-1.1.0/tests/widgets/render_page.py +58 -0
  17. webwidgets-1.1.0/tests/widgets/test_render_page.py +61 -0
  18. webwidgets-1.1.0/tests/wrap_core_css.py +37 -0
  19. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/__init__.py +1 -1
  20. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/compilation/css/__init__.py +3 -2
  21. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/compilation/css/css.py +105 -122
  22. webwidgets-1.1.0/webwidgets/compilation/css/css_rule.py +104 -0
  23. webwidgets-1.1.0/webwidgets/compilation/css/sections/__init__.py +14 -0
  24. webwidgets-1.1.0/webwidgets/compilation/css/sections/css_section.py +106 -0
  25. webwidgets-1.1.0/webwidgets/compilation/css/sections/preamble.py +40 -0
  26. webwidgets-1.1.0/webwidgets/compilation/css/sections/rule_section.py +43 -0
  27. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/compilation/html/__init__.py +1 -1
  28. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/compilation/html/html_tags.py +5 -0
  29. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/utility/validation.py +68 -1
  30. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/website/website.py +5 -5
  31. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/widgets/containers/__init__.py +1 -0
  32. webwidgets-1.1.0/webwidgets/widgets/containers/box.py +70 -0
  33. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/widgets/widget.py +1 -2
  34. {webwidgets-1.0.0 → webwidgets-1.1.0}/.github/workflows/cd.yml +0 -0
  35. {webwidgets-1.0.0 → webwidgets-1.1.0}/.gitignore +0 -0
  36. {webwidgets-1.0.0 → webwidgets-1.1.0}/LICENSE +0 -0
  37. {webwidgets-1.0.0 → webwidgets-1.1.0}/README.md +0 -0
  38. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/__init__.py +0 -0
  39. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/compilation/__init__.py +0 -0
  40. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/compilation/test_html_node.py +0 -0
  41. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/utility/__init__.py +0 -0
  42. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/utility/test_indentation.py +0 -0
  43. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/utility/test_representation.py +0 -0
  44. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/utility/test_sanitizing.py +0 -0
  45. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/website/__init__.py +0 -0
  46. {webwidgets-1.0.0 → webwidgets-1.1.0}/tests/widgets/__init__.py +0 -0
  47. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/compilation/__init__.py +0 -0
  48. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/compilation/html/html_node.py +0 -0
  49. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/utility/__init__.py +0 -0
  50. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/utility/indentation.py +0 -0
  51. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/utility/representation.py +0 -0
  52. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/utility/sanitizing.py +0 -0
  53. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/website/__init__.py +0 -0
  54. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/website/compiled_website.py +0 -0
  55. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/widgets/__init__.py +0 -0
  56. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/widgets/containers/container.py +0 -0
  57. {webwidgets-1.0.0 → webwidgets-1.1.0}/webwidgets/widgets/containers/page.py +0 -0
@@ -64,15 +64,14 @@ jobs:
64
64
  uses: actions/setup-python@v3
65
65
  with:
66
66
  python-version: ${{ matrix.python-version }}
67
- - name: Install pytest
67
+ - name: Upgrading pip
68
68
  run: |
69
69
  python -c "import platform; print('OS', platform.system())"
70
70
  python -c "import sys; print('Python version', sys.version)"
71
71
  python -m pip install --upgrade pip
72
- pip install pytest
73
72
  - name: Build and install
74
73
  run: |
75
- pip install .
74
+ pip install .[dev]
76
75
  # Removing webwidgets directory so imports come from build
77
76
  rm -r webwidgets
78
77
  - name: Test with pytest
@@ -59,16 +59,15 @@ jobs:
59
59
  uses: actions/setup-python@v3
60
60
  with:
61
61
  python-version: ${{ matrix.python-version }}
62
- - name: Install pytest
62
+ - name: Upgrading pip
63
63
  run: |
64
64
  python -c "import sys; print('Python version', sys.version)"
65
65
  python -m pip install --upgrade pip
66
- pip install pytest
67
66
  - name: Build and install
68
67
  run: |
69
68
  echo "Current directory:"
70
69
  ls -la
71
- pip install .
70
+ pip install .[dev]
72
71
  # Removing webwidgets directory so imports come from build
73
72
  rm -r webwidgets
74
73
  echo "Removed webwidgets directory. New content:"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webwidgets
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: A Python package for designing web UIs.
5
5
  Project-URL: Source code, https://github.com/mlaasri/WebWidgets
6
6
  Author: mlaasri
@@ -9,6 +9,11 @@ Keywords: design,webui
9
9
  Classifier: Operating System :: OS Independent
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Requires-Python: >=3.9
12
+ Provides-Extra: dev
13
+ Requires-Dist: numpy; extra == 'dev'
14
+ Requires-Dist: pillow; extra == 'dev'
15
+ Requires-Dist: pytest; extra == 'dev'
16
+ Requires-Dist: selenium; extra == 'dev'
12
17
  Description-Content-Type: text/markdown
13
18
 
14
19
  # WebWidgets
@@ -21,6 +21,14 @@ classifiers = [
21
21
  [project.urls]
22
22
  "Source code" = "https://github.com/mlaasri/WebWidgets"
23
23
 
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "numpy",
27
+ "pillow",
28
+ "pytest",
29
+ "selenium"
30
+ ]
31
+
24
32
  [tool.hatch.version]
25
33
  path = "webwidgets/__init__.py"
26
34
 
@@ -14,8 +14,10 @@ import pytest
14
14
  from typing import Any, Dict, List
15
15
  from webwidgets.compilation.html.html_node import HTMLNode
16
16
  from webwidgets.compilation.html.html_tags import TextNode
17
- from webwidgets.compilation.css.css import compile_css, CSSRule, CompiledCSS, \
18
- apply_css, default_rule_namer
17
+ from webwidgets.compilation.css.css import apply_css, compile_css, CompiledCSS, \
18
+ default_class_namer
19
+ from webwidgets.compilation.css.css_rule import ClassRule, CSSRule
20
+ from webwidgets.compilation.css.sections import RuleSection
19
21
 
20
22
 
21
23
  class TestCompileCSS:
@@ -29,19 +31,20 @@ class TestCompileCSS:
29
31
  :return: List of the member variables of each :py:class:`CSSRule`.
30
32
  :rtype: Dict[int, Any]
31
33
  """
32
- return [vars(rule) for rule in rules]
34
+ return [{a: getattr(rule, a) for a in ("selector", "declarations")}
35
+ for rule in rules]
33
36
 
34
37
  @staticmethod
35
- def _serialize_mapping(mapping: Dict[int, List[CSSRule]]) -> Dict[int, List[str]]:
38
+ def _serialize_mapping(mapping: Dict[int, List[ClassRule]]) -> Dict[int, List[str]]:
36
39
  """Utility function to convert a :py:attr:`CompiledCSS.mapping` object
37
40
  into a dictionary that can be used in testing.
38
41
 
39
42
  :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
+ :type mapping: Dict[int, List[ClassRule]]
44
+ :return: Dictionary mapping each node ID to the selectors of the rules
45
+ that achieve the same style.
43
46
  """
44
- return {i: [r.name for r in rules] for i, rules in mapping.items()}
47
+ return {i: [r.selector for r in rules] for i, rules in mapping.items()}
45
48
 
46
49
  def test_argument_type(self):
47
50
  """Compares compilation when given a node object versus a list of
@@ -57,12 +60,12 @@ class TestCompileCSS:
57
60
 
58
61
  # Define expected compilation results
59
62
  expected_rules = [
60
- {"name": "r0", "declarations": {"a": "5"}},
61
- {"name": "r1", "declarations": {"b": "4"}}
63
+ {"selector": ".c0", "declarations": {"a": "5"}},
64
+ {"selector": ".c1", "declarations": {"b": "4"}}
62
65
  ]
63
66
  expected_mapping = {
64
- id(tree): ['r0', 'r1'],
65
- id(tree.children[0]): ['r0']
67
+ id(tree): ['.c0', '.c1'],
68
+ id(tree.children[0]): ['.c0']
66
69
  }
67
70
 
68
71
  # Compile tree as single node object
@@ -72,7 +75,7 @@ class TestCompileCSS:
72
75
  assert compiled_css.trees == [tree]
73
76
  assert [id(t) for t in compiled_css.trees] == [id(tree)]
74
77
  assert TestCompileCSS._serialize_rules(
75
- compiled_css.rules) == expected_rules
78
+ compiled_css.core.rules) == expected_rules
76
79
  assert TestCompileCSS._serialize_mapping(
77
80
  compiled_css.mapping) == expected_mapping
78
81
 
@@ -83,7 +86,7 @@ class TestCompileCSS:
83
86
  assert compiled_css2.trees == [tree]
84
87
  assert [id(t) for t in compiled_css2.trees] == [id(tree)]
85
88
  assert TestCompileCSS._serialize_rules(
86
- compiled_css2.rules) == expected_rules
89
+ compiled_css2.core.rules) == expected_rules
87
90
  assert TestCompileCSS._serialize_mapping(
88
91
  compiled_css2.mapping) == expected_mapping
89
92
 
@@ -103,16 +106,16 @@ class TestCompileCSS:
103
106
 
104
107
  # Check that the rules are correctly generated
105
108
  expected_rules = [
106
- {"name": "r0", "declarations": {"color": "blue"}},
107
- {"name": "r1", "declarations": {"margin": "0"}},
108
- {"name": "r2", "declarations": {"padding": "0"}}
109
+ {"selector": ".c0", "declarations": {"color": "blue"}},
110
+ {"selector": ".c1", "declarations": {"margin": "0"}},
111
+ {"selector": ".c2", "declarations": {"padding": "0"}}
109
112
  ]
110
113
  assert TestCompileCSS._serialize_rules(
111
- compiled_css.rules) == expected_rules
114
+ compiled_css.core.rules) == expected_rules
112
115
 
113
116
  # Check that the mapping is correctly generated
114
- expected_mapping = {id(node1): ['r1', 'r2'], id(
115
- node2): ['r0', 'r1'], id(node3): ['r1', 'r2']}
117
+ expected_mapping = {id(node1): ['.c1', '.c2'], id(
118
+ node2): ['.c0', '.c1'], id(node3): ['.c1', '.c2']}
116
119
  assert TestCompileCSS._serialize_mapping(
117
120
  compiled_css.mapping) == expected_mapping
118
121
 
@@ -137,19 +140,19 @@ class TestCompileCSS:
137
140
 
138
141
  # Check that the rules are correctly generated
139
142
  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"}}
143
+ {"selector": ".c0", "declarations": {"color": "blue"}},
144
+ {"selector": ".c1", "declarations": {"margin": "0"}},
145
+ {"selector": ".c2", "declarations": {"margin": "5"}},
146
+ {"selector": ".c3", "declarations": {"padding": "0"}}
144
147
  ]
145
148
  assert TestCompileCSS._serialize_rules(
146
- compiled_css.rules) == expected_rules
149
+ compiled_css.core.rules) == expected_rules
147
150
 
148
151
  # Check that the mapping is correctly generated
149
152
  expected_mapping = {
150
- id(tree): ['r1', 'r3'],
151
- id(tree.children[0]): ['r0', 'r2'],
152
- id(tree.children[1]): ['r0', 'r3'],
153
+ id(tree): ['.c1', '.c3'],
154
+ id(tree.children[0]): ['.c0', '.c2'],
155
+ id(tree.children[1]): ['.c0', '.c3'],
153
156
  id(tree.children[0].children[0]): [],
154
157
  id(tree.children[1].children[0]): []
155
158
  }
@@ -181,20 +184,20 @@ class TestCompileCSS:
181
184
 
182
185
  # Check that the rules are correctly generated
183
186
  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"}}
187
+ {"selector": ".c0", "declarations": {"color": "red"}},
188
+ {"selector": ".c1", "declarations": {"margin": "10"}},
189
+ {"selector": ".c2", "declarations": {"margin": "5"}},
190
+ {"selector": ".c3", "declarations": {"padding": "0"}}
188
191
  ]
189
192
  assert TestCompileCSS._serialize_rules(
190
- compiled_css.rules) == expected_rules
193
+ compiled_css.core.rules) == expected_rules
191
194
 
192
195
  # Check that the mapping is correctly generated
193
196
  expected_mapping = {
194
- id(tree1): ['r1', 'r3'],
195
- id(tree1.children[0]): ['r0'],
196
- id(tree2): ['r2', 'r3'],
197
- id(tree2.children[0]): ['r1']
197
+ id(tree1): ['.c1', '.c3'],
198
+ id(tree1.children[0]): ['.c0'],
199
+ id(tree2): ['.c2', '.c3'],
200
+ id(tree2.children[0]): ['.c1']
198
201
  }
199
202
  assert TestCompileCSS._serialize_mapping(
200
203
  compiled_css.mapping) == expected_mapping
@@ -211,14 +214,14 @@ class TestCompileCSS:
211
214
  )
212
215
  compiled_css = compile_css(tree)
213
216
  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"}},
217
+ {"selector": ".c0", "declarations": {"a": "10"}},
218
+ {"selector": ".c1", "declarations": {"a": "5"}},
219
+ {"selector": ".c2", "declarations": {"b": "10"}},
220
+ {"selector": ".c3", "declarations": {"b": "4"}},
221
+ {"selector": ".c4", "declarations": {"c": "5"}},
219
222
  ]
220
223
  assert TestCompileCSS._serialize_rules(
221
- compiled_css.rules) == expected_rules
224
+ compiled_css.core.rules) == expected_rules
222
225
 
223
226
  def test_duplicate_node(self):
224
227
  """Test that adding the same node twice does not impact compilation"""
@@ -231,20 +234,20 @@ class TestCompileCSS:
231
234
  ]
232
235
  )
233
236
  expected_rules = [
234
- {"name": "r0", "declarations": {"a": "5"}},
235
- {"name": "r1", "declarations": {"b": "10"}},
236
- {"name": "r2", "declarations": {"b": "4"}}
237
+ {"selector": ".c0", "declarations": {"a": "5"}},
238
+ {"selector": ".c1", "declarations": {"b": "10"}},
239
+ {"selector": ".c2", "declarations": {"b": "4"}}
237
240
  ]
238
241
  expected_mapping = {
239
- id(tree): ['r0', 'r2'],
240
- id(tree.children[0]): ['r0'],
241
- id(tree.children[1]): ['r1']
242
+ id(tree): ['.c0', '.c2'],
243
+ id(tree.children[0]): ['.c0'],
244
+ id(tree.children[1]): ['.c1']
242
245
  }
243
246
  compiled_css = compile_css([tree])
244
247
  assert compiled_css.trees == [tree]
245
248
  assert [id(t) for t in compiled_css.trees] == [id(tree)]
246
249
  assert TestCompileCSS._serialize_rules(
247
- compiled_css.rules) == expected_rules
250
+ compiled_css.core.rules) == expected_rules
248
251
  assert TestCompileCSS._serialize_mapping(
249
252
  compiled_css.mapping) == expected_mapping
250
253
 
@@ -256,21 +259,21 @@ class TestCompileCSS:
256
259
  assert [id(t) for t in compiled_css2.trees] == [
257
260
  id(tree), id(tree.children[0])]
258
261
  assert TestCompileCSS._serialize_rules(
259
- compiled_css2.rules) == expected_rules
262
+ compiled_css2.core.rules) == expected_rules
260
263
  assert TestCompileCSS._serialize_mapping(
261
264
  compiled_css2.mapping) == expected_mapping
262
265
 
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
+ @pytest.mark.parametrize("class_namer, selectors", [
267
+ (lambda _, i: f"rule{i}", [".rule0", ".rule1", ".rule2"]),
268
+ (lambda _, i: f"rule-{i + 1}", [".rule-1", ".rule-2", ".rule-3"]),
266
269
  (lambda r, i: f"{list(r[i].declarations.items())[0][0]}{i}", [
267
- "az0", "bz1", "bz2"]),
270
+ ".az0", ".bz1", ".bz2"]),
268
271
  (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
+ ".a0", ".b1", ".b2"]),
273
+ (lambda r, i: f"c{list(r[i].declarations.items())[0][1]}-{i}", [
274
+ ".c10-1", ".c4-2", ".c5-0"]),
272
275
  ])
273
- def test_custom_rule_names(self, rule_namer, names):
276
+ def test_custom_class_names(self, class_namer, selectors):
274
277
  tree = HTMLNode(
275
278
  style={"az": "5", "bz": "4"},
276
279
  children=[
@@ -278,39 +281,43 @@ class TestCompileCSS:
278
281
  HTMLNode(style={"bz": "10"}),
279
282
  ]
280
283
  )
281
- compiled_css = compile_css(tree, rule_namer=rule_namer)
282
- assert [r.name for r in compiled_css.rules] == names
284
+ compiled_css = compile_css(tree, class_namer=class_namer)
285
+ assert [r.selector for r in compiled_css.core.rules] == selectors
283
286
 
284
287
 
285
288
  class TestCompiledCSS:
286
- def test_export_custom_compiled_css(self):
287
- rules = [
288
- CSSRule(name="r0", declarations={"margin": "0", "padding": "0"}),
289
- CSSRule(name="r1", declarations={"color": "blue"}),
290
- CSSRule(name="r2", declarations={
289
+ def test_export_custom_compiled_css(self, wrap_core_css):
290
+ core = RuleSection(
291
+ rules=[
292
+ CSSRule(selector=".c0", declarations={
293
+ "margin": "0", "padding": "0"}),
294
+ CSSRule(selector=".c1", declarations={"color": "blue"}),
295
+ CSSRule(selector=".c2", declarations={
291
296
  "background-color": "white", "font-size": "16px"})
292
- ]
297
+ ],
298
+ title="Core"
299
+ )
293
300
  compiled_css = CompiledCSS(trees=None,
294
- rules=rules,
301
+ core=core,
295
302
  mapping=None)
296
- expected_css = '\n'.join([
297
- ".r0 {",
303
+ expected_core_css = '\n'.join([
304
+ ".c0 {",
298
305
  " margin: 0;",
299
306
  " padding: 0;",
300
307
  "}",
301
308
  "",
302
- ".r1 {",
309
+ ".c1 {",
303
310
  " color: blue;",
304
311
  "}",
305
312
  "",
306
- ".r2 {",
313
+ ".c2 {",
307
314
  " background-color: white;",
308
315
  " font-size: 16px;",
309
316
  "}"
310
317
  ])
311
- assert compiled_css.to_css() == expected_css
318
+ assert compiled_css.to_css() == wrap_core_css(expected_core_css)
312
319
 
313
- def test_export_real_compiled_css(self):
320
+ def test_export_real_compiled_css(self, wrap_core_css):
314
321
  tree = HTMLNode(
315
322
  style={"margin": "0", "padding": "0"},
316
323
  children=[
@@ -319,33 +326,33 @@ class TestCompiledCSS:
319
326
  ]
320
327
  )
321
328
  compiled_css = compile_css(tree)
322
- expected_css = '\n'.join([
323
- ".r0 {",
329
+ expected_core_css = '\n'.join([
330
+ ".c0 {",
324
331
  " color: blue;",
325
332
  "}",
326
333
  "",
327
- ".r1 {",
334
+ ".c1 {",
328
335
  " color: green;",
329
336
  "}",
330
337
  "",
331
- ".r2 {",
338
+ ".c2 {",
332
339
  " margin: 0;",
333
340
  "}",
334
341
  "",
335
- ".r3 {",
342
+ ".c3 {",
336
343
  " padding: 0;",
337
344
  "}"
338
345
  ])
339
- assert compiled_css.to_css() == expected_css
346
+ assert compiled_css.to_css() == wrap_core_css(expected_core_css)
340
347
 
341
- def test_export_empty_style(self):
348
+ def test_export_empty_style(self, wrap_core_css):
342
349
  node = HTMLNode()
343
350
  css = compile_css(node).to_css()
344
- assert css == ""
351
+ assert css == wrap_core_css("")
345
352
  other_css = CompiledCSS(trees=None,
346
- rules={},
353
+ core=RuleSection(title="Core"),
347
354
  mapping=None).to_css()
348
- assert other_css == ""
355
+ assert other_css == wrap_core_css("")
349
356
 
350
357
  def test_export_invalid_style(self):
351
358
  node = HTMLNode(style={"marg!in": "0", "padding": "0"})
@@ -354,29 +361,29 @@ class TestCompiledCSS:
354
361
  compiled_css.to_css()
355
362
 
356
363
  @pytest.mark.parametrize("indent_size", [0, 2, 4, 8])
357
- def test_css_indentation(self, indent_size):
364
+ def test_css_indentation(self, indent_size, wrap_core_css):
358
365
  node = HTMLNode(style={"a": "0", "b": "1"})
359
- expected_css = '\n'.join([
360
- ".r0 {",
366
+ expected_core_css = '\n'.join([
367
+ ".c0 {",
361
368
  f"{' ' * indent_size}a: 0;",
362
369
  "}",
363
370
  "",
364
- ".r1 {",
371
+ ".c1 {",
365
372
  f"{' ' * indent_size}b: 1;",
366
373
  "}"
367
374
  ])
368
375
  css = compile_css(node).to_css(indent_size=indent_size)
369
- assert css == expected_css
376
+ assert css == wrap_core_css(expected_core_css, indent_size=indent_size)
370
377
 
371
378
 
372
379
  class TestApplyCSS:
373
380
  @pytest.mark.parametrize("class_in, class_out", [
374
- (None, "r0 r1"), # No class attribute
375
- ("", "r0 r1"), # Empty class
376
- ("z", "z r0 r1"), # Existing class
377
- ("r1", "r1 r0"), # Existing rule
378
- ("z r1", "z r1 r0"), # Existing class and rule
379
- ("r1 z", "r1 z r0") # Existing rule and class
381
+ (None, "c0 c1"), # No class attribute
382
+ ("", "c0 c1"), # Empty class
383
+ ("z", "z c0 c1"), # Existing class
384
+ ("c1", "c1 c0"), # Existing rule
385
+ ("z c1", "z c1 c0"), # Existing class and rule
386
+ ("c1 z", "c1 z c0") # Existing rule and class
380
387
  ])
381
388
  def test_apply_css_to_node(self, class_in, class_out):
382
389
  tree = HTMLNode(attributes=None if class_in is None else {"class": class_in},
@@ -386,22 +393,22 @@ class TestApplyCSS:
386
393
  assert tree.to_html() == f'<htmlnode class="{class_out}"></htmlnode>'
387
394
 
388
395
  @pytest.mark.parametrize("cl1_in, cl1_out", [
389
- (None, "r2 r3"), # No class attribute
390
- ("", "r2 r3"), # Empty class
391
- ("c", "c r2 r3"), # Existing class
392
- ("r3", "r3 r2"), # Existing rule
393
- ("c r3", "c r3 r2"), # Existing class and rule
394
- ("r3 c", "r3 c r2"), # Existing rule and class
395
- ("rr3", "rr3 r2 r3") # Rule decoy
396
+ (None, "c2 c3"), # No class attribute
397
+ ("", "c2 c3"), # Empty class
398
+ ("c", "c c2 c3"), # Existing class
399
+ ("c3", "c3 c2"), # Existing rule
400
+ ("c c3", "c c3 c2"), # Existing class and rule
401
+ ("c3 c", "c3 c c2"), # Existing rule and class
402
+ ("rc3", "rc3 c2 c3") # Rule decoy
396
403
  ])
397
404
  @pytest.mark.parametrize("cl2_in, cl2_out", [
398
- (None, "r1 r2"), # No class attribute
399
- ("", "r1 r2"), # Empty class
400
- ("z", "z r1 r2"), # Existing class
401
- ("r1", "r1 r2"), # Existing rule
402
- ("z r1", "z r1 r2"), # Existing class and rule
403
- ("r1 z", "r1 z r2"), # Existing rule and class
404
- ("rr1", "rr1 r1 r2") # Rule decoy
405
+ (None, "c1 c2"), # No class attribute
406
+ ("", "c1 c2"), # Empty class
407
+ ("z", "z c1 c2"), # Existing class
408
+ ("c1", "c1 c2"), # Existing rule
409
+ ("z c1", "z c1 c2"), # Existing class and rule
410
+ ("c1 z", "c1 z c2"), # Existing rule and class
411
+ ("cc1", "cc1 c1 c2") # Rule decoy
405
412
  ])
406
413
  @pytest.mark.parametrize("mix", [False, True])
407
414
  def test_apply_css_to_tree(self, cl1_in, cl1_out, cl2_in, cl2_out, mix):
@@ -419,22 +426,22 @@ class TestApplyCSS:
419
426
 
420
427
  # Compiling and applying CSS to the tree
421
428
  compiled_css = compile_css(tree)
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"}}
429
+ assert TestCompileCSS._serialize_rules(compiled_css.core.rules) == [
430
+ {"selector": ".c0", "declarations": {"color": "blue"}},
431
+ {"selector": ".c1", "declarations": {"color": "green"}},
432
+ {"selector": ".c2", "declarations": {"margin": "0"}},
433
+ {"selector": ".c3", "declarations": {"padding": "0"}}
427
434
  ]
428
435
  apply_css(compiled_css, tree)
429
436
 
430
437
  # Checking the tree's new classes
431
438
  assert tree.attributes["class"] == cl1_out
432
- assert tree.children[0].attributes["class"] == "r0 r2"
439
+ assert tree.children[0].attributes["class"] == "c0 c2"
433
440
  assert tree.children[1].attributes["class"] == cl2_out
434
441
 
435
442
  # Checking the final HTML code
436
- mix_node = '<textnode class="r0 r2">a</textnode>' if mix else \
437
- '<htmlnode class="r0 r2"></htmlnode>'
443
+ mix_node = '<textnode class="c0 c2">a</textnode>' if mix else \
444
+ '<htmlnode class="c0 c2"></htmlnode>'
438
445
  expected_html = '\n'.join([
439
446
  f'<htmlnode class="{cl1_out}">',
440
447
  f' {mix_node}',
@@ -453,7 +460,7 @@ class TestApplyCSS:
453
460
  )
454
461
  html_before = tree.to_html()
455
462
  compiled_css = compile_css(tree)
456
- assert compiled_css.rules == []
463
+ assert compiled_css.core.rules == []
457
464
  apply_css(compiled_css, tree)
458
465
  html_after = tree.to_html()
459
466
 
@@ -486,24 +493,24 @@ class TestApplyCSS:
486
493
 
487
494
  # Checking the tree's new classes
488
495
  assert "class" not in tree.attributes
489
- assert tree.children[0].attributes["class"] == "r0 r1"
496
+ assert tree.children[0].attributes["class"] == "c0 c1"
490
497
  assert tree.children[1].attributes["class"] == "z"
491
498
 
492
499
  # Checking the final HTML code
493
500
  expected_html = '\n'.join([
494
501
  '<htmlnode>',
495
- ' <textnode class="r0 r1">a</textnode>',
502
+ ' <textnode class="c0 c1">a</textnode>',
496
503
  ' <htmlnode class="z"></htmlnode>',
497
504
  '</htmlnode>'
498
505
  ])
499
506
  assert tree.to_html() == expected_html
500
507
 
501
508
  @pytest.mark.parametrize("class_in, class_out", [
502
- (None, "r0 r1"),
503
- ("", "r0 r1"),
504
- ("z", "z r0 r1"),
505
- ("r0", "r0 r1"),
506
- ("r1", "r1 r0"),
509
+ (None, "c0 c1"),
510
+ ("", "c0 c1"),
511
+ ("z", "z c0 c1"),
512
+ ("c0", "c0 c1"),
513
+ ("c1", "c1 c0"),
507
514
  ])
508
515
  def test_apply_css_multiple_times(self, class_in, class_out):
509
516
  tree = HTMLNode(style={"a": "0", "b": "1"}) if class_in is None else \
@@ -533,20 +540,20 @@ class TestApplyCSS:
533
540
 
534
541
 
535
542
  class TestDefaultRuleNamer:
536
- def test_default_rule_namer(self):
537
- rules = [CSSRule(None, {"color": "red"}),
538
- CSSRule(None, {"margin": "0"})]
543
+ def test_default_class_namer(self):
544
+ rules = [ClassRule(None, {"color": "red"}),
545
+ ClassRule(None, {"margin": "0"})]
539
546
  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"
547
+ rule.name = default_class_namer(rules=rules, index=i)
548
+ assert rules[0].name == "c0"
549
+ assert rules[1].name == "c1"
543
550
 
544
- def test_default_rule_namer_override(self):
545
- rules = [CSSRule("first", {"color": "red"}),
546
- CSSRule("second", {"margin": "0"})]
551
+ def test_default_class_namer_override(self):
552
+ rules = [ClassRule("first", {"color": "red"}),
553
+ ClassRule("second", {"margin": "0"})]
547
554
  assert rules[0].name == "first"
548
555
  assert rules[1].name == "second"
549
556
  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"
557
+ rule.name = default_class_namer(rules=rules, index=i)
558
+ assert rules[0].name == "c0"
559
+ assert rules[1].name == "c1"