file', field)
+ self.assertNotIn(field.url, output)
+ self.assertIn('href="something?chapter=1§=2©=3&lang=en"', output)
+ self.assertNotIn(six.text_type(field), output)
+ self.assertIn('something<div onclick="alert('oops')">.jpg', output)
+ self.assertIn('my<div>file', output)
+ self.assertNotIn('my
file', output)
+
+ def test_html_does_not_mask_exceptions(self):
+ """
+ A ClearableFileInput should not mask exceptions produced while
+ checking that it has a value.
+ """
+ @python_2_unicode_compatible
+ class FailingURLFieldFile(object):
+ @property
+ def url(self):
+ raise RuntimeError('Canary')
+
+ def __str__(self):
+ return 'value'
+
+ widget = ClearableFileInput()
+ field = FailingURLFieldFile()
+ with self.assertRaisesMessage(RuntimeError, 'Canary'):
+ widget.render('myfile', field)
+
+ def test_clear_input_renders_only_if_not_required(self):
+ """
+ A ClearableFileInput with is_required=False does not render a clear
+ checkbox.
+ """
+ widget = ClearableFileInput()
+ widget.is_required = True
+ self.check_html(widget, 'myfile', FakeFieldFile(), html=(
+ """
+ Currently:
something
+ Change:
+ """
+ ))
+
+ def test_clear_input_renders_only_if_initial(self):
+ """
+ A ClearableFileInput instantiated with no initial value does not render
+ a clear checkbox.
+ """
+ self.check_html(self.widget, 'myfile', None, html='
')
+
+ def test_clear_input_checked_returns_false(self):
+ """
+ ClearableFileInput.value_from_datadict returns False if the clear
+ checkbox is checked, if not required.
+ """
+ value = self.widget.value_from_datadict(
+ data={'myfile-clear': True},
+ files={},
+ name='myfile',
+ )
+ self.assertEqual(value, False)
+
+ def test_clear_input_checked_returns_false_only_if_not_required(self):
+ """
+ ClearableFileInput.value_from_datadict never returns False if the field
+ is required.
+ """
+ widget = ClearableFileInput()
+ widget.is_required = True
+ field = SimpleUploadedFile('something.txt', b'content')
+
+ value = widget.value_from_datadict(
+ data={'myfile-clear': True},
+ files={'myfile': field},
+ name='myfile',
+ )
+ self.assertEqual(value, field)
diff --git a/tests/forms_tests/widget_tests/test_dateinput.py b/tests/forms_tests/widget_tests/test_dateinput.py
new file mode 100644
index 0000000000..3cf15fed8a
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_dateinput.py
@@ -0,0 +1,47 @@
+from datetime import date
+
+from django.forms import DateInput
+from django.test import override_settings
+from django.utils import translation
+
+from .base import WidgetTest
+
+
+class DateInputTest(WidgetTest):
+ widget = DateInput()
+
+ def test_render_none(self):
+ self.check_html(self.widget, 'date', None, html='
')
+
+ def test_render_value(self):
+ d = date(2007, 9, 17)
+ self.assertEqual(str(d), '2007-09-17')
+
+ self.check_html(self.widget, 'date', d, html='
')
+ self.check_html(self.widget, 'date', date(2007, 9, 17), html=(
+ '
'
+ ))
+
+ def test_string(self):
+ """
+ Should be able to initialize from a string value.
+ """
+ self.check_html(self.widget, 'date', '2007-09-17', html=(
+ '
'
+ ))
+
+ def test_format(self):
+ """
+ Use 'format' to change the way a value is displayed.
+ """
+ d = date(2007, 9, 17)
+ widget = DateInput(format='%d/%m/%Y', attrs={'type': 'date'})
+ self.check_html(widget, 'date', d, html='
')
+
+ @override_settings(USE_L10N=True)
+ @translation.override('de-at')
+ def test_l10n(self):
+ self.check_html(
+ self.widget, 'date', date(2007, 9, 17),
+ html='
',
+ )
diff --git a/tests/forms_tests/widget_tests/test_datetimeinput.py b/tests/forms_tests/widget_tests/test_datetimeinput.py
new file mode 100644
index 0000000000..50fd7f5442
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_datetimeinput.py
@@ -0,0 +1,63 @@
+from datetime import datetime
+
+from django.forms import DateTimeInput
+from django.test import override_settings
+from django.utils import translation
+
+from .base import WidgetTest
+
+
+class DateTimeInputTest(WidgetTest):
+ widget = DateTimeInput()
+
+ def test_render_none(self):
+ self.check_html(self.widget, 'date', None, '
')
+
+ def test_render_value(self):
+ """
+ The microseconds are trimmed on display, by default.
+ """
+ d = datetime(2007, 9, 17, 12, 51, 34, 482548)
+ self.assertEqual(str(d), '2007-09-17 12:51:34.482548')
+ self.check_html(self.widget, 'date', d, html=(
+ '
'
+ ))
+ self.check_html(self.widget, 'date', datetime(2007, 9, 17, 12, 51, 34), html=(
+ '
'
+ ))
+ self.check_html(self.widget, 'date', datetime(2007, 9, 17, 12, 51), html=(
+ '
'
+ ))
+
+ def test_render_formatted(self):
+ """
+ Use 'format' to change the way a value is displayed.
+ """
+ widget = DateTimeInput(
+ format='%d/%m/%Y %H:%M', attrs={'type': 'datetime'},
+ )
+ d = datetime(2007, 9, 17, 12, 51, 34, 482548)
+ self.check_html(widget, 'date', d, html='
')
+
+ @override_settings(USE_L10N=True)
+ @translation.override('de-at')
+ def test_l10n(self):
+ d = datetime(2007, 9, 17, 12, 51, 34, 482548)
+ self.check_html(self.widget, 'date', d, html=(
+ '
'
+ ))
+
+ @override_settings(USE_L10N=True)
+ @translation.override('de-at')
+ def test_locale_aware(self):
+ d = datetime(2007, 9, 17, 12, 51, 34, 482548)
+ with self.settings(USE_L10N=False):
+ self.check_html(
+ self.widget, 'date', d,
+ html='
',
+ )
+ with translation.override('es'):
+ self.check_html(
+ self.widget, 'date', d,
+ html='
',
+ )
diff --git a/tests/forms_tests/widget_tests/test_fileinput.py b/tests/forms_tests/widget_tests/test_fileinput.py
new file mode 100644
index 0000000000..eb1c9d81ed
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_fileinput.py
@@ -0,0 +1,16 @@
+from django.forms import FileInput
+
+from .base import WidgetTest
+
+
+class FileInputTest(WidgetTest):
+ widget = FileInput()
+
+ def test_render(self):
+ """
+ FileInput widgets never render the value attribute. The old value
+ isn't useful if a form is updated or an error occurred.
+ """
+ self.check_html(self.widget, 'email', 'test@example.com', html='
')
+ self.check_html(self.widget, 'email', '', html='
')
+ self.check_html(self.widget, 'email', None, html='
')
diff --git a/tests/forms_tests/widget_tests/test_hiddeninput.py b/tests/forms_tests/widget_tests/test_hiddeninput.py
new file mode 100644
index 0000000000..039e89d5cc
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_hiddeninput.py
@@ -0,0 +1,10 @@
+from django.forms import HiddenInput
+
+from .base import WidgetTest
+
+
+class HiddenInputTest(WidgetTest):
+ widget = HiddenInput()
+
+ def test_render(self):
+ self.check_html(self.widget, 'email', '', html='
')
diff --git a/tests/forms_tests/widget_tests/test_multiplehiddeninput.py b/tests/forms_tests/widget_tests/test_multiplehiddeninput.py
new file mode 100644
index 0000000000..d1c1ae6fa5
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_multiplehiddeninput.py
@@ -0,0 +1,75 @@
+from django.forms import MultipleHiddenInput
+
+from .base import WidgetTest
+
+
+class MultipleHiddenInputTest(WidgetTest):
+ widget = MultipleHiddenInput()
+
+ def test_render_single(self):
+ self.check_html(
+ self.widget, 'email', ['test@example.com'],
+ html='
',
+ )
+
+ def test_render_multiple(self):
+ self.check_html(
+ self.widget, 'email', ['test@example.com', 'foo@example.com'],
+ html=(
+ '
\n'
+ '
'
+ ),
+ )
+
+ def test_render_attrs(self):
+ self.check_html(
+ self.widget, 'email', ['test@example.com'], attrs={'class': 'fun'},
+ html='
',
+ )
+
+ def test_render_attrs_multiple(self):
+ self.check_html(
+ self.widget, 'email', ['test@example.com', 'foo@example.com'], attrs={'class': 'fun'},
+ html=(
+ '
\n'
+ '
'
+ ),
+ )
+
+ def test_render_attrs_constructor(self):
+ widget = MultipleHiddenInput(attrs={'class': 'fun'})
+ self.check_html(widget, 'email', [], '')
+ self.check_html(
+ widget, 'email', ['foo@example.com'],
+ html='
',
+ )
+ self.check_html(
+ widget, 'email', ['foo@example.com', 'test@example.com'],
+ html=(
+ '
\n'
+ '
'
+ ),
+ )
+ self.check_html(
+ widget, 'email', ['foo@example.com'], attrs={'class': 'special'},
+ html='
',
+ )
+
+ def test_render_empty(self):
+ self.check_html(self.widget, 'email', [], '')
+
+ def test_render_none(self):
+ self.check_html(self.widget, 'email', None, '')
+
+ def test_render_increment_id(self):
+ """
+ Each input should get a separate ID.
+ """
+ self.check_html(
+ self.widget, 'letters', ['a', 'b', 'c'], attrs={'id': 'hideme'},
+ html=(
+ '
\n'
+ '
\n'
+ '
'
+ ),
+ )
diff --git a/tests/forms_tests/widget_tests/test_multiwidget.py b/tests/forms_tests/widget_tests/test_multiwidget.py
new file mode 100644
index 0000000000..bb6f8bfc4f
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_multiwidget.py
@@ -0,0 +1,163 @@
+import copy
+from datetime import datetime
+
+from django.forms import (
+ CharField, FileInput, MultipleChoiceField, MultiValueField, MultiWidget,
+ RadioSelect, SelectMultiple, SplitDateTimeField, SplitDateTimeWidget,
+ TextInput,
+)
+
+from .base import WidgetTest
+
+
+class MyMultiWidget(MultiWidget):
+ def decompress(self, value):
+ if value:
+ return value.split('__')
+ return ['', '']
+
+
+class ComplexMultiWidget(MultiWidget):
+ def __init__(self, attrs=None):
+ widgets = (
+ TextInput(),
+ SelectMultiple(choices=WidgetTest.beatles),
+ SplitDateTimeWidget(),
+ )
+ super(ComplexMultiWidget, self).__init__(widgets, attrs)
+
+ def decompress(self, value):
+ if value:
+ data = value.split(',')
+ return [
+ data[0], list(data[1]), datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S")
+ ]
+ return [None, None, None]
+
+ def format_output(self, rendered_widgets):
+ return '\n'.join(rendered_widgets)
+
+
+class ComplexField(MultiValueField):
+ def __init__(self, required=True, widget=None, label=None, initial=None):
+ fields = (
+ CharField(),
+ MultipleChoiceField(choices=WidgetTest.beatles),
+ SplitDateTimeField(),
+ )
+ super(ComplexField, self).__init__(
+ fields, required, widget, label, initial,
+ )
+
+ def compress(self, data_list):
+ if data_list:
+ return '%s,%s,%s' % (
+ data_list[0], ''.join(data_list[1]), data_list[2],
+ )
+ return None
+
+
+class DeepCopyWidget(MultiWidget):
+ """
+ Used to test MultiWidget.__deepcopy__().
+ """
+ def __init__(self, choices=[]):
+ widgets = [
+ RadioSelect(choices=choices),
+ TextInput,
+ ]
+ super(DeepCopyWidget, self).__init__(widgets)
+
+ def _set_choices(self, choices):
+ """
+ When choices are set for this widget, we want to pass those along to
+ the Select widget.
+ """
+ self.widgets[0].choices = choices
+
+ def _get_choices(self):
+ """
+ The choices for this widget are the Select widget's choices.
+ """
+ return self.widgets[0].choices
+ choices = property(_get_choices, _set_choices)
+
+
+class MultiWidgetTest(WidgetTest):
+
+ def test_text_inputs(self):
+ widget = MyMultiWidget(
+ widgets=(
+ TextInput(attrs={'class': 'big'}),
+ TextInput(attrs={'class': 'small'}),
+ )
+ )
+ self.check_html(widget, 'name', ['john', 'lennon'], html=(
+ '
'
+ '
'
+ ))
+ self.check_html(widget, 'name', 'john__lennon', html=(
+ '
'
+ '
'
+ ))
+ self.check_html(widget, 'name', 'john__lennon', attrs={'id': 'foo'}, html=(
+ '
'
+ '
'
+ ))
+
+ def test_constructor_attrs(self):
+ widget = MyMultiWidget(
+ widgets=(
+ TextInput(attrs={'class': 'big'}),
+ TextInput(attrs={'class': 'small'}),
+ ),
+ attrs={'id': 'bar'},
+ )
+ self.check_html(widget, 'name', ['john', 'lennon'], html=(
+ '
'
+ '
'
+ ))
+
+ def test_needs_multipart_true(self):
+ """
+ needs_multipart_form should be True if any widgets need it.
+ """
+ widget = MyMultiWidget(widgets=(TextInput(), FileInput()))
+ self.assertTrue(widget.needs_multipart_form)
+
+ def test_needs_multipart_false(self):
+ """
+ needs_multipart_form should be False if no widgets need it.
+ """
+ widget = MyMultiWidget(widgets=(TextInput(), TextInput()))
+ self.assertFalse(widget.needs_multipart_form)
+
+ def test_nested_multiwidget(self):
+ """
+ MultiWidgets can be composed of other MultiWidgets.
+ """
+ widget = ComplexMultiWidget()
+ self.check_html(widget, 'name', 'some text,JP,2007-04-25 06:24:00', html=(
+ """
+
+
+
+
+ """
+ ))
+
+ def test_deepcopy(self):
+ """
+ MultiWidget should define __deepcopy__() (#12048).
+ """
+ w1 = DeepCopyWidget(choices=[1, 2, 3])
+ w2 = copy.deepcopy(w1)
+ w2.choices = [4, 5, 6]
+ # w2 ought to be independent of w1, since MultiWidget ought
+ # to make a copy of its sub-widgets when it is copied.
+ self.assertEqual(w1.choices, [1, 2, 3])
diff --git a/tests/forms_tests/widget_tests/test_nullbooleanselect.py b/tests/forms_tests/widget_tests/test_nullbooleanselect.py
new file mode 100644
index 0000000000..42b1dbf7c1
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_nullbooleanselect.py
@@ -0,0 +1,64 @@
+from django.forms import NullBooleanSelect
+from django.test import override_settings
+from django.utils import translation
+
+from .base import WidgetTest
+
+
+class NullBooleanSelectTest(WidgetTest):
+ widget = NullBooleanSelect()
+
+ def test_render_true(self):
+ self.check_html(self.widget, 'is_cool', True, html=(
+ """
"""
+ ))
+
+ def test_render_false(self):
+ self.check_html(self.widget, 'is_cool', False, html=(
+ """
"""
+ ))
+
+ def test_render_none(self):
+ self.check_html(self.widget, 'is_cool', None, html=(
+ """
"""
+ ))
+
+ def test_render_value(self):
+ self.check_html(self.widget, 'is_cool', '2', html=(
+ """
"""
+ ))
+
+ @override_settings(USE_L10N=True)
+ def test_l10n(self):
+ """
+ Ensure that the NullBooleanSelect widget's options are lazily
+ localized (#17190).
+ """
+ widget = NullBooleanSelect()
+
+ with translation.override('de-at'):
+ self.check_html(widget, 'id_bool', True, html=(
+ """
+
+ """
+ ))
diff --git a/tests/forms_tests/widget_tests/test_passwordinput.py b/tests/forms_tests/widget_tests/test_passwordinput.py
new file mode 100644
index 0000000000..4cb7c4ef47
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_passwordinput.py
@@ -0,0 +1,26 @@
+from django.forms import PasswordInput
+
+from .base import WidgetTest
+
+
+class PasswordInputTest(WidgetTest):
+ widget = PasswordInput()
+
+ def test_render(self):
+ self.check_html(self.widget, 'password', '', html='
')
+
+ def test_render_ignore_value(self):
+ self.check_html(self.widget, 'password', 'secret', html='
')
+
+ def test_render_value_true(self):
+ """
+ The render_value argument lets you specify whether the widget should
+ render its value. For security reasons, this is off by default.
+ """
+ widget = PasswordInput(render_value=True)
+ self.check_html(widget, 'password', '', html='
')
+ self.check_html(widget, 'password', None, html='
')
+ self.check_html(
+ widget, 'password', 'test@example.com',
+ html='
',
+ )
diff --git a/tests/forms_tests/widget_tests/test_radioselect.py b/tests/forms_tests/widget_tests/test_radioselect.py
new file mode 100644
index 0000000000..e37dcf7725
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_radioselect.py
@@ -0,0 +1,84 @@
+from django.forms import RadioSelect
+
+from .base import WidgetTest
+
+
+class RadioSelectTest(WidgetTest):
+ widget = RadioSelect()
+
+ def test_render(self):
+ self.check_html(self.widget, 'beatle', 'J', choices=self.beatles, html=(
+ """
"""
+ ))
+
+ def test_nested_choices(self):
+ nested_choices = (
+ ('unknown', 'Unknown'),
+ ('Audio', (('vinyl', 'Vinyl'), ('cd', 'CD'))),
+ ('Video', (('vhs', 'VHS'), ('dvd', 'DVD'))),
+ )
+ html = """
+
+ """
+ self.check_html(
+ self.widget, 'nestchoice', 'dvd', choices=nested_choices,
+ attrs={'id': 'media'}, html=html,
+ )
+
+ def test_constructor_attrs(self):
+ """
+ Attributes provided at instantiation are passed to the constituent
+ inputs.
+ """
+ widget = RadioSelect(attrs={'id': 'foo'})
+ html = """
+
+ """
+ self.check_html(widget, 'beatle', 'J', choices=self.beatles, html=html)
+
+ def test_render_attrs(self):
+ """
+ Attributes provided at render-time are passed to the constituent
+ inputs.
+ """
+ html = """
+
+ """
+ self.check_html(self.widget, 'beatle', 'J', choices=self.beatles, attrs={'id': 'bar'}, html=html)
diff --git a/tests/forms_tests/widget_tests/test_select.py b/tests/forms_tests/widget_tests/test_select.py
new file mode 100644
index 0000000000..86ef355c62
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_select.py
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import copy
+
+from django.forms import Select
+from django.utils.safestring import mark_safe
+
+from .base import WidgetTest
+
+
+class SelectTest(WidgetTest):
+ widget = Select()
+ nested_widget = Select(choices=(
+ ('outer1', 'Outer 1'),
+ ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))),
+ ))
+
+ def test_render(self):
+ self.check_html(self.widget, 'beatle', 'J', choices=self.beatles, html=(
+ """
"""
+ ))
+
+ def test_render_none(self):
+ """
+ If the value is None, none of the options are selected.
+ """
+ self.check_html(self.widget, 'beatle', None, choices=self.beatles, 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.
+ """
+ self.check_html(self.widget, 'beatle', 'John', choices=self.beatles, html=(
+ """
"""
+ ))
+
+ def test_render_selected(self):
+ """
+ Only one option can be selected (#8103).
+ """
+ choices = [('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('0', 'extra')]
+
+ self.check_html(self.widget, 'choices', '0', choices=choices, html=(
+ """
"""
+ ))
+
+ def test_constructor_attrs(self):
+ """
+ Select options shouldn't inherit the parent widget attrs.
+ """
+ widget = Select(
+ attrs={'class': 'super', 'id': 'super'},
+ choices=[(1, 1), (2, 2), (3, 3)],
+ )
+ self.check_html(widget, 'num', 2, html=(
+ """
"""
+ ))
+
+ def test_compare_to_str(self):
+ """
+ The value is compared to its str().
+ """
+ self.check_html(
+ self.widget, 'num', 2,
+ choices=[('1', '1'), ('2', '2'), ('3', '3')],
+ html=(
+ """
"""
+ ),
+ )
+ self.check_html(
+ self.widget, 'num', '2',
+ choices=[(1, 1), (2, 2), (3, 3)],
+ html=(
+ """
"""
+ ),
+ )
+ self.check_html(
+ self.widget, 'num', 2,
+ choices=[(1, 1), (2, 2), (3, 3)],
+ html=(
+ """
"""
+ ),
+ )
+
+ def test_choices_constuctor(self):
+ widget = Select(choices=[(1, 1), (2, 2), (3, 3)])
+ self.check_html(widget, 'num', 2, 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(5):
+ yield (i, i)
+
+ widget = Select(choices=get_choices())
+ self.check_html(widget, 'num', 2, html=(
+ """
"""
+ ))
+ self.check_html(widget, 'num', 3, html=(
+ """
"""
+ ))
+
+ def test_choices_constuctor_and_render(self):
+ """
+ If 'choices' is passed to both the constructor and render(), then
+ they'll both be in the output.
+ """
+ widget = Select(choices=[(1, 1), (2, 2), (3, 3)])
+ self.check_html(widget, 'num', 2, choices=[(4, 4), (5, 5)], html=(
+ """
"""
+ ))
+
+ def test_choices_escaping(self):
+ choices = (('bad', 'you & me'), ('good', mark_safe('you > me')))
+ self.check_html(self.widget, 'escape', None, choices=choices, html=(
+ """
"""
+ ))
+
+ def test_choices_unicode(self):
+ self.check_html(
+ self.widget, 'email', 'ŠĐĆŽćžšđ',
+ choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')],
+ html=(
+ """
"""
+ ),
+ )
+
+ def test_choices_optgroup(self):
+ """
+ Choices can be nested one level in order to create HTML optgroups.
+ """
+ self.check_html(self.nested_widget, 'nestchoice', None, html=(
+ """
"""
+ ))
+
+ def test_choices_select_outer(self):
+ self.check_html(self.nested_widget, 'nestchoice', 'outer1', html=(
+ """
"""
+ ))
+
+ def test_choices_select_inner(self):
+ self.check_html(self.nested_widget, 'nestchoice', 'inner1', html=(
+ """
"""
+ ))
+
+ 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)
diff --git a/tests/forms_tests/widget_tests/test_selectdatewidget.py b/tests/forms_tests/widget_tests/test_selectdatewidget.py
new file mode 100644
index 0000000000..e95c083532
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_selectdatewidget.py
@@ -0,0 +1,479 @@
+from datetime import date
+
+from django.forms import DateField, Form, SelectDateWidget
+from django.test import override_settings
+from django.utils import translation
+from django.utils.dates import MONTHS_AP
+
+from .base import WidgetTest
+
+
+class SelectDateWidgetTest(WidgetTest):
+ maxDiff = None
+ widget = SelectDateWidget(
+ years=('2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016'),
+ )
+
+ def test_render_empty(self):
+ self.check_html(self.widget, 'mydate', '', html=(
+ """
+
+
+
+
+
+ """
+ ))
+
+ def test_render_none(self):
+ """
+ Rendering the None or '' values should yield the same output.
+ """
+ self.assertHTMLEqual(
+ self.widget.render('mydate', None),
+ self.widget.render('mydate', ''),
+ )
+
+ def test_render_string(self):
+ self.check_html(self.widget, 'mydate', '2010-04-15', html=(
+ """
+
+
+
+
+
+ """
+ ))
+
+ def test_render_datetime(self):
+ self.assertHTMLEqual(
+ self.widget.render('mydate', date(2010, 4, 15)),
+ self.widget.render('mydate', '2010-04-15'),
+ )
+
+ def test_render_invalid_date(self):
+ """
+ Invalid dates should still render the failed date.
+ """
+ self.check_html(self.widget, 'mydate', '2010-02-31', html=(
+ """
+
+
+
+
+
+ """
+ ))
+
+ def test_custom_months(self):
+ widget = SelectDateWidget(months=MONTHS_AP, years=('2013',))
+ self.check_html(widget, 'mydate', '', html=(
+ """
+
+
+
+
+
+ """
+ ))
+
+ def test_selectdate_required(self):
+ class GetNotRequiredDate(Form):
+ mydate = DateField(widget=SelectDateWidget, required=False)
+
+ class GetRequiredDate(Form):
+ mydate = DateField(widget=SelectDateWidget, required=True)
+
+ self.assertFalse(GetNotRequiredDate().fields['mydate'].widget.is_required)
+ self.assertTrue(GetRequiredDate().fields['mydate'].widget.is_required)
+
+ def test_selectdate_empty_label(self):
+ w = SelectDateWidget(years=('2014',), empty_label='empty_label')
+
+ # Rendering the default state with empty_label setted as string.
+ self.assertInHTML('
', w.render('mydate', ''), count=3)
+
+ w = SelectDateWidget(years=('2014',), empty_label=('empty_year', 'empty_month', 'empty_day'))
+
+ # Rendering the default state with empty_label tuple.
+ self.assertHTMLEqual(
+ w.render('mydate', ''),
+ """
+
+
+
+
+
+ """,
+ )
+
+ self.assertRaisesMessage(ValueError, 'empty_label list/tuple must have 3 elements.',
+ SelectDateWidget, years=('2014',), empty_label=('not enough', 'values'))
+
+ @override_settings(USE_L10N=True)
+ @translation.override('nl')
+ def test_l10n(self):
+ w = SelectDateWidget(
+ years=('2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016')
+ )
+ self.assertEqual(
+ w.value_from_datadict({'date_year': '2010', 'date_month': '8', 'date_day': '13'}, {}, 'date'),
+ '13-08-2010',
+ )
+
+ self.assertHTMLEqual(
+ w.render('date', '13-08-2010'),
+ """
+
+
+
+
+
+ """,
+ )
+
+ # Even with an invalid date, the widget should reflect the entered value (#17401).
+ self.assertEqual(w.render('mydate', '2010-02-30').count('selected="selected"'), 3)
+
+ # Years before 1900 should work.
+ w = SelectDateWidget(years=('1899',))
+ self.assertEqual(
+ w.value_from_datadict({'date_year': '1899', 'date_month': '8', 'date_day': '13'}, {}, 'date'),
+ '13-08-1899',
+ )
diff --git a/tests/forms_tests/widget_tests/test_selectmultiple.py b/tests/forms_tests/widget_tests/test_selectmultiple.py
new file mode 100644
index 0000000000..502aba3bf4
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_selectmultiple.py
@@ -0,0 +1,125 @@
+from django.forms import SelectMultiple
+
+from .base import WidgetTest
+
+
+class SelectMultipleTest(WidgetTest):
+ widget = SelectMultiple()
+ numeric_choices = (('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('0', 'extra'))
+
+ def test_render_selected(self):
+ self.check_html(self.widget, 'beatles', ['J'], choices=self.beatles, html=(
+ """
"""
+ ))
+
+ def test_render_multiple_selected(self):
+ self.check_html(self.widget, 'beatles', ['J', 'P'], choices=self.beatles, html=(
+ """
"""
+ ))
+
+ def test_render_none(self):
+ """
+ If the value is None, none of the options are selected.
+ """
+ self.check_html(self.widget, 'beatles', None, choices=self.beatles, html=(
+ """
"""
+ ))
+
+ def test_render_value_label(self):
+ """
+ If the value corresponds to a label (but not to an option value), none
+ of the options are selected.
+ """
+ self.check_html(self.widget, 'beatles', ['John'], choices=self.beatles, html=(
+ """
"""
+ ))
+
+ def test_multiple_options_same_value(self):
+ """
+ Multiple options with the same value can be selected (#8103).
+ """
+ self.check_html(self.widget, 'choices', ['0'], choices=self.numeric_choices, html=(
+ """
"""
+ ))
+
+ def test_multiple_values_invalid(self):
+ """
+ If multiple values are given, but some of them are not valid, the valid
+ ones are selected.
+ """
+ self.check_html(self.widget, 'beatles', ['J', 'G', 'foo'], choices=self.beatles, html=(
+ """
"""
+ ))
+
+ def test_compare_string(self):
+ choices = [('1', '1'), ('2', '2'), ('3', '3')]
+
+ self.check_html(self.widget, 'nums', [2], choices=choices, html=(
+ """
"""
+ ))
+
+ self.check_html(self.widget, 'nums', ['2'], choices=choices, html=(
+ """
"""
+ ))
+
+ self.check_html(self.widget, 'nums', [2], choices=choices, html=(
+ """
"""
+ ))
+
+ def test_optgroup_select_multiple(self):
+ widget = SelectMultiple(choices=(
+ ('outer1', 'Outer 1'),
+ ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))),
+ ))
+ self.check_html(widget, 'nestchoice', ['outer1', 'inner2'], html=(
+ """
"""
+ ))
diff --git a/tests/forms_tests/widget_tests/test_splitdatetimewidget.py b/tests/forms_tests/widget_tests/test_splitdatetimewidget.py
new file mode 100644
index 0000000000..172bcbbe8d
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_splitdatetimewidget.py
@@ -0,0 +1,51 @@
+from datetime import date, datetime, time
+
+from django.forms import SplitDateTimeWidget
+
+from .base import WidgetTest
+
+
+class SplitDateTimeWidgetTest(WidgetTest):
+ widget = SplitDateTimeWidget()
+
+ def test_render_empty(self):
+ self.check_html(self.widget, 'date', '', html=(
+ '
'
+ ))
+
+ def test_render_none(self):
+ self.check_html(self.widget, 'date', None, html=(
+ '
'
+ ))
+
+ def test_render_datetime(self):
+ self.check_html(self.widget, 'date', datetime(2006, 1, 10, 7, 30), html=(
+ '
'
+ '
'
+ ))
+
+ def test_render_date_and_time(self):
+ self.check_html(self.widget, 'date', [date(2006, 1, 10), time(7, 30)], html=(
+ '
'
+ '
'
+ ))
+
+ def test_constructor_attrs(self):
+ widget = SplitDateTimeWidget(attrs={'class': 'pretty'})
+ self.check_html(widget, 'date', datetime(2006, 1, 10, 7, 30), html=(
+ '
'
+ '
'
+ ))
+
+ def test_formatting(self):
+ """
+ Use 'date_format' and 'time_format' to change the way a value is
+ displayed.
+ """
+ widget = SplitDateTimeWidget(
+ date_format='%d/%m/%Y', time_format='%H:%M',
+ )
+ self.check_html(widget, 'date', datetime(2006, 1, 10, 7, 30), html=(
+ '
'
+ '
'
+ ))
diff --git a/tests/forms_tests/widget_tests/test_splithiddendatetimewidget.py b/tests/forms_tests/widget_tests/test_splithiddendatetimewidget.py
new file mode 100644
index 0000000000..07ee15690c
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_splithiddendatetimewidget.py
@@ -0,0 +1,42 @@
+from datetime import datetime
+
+from django.forms import SplitHiddenDateTimeWidget
+from django.test import override_settings
+from django.utils import translation
+
+from .base import WidgetTest
+
+
+class SplitHiddenDateTimeWidgetTest(WidgetTest):
+ widget = SplitHiddenDateTimeWidget()
+
+ def test_render_empty(self):
+ self.check_html(self.widget, 'date', '', html=(
+ '
'
+ ))
+
+ def test_render_value(self):
+ d = datetime(2007, 9, 17, 12, 51, 34, 482548)
+ self.check_html(self.widget, 'date', d, html=(
+ '
'
+ '
'
+ ))
+ self.check_html(self.widget, 'date', datetime(2007, 9, 17, 12, 51, 34), html=(
+ '
'
+ '
'
+ ))
+ self.check_html(self.widget, 'date', datetime(2007, 9, 17, 12, 51), html=(
+ '
'
+ '
'
+ ))
+
+ @override_settings(USE_L10N=True)
+ @translation.override('de-at')
+ def test_l10n(self):
+ d = datetime(2007, 9, 17, 12, 51)
+ self.check_html(self.widget, 'date', d, html=(
+ """
+
+
+ """
+ ))
diff --git a/tests/forms_tests/widget_tests/test_textarea.py b/tests/forms_tests/widget_tests/test_textarea.py
new file mode 100644
index 0000000000..490848fab3
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_textarea.py
@@ -0,0 +1,34 @@
+from django.forms import Textarea
+from django.utils.safestring import mark_safe
+
+from .base import WidgetTest
+
+
+class TextareaTest(WidgetTest):
+ widget = Textarea()
+
+ def test_render(self):
+ self.check_html(self.widget, 'msg', 'value', html=(
+ '
'
+ ))
+
+ def test_render_required(self):
+ widget = Textarea()
+ widget.is_required = True
+ self.check_html(widget, 'msg', 'value', html='
')
+
+ def test_render_empty(self):
+ self.check_html(self.widget, 'msg', '', html='
')
+
+ def test_render_none(self):
+ self.check_html(self.widget, 'msg', None, html='
')
+
+ def test_escaping(self):
+ self.check_html(self.widget, 'msg', 'some "quoted" & ampersanded value', html=(
+ '
'
+ ))
+
+ def test_mark_safe(self):
+ self.check_html(self.widget, 'msg', mark_safe('pre "quoted" value'), html=(
+ '
'
+ ))
diff --git a/tests/forms_tests/widget_tests/test_textinput.py b/tests/forms_tests/widget_tests/test_textinput.py
new file mode 100644
index 0000000000..33e455a160
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_textinput.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.forms import TextInput
+from django.utils.safestring import mark_safe
+
+from .base import WidgetTest
+
+
+class TextInputTest(WidgetTest):
+ widget = TextInput()
+
+ def test_render(self):
+ self.check_html(self.widget, 'email', '', html='
')
+
+ def test_render_none(self):
+ self.check_html(self.widget, 'email', None, html='
')
+
+ def test_render_value(self):
+ self.check_html(self.widget, 'email', 'test@example.com', html=(
+ '
'
+ ))
+
+ def test_render_boolean(self):
+ """
+ Boolean values are rendered to their string forms ("True" and
+ "False").
+ """
+ self.check_html(self.widget, 'get_spam', False, html=(
+ '
'
+ ))
+ self.check_html(self.widget, 'get_spam', True, html=(
+ '
'
+ ))
+
+ def test_render_quoted(self):
+ self.check_html(
+ self.widget, 'email', 'some "quoted" & ampersanded value',
+ html='
',
+ )
+
+ def test_render_custom_attrs(self):
+ self.check_html(
+ self.widget, 'email', 'test@example.com', attrs={'class': 'fun'},
+ html='
',
+ )
+
+ def test_render_unicode(self):
+ self.check_html(
+ self.widget, 'email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'},
+ html=(
+ '
'
+ ),
+ )
+
+ def test_constructor_attrs(self):
+ widget = TextInput(attrs={'class': 'fun', 'type': 'email'})
+ self.check_html(widget, 'email', '', html='
')
+ self.check_html(
+ widget, 'email', 'foo@example.com',
+ html='
',
+ )
+
+ def test_attrs_precedence(self):
+ """
+ `attrs` passed to render() get precedence over those passed to the
+ constructor
+ """
+ widget = TextInput(attrs={'class': 'pretty'})
+ self.check_html(
+ widget, 'email', '', attrs={'class': 'special'},
+ html='
',
+ )
+
+ def test_attrs_safestring(self):
+ widget = TextInput(attrs={'onBlur': mark_safe("function('foo')")})
+ self.check_html(widget, 'email', '', html='
')
diff --git a/tests/forms_tests/widget_tests/test_timeinput.py b/tests/forms_tests/widget_tests/test_timeinput.py
new file mode 100644
index 0000000000..96fb04e24c
--- /dev/null
+++ b/tests/forms_tests/widget_tests/test_timeinput.py
@@ -0,0 +1,50 @@
+from datetime import time
+
+from django.forms import TimeInput
+from django.test import override_settings
+from django.utils import translation
+
+from .base import WidgetTest
+
+
+class TimeInputTest(WidgetTest):
+ widget = TimeInput()
+
+ def test_render_none(self):
+ self.check_html(self.widget, 'time', None, html='
')
+
+ def test_render_value(self):
+ """
+ The microseconds are trimmed on display, by default.
+ """
+ t = time(12, 51, 34, 482548)
+ self.assertEqual(str(t), '12:51:34.482548')
+ self.check_html(self.widget, 'time', t, html='
')
+ self.check_html(self.widget, 'time', time(12, 51, 34), html=(
+ '
'
+ ))
+ self.check_html(self.widget, 'time', time(12, 51), html=(
+ '
'
+ ))
+
+ def test_string(self):
+ """
+ We should be able to initialize from a unicode value.
+ """
+ self.check_html(self.widget, 'time', '13:12:11', html=(
+ '
'
+ ))
+
+ def test_format(self):
+ """
+ Use 'format' to change the way a value is displayed.
+ """
+ t = time(12, 51, 34, 482548)
+ widget = TimeInput(format='%H:%M', attrs={'type': 'time'})
+ self.check_html(widget, 'time', t, html='
')
+
+ @override_settings(USE_L10N=True)
+ @translation.override('de-at')
+ def test_l10n(self):
+ t = time(12, 51, 34, 482548)
+ self.check_html(self.widget, 'time', t, html='
')