diff --git a/AUTHORS b/AUTHORS index 834786736d..df2f890761 100644 --- a/AUTHORS +++ b/AUTHORS @@ -431,6 +431,7 @@ answer newbie questions, and generally made Django that much better: Jacob Kaplan-Moss Jacob Rief Jacob Walls + Jakub Bagiński Jakub Paczkowski Jakub Wilk Jakub Wiśniowski diff --git a/tests/forms_tests/widget_tests/test_choicewidget.py b/tests/forms_tests/widget_tests/test_choicewidget.py new file mode 100644 index 0000000000..129178f207 --- /dev/null +++ b/tests/forms_tests/widget_tests/test_choicewidget.py @@ -0,0 +1,71 @@ +import copy + +from django.forms.widgets import ChoiceWidget + +from .base import WidgetTest + + +class ChoiceWidgetTest(WidgetTest): + widget = ChoiceWidget + + @property + def nested_widget(self): + return self.widget( + choices=( + ("outer1", "Outer 1"), + ('Group "1"', (("inner1", "Inner 1"), ("inner2", "Inner 2"))), + ) + ) + + def test_deepcopy(self): + """ + __deepcopy__() should copy all attributes properly. + """ + widget = self.widget() + obj = copy.deepcopy(widget) + self.assertIsNot(widget, obj) + self.assertEqual(widget.choices, obj.choices) + self.assertIsNot(widget.choices, obj.choices) + self.assertEqual(widget.attrs, obj.attrs) + self.assertIsNot(widget.attrs, obj.attrs) + + def test_options(self): + options = list( + self.widget(choices=self.beatles).options( + "name", + ["J"], + attrs={"class": "super"}, + ) + ) + self.assertEqual(len(options), 4) + self.assertEqual(options[0]["name"], "name") + self.assertEqual(options[0]["value"], "J") + self.assertEqual(options[0]["label"], "John") + self.assertEqual(options[0]["index"], "0") + self.assertIs(options[0]["selected"], True) + # Template-related attributes + self.assertEqual(options[1]["name"], "name") + self.assertEqual(options[1]["value"], "P") + self.assertEqual(options[1]["label"], "Paul") + self.assertEqual(options[1]["index"], "1") + self.assertIs(options[1]["selected"], False) + + def test_optgroups_integer_choices(self): + """The option 'value' is the same type as what's in `choices`.""" + groups = list( + self.widget(choices=[[0, "choice text"]]).optgroups("name", ["vhs"]) + ) + label, options, index = groups[0] + self.assertEqual(options[0]["value"], 0) + + def test_renders_required_when_possible_to_select_empty_field_none(self): + widget = self.widget(choices=[(None, "select please"), ("P", "Paul")]) + self.assertIs(widget.use_required_attribute(initial=None), True) + + def test_renders_required_when_possible_to_select_empty_field_list(self): + widget = self.widget(choices=[["", "select please"], ["P", "Paul"]]) + self.assertIs(widget.use_required_attribute(initial=None), True) + + def test_renders_required_when_possible_to_select_empty_field_str(self): + widget = self.widget(choices=[("", "select please"), ("P", "Paul")]) + self.assertIs(widget.use_required_attribute(initial=None), True) diff --git a/tests/forms_tests/widget_tests/test_radioselect.py b/tests/forms_tests/widget_tests/test_radioselect.py index dc3f3d9bad..a861ccf0f3 100644 --- a/tests/forms_tests/widget_tests/test_radioselect.py +++ b/tests/forms_tests/widget_tests/test_radioselect.py @@ -1,32 +1,39 @@ import datetime -from django.forms import ChoiceField, Form, MultiWidget, RadioSelect +from django.forms import ChoiceField, Form, MultiWidget, RadioSelect, TextInput from django.test import override_settings +from django.utils.safestring import mark_safe -from .base import WidgetTest +from .test_choicewidget import ChoiceWidgetTest + +BLANK_CHOICE_DASH = (("", "------"),) -class RadioSelectTest(WidgetTest): +class RadioSelectTest(ChoiceWidgetTest): widget = RadioSelect def test_render(self): - choices = (("", "------"),) + self.beatles - self.check_html( - self.widget(choices=choices), - "beatle", - "J", - html=""" -
-
-
-
-
-
-
- """, - ) + choices = BLANK_CHOICE_DASH + self.beatles + html = """ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ """ + self.check_html(self.widget(choices=choices), "beatle", "J", html=html) def test_nested_choices(self): nested_choices = ( @@ -70,28 +77,287 @@ class RadioSelectTest(WidgetTest): html=html, ) + def test_render_none(self): + """ + If value is None, none of the options are selected. + """ + choices = BLANK_CHOICE_DASH + self.beatles + html = """ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ """ + self.check_html(self.widget(choices=choices), "beatle", None, html=html) + + def test_render_label_value(self): + """ + If the value corresponds to a label (but not to an option value), none + of the options are selected. + """ + html = """ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ """ + self.check_html(self.widget(choices=self.beatles), "beatle", "Ringo", html=html) + + def test_render_selected(self): + """ + Only one option can be selected. + """ + choices = [("0", "0"), ("1", "1"), ("2", "2"), ("3", "3"), ("0", "extra")] + html = """ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ """ + self.check_html(self.widget(choices=choices), "choices", "0", html=html) + def test_constructor_attrs(self): """ Attributes provided at instantiation are passed to the constituent inputs. """ - widget = RadioSelect(attrs={"id": "foo"}, choices=self.beatles) + widget = self.widget(attrs={"id": "foo"}, choices=self.beatles) html = """
-
- -
-
-
-
+
+ +
+
+
+
+
+
+
""" self.check_html(widget, "beatle", "J", html=html) + def test_compare_to_str(self): + """ + The value is compared to its str() + """ + html = """ +
+
+ +
+
+ +
+
+ +
+
+ """ + self.check_html( + self.widget(choices=[("1", "1"), ("2", "2"), ("3", "3")]), + "num", + 3, + html=html, + ) + self.check_html( + self.widget(choices=[(1, 1), (2, 2), (3, 3)]), "num", "3", html=html + ) + self.check_html( + self.widget(choices=[(1, 1), (2, 2), (3, 3)]), "num", 3, html=html + ) + + def test_choices_constructor(self): + widget = self.widget(choices=[(1, 1), (2, 2), (3, 3)]) + html = """ +
+
+ +
+
+ +
+
+ +
+
+ """ + self.check_html(widget, "num", 3, html=html) + + def test_choices_constructor_generator(self): + """ + If choices is passed to the constructor and is a generator, it can be + iterated over multiple times without getting consumed. + """ + + def get_choices(): + for i in range(4): + yield (i, i) + + html = """ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ """ + widget = self.widget(choices=get_choices()) + self.check_html(widget, "num", 3, html=html) + + def test_choices_escaping(self): + choices = (("bad", "you & me"), ("good", mark_safe("you > me"))) + html = """ +
+
+ +
+
+ +
+
+ """ + self.check_html(self.widget(choices=choices), "escape", None, html=html) + + def test_choices_unicode(self): + html = """ +
+
+ +
+
+ +
+
+ """ + self.check_html( + self.widget(choices=[("ŠĐĆŽćžšđ", "ŠĐabcĆŽćžšđ"), ("ćžšđ", "abcćžšđ")]), + "email", + "ŠĐĆŽćžšđ", + html=html, + ) + + def test_choices_optgroup(self): + """ + Choices can be nested one level in order to create HTML optgroups + """ + html = """ +
+
+ +
+
+ +
+ +
+
+ +
+
+
+ """ + self.check_html(self.nested_widget, "nestchoice", None, html=html) + + def test_choices_select_outer(self): + html = """ +
+
+ +
+
+ +
+ +
+
+ +
+
+
+ """ + self.check_html(self.nested_widget, "nestchoice", "outer1", html=html) + + def test_choices_select_inner(self): + html = """ +
+
+ +
+
+ +
+ +
+
+ +
+
+
+ """ + self.check_html(self.nested_widget, "nestchoice", "inner2", html=html) + def test_render_attrs(self): """ Attributes provided at render-time are passed to the constituent @@ -99,16 +365,19 @@ class RadioSelectTest(WidgetTest): """ html = """
-
- -
-
-
-
+
+ +
+
+
+
+
+
+
""" self.check_html( @@ -126,15 +395,18 @@ class RadioSelectTest(WidgetTest): """ html = """
-
-
-
-
-
+
+
+
+
+
+
+
+
""" self.check_html( @@ -154,11 +426,13 @@ class RadioSelectTest(WidgetTest): ] html = """
-
-
-
+
+
+ +
+
+ +
""" self.check_html(self.widget(choices=choices), "number", None, html=html) @@ -169,35 +443,44 @@ class RadioSelectTest(WidgetTest): ] html = """
-
-
+
+ +
+
+ +
""" self.check_html(self.widget(choices=choices), "time", None, html=html) def test_render_as_subwidget(self): """A RadioSelect as a subwidget of MultiWidget.""" - choices = (("", "------"),) + self.beatles + choices = BLANK_CHOICE_DASH + self.beatles + html = """ +
+
+
+
+
+
+
+
+
+
+
+
+ + """ self.check_html( - MultiWidget([self.widget(choices=choices)]), + MultiWidget([self.widget(choices=choices), TextInput()]), "beatle", - ["J"], - html=""" -
-
-
-
-
-
-
- """, + ["J", "Some text"], + html=html, ) def test_fieldset(self): diff --git a/tests/forms_tests/widget_tests/test_select.py b/tests/forms_tests/widget_tests/test_select.py index 77450e3716..60a0b72880 100644 --- a/tests/forms_tests/widget_tests/test_select.py +++ b/tests/forms_tests/widget_tests/test_select.py @@ -1,21 +1,14 @@ -import copy import datetime -from django.forms import ChoiceField, Form, Select +from django.forms import ChoiceField, Form, MultiWidget, Select, TextInput from django.test import override_settings from django.utils.safestring import mark_safe -from .base import WidgetTest +from .test_choicewidget import ChoiceWidgetTest -class SelectTest(WidgetTest): +class SelectTest(ChoiceWidgetTest): widget = Select - nested_widget = Select( - choices=( - ("outer1", "Outer 1"), - ('Group "1"', (("inner1", "Inner 1"), ("inner2", "Inner 2"))), - ) - ) def test_render(self): self.check_html( @@ -319,27 +312,6 @@ class SelectTest(WidgetTest): """ self.check_html(self.widget(choices=choices), "time", None, html=html) - def test_options(self): - options = list( - self.widget(choices=self.beatles).options( - "name", - ["J"], - attrs={"class": "super"}, - ) - ) - self.assertEqual(len(options), 4) - self.assertEqual(options[0]["name"], "name") - self.assertEqual(options[0]["value"], "J") - self.assertEqual(options[0]["label"], "John") - self.assertEqual(options[0]["index"], "0") - self.assertIs(options[0]["selected"], True) - # Template-related attributes - self.assertEqual(options[1]["name"], "name") - self.assertEqual(options[1]["value"], "P") - self.assertEqual(options[1]["label"], "Paul") - self.assertEqual(options[1]["index"], "1") - self.assertIs(options[1]["selected"], False) - def test_optgroups(self): choices = [ ( @@ -446,46 +418,35 @@ class SelectTest(WidgetTest): ) self.assertEqual(index, 2) - def test_optgroups_integer_choices(self): - """The option 'value' is the same type as what's in `choices`.""" - groups = list( - self.widget(choices=[[0, "choice text"]]).optgroups("name", ["vhs"]) - ) - label, options, index = groups[0] - self.assertEqual(options[0]["value"], 0) - - def test_deepcopy(self): - """ - __deepcopy__() should copy all attributes properly (#25085). - """ - widget = Select() - obj = copy.deepcopy(widget) - self.assertIsNot(widget, obj) - self.assertEqual(widget.choices, obj.choices) - self.assertIsNot(widget.choices, obj.choices) - self.assertEqual(widget.attrs, obj.attrs) - self.assertIsNot(widget.attrs, obj.attrs) - def test_doesnt_render_required_when_impossible_to_select_empty_field(self): widget = self.widget(choices=[("J", "John"), ("P", "Paul")]) self.assertIs(widget.use_required_attribute(initial=None), False) - def test_renders_required_when_possible_to_select_empty_field_str(self): - widget = self.widget(choices=[("", "select please"), ("P", "Paul")]) - self.assertIs(widget.use_required_attribute(initial=None), True) - - def test_renders_required_when_possible_to_select_empty_field_list(self): - widget = self.widget(choices=[["", "select please"], ["P", "Paul"]]) - self.assertIs(widget.use_required_attribute(initial=None), True) - - def test_renders_required_when_possible_to_select_empty_field_none(self): - widget = self.widget(choices=[(None, "select please"), ("P", "Paul")]) - self.assertIs(widget.use_required_attribute(initial=None), True) - def test_doesnt_render_required_when_no_choices_are_available(self): widget = self.widget(choices=[]) self.assertIs(widget.use_required_attribute(initial=None), False) + def test_render_as_subwidget(self): + """A RadioSelect as a subwidget of MultiWidget.""" + choices = (("", "------"),) + self.beatles + self.check_html( + MultiWidget([self.widget(choices=choices), TextInput()]), + "beatle", + ["J", "Some text"], + html=( + """ + + + """ + ), + ) + def test_fieldset(self): class TestForm(Form): template_name = "forms_tests/use_fieldset.html"