Skip to content

Commit

Permalink
Merge pull request #7 from tbrlpld/improve-docs
Browse files Browse the repository at this point in the history
Improve docs
  • Loading branch information
tbrlpld authored Feb 10, 2024
2 parents 06f3376 + 9a83501 commit 1785299
Show file tree
Hide file tree
Showing 9 changed files with 711 additions and 96 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add more tests and example usage. ([#6](https://github.com/tbrlpld/laces/pull/6))
- Added more tests and example usage. ([#6](https://github.com/tbrlpld/laces/pull/6))
- Added support for Python 3.12 and Django 5.0. ([#15](https://github.com/tbrlpld/laces/pull/15))
- Added type hints and type checking with `mypy` in CI. ([#18](https://github.com/tbrlpld/laces/pull/18))

### Changed

- Fixed tox configuration to actually run Django 3.2 in CI. Tox also uses the "testing" dependencies without the need to duplicate them in the `tox.ini`. ([#10](https://github.com/tbrlpld/laces/pull/10))
- Bumped GitHub Actions to latest versions. This removes a reliance on the now deprecated Node 16. ([#10](https://github.com/tbrlpld/laces/pull/10))
- Extend documentation in README to simplify first examples and improve structure. ([#7](https://github.com/tbrlpld/laces/pull/7))

### Removed

Expand Down
592 changes: 539 additions & 53 deletions README.md

Large diffs are not rendered by default.

70 changes: 54 additions & 16 deletions laces/test/example/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@


if TYPE_CHECKING:
from typing import Any, Dict, Optional
from typing import Any, Dict, List, Optional

from django.utils.safestring import SafeString

Expand Down Expand Up @@ -109,6 +109,24 @@ def get_context_data(
}


class ListSectionComponent(Component):
template_name = "components/list-section.html"

def __init__(self, heading: "HeadingComponent", items: "List[Component]") -> None:
super().__init__()
self.heading = heading
self.items = items

def get_context_data(
self,
parent_context: "Optional[RenderContext]" = None,
) -> "RenderContext":
return {
"heading": self.heading,
"items": self.items,
}


class HeadingComponent(Component):
def __init__(self, text: str):
super().__init__()
Expand All @@ -133,31 +151,51 @@ def render_html(
return format_html("<p>{}</p>\n", self.text)


class ListSectionComponent(Component):
template_name = "components/list-section.html"

def __init__(self, heading: "HeadingComponent", items: "list[Component]"):
class BlockquoteComponent(Component):
def __init__(self, text: str):
super().__init__()
self.heading = heading
self.items = items
self.text = text

def render_html(
self,
parent_context: "Optional[RenderContext]" = None,
) -> "SafeString":
return format_html("<blockquote>{}</blockquote>\n", self.text)


class MediaDefiningComponent(Component):
template_name = "components/hello-name.html"

def get_context_data(
self,
parent_context: "Optional[RenderContext]" = None,
) -> "RenderContext":
return {
"heading": self.heading,
"items": self.items,
}
return {"name": "Media"}

class Media:
css = {"all": ("component.css",)}
js = ("component.js",)

class BlockquoteComponent(Component):
def __init__(self, text: str):
super().__init__()
self.text = text

class HeaderWithMediaComponent(Component):
def render_html(
self,
parent_context: "Optional[RenderContext]" = None,
) -> "SafeString":
return format_html("<blockquote>{}</blockquote>\n", self.text)
return format_html("<header>Header with Media</header>")

class Media:
css = {"all": ("header.css",)}
js = ("header.js", "common.js")


class FooterWithMediaComponent(Component):
def render_html(
self,
parent_context: "Optional[RenderContext]" = None,
) -> "SafeString":
return format_html("<footer>Footer with Media</footer>")

class Media:
css = {"all": ("footer.css",)}
js = ("footer.js", "common.js")
6 changes: 6 additions & 0 deletions laces/test/example/templates/pages/kitchen-sink.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<html>
<head>
<title>Kitchen Sink</title>
{{ media_defining_component.media }}
{{ components_with_media.media }}
</head>
<body>
{% component fixed_content_template %}
Expand All @@ -29,5 +31,9 @@

{% component section_with_heading_and_paragraph %}
{% component list_section %}
{% component media_defining_component %}
{% for comp in components_with_media %}
{% component comp %}
{% endfor %}
</body>
</html>
13 changes: 13 additions & 0 deletions laces/test/example/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

from django.shortcuts import render

from laces.components import MediaContainer
from laces.test.example.components import (
BlockquoteComponent,
DataclassAsDictContextComponent,
FooterWithMediaComponent,
HeaderWithMediaComponent,
HeadingComponent,
ListSectionComponent,
MediaDefiningComponent,
ParagraphComponent,
PassesFixedNameToContextComponent,
PassesInstanceAttributeToContextComponent,
Expand Down Expand Up @@ -43,6 +47,13 @@ def kitchen_sink(request: "HttpRequest") -> "HttpResponse":
ParagraphComponent(text="Item 3"),
],
)
media_defining_component = MediaDefiningComponent()
components_with_media = MediaContainer(
[
HeaderWithMediaComponent(),
FooterWithMediaComponent(),
]
)

return render(
request,
Expand All @@ -58,5 +69,7 @@ def kitchen_sink(request: "HttpRequest") -> "HttpResponse":
"name": "Dan", # Provide as an example of parent context.
"section_with_heading_and_paragraph": section_with_heading_and_paragraph,
"list_section": list_section,
"media_defining_component": media_defining_component,
"components_with_media": components_with_media,
},
)
24 changes: 24 additions & 0 deletions laces/test/tests/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
desired. More thorough tests can be found in the `laces.tests.test_components` module.
"""

from django.forms import widgets
from django.test import SimpleTestCase

from laces.test.example.components import (
DataclassAsDictContextComponent,
HeadingComponent,
ListSectionComponent,
MediaDefiningComponent,
ParagraphComponent,
PassesFixedNameToContextComponent,
PassesInstanceAttributeToContextComponent,
Expand All @@ -20,6 +22,7 @@
ReturnsFixedContentComponent,
SectionWithHeadingAndParagraphComponent,
)
from laces.tests.utils import MediaAssertionMixin


class TestRendersTemplateWithFixedContentComponent(SimpleTestCase):
Expand Down Expand Up @@ -250,3 +253,24 @@ def test_render_html(self) -> None:
</section>
""",
)


class TestMediaDefiningComponent(MediaAssertionMixin, SimpleTestCase):
def setUp(self) -> None:
self.component = MediaDefiningComponent()

def test_media(self) -> None:
self.assertMediaEqual(
self.component.media,
widgets.Media(
css={
"all": [
"component.css",
]
},
js=[
"component.js",
"test.js",
],
),
)
43 changes: 43 additions & 0 deletions laces/test/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from http import HTTPStatus

import django

from django.test import RequestFactory, TestCase

from laces.test.example.views import kitchen_sink
Expand Down Expand Up @@ -54,3 +56,44 @@ def test_get(self) -> None:
""",
response_html,
)
self.assertInHTML("<h1>Hello Media</h1>", response_html)
if django.VERSION < (4, 0):
# Before Django 4.0 the markup was including the (useless)
# `type="text/css"` attribute.
self.assertInHTML(
'<link href="/static/component.css" type="text/css" media="all" rel="stylesheet">', # noqa: E501
response_html,
)
else:
self.assertInHTML(
'<link href="/static/component.css" media="all" rel="stylesheet">',
response_html,
)
self.assertInHTML('<script src="/static/component.js"></script>', response_html)
self.assertInHTML("<header>Header with Media</header>", response_html)
self.assertInHTML("<footer>Footer with Media</footer>", response_html)
if django.VERSION < (4, 0):
self.assertInHTML(
'<link href="/static/header.css" type="text/css" media="all" rel="stylesheet">', # noqa: E501
response_html,
)
self.assertInHTML(
'<link href="/static/footer.css" type="text/css" media="all" rel="stylesheet">', # noqa: E501
response_html,
)
else:
self.assertInHTML(
'<link href="/static/header.css" media="all" rel="stylesheet">',
response_html,
)
self.assertInHTML(
'<link href="/static/footer.css" media="all" rel="stylesheet">',
response_html,
)
self.assertInHTML('<script src="/static/header.js"></script>', response_html)
self.assertInHTML('<script src="/static/footer.js"></script>', response_html)
self.assertInHTML(
'<script src="/static/common.js"></script>',
response_html,
count=1,
)
27 changes: 1 addition & 26 deletions laces/tests/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.utils.safestring import SafeString

from laces.components import Component, MediaContainer
from laces.tests.utils import MediaAssertionMixin


if TYPE_CHECKING:
Expand All @@ -19,32 +20,6 @@
from laces.typing import RenderContext


class MediaAssertionMixin:
@staticmethod
def assertMediaEqual(first: widgets.Media, second: widgets.Media) -> bool:
"""
Compare two `Media` instances.
The `Media` class does not implement `__eq__`, but its `__repr__` shows how to
recreate the instance.
We can use this to compare two `Media` instances.
Parameters
----------
first : widgets.Media
First `Media` instance.
second : widgets.Media
Second `Media` instance.
Returns
-------
bool
Whether the two `Media` instances are equal.
"""
return repr(first) == repr(second)


class TestComponent(MediaAssertionMixin, SimpleTestCase):
"""Directly test the Component class."""

Expand Down
29 changes: 29 additions & 0 deletions laces/tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Utilities for tests in the `laces` package."""

from django.forms import widgets


class MediaAssertionMixin:
@staticmethod
def assertMediaEqual(first: widgets.Media, second: widgets.Media) -> bool:
"""
Compare two `Media` instances.
The `Media` class does not implement `__eq__`, but its `__repr__` shows how to
recreate the instance.
We can use this to compare two `Media` instances.
Parameters
----------
first : widgets.Media
First `Media` instance.
second : widgets.Media
Second `Media` instance.
Returns
-------
bool
Whether the two `Media` instances are equal.
"""
return repr(first) == repr(second)

0 comments on commit 1785299

Please sign in to comment.