Skip to content

Commit

Permalink
Test ExtendedChoices enum
Browse files Browse the repository at this point in the history
  • Loading branch information
christophehenry committed Sep 5, 2024
1 parent 5658270 commit 2a87139
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 31 deletions.
37 changes: 14 additions & 23 deletions dsfr/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
from types import DynamicClassAttribute as enum_property


__all__ = ["ExtendedChoices", "RichRadioButtonChoices"]


def _is_dunder(name):
"""
Returns True if a __dunder__ name, False otherwise.
Expand Down Expand Up @@ -86,7 +89,6 @@ def __setitem__(self, member, value):
return _EnumDict(classdict)

def __new__(metacls, classname, bases, classdict, **kwds):

cls = super().__new__(metacls, classname, bases, classdict, **kwds)
cls._additional_attributes = list(classdict._additional_attributes.keys())

Expand All @@ -103,36 +105,25 @@ def __new__(metacls, classname, bases, classdict, **kwds):
if instance.name in attr_value:
setattr(instance, private_name, attr_value[instance.name])

def default_dynamic_attribute_value(enum_item, name):
raise NotImplementedError(
(
"{}.{} does not contain key {}. Please add key or implement "
"a 'dynamic_attribute_value(cls, instance, name)' classmethod in you enum "
"to provide the value"
).format(cls.__name__, instance.name, cls.__name__)
)

def instance_property_getter(name, default_value_getter):
def instance_property_getter(name):
@enum_property
def _instance_property_getter(enum_item):
if hasattr(enum_item, private_name):
return getattr(enum_item, private_name)
elif hasattr(enum_item, "dynamic_attribute_value"):
return enum_item.dynamic_attribute_value(name)
else:
return default_value_getter(enum_item, name)
raise AttributeError(
(
"{}.{} does not contain key '{}'. Please add key or "
"implement a 'dynamic_attribute_value(self, name)' "
"method in you enum to provide a value"
).format(cls.__name__, instance.name, name)
)

return _instance_property_getter

setattr(
cls,
attr_name,
instance_property_getter(
attr_name,
classdict.get(
"dynamic_attribute_value",
default_dynamic_attribute_value,
),
),
)
setattr(cls, attr_name, instance_property_getter(attr_name))

return cls

Expand Down
109 changes: 101 additions & 8 deletions dsfr/test/test_enums.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,115 @@
from enum import auto, nonmember

from django.db.models import IntegerChoices
from django.test import SimpleTestCase
from dsfr.utils import generate_random_id, generate_summary_items

from dsfr.enums import ExtendedChoices


class ExtendedChoicesTestCase(SimpleTestCase):
def test_create_dict_enum(self):
...
class TestExtendedChoices(ExtendedChoices, IntegerChoices):
TEST_1 = {
"value": auto(),
"label": "Test 1",
}
TEST_2 = {
"value": auto(),
"label": "Test 2",
}

self.assertEqual(
{(1, "Test 1"), (2, "Test 2")}, set(TestExtendedChoices.choices)
)
self.assertEqual({"Test 1", "Test 2"}, set(TestExtendedChoices.labels))
self.assertEqual({"TEST_1", "TEST_2"}, set(TestExtendedChoices.names))
self.assertEqual({1, 2}, set(TestExtendedChoices.values))

def test_additional_attributes(self):
class TestExtendedChoices(ExtendedChoices, IntegerChoices):
TEST_1 = {
"value": auto(),
"additionnal_attribute": {"lorem": "ipsum 1"},
}
TEST_2 = {
"value": auto(),
"additionnal_attribute": {"lorem": "ipsum 2"},
}

self.assertEqual(
{"additionnal_attribute"},
set(TestExtendedChoices.additional_attributes),
)
self.assertEqual(
[{"lorem": "ipsum 1"}, {"lorem": "ipsum 2"}],
[it.additionnal_attribute for it in TestExtendedChoices],
)

def test_nonmember_attributes(self):
class TestExtendedChoices(ExtendedChoices, IntegerChoices):
TEST_1 = {"value": auto()}
TEST_2 = {"value": auto()}

additional_attribute = nonmember({"lorem": "ipsum 1"})

self.assertEqual(
{(1, "Test 1"), (2, "Test 2")}, set(TestExtendedChoices.choices)
)

def test_absent_value_key_raises_error(self):
...
with self.assertRaises(ValueError) as e:

class TestExtendedChoices(ExtendedChoices, IntegerChoices):
TEST_1 = {"label": "Test 1"}
TEST_2 = {"label": "Test 2"}

self.assertEqual(
"enum value for TEST_1 should contain member 'value' "
"when using a dict as value; got TEST_1 = {'label': 'Test 1'}",
str(e.exception),
)

def test_absent_label_computes_default(self):
...
class TestExtendedChoices(ExtendedChoices, IntegerChoices):
TEST_1 = {"value": auto()}
TEST_2 = auto(), "Autre label"

self.assertEqual({"Test 1", "Autre label"}, set(TestExtendedChoices.labels))

def test_absent_additionnal_key_calls_dynamic_attribute_value_method(self):
...
class TestExtendedChoices(ExtendedChoices, IntegerChoices):
TEST_1 = {
"value": auto(),
"additionnal_attribute": {"lorem": "ipsum 1"},
}
TEST_2 = auto()

def dynamic_attribute_value(self, name):
match name:
case "additionnal_attribute":
return {"lorem": "ipsum {}".format(self.value)}
case _:
return {"lorem": "ipsum x"}

self.assertEqual(
[{"lorem": "ipsum 1"}, {"lorem": "ipsum 2"}],
[it.additionnal_attribute for it in TestExtendedChoices],
)

def test_absent_additionnal_key_no_dynamic_attribute_value_method(self):
...
class TestExtendedChoices(ExtendedChoices, IntegerChoices):
TEST_1 = {
"value": auto(),
"additionnal_attribute": {"lorem": "ipsum 1"},
}
TEST_2 = auto()

def test_additional_attributes(self):
...
with self.assertRaises(AttributeError) as e:
TestExtendedChoices.TEST_2.additionnal_attribute

self.assertEqual(
"TestExtendedChoices.TEST_2 does not contain key 'additionnal_attribute'. "
"Please add key or implement a 'dynamic_attribute_value(self, name)' "
"method in you enum to provide a value",
str(e.exception),
)
3 changes: 3 additions & 0 deletions dsfr/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from dsfr.enums import RichRadioButtonChoices


__all__ = ["RichRadioSelect", "RichCheckboxSelect"]


class _RichChoiceWidget(ChoiceWidget):
def __init__(self, rich_choices: Type[RichRadioButtonChoices], attrs=None):
super().__init__(attrs)
Expand Down

0 comments on commit 2a87139

Please sign in to comment.