summaryrefslogtreecommitdiff
path: root/tests/forms_tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests/forms_tests')
-rw-r--r--tests/forms_tests/field_tests/test_filepathfield.py3
-rw-r--r--tests/forms_tests/jinja2/forms_tests/custom_widget.html1
-rw-r--r--tests/forms_tests/templates/forms_tests/custom_widget.html1
-rw-r--r--tests/forms_tests/tests/test_forms.py93
-rw-r--r--tests/forms_tests/tests/test_renderers.py52
-rw-r--r--tests/forms_tests/tests/test_widgets.py201
-rw-r--r--tests/forms_tests/widget_tests/base.py20
-rw-r--r--tests/forms_tests/widget_tests/test_select.py62
8 files changed, 230 insertions, 203 deletions
diff --git a/tests/forms_tests/field_tests/test_filepathfield.py b/tests/forms_tests/field_tests/test_filepathfield.py
index 5c4bd1bbb2..d518e65db1 100644
--- a/tests/forms_tests/field_tests/test_filepathfield.py
+++ b/tests/forms_tests/field_tests/test_filepathfield.py
@@ -39,6 +39,7 @@ class FilePathFieldTest(SimpleTestCase):
('/django/forms/forms.py', 'forms.py'),
('/django/forms/formsets.py', 'formsets.py'),
('/django/forms/models.py', 'models.py'),
+ ('/django/forms/renderers.py', 'renderers.py'),
('/django/forms/utils.py', 'utils.py'),
('/django/forms/widgets.py', 'widgets.py')
]
@@ -62,6 +63,7 @@ class FilePathFieldTest(SimpleTestCase):
('/django/forms/forms.py', 'forms.py'),
('/django/forms/formsets.py', 'formsets.py'),
('/django/forms/models.py', 'models.py'),
+ ('/django/forms/renderers.py', 'renderers.py'),
('/django/forms/utils.py', 'utils.py'),
('/django/forms/widgets.py', 'widgets.py')
]
@@ -83,6 +85,7 @@ class FilePathFieldTest(SimpleTestCase):
('/django/forms/forms.py', 'forms.py'),
('/django/forms/formsets.py', 'formsets.py'),
('/django/forms/models.py', 'models.py'),
+ ('/django/forms/renderers.py', 'renderers.py'),
('/django/forms/utils.py', 'utils.py'),
('/django/forms/widgets.py', 'widgets.py')
]
diff --git a/tests/forms_tests/jinja2/forms_tests/custom_widget.html b/tests/forms_tests/jinja2/forms_tests/custom_widget.html
new file mode 100644
index 0000000000..c6e8abd4ad
--- /dev/null
+++ b/tests/forms_tests/jinja2/forms_tests/custom_widget.html
@@ -0,0 +1 @@
+<input type="text" name="custom">
diff --git a/tests/forms_tests/templates/forms_tests/custom_widget.html b/tests/forms_tests/templates/forms_tests/custom_widget.html
new file mode 100644
index 0000000000..c6e8abd4ad
--- /dev/null
+++ b/tests/forms_tests/templates/forms_tests/custom_widget.html
@@ -0,0 +1 @@
+<input type="text" name="custom">
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index 8e94d29463..4079be1235 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -17,6 +17,7 @@ from django.forms import (
SplitDateTimeField, SplitHiddenDateTimeWidget, Textarea, TextInput,
TimeField, ValidationError, forms,
)
+from django.forms.renderers import DjangoTemplates, get_default_renderer
from django.forms.utils import ErrorList
from django.http import QueryDict
from django.template import Context, Template
@@ -678,6 +679,50 @@ Java</label></li>
<div><label><input type="radio" name="name" value="ringo" required /> Ringo</label></div>"""
)
+ def test_form_with_iterable_boundfield_id(self):
+ class BeatleForm(Form):
+ name = ChoiceField(
+ choices=[('john', 'John'), ('paul', 'Paul'), ('george', 'George'), ('ringo', 'Ringo')],
+ widget=RadioSelect,
+ )
+ fields = list(BeatleForm()['name'])
+ self.assertEqual(len(fields), 4)
+
+ self.assertEqual(fields[0].id_for_label, 'id_name_0')
+ self.assertEqual(fields[0].choice_label, 'John')
+ self.assertHTMLEqual(
+ fields[0].tag(),
+ '<input type="radio" name="name" value="john" id="id_name_0" required />'
+ )
+ self.assertHTMLEqual(
+ str(fields[0]),
+ '<label for="id_name_0"><input type="radio" name="name" '
+ 'value="john" id="id_name_0" required /> John</label>'
+ )
+
+ self.assertEqual(fields[1].id_for_label, 'id_name_1')
+ self.assertEqual(fields[1].choice_label, 'Paul')
+ self.assertHTMLEqual(
+ fields[1].tag(),
+ '<input type="radio" name="name" value="paul" id="id_name_1" required />'
+ )
+ self.assertHTMLEqual(
+ str(fields[1]),
+ '<label for="id_name_1"><input type="radio" name="name" '
+ 'value="paul" id="id_name_1" required /> Paul</label>'
+ )
+
+ def test_iterable_boundfield_select(self):
+ class BeatleForm(Form):
+ name = ChoiceField(choices=[('john', 'John'), ('paul', 'Paul'), ('george', 'George'), ('ringo', 'Ringo')])
+ fields = list(BeatleForm(auto_id=False)['name'])
+ self.assertEqual(len(fields), 4)
+
+ self.assertEqual(fields[0].id_for_label, 'id_name_0')
+ self.assertEqual(fields[0].choice_label, 'John')
+ self.assertHTMLEqual(fields[0].tag(), '<option value="john">John</option>')
+ self.assertHTMLEqual(str(fields[0]), '<option value="john">John</option>')
+
def test_form_with_noniterable_boundfield(self):
# You can iterate over any BoundField, not just those with widget=RadioSelect.
class BeatleForm(Form):
@@ -1993,8 +2038,9 @@ Password: <input type="password" name="password" required /></li>
doesn't lose it's safe string status (#22950).
"""
class CustomWidget(TextInput):
- def render(self, name, value, attrs=None):
- return format_html(str('<input{} required />'), ' id=custom')
+ def render(self, name, value, attrs=None, choices=None,
+ renderer=None, extra_context=None):
+ return format_html(str('<input{} />'), ' id=custom')
class SampleForm(Form):
name = CharField(widget=CustomWidget)
@@ -3573,3 +3619,46 @@ Good luck picking a username that doesn&#39;t already exist.</p>
f = DataForm({'data': 'xyzzy'})
self.assertTrue(f.is_valid())
self.assertEqual(f.cleaned_data, {'data': 'xyzzy'})
+
+
+class CustomRenderer(DjangoTemplates):
+ pass
+
+
+class RendererTests(SimpleTestCase):
+
+ def test_default(self):
+ form = Form()
+ self.assertEqual(form.renderer, get_default_renderer())
+
+ def test_kwarg_instance(self):
+ custom = CustomRenderer()
+ form = Form(renderer=custom)
+ self.assertEqual(form.renderer, custom)
+
+ def test_kwarg_class(self):
+ custom = CustomRenderer()
+ form = Form(renderer=custom)
+ self.assertEqual(form.renderer, custom)
+
+ def test_attribute_instance(self):
+ class CustomForm(Form):
+ default_renderer = DjangoTemplates()
+
+ form = CustomForm()
+ self.assertEqual(form.renderer, CustomForm.default_renderer)
+
+ def test_attribute_class(self):
+ class CustomForm(Form):
+ default_renderer = CustomRenderer
+
+ form = CustomForm()
+ self.assertTrue(isinstance(form.renderer, CustomForm.default_renderer))
+
+ def test_attribute_override(self):
+ class CustomForm(Form):
+ default_renderer = DjangoTemplates()
+
+ custom = CustomRenderer()
+ form = CustomForm(renderer=custom)
+ self.assertEqual(form.renderer, custom)
diff --git a/tests/forms_tests/tests/test_renderers.py b/tests/forms_tests/tests/test_renderers.py
new file mode 100644
index 0000000000..7c37b6fd4e
--- /dev/null
+++ b/tests/forms_tests/tests/test_renderers.py
@@ -0,0 +1,52 @@
+import os
+import unittest
+
+from django.forms.renderers import (
+ BaseRenderer, DjangoTemplates, Jinja2, TemplatesSetting,
+)
+from django.test import SimpleTestCase
+from django.utils._os import upath
+
+try:
+ import jinja2
+except ImportError:
+ jinja2 = None
+
+
+class SharedTests(object):
+ expected_widget_dir = 'templates'
+
+ def test_installed_apps_template_found(self):
+ """Can find a custom template in INSTALLED_APPS."""
+ renderer = self.renderer()
+ # Found because forms_tests is .
+ tpl = renderer.get_template('forms_tests/custom_widget.html')
+ expected_path = os.path.abspath(
+ os.path.join(
+ upath(os.path.dirname(__file__)),
+ '..',
+ self.expected_widget_dir + '/forms_tests/custom_widget.html',
+ )
+ )
+ self.assertEqual(tpl.origin.name, expected_path)
+
+
+class BaseTemplateRendererTests(SimpleTestCase):
+
+ def test_get_renderer(self):
+ with self.assertRaisesMessage(NotImplementedError, 'subclasses must implement get_template()'):
+ BaseRenderer().get_template('')
+
+
+class DjangoTemplatesTests(SharedTests, SimpleTestCase):
+ renderer = DjangoTemplates
+
+
+@unittest.skipIf(jinja2 is None, 'jinja2 required')
+class Jinja2Tests(SharedTests, SimpleTestCase):
+ renderer = Jinja2
+ expected_widget_dir = 'jinja2'
+
+
+class TemplatesSettingTests(SharedTests, SimpleTestCase):
+ renderer = TemplatesSetting
diff --git a/tests/forms_tests/tests/test_widgets.py b/tests/forms_tests/tests/test_widgets.py
index 7ea2b35266..f67954fd3a 100644
--- a/tests/forms_tests/tests/test_widgets.py
+++ b/tests/forms_tests/tests/test_widgets.py
@@ -1,182 +1,12 @@
-# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib.admin.tests import AdminSeleniumTestCase
-from django.forms import (
- CheckboxSelectMultiple, ClearableFileInput, RadioSelect, TextInput,
-)
-from django.forms.widgets import (
- ChoiceFieldRenderer, ChoiceInput, RadioFieldRenderer,
-)
-from django.test import SimpleTestCase, override_settings
+from django.test import override_settings
from django.urls import reverse
-from django.utils import six
-from django.utils.encoding import force_text, python_2_unicode_compatible
-from django.utils.safestring import SafeData
from ..models import Article
-class FormsWidgetTests(SimpleTestCase):
-
- def test_radiofieldrenderer(self):
- # RadioSelect uses a RadioFieldRenderer to render the individual radio inputs.
- # You can manipulate that object directly to customize the way the RadioSelect
- # is rendered.
- w = RadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
- r = w.get_renderer('beatle', 'J')
- inp_set1 = []
- inp_set2 = []
- inp_set3 = []
- inp_set4 = []
-
- for inp in r:
- inp_set1.append(str(inp))
- inp_set2.append('%s<br />' % inp)
- inp_set3.append('<p>%s %s</p>' % (inp.tag(), inp.choice_label))
- inp_set4.append(
- '%s %s %s %s %s' % (
- inp.name,
- inp.value,
- inp.choice_value,
- inp.choice_label,
- inp.is_checked(),
- )
- )
-
- self.assertHTMLEqual('\n'.join(inp_set1), """<label><input checked type="radio" name="beatle" value="J" /> John</label>
-<label><input type="radio" name="beatle" value="P" /> Paul</label>
-<label><input type="radio" name="beatle" value="G" /> George</label>
-<label><input type="radio" name="beatle" value="R" /> Ringo</label>""")
- self.assertHTMLEqual('\n'.join(inp_set2), """<label><input checked type="radio" name="beatle" value="J" /> John</label><br />
-<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
-<label><input type="radio" name="beatle" value="G" /> George</label><br />
-<label><input type="radio" name="beatle" value="R" /> Ringo</label><br />""")
- self.assertHTMLEqual('\n'.join(inp_set3), """<p><input checked type="radio" name="beatle" value="J" /> John</p>
-<p><input type="radio" name="beatle" value="P" /> Paul</p>
-<p><input type="radio" name="beatle" value="G" /> George</p>
-<p><input type="radio" name="beatle" value="R" /> Ringo</p>""")
- self.assertHTMLEqual('\n'.join(inp_set4), """beatle J J John True
-beatle J P Paul False
-beatle J G George False
-beatle J R Ringo False""")
-
- # A RadioFieldRenderer object also allows index access to individual RadioChoiceInput
- w = RadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
- r = w.get_renderer('beatle', 'J')
- self.assertHTMLEqual(str(r[1]), '<label><input type="radio" name="beatle" value="P" /> Paul</label>')
- self.assertHTMLEqual(
- str(r[0]),
- '<label><input checked type="radio" name="beatle" value="J" /> John</label>'
- )
- self.assertTrue(r[0].is_checked())
- self.assertFalse(r[1].is_checked())
- self.assertEqual((r[1].name, r[1].value, r[1].choice_value, r[1].choice_label), ('beatle', 'J', 'P', 'Paul'))
-
- # These individual widgets can accept extra attributes if manually rendered.
- self.assertHTMLEqual(
- r[1].render(attrs={'extra': 'value'}),
- '<label><input type="radio" extra="value" name="beatle" value="P" /> Paul</label>'
- )
-
- with self.assertRaises(IndexError):
- r[10]
-
- # You can create your own custom renderers for RadioSelect to use.
- class MyRenderer(RadioFieldRenderer):
- def render(self):
- return '<br />\n'.join(six.text_type(choice) for choice in self)
- w = RadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), renderer=MyRenderer)
- self.assertHTMLEqual(
- w.render('beatle', 'G'),
- """<label><input type="radio" name="beatle" value="J" /> John</label><br />
-<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
-<label><input checked type="radio" name="beatle" value="G" /> George</label><br />
-<label><input type="radio" name="beatle" value="R" /> Ringo</label>"""
- )
-
- # Or you can use custom RadioSelect fields that use your custom renderer.
- class CustomRadioSelect(RadioSelect):
- renderer = MyRenderer
- w = CustomRadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
- self.assertHTMLEqual(
- w.render('beatle', 'G'),
- """<label><input type="radio" name="beatle" value="J" /> John</label><br />
-<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
-<label><input checked type="radio" name="beatle" value="G" /> George</label><br />
-<label><input type="radio" name="beatle" value="R" /> Ringo</label>"""
- )
-
- # You can customize rendering with outer_html/inner_html renderer variables (#22950)
- class MyRenderer(RadioFieldRenderer):
- # str is just to test some Python 2 issue with bytestrings
- outer_html = str('<div{id_attr}>{content}</div>')
- inner_html = '<p>{choice_value}{sub_widgets}</p>'
- w = RadioSelect(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), renderer=MyRenderer)
- output = w.render('beatle', 'J', attrs={'id': 'bar'})
- self.assertIsInstance(output, SafeData)
- self.assertHTMLEqual(
- output,
- """<div id="bar">
-<p><label for="bar_0"><input checked type="radio" id="bar_0" value="J" name="beatle" /> John</label></p>
-<p><label for="bar_1"><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></p>
-<p><label for="bar_2"><input type="radio" id="bar_2" value="G" name="beatle" /> George</label></p>
-<p><label for="bar_3"><input type="radio" id="bar_3" value="R" name="beatle" /> Ringo</label></p>
-</div>""")
-
- def test_subwidget(self):
- # Each subwidget tag gets a separate ID when the widget has an ID specified
- self.assertHTMLEqual(
- "\n".join(
- c.tag() for c in CheckboxSelectMultiple(
- attrs={'id': 'abc'},
- choices=zip('abc', 'ABC')
- ).subwidgets('letters', list('ac'))
- ),
- """<input checked type="checkbox" name="letters" value="a" id="abc_0" />
-<input type="checkbox" name="letters" value="b" id="abc_1" />
-<input checked type="checkbox" name="letters" value="c" id="abc_2" />""")
-
- # Each subwidget tag does not get an ID if the widget does not have an ID specified
- self.assertHTMLEqual(
- "\n".join(c.tag() for c in CheckboxSelectMultiple(
- choices=zip('abc', 'ABC'),
- ).subwidgets('letters', list('ac'))),
- """<input checked type="checkbox" name="letters" value="a" />
-<input type="checkbox" name="letters" value="b" />
-<input checked type="checkbox" name="letters" value="c" />""")
-
- # The id_for_label property of the subwidget should return the ID that is used on the subwidget's tag
- self.assertHTMLEqual(
- "\n".join(
- '<input type="checkbox" name="letters" value="%s" id="%s" />'
- % (c.choice_value, c.id_for_label) for c in CheckboxSelectMultiple(
- attrs={'id': 'abc'},
- choices=zip('abc', 'ABC'),
- ).subwidgets('letters', [])
- ),
- """<input type="checkbox" name="letters" value="a" id="abc_0" />
-<input type="checkbox" name="letters" value="b" id="abc_1" />
-<input type="checkbox" name="letters" value="c" id="abc_2" />""")
-
- def test_sub_widget_html_safe(self):
- widget = TextInput()
- subwidget = next(widget.subwidgets('username', 'John Doe'))
- self.assertTrue(hasattr(subwidget, '__html__'))
- self.assertEqual(force_text(subwidget), subwidget.__html__())
-
- def test_choice_input_html_safe(self):
- widget = ChoiceInput('choices', 'CHOICE1', {}, ('CHOICE1', 'first choice'), 0)
- self.assertTrue(hasattr(ChoiceInput, '__html__'))
- self.assertEqual(force_text(widget), widget.__html__())
-
- def test_choice_field_renderer_html_safe(self):
- renderer = ChoiceFieldRenderer('choices', 'CHOICE1', {}, [('CHOICE1', 'first_choice')])
- renderer.choice_input_class = lambda *args: args
- self.assertTrue(hasattr(ChoiceFieldRenderer, '__html__'))
- self.assertEqual(force_text(renderer), renderer.__html__())
-
-
@override_settings(ROOT_URLCONF='forms_tests.urls')
class LiveWidgetTests(AdminSeleniumTestCase):
@@ -190,33 +20,4 @@ class LiveWidgetTests(AdminSeleniumTestCase):
self.selenium.get(self.live_server_url + reverse('article_form', args=[article.pk]))
self.selenium.find_element_by_id('submit').submit()
article = Article.objects.get(pk=article.pk)
- # Should be "\nTst\n" after #19251 is fixed
self.assertEqual(article.content, "\r\nTst\r\n")
-
-
-@python_2_unicode_compatible
-class FakeFieldFile(object):
- """
- Quacks like a FieldFile (has a .url and unicode representation), but
- doesn't require us to care about storages etc.
- """
- url = 'something'
-
- def __str__(self):
- return self.url
-
-
-class ClearableFileInputTests(SimpleTestCase):
-
- def test_render_custom_template(self):
- widget = ClearableFileInput()
- widget.template_with_initial = (
- '%(initial_text)s: <img src="%(initial_url)s" alt="%(initial)s" /> '
- '%(clear_template)s<br />%(input_text)s: %(input)s'
- )
- self.assertHTMLEqual(
- widget.render('myfile', FakeFieldFile()),
- 'Currently: <img src="something" alt="something" /> '
- '<input type="checkbox" name="myfile-clear" id="myfile-clear_id" /> '
- '<label for="myfile-clear_id">Clear</label><br />Change: <input type="file" name="myfile" />'
- )
diff --git a/tests/forms_tests/widget_tests/base.py b/tests/forms_tests/widget_tests/base.py
index 3127cb85f2..dbbea25d7a 100644
--- a/tests/forms_tests/widget_tests/base.py
+++ b/tests/forms_tests/widget_tests/base.py
@@ -1,9 +1,27 @@
+from django.forms.renderers import DjangoTemplates, Jinja2
from django.test import SimpleTestCase
+try:
+ import jinja2
+except ImportError:
+ jinja2 = None
+
class WidgetTest(SimpleTestCase):
beatles = (('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))
+ @classmethod
+ def setUpClass(cls):
+ cls.django_renderer = DjangoTemplates()
+ cls.jinja2_renderer = Jinja2() if jinja2 else None
+ cls.renderers = [cls.django_renderer] + ([cls.jinja2_renderer] if cls.jinja2_renderer else [])
+ super(WidgetTest, cls).setUpClass()
+
def check_html(self, widget, name, value, html='', attrs=None, **kwargs):
- output = widget.render(name, value, attrs=attrs, **kwargs)
+ if self.jinja2_renderer:
+ output = widget.render(name, value, attrs=attrs, renderer=self.jinja2_renderer, **kwargs)
+ # Django escapes quotes with '&quot;' while Jinja2 uses '&#34;'.
+ self.assertHTMLEqual(output.replace('&#34;', '&quot;'), html)
+
+ output = widget.render(name, value, attrs=attrs, renderer=self.django_renderer, **kwargs)
self.assertHTMLEqual(output, html)
diff --git a/tests/forms_tests/widget_tests/test_select.py b/tests/forms_tests/widget_tests/test_select.py
index dde84c476f..4b717b6043 100644
--- a/tests/forms_tests/widget_tests/test_select.py
+++ b/tests/forms_tests/widget_tests/test_select.py
@@ -221,6 +221,68 @@ class SelectTest(WidgetTest):
</select>"""
))
+ 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.assertEqual(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.assertEqual(options[1]['selected'], False)
+
+ def test_optgroups(self):
+ choices = [
+ ('Audio', [
+ ('vinyl', 'Vinyl'),
+ ('cd', 'CD'),
+ ]),
+ ('Video', [
+ ('vhs', 'VHS Tape'),
+ ('dvd', 'DVD'),
+ ]),
+ ('unknown', 'Unknown'),
+ ]
+ groups = list(self.widget(choices=choices).optgroups(
+ 'name', ['vhs'], attrs={'class': 'super'},
+ ))
+ self.assertEqual(len(groups), 3)
+ self.assertEqual(groups[0][0], None)
+ self.assertEqual(groups[0][2], 0)
+ self.assertEqual(len(groups[0][1]), 1)
+ options = groups[0][1]
+ self.assertEqual(options[0]['name'], 'name')
+ self.assertEqual(options[0]['value'], 'unknown')
+ self.assertEqual(options[0]['label'], 'Unknown')
+ self.assertEqual(options[0]['index'], '0')
+ self.assertEqual(options[0]['selected'], False)
+ self.assertEqual(groups[1][0], 'Audio')
+ self.assertEqual(groups[1][2], 1)
+ self.assertEqual(len(groups[1][1]), 2)
+ options = groups[1][1]
+ self.assertEqual(options[0]['name'], 'name')
+ self.assertEqual(options[0]['value'], 'vinyl')
+ self.assertEqual(options[0]['label'], 'Vinyl')
+ self.assertEqual(options[0]['index'], '1_0')
+ self.assertEqual(options[1]['index'], '1_1')
+ self.assertEqual(groups[2][0], 'Video')
+ self.assertEqual(groups[2][2], 2)
+ self.assertEqual(len(groups[2][1]), 2)
+ options = groups[2][1]
+ self.assertEqual(options[0]['name'], 'name')
+ self.assertEqual(options[0]['value'], 'vhs')
+ self.assertEqual(options[0]['label'], 'VHS Tape')
+ self.assertEqual(options[0]['index'], '2_0')
+ self.assertEqual(options[0]['selected'], True)
+ self.assertEqual(options[1]['index'], '2_1')
+
def test_deepcopy(self):
"""
__deepcopy__() should copy all attributes properly (#25085).