示例#1
0
    def _build_app_with_groups(self, factory):
        module1, form1 = factory.new_basic_module('open_case', 'household')
        form1_builder = XFormBuilder(form1.name)

        # question 0
        form1_builder.new_question('name', 'Name')

        # question 1 (a group)
        form1_builder.new_group('demographics', 'Demographics')
        # question 2 (a question in a group)
        form1_builder.new_question('age', 'Age', group='demographics')
        # question 3 (a question that has a load property)
        form1_builder.new_question('polar_bears_seen',
                                   'Number of polar bears seen')

        form1.source = form1_builder.tostring(
            pretty_print=True).decode('utf-8')
        factory.form_requires_case(form1,
                                   case_type='household',
                                   update={
                                       'name': '/data/name',
                                       'age': '/data/demographics/age',
                                   },
                                   preload={
                                       '/data/polar_bears_seen':
                                       'polar_bears_seen',
                                   })
        factory.app.save()
    def test_form_data_with_case_properties(self):
        factory = AppFactory()
        app = factory.app
        module1, form1 = factory.new_basic_module('open_case', 'household')
        form1_builder = XFormBuilder(form1.name)

        # question 0
        form1_builder.new_question('name', 'Name')

        # question 1 (a group)
        form1_builder.new_group('demographics', 'Demographics')
        # question 2 (a question in a group)
        form1_builder.new_question('age', 'Age', group='demographics')

        # question 3 (a question that has a load property)
        form1_builder.new_question('polar_bears_seen',
                                   'Number of polar bears seen')

        form1.source = form1_builder.tostring(
            pretty_print=True).decode('utf-8')
        factory.form_requires_case(form1,
                                   case_type='household',
                                   update={
                                       'name': '/data/name',
                                       'age': '/data/demographics/age',
                                   },
                                   preload={
                                       '/data/polar_bears_seen':
                                       'polar_bears_seen',
                                   })
        app.save()

        modules, errors = get_app_summary_formdata(app.domain, app)

        q1_saves = modules[0]['forms'][0]['questions'][0]['save_properties'][0]
        self.assertEqual(q1_saves.case_type, 'household')
        self.assertEqual(q1_saves.property, 'name')

        group_saves = modules[0]['forms'][0]['questions'][2][
            'save_properties'][0]
        self.assertEqual(group_saves.case_type, 'household')
        self.assertEqual(group_saves.property, 'age')

        q3_loads = modules[0]['forms'][0]['questions'][3]['load_properties'][0]
        self.assertEqual(q3_loads.case_type, 'household')
        self.assertEqual(q3_loads.property, 'polar_bears_seen')
示例#3
0
 def build_xform(self):
     xform = XFormBuilder(self.name)
     for ig in self.iter_item_groups():
         data_type = 'repeatGroup' if self.is_repeating else 'group'
         group = xform.new_group(ig.question_name, ig.question_label, data_type)
         for item in ig.iter_items():
             group.new_question(item.question_name, item.question_label, ODK_DATA_TYPES[item.data_type],
                                choices=item.choices)
     return xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
示例#4
0
    def _build_app_with_groups(self, factory):
        module1, form1 = factory.new_basic_module('open_case', 'household')
        form1_builder = XFormBuilder(form1.name)

        # question 0
        form1_builder.new_question('name', 'Name')

        # question 1 (a group)
        form1_builder.new_group('demographics', 'Demographics')
        # question 2 (a question in a group)
        form1_builder.new_question('age', 'Age', group='demographics')
        # question 3 (a question that has a load property)
        form1_builder.new_question('polar_bears_seen', 'Number of polar bears seen')

        form1.source = form1_builder.tostring(pretty_print=True).decode('utf-8')
        factory.form_requires_case(form1, case_type='household', update={
            'name': '/data/name',
            'age': '/data/demographics/age',
        }, preload={
            '/data/polar_bears_seen': 'polar_bears_seen',
        })
        factory.app.save()
示例#5
0
 def _add_question_to_group(self, form, options):
     builder = XFormBuilder(form.name, form.source if form.source else None)
     group_name = options['group']
     builder.new_group(group_name, group_name)
     builder.new_question(**options)
     form.source = builder.tostring(pretty_print=True).decode('utf-8')
