Skip to content

Commit

Permalink
Add classnames util function
Browse files Browse the repository at this point in the history
  • Loading branch information
j4mie committed Jan 18, 2023
1 parent 2878b7a commit faa0c03
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,19 @@ def current_user_panel():

The `render` function takes an `indent` argument, which is a integer used to control how many spaces are used as indentation in the generated HTML. The default is 0, meaning the entire HTML string will be returned on a single line. You may wish to use (say) `indent=2` for development, and `indent=0` for production (essentially minifying your HTML).

## Generating class names

A function is provided that can be used to generate strings of class names based on various arguments. This is closely based on the [classnames](https://github.com/JedWatson/classnames/) JavaScript library.

```python
from hotmetal.utils.classnames import classnames


def header(title):
title_is_green = title.lower() == "green"
return ("h1", {"class": classnames("title", {"green": title_is_green})}, [title])
```

## Testing tools

When writing tests for components, it's often useful to be able to search through a tree to find particular nodes and make assertions about them. To help with this, `hotmetal` provides a `find` function, which takes an iterable of nodes and a predicate callable, and returns a generator that yields nodes that match the predicate (using depth-first pre-order traversal of the nodes, much like the browser's [`querySelectorAll`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) function).
Expand Down
18 changes: 18 additions & 0 deletions hotmetal/utils/classnames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
def classnames(*args):
classes = []
for arg in args:
if isinstance(arg, bool):
continue
if isinstance(arg, dict):
for key, value in arg.items():
if value:
classes.append(key)
elif isinstance(arg, str) and arg.strip():
classes.append(arg.strip())
elif isinstance(arg, (int, float)) and arg:
classes.append(str(arg))
elif isinstance(arg, (list, set)):
child = classnames(*arg)
if child:
classes.append(child)
return " ".join(classes)
79 changes: 79 additions & 0 deletions tests/utils/test_classnames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from hotmetal.utils.classnames import classnames
from unittest import TestCase


class ClassnamesTestCase(TestCase):
def test_dict_keys_with_truthy_values(self):
self.assertEqual(
classnames({"a": True, "b": False, "c": 0, "d": None, "e": 1}),
"a e",
)

def test_joins_multiple_classnames_and_ignore_falsy_values(self):
self.assertEqual(
classnames("a", 0, None, True, 1, "b"),
"a 1 b",
)

def test_heterogenous_args(self):
self.assertEqual(
classnames({"a": True}, "b", 0),
"a b",
)

def test_result_trimmed(self):
self.assertEqual(
classnames("", "b", {}, ""),
"b",
)

def test_returns_empty_string_for_empty_args(self):
self.assertEqual(
classnames({}),
"",
)

def test_supports_list_of_classnames(self):
self.assertEqual(
classnames(["a", "b"]),
"a b",
)

def test_supports_sets_of_classnames(self):
self.assertTrue(
classnames({"a", "b"}) in {"a b", "b a"},
)

def test_joins_lists_args_with_string_args(self):
self.assertEqual(
classnames(["a", "b"], "c"),
"a b c",
)
self.assertEqual(
classnames("c", ["a", "b"]),
"c a b",
)

def test_handles_lists_that_include_falsy_and_true_values(self):
self.assertEqual(
classnames(["a", 0, None, False, True, "b"]),
"a b",
)

def test_handles_lists_that_include_lists(self):
self.assertEqual(
classnames(["a", ["b", "c"]]),
"a b c",
)

def test_handles_lists_that_include_dicts(self):
self.assertEqual(
classnames(["a", {"b": True, "c": False}]),
"a b",
)

def test_handles_nested_lists_that_include_empty_nested_lists(self):
self.assertEqual(
classnames(["a", [[]]]),
"a",
)

0 comments on commit faa0c03

Please sign in to comment.