示例#6
0
class XFormBuilderTests(SimpleTestCase, TestXmlMixin):
    file_path = ('data', 'xform_builder')

    def setUp(self):
        self.xform = XFormBuilder()

    def replace_xmlns(self, xml, xmlns):
        xmlns = xmlns.encode('utf-8')
        return re.sub(br'http://openrosa\.org/formdesigner/[\w-]{36}', xmlns,
                      xml)

    def test_new_question_group(self):
        """
        XFormBuilder.new_question should be able to add a group
        """
        self.xform.new_question('personal',
                                'Personal Questions',
                                data_type='group')
        self.xform.new_question('name', 'What is your name?', group='personal')
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('group'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))

    def test_new_question_repeat_group(self):
        num_names = self.xform.new_question('num_names',
                                            'How many names do you have?',
                                            data_type='int')
        self.xform.new_question('personal',
                                'Personal Questions',
                                data_type='repeatGroup',
                                repeat_count=num_names)
        self.xform.new_question(
            'name',
            'What is your <output value="ordinal(position(..) + 1)" /> name?',
            group='personal',
            label_safe=True)
        # Yes, that was plug for an ordinal function. cf. UserVoice:
        # https://dimagi.uservoice.com/forums/176376-form-builder/suggestions/10610517--ordinal-function
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('repeat_group'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))

    def test_new_group_group(self):
        personal = self.xform.new_group('personal', 'Personal Questions')
        personal.new_question('name', 'What is your name?')
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('group'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))

    def test_new_group_repeat_group(self):
        num_names = self.xform.new_question('num_names',
                                            'How many names do you have?',
                                            data_type='int')
        personal = self.xform.new_group('personal',
                                        'Personal Questions',
                                        data_type='repeatGroup',
                                        repeat_count=num_names)
        personal.new_question(
            'name',
            'What is your <output value="ordinal(position(..) + 1)" /> name?',
            label_safe=True)
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('repeat_group'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))

    def test_unicode(self):
        self.xform.new_question('name', 'သင့်နာမည်ဘယ်လိုခေါ်လဲ?'
                                )  # ("What is your name?" in Myanmar/Burmese)
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('unicode'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))

    def test_select_question(self):
        self.xform.new_question('fav_colors',
                                'What are your favorite colors?',
                                data_type='select',
                                choices=OrderedDict([
                                    ('r', 'Red'),
                                    ('o', 'Orange'),
                                    ('y', 'Yellow'),
                                    ('g', 'Green'),
                                    ('b', 'Blue'),
                                    ('i', 'Indigo'),
                                    ('v', 'Violet'),
                                ]))
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('select_question'),
                               self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))

    def test_select1_question(self):
        self.xform.new_question('you_aint_been_blue',
                                'What kind of blue are you?',
                                data_type='select1',
                                choices={
                                    1: 'Blue',
                                    2: 'Indigo',
                                    3: 'Black',
                                })
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('select1_question'),
                               self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))

    def test_data_types(self):
        self.xform.new_question('name', 'Child name')
        self.xform.new_question('dob', 'Child date of birth', 'date')
        self.xform.new_question('with_mother',
                                'Does child live with mother?',
                                'boolean',
                                value='true')
        self.xform.new_question('height', 'Child height (cm)', 'int')
        self.xform.new_question('weight', 'Child weight (metric tonnes)',
                                'decimal')
        self.xform.new_question('time', 'Arrival time', 'time')
        self.xform.new_question('now', 'Current timestamp', 'dateTime')
        self.xform.new_question(
            'mothers_name',
            None,
            None,  # Hidden values have no data type
            calculate="concat('Jane', ' ', 'Smith')")
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('data_types'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))

    def test_xform_title(self):
        self.xform = XFormBuilder('Built by XFormBuilder')
        self.xform.new_question('name', 'What is your name?')
        group = self.xform.new_group('personal', 'Personal Questions')
        group.new_question('fav_color',
                           'Quelle est ta couleur préférée?',
                           choices=OrderedDict([('r', 'Rot'), ('b', 'Blau'),
                                                ('g', 'Grün')]))
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('xform_title'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))

    def test_question_params(self):
        self.xform = XFormBuilder('Built by XFormBuilder')
        params = {
            'constraint': ". != 'Ford Prefect'",
            'jr:constraintMsg':
            'That name is not as inconspicuous as you think.'
        }
        self.xform.new_question('name', 'What is your name?', **params)
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('question_params'),
                               self.xform.xmlns),
            self.xform.tostring(pretty_print=True,
                                encoding='utf-8',
                                xml_declaration=True))
    def test_get_data_cleaning_data(self, questions_patch):
        builder = XFormBuilder()
        responses = OrderedDict()

        # Simple question
        builder.new_question('something', 'Something')
        responses['something'] = 'blue'

        # Skipped question - doesn't appear in repsonses, shouldn't appear in data cleaning data
        builder.new_question('skip', 'Skip me')

        # Simple group
        lights = builder.new_group('lights',
                                   'Traffic Lights',
                                   data_type='group')
        lights.new_question('red', 'Red means')
        lights.new_question('green', 'Green means')
        responses['lights'] = OrderedDict([('red', 'stop'), ('green', 'go')])

        # Simple repeat group, one response
        one_hit_wonders = builder.new_group('one_hit_wonders',
                                            'One-Hit Wonders',
                                            data_type='repeatGroup')
        one_hit_wonders.new_question('name', 'Name')
        responses['one_hit_wonders'] = [
            {
                'name': 'A-Ha'
            },
        ]

        # Simple repeat group, multiple responses
        snacks = builder.new_group('snacks', 'Snacks', data_type='repeatGroup')
        snacks.new_question('kind_of_snack', 'Kind of snack')
        responses['snacks'] = [
            {
                'kind_of_snack': 'samosa'
            },
            {
                'kind_of_snack': 'pakora'
            },
        ]

        # Repeat group with nested group
        cups = builder.new_group('cups_of_tea',
                                 'Cups of tea',
                                 data_type='repeatGroup')
        details = cups.new_group('details_of_cup',
                                 'Details',
                                 data_type='group')
        details.new_question('kind_of_cup', 'Flavor')
        details.new_question('secret', 'Secret', data_type=None)
        responses['cups_of_tea'] = [
            {
                'details_of_cup': {
                    'kind_of_cup': 'green',
                    'secret': 'g'
                }
            },
            {
                'details_of_cup': {
                    'kind_of_cup': 'black',
                    'secret': 'b'
                }
            },
            {
                'details_of_cup': {
                    'kind_of_cup': 'more green',
                    'secret': 'mg'
                }
            },
        ]

        xform = XForm(builder.tostring())
        questions_patch.return_value = get_questions_from_xform_node(
            xform, ['en'])
        xml = FormSubmissionBuilder(form_id='123',
                                    form_properties=responses).as_xml_string()
        submitted_xform = submit_form_locally(xml, self.domain).xform
        form_data, _ = get_readable_data_for_submission(submitted_xform)
        question_response_map, ordered_question_values = get_data_cleaning_data(
            form_data, submitted_xform)

        expected_question_values = [
            '/data/something',
            '/data/lights/red',
            '/data/lights/green',
            '/data/one_hit_wonders/name',
            '/data/snacks[1]/kind_of_snack',
            '/data/snacks[2]/kind_of_snack',
            '/data/cups_of_tea[1]/details_of_cup/kind_of_cup',
            '/data/cups_of_tea[1]/details_of_cup/secret',
            '/data/cups_of_tea[2]/details_of_cup/kind_of_cup',
            '/data/cups_of_tea[2]/details_of_cup/secret',
            '/data/cups_of_tea[3]/details_of_cup/kind_of_cup',
            '/data/cups_of_tea[3]/details_of_cup/secret',
        ]
        self.assertListEqual(ordered_question_values, expected_question_values)

        expected_response_map = {
            '/data/something': 'blue',
            '/data/lights/red': 'stop',
            '/data/lights/green': 'go',
            '/data/one_hit_wonders/name': 'A-Ha',
            '/data/snacks[1]/kind_of_snack': 'samosa',
            '/data/snacks[2]/kind_of_snack': 'pakora',
            '/data/cups_of_tea[1]/details_of_cup/kind_of_cup': 'green',
            '/data/cups_of_tea[1]/details_of_cup/secret': 'g',
            '/data/cups_of_tea[2]/details_of_cup/kind_of_cup': 'black',
            '/data/cups_of_tea[2]/details_of_cup/secret': 'b',
            '/data/cups_of_tea[3]/details_of_cup/kind_of_cup': 'more green',
            '/data/cups_of_tea[3]/details_of_cup/secret': 'mg',
        }
        self.assertDictEqual(
            {k: v['value']
             for k, v in question_response_map.items()}, expected_response_map)
class XFormBuilderTests(SimpleTestCase, TestXmlMixin):
    file_path = ('data', 'xform_builder')

    def setUp(self):
        self.xform = XFormBuilder()

    def replace_xmlns(self, xml, xmlns):
        return re.sub(r'http://openrosa\.org/formdesigner/[\w-]{36}', xmlns, xml)

    def test_new_question_group(self):
        """
        XFormBuilder.new_question should be able to add a group
        """
        self.xform.new_question('personal', 'Personal Questions', data_type='group')
        self.xform.new_question('name', 'What is your name?', group='personal')
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('group'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )

    def test_new_question_repeat_group(self):
        num_names = self.xform.new_question('num_names', 'How many names do you have?', data_type='int')
        self.xform.new_question('personal', 'Personal Questions', data_type='repeatGroup',
                                repeat_count=num_names)
        self.xform.new_question('name', 'What is your <output value="ordinal(position(..) + 1)" /> name?',
                                group='personal', label_safe=True)
        # Yes, that was plug for an ordinal function. cf. UserVoice:
        # https://dimagi.uservoice.com/forums/176376-form-builder/suggestions/10610517--ordinal-function
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('repeat_group'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )

    def test_new_group_group(self):
        personal = self.xform.new_group('personal', 'Personal Questions')
        personal.new_question('name', 'What is your name?')
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('group'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )

    def test_new_group_repeat_group(self):
        num_names = self.xform.new_question('num_names', 'How many names do you have?', data_type='int')
        personal = self.xform.new_group('personal', 'Personal Questions', data_type='repeatGroup',
                                        repeat_count=num_names)
        personal.new_question('name', 'What is your <output value="ordinal(position(..) + 1)" /> name?',
                              label_safe=True)
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('repeat_group'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )

    def test_unicode(self):
        self.xform.new_question('name', u'သင့်နာမည်ဘယ်လိုခေါ်လဲ?')  # ("What is your name?" in Myanmar/Burmese)
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('unicode'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )

    def test_select_question(self):
        self.xform.new_question('fav_colors', 'What are your favorite colors?', data_type='select', choices={
            'r': 'Red',
            'o': 'Orange',
            'y': 'Yellow',
            'g': 'Green',
            'b': 'Blue',
            'i': 'Indigo',
            'v': 'Violet',
        })
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('select_question'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )

    def test_select1_question(self):
        self.xform.new_question('you_aint_been_blue', 'What kind of blue are you?', data_type='select1', choices={
            1: 'Blue',
            2: 'Indigo',
            3: 'Black',
        })
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('select1_question'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )

    def test_data_types(self):
        self.xform.new_question('name', 'Child name')
        self.xform.new_question('dob', 'Child date of birth', 'date')
        self.xform.new_question('with_mother', 'Does child live with mother?', 'boolean',
                                value='true')
        self.xform.new_question('height', 'Child height (cm)', 'int')
        self.xform.new_question('weight', 'Child weight (metric tonnes)', 'decimal')
        self.xform.new_question('time', 'Arrival time', 'time')
        self.xform.new_question('now', 'Current timestamp', 'dateTime')
        self.xform.new_question('mothers_name', None, None,  # Hidden values have no data type
                                calculate="concat('Jane', ' ', 'Smith')")
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('data_types'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )

    def test_xform_title(self):
        self.xform = XFormBuilder('Built by XFormBuilder')
        self.xform.new_question('name', 'What is your name?')
        group = self.xform.new_group('personal', 'Personal Questions')
        group.new_question('fav_color', u'Quelle est ta couleur préférée?',
                           choices={'r': 'Rot', 'g': u'Grün', 'b': 'Blau'})
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('xform_title'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )

    def test_question_params(self):
        self.xform = XFormBuilder('Built by XFormBuilder')
        params = {
            'constraint': ". != 'Ford Prefect'",
            'jr:constraintMsg': 'That name is not as inconspicuous as you think.'
        }
        self.xform.new_question('name', 'What is your name?', **params)
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml('question_params'), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding='utf-8', xml_declaration=True)
        )
    def test_get_data_cleaning_data(self, questions_patch):
        builder = XFormBuilder()
        responses = OrderedDict()

        # Simple question
        builder.new_question('something', 'Something')
        responses['something'] = 'blue'

        # Skipped question - doesn't appear in repsonses, shouldn't appear in data cleaning data
        builder.new_question('skip', 'Skip me')

        # Simple group
        lights = builder.new_group('lights', 'Traffic Lights', data_type='group')
        lights.new_question('red', 'Red means')
        lights.new_question('green', 'Green means')
        responses['lights'] = OrderedDict([('red', 'stop'), ('green', 'go')])

        # Simple repeat group, one response
        one_hit_wonders = builder.new_group('one_hit_wonders', 'One-Hit Wonders', data_type='repeatGroup')
        one_hit_wonders.new_question('name', 'Name')
        responses['one_hit_wonders'] = [
            {'name': 'A-Ha'},
        ]

        # Simple repeat group, multiple responses
        snacks = builder.new_group('snacks', 'Snacks', data_type='repeatGroup')
        snacks.new_question('kind_of_snack', 'Kind of snack')
        responses['snacks'] = [
            {'kind_of_snack': 'samosa'},
            {'kind_of_snack': 'pakora'},
        ]

        # Repeat group with nested group
        cups = builder.new_group('cups_of_tea', 'Cups of tea', data_type='repeatGroup')
        details = cups.new_group('details_of_cup', 'Details', data_type='group')
        details.new_question('kind_of_cup', 'Flavor')
        details.new_question('secret', 'Secret', data_type=None)
        responses['cups_of_tea'] = [
            {'details_of_cup': {'kind_of_cup': 'green', 'secret': 'g'}},
            {'details_of_cup': {'kind_of_cup': 'black', 'secret': 'b'}},
            {'details_of_cup': {'kind_of_cup': 'more green', 'secret': 'mg'}},
        ]

        xform = XForm(builder.tostring())
        questions_patch.return_value = get_questions_from_xform_node(xform, ['en'])
        xml = FormSubmissionBuilder(form_id='123', form_properties=responses).as_xml_string()
        submitted_xform = submit_form_locally(xml, self.domain).xform
        form_data, _ = get_readable_data_for_submission(submitted_xform)
        question_response_map, ordered_question_values = get_data_cleaning_data(form_data, submitted_xform)

        expected_question_values = [
            '/data/something',
            '/data/lights/red',
            '/data/lights/green',
            '/data/one_hit_wonders/name',
            '/data/snacks[1]/kind_of_snack',
            '/data/snacks[2]/kind_of_snack',
            '/data/cups_of_tea[1]/details_of_cup/kind_of_cup',
            '/data/cups_of_tea[1]/details_of_cup/secret',
            '/data/cups_of_tea[2]/details_of_cup/kind_of_cup',
            '/data/cups_of_tea[2]/details_of_cup/secret',
            '/data/cups_of_tea[3]/details_of_cup/kind_of_cup',
            '/data/cups_of_tea[3]/details_of_cup/secret',
        ]
        self.assertListEqual(ordered_question_values, expected_question_values)

        expected_response_map = {
            '/data/something': 'blue',
            '/data/lights/red': 'stop',
            '/data/lights/green': 'go',
            '/data/one_hit_wonders/name': 'A-Ha',
            '/data/snacks[1]/kind_of_snack': 'samosa',
            '/data/snacks[2]/kind_of_snack': 'pakora',
            '/data/cups_of_tea[1]/details_of_cup/kind_of_cup': 'green',
            '/data/cups_of_tea[1]/details_of_cup/secret': 'g',
            '/data/cups_of_tea[2]/details_of_cup/kind_of_cup': 'black',
            '/data/cups_of_tea[2]/details_of_cup/secret': 'b',
            '/data/cups_of_tea[3]/details_of_cup/kind_of_cup': 'more green',
            '/data/cups_of_tea[3]/details_of_cup/secret': 'mg',
        }
        self.assertDictEqual({k: v['value'] for k, v in question_response_map.items()}, expected_response_map)
示例#10
0
 def _add_question_to_group(self, form, options):
     builder = XFormBuilder(form.name, form.source if form.source else None)
     group_name = options['group']
     builder.new_group(group_name, group_name)
     builder.new_question(**options)
     form.source = builder.tostring(pretty_print=True).decode('utf-8')
示例#11
0
class XFormBuilderTests(SimpleTestCase, TestXmlMixin):
    file_path = ("data", "xform_builder")

    def setUp(self):
        self.xform = XFormBuilder()

    def replace_xmlns(self, xml, xmlns):
        return re.sub(r"http://openrosa\.org/formdesigner/[\w-]{36}", xmlns, xml)

    def test_new_question_group(self):
        """
        XFormBuilder.new_question should be able to add a group
        """
        self.xform.new_question("personal", "Personal Questions", data_type="group")
        self.xform.new_question("name", "What is your name?", group="personal")
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("group"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )

    def test_new_question_repeat_group(self):
        num_names = self.xform.new_question("num_names", "How many names do you have?", data_type="int")
        self.xform.new_question("personal", "Personal Questions", data_type="repeatGroup", repeat_count=num_names)
        self.xform.new_question(
            "name", 'What is your <output value="ordinal(position(..) + 1)" /> name?', group="personal", label_safe=True
        )
        # Yes, that was plug for an ordinal function. cf. UserVoice:
        # https://dimagi.uservoice.com/forums/176376-form-builder/suggestions/10610517--ordinal-function
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("repeat_group"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )

    def test_new_group_group(self):
        personal = self.xform.new_group("personal", "Personal Questions")
        personal.new_question("name", "What is your name?")
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("group"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )

    def test_new_group_repeat_group(self):
        num_names = self.xform.new_question("num_names", "How many names do you have?", data_type="int")
        personal = self.xform.new_group(
            "personal", "Personal Questions", data_type="repeatGroup", repeat_count=num_names
        )
        personal.new_question(
            "name", 'What is your <output value="ordinal(position(..) + 1)" /> name?', label_safe=True
        )
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("repeat_group"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )

    def test_unicode(self):
        self.xform.new_question("name", u"သင့်နာမည်ဘယ်လိုခေါ်လဲ?")  # ("What is your name?" in Myanmar/Burmese)
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("unicode"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )

    def test_select_question(self):
        self.xform.new_question(
            "fav_colors",
            "What are your favorite colors?",
            data_type="select",
            choices={"r": "Red", "o": "Orange", "y": "Yellow", "g": "Green", "b": "Blue", "i": "Indigo", "v": "Violet"},
        )
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("select_question"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )

    def test_select1_question(self):
        self.xform.new_question(
            "you_aint_been_blue",
            "What kind of blue are you?",
            data_type="select1",
            choices={1: "Blue", 2: "Indigo", 3: "Black"},
        )
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("select1_question"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )

    def test_data_types(self):
        self.xform.new_question("name", "Child name")
        self.xform.new_question("dob", "Child date of birth", "date")
        self.xform.new_question("with_mother", "Does child live with mother?", "boolean", value="true")
        self.xform.new_question("height", "Child height (cm)", "int")
        self.xform.new_question("weight", "Child weight (metric tonnes)", "decimal")
        self.xform.new_question("time", "Arrival time", "time")
        self.xform.new_question("now", "Current timestamp", "dateTime")
        self.xform.new_question(
            "mothers_name", None, None, calculate="concat('Jane', ' ', 'Smith')"  # Hidden values have no data type
        )
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("data_types"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )

    def test_xform_title(self):
        self.xform = XFormBuilder("Built by XFormBuilder")
        self.xform.new_question("name", "What is your name?")
        group = self.xform.new_group("personal", "Personal Questions")
        group.new_question(
            "fav_color", u"Quelle est ta couleur préférée?", choices={"r": "Rot", "g": u"Grün", "b": "Blau"}
        )
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("xform_title"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )

    def test_question_params(self):
        self.xform = XFormBuilder("Built by XFormBuilder")
        params = {
            "constraint": ". != 'Ford Prefect'",
            "jr:constraintMsg": "That name is not as inconspicuous as you think.",
        }
        self.xform.new_question("name", "What is your name?", **params)
        self.assertXmlEqual(
            self.replace_xmlns(self.get_xml("question_params"), self.xform.xmlns),
            self.xform.tostring(pretty_print=True, encoding="utf-8", xml_declaration=True),
        )