From 5ccbaf081da3eda90f797e7f85b1fbb6d6400740 Mon Sep 17 00:00:00 2001 From: lindsay stevens Date: Mon, 29 Jan 2024 20:37:24 +1100 Subject: [PATCH] add: test cases for select search() appearance behaviour --- tests/example_xls/search_and_select.xlsx | Bin 5904 -> 0 bytes .../search_and_select.xml | 40 ---- tests/test_translations.py | 220 +++++++++++++++--- tests/xform_test_case/xlsform_spec_test.py | 3 - 4 files changed, 184 insertions(+), 79 deletions(-) delete mode 100644 tests/example_xls/search_and_select.xlsx delete mode 100644 tests/test_expected_output/search_and_select.xml diff --git a/tests/example_xls/search_and_select.xlsx b/tests/example_xls/search_and_select.xlsx deleted file mode 100644 index ae5962db6a07aee2aaf526703f3b4c4278dec17a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5904 zcmaJ_1yt1C_8mF}q#Kb2Noi>i2}xjv*4#7e?z8v3=WKOl6jWjWCMG5z)~HYpa4T>SpG`nk4zAo>H?QT8 zVI>q^0`QJsWW0}OJ&BxUO=|=^L%}Z)I&ftsl*U8h?&lMNX;4y#5vS8p8BX_vW^?RTZnLZ-KbR)4Q+E5YXXB_OA@VxR$RG?G-Arf zU9~_|*86S0w2!dZN&?iCF)*#)1~)7qw*3ehfcXA*zz`sQVF|QQ2LYX3xy_uNxx5@5 zUc<%hyLbrD7h2moBg*QvW}pO?;Ly)(a6G4VYvT3HVm&&AwS|lGx19WGk7##(?#}(( zGJms)O#i~ha8Hj15|pfJ=kub^@H(?nkyJyEiP?FJwj&j8_N#P)5G{t2MFw*j&Mf~;;3a!eR>>f|8^ zMewKh_ycbacG(AN1D6+tN`VH4)*>^e0#^x+m@R%=0Jic*Z?T%}iRLeR#js7lkaE7g zTqL1YPMBSRlYQ-M*`CNJ-v!v>>k;*QBClFY{NySvatz>*p0jlD@O=dzc83@bdcDYZ zWRKLplJVx|pIT)u(JM`u_jd~ck<>oz-Q~b+Sqxf69oSNkBeP_XFPLq10`MctVqm-N zLWxnYtx<$mcSv!ER5bgXtDS{j3iKR|^z%+0`;Dl9Z8x(Q;wDlLcfUOU!OxBqp`9vU z?A(sRua64fchz0Se|5cm@PBMB)H7<)Nz|0PgQAjsYv z2(-V!`IlH*A;T}acyJM7#m+xpVx+Q&7H&Rz z*IeqKe+Z?YKS;3VXey9q**Nl$GD=-7f5U__&G2U01KoB;JI4bY8erD*o{q*T&nJzy z_Z8+K2pa!zcm1(@~TAKkIaEX$^Nl5OD8T-av)YS zOKyu*|LKF7dA?G4x-H{#q#G7GuU4&3BlchYj|dU}g^;VQ8OX}=nH$K?$;S0BA`Yt} zu=%(n_L!I+6(c2>;GS)B7h{%bCP(5+Ww|SjW+IZM*}ic_1xK}40axa0HT{PTo=br= zQ{?R44eSN&ITrR^?;$%g4Kp@u5%jeS=Iu&&3LqnsB)JEBqf*KI1M833KBJ4n*_m-g zYNB3a44F0PCW(fo;NJHs+KyV%@UD`^ra5nK+Dat$t1=OH8d{JZXPSboq=Q2Xmta~w zUY1A~(IO{OLSgZQG8odsE1`G-LQ{#8?yXko*s5;WLd^0qNanoCO{szty7Bj0W3_wE z2M459no0)JyJ3c|R|Sn1BCp%-9}byi*d54>nhD}&Ow{lfT2(%=+JDt0sq5W#NOyyy z;%9ynbOh6A{t-v=zi_CWD=k6rof}A-k z9(f$<#eH>bO;KZ1eaT zMtnk7++=XT?GJM^eg7SIbf~E|5Ei*(hO=$IdblkOREL*zG2+d1DQ03tOBM5=oZfHD zGU8;Y-bJ5BrQWg~jFquEYvi4l{vnnWqil`i_uLGrZsjO!7sya;mSaeI8x^eTEo0$G+h;N=NTyEx6z{Q)2;@Oy1jIQ=$_cS=77*dx!Ylai#s_p55KqrhB` zNCVj8N9A;ia{fhQ5{P1&xWHT-Ns*RW#icS-_~(S4q+O2GvQvsG#`bJf*l`3D?Gn~1 z+}q6#G%c)UNtO0|Q=@ENHhTt?k>2|h8^|S>(gELxTo;_>iEyE*ki@`=kRCHd4Un*A zsC*kaxI6%P_>;aK9`^mDf=hQ2{wV8%JYCvPywB63MuO|OF3oOg4{ghuBMow$Dmpo- zrOvdBNrDeGgkRJhl6pazf5Z=1yLjc`8YW84^LX36^L4y?NJ>vSu1j{g^doPg%W7D@ypB9;Lxq^iJLEO0Q}>0C%XYLPLdH@jVI0Q~H9E2VOdvYw2$~$S z=Y79&OsbFPorA`vrt1_IqD_r0&<1`!2bZA>6rMP)Gko*5QM?X+(sFO$C z#Ul>_?1l$&IA$&)zsj$#qQB_)%0DWx(2^+T*P=6ZEYo;JW16jfj~1^m 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 LA?oe^3;TZo)DHo7 diff --git a/tests/test_expected_output/search_and_select.xml b/tests/test_expected_output/search_and_select.xml deleted file mode 100644 index 73c67a2a8..000000000 --- a/tests/test_expected_output/search_and_select.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - search_and_select - - - - - - - - - - - - - - name_key - - - - - - - - - - - - - - - name_key - - - - - - - diff --git a/tests/test_translations.py b/tests/test_translations.py index bf35983d5..1346cf88f 100644 --- a/tests/test_translations.py +++ b/tests/test_translations.py @@ -1554,70 +1554,218 @@ def test_choice_name_containing_dash_output_itext(self): class TestTranslationsSearchAppearance(PyxformTestCase): - """Translations behaviour with the search() appearance.""" + """ + Translations behaviour with the search() appearance. + + The search() appearance is a Collect-only feature, so ODK Validate is run for these + tests to try and ensure that the output will be accepted by Collect. In particular, + the search() appearance requires in-line (body) items for choices. + """ + + @classmethod + def setUpClass(cls) -> None: + cls.run_odk_validate = True + + @staticmethod + def xpath_body_select_search_appearance( + qname: str, appearance: str = "search('my_file')" + ) -> str: + return f""" + /h:html/h:body/x:select1[ + @ref='/test_name/{qname}' + and @appearance="{appearance}" + ] + """ + + @staticmethod + def xpath_body_select_config_choice(qname: str, cname: str, cvname: str) -> str: + return f""" + /h:html/h:body/x:select1[@ref='/test_name/{qname}']/x:item[ + ./x:value/text()='{cvname}' + and ./x:label[@ref="jr:itext('{cname}-0')"] + ] + """ def test_shared_choice_list(self): - """Should include translation for search() items, sharing the choice list""" + """Should include translation for search() items, when sharing the choice list""" md = """ - | survey | | | | | | - | | type | name | label::en | label::fr | appearance | - | | select_one c1 | q1 | Question 1 | Chose 1 | search('my_file') | - | | select_one c2 | q2 | Question 2 | Chose 2 | | - | choices | | | | | - | | list_name | name | label::en | label::fr | - | | c1 | na | la-e | la-f | - | | c1 | nb | lb-e | lb-f | - | | c2 | na | la-e | la-f | + | survey | | | | | | + | | type | name | label::en | label::fr | appearance | + | | select_one c1 | q1 | Question 1 | Chose 1 | search('my_file') | + | | select_one c1 | q2 | Question 2 | Chose 2 | search('my_file', 'matches', 'filtercol', 'x1') | + | choices | | | | | + | | list_name | name | label::en | label::fr | + | | c1 | id | label_en | label_fr | """ self.assertPyxformXform( md=md, - run_odk_validate=True, + run_odk_validate=self.run_odk_validate, xml__xpath_match=[ - "/h:html/h:body/x:select1/x:item[./x:value/text()='na']", - xpc.model_itext_choice_text_label_by_pos("en", "c1", ("la-e", "lb-e")), - xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("la-f", "lb-f")), - xpc.model_itext_choice_text_label_by_pos("en", "c2", ("la-e",)), - xpc.model_itext_choice_text_label_by_pos("fr", "c2", ("la-f",)), + self.xpath_body_select_search_appearance("q1"), + self.xpath_body_select_config_choice("q1", "c1", "id"), + self.xpath_body_select_search_appearance( + "q2", "search('my_file', 'matches', 'filtercol', 'x1')" + ), + self.xpath_body_select_config_choice("q2", "c1", "id"), + xpc.model_instance_choices_itext("c1", ("id",)), + xpc.model_itext_choice_text_label_by_pos("en", "c1", ("label_en",)), + xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("label_fr",)), ], ) - def test_single_question_single_choice(self): - """Should include translation for search() items, edge case of single elements""" + def test_usage_with_other_selects(self): + """Should include translation for search() items, when used with other selects""" md = """ - | survey | | | | | | - | | type | name | label::en | label::fr | appearance | - | | select_one c1 | q1 | Question 1 | Chose 1 | search('my_file') | + | survey | | | | | | + | | type | name | label::en | label::fr | appearance | + | | select_one c1 | q1 | Question 1 | Chose 1 | search('my_file') | + | | select_one c2 | q2 | Question 2 | Chose 2 | | | choices | | | | | - | | list_name | name | label::en | label::fr | - | | c1 | na | la-e | la-f | + | | list_name | name | label::en | label::fr | + | | c1 | id | label_en | label_fr | + | | c2 | na | la-e | la-f | + | | c2 | nb | lb-e | lb-f | + """ + self.assertPyxformXform( + md=md, + run_odk_validate=self.run_odk_validate, + xml__xpath_match=[ + self.xpath_body_select_search_appearance("q1"), + self.xpath_body_select_config_choice("q1", "c1", "id"), + xpc.model_instance_choices_itext("c1", ("id",)), + xpc.model_itext_choice_text_label_by_pos("en", "c1", ("label_en",)), + xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("label_fr",)), + xpc.model_instance_choices_itext("c2", ("na", "nb")), + xpc.model_itext_choice_text_label_by_pos("en", "c2", ("la-e", "lb-e")), + xpc.model_itext_choice_text_label_by_pos("fr", "c2", ("la-f", "lb-f")), + ], + ) + + def test_usage_with_other_selects__invalid_list_reuse_by_non_search_question(self): + """By design, q2 won't pull data but the test is to document output behaviour.""" + md = """ + | survey | | | | | | + | | type | name | label::en | label::fr | appearance | + | | select_one c1 | q1 | Question 1 | Chose 1 | search('my_file') | + | | select_one c1 | q2 | Question 2 | Chose 2 | | + | choices | | | | | + | | list_name | name | label::en | label::fr | + | | c1 | id | label_en | label_fr | + """ + self.assertPyxformXform( + md=md, + run_odk_validate=self.run_odk_validate, + xml__xpath_match=[ + self.xpath_body_select_search_appearance("q1"), + self.xpath_body_select_config_choice("q1", "c1", "id"), + xpq.body_select1_itemset("q2"), + xpc.model_instance_choices_itext("c1", ("id",)), + xpc.model_itext_choice_text_label_by_pos("en", "c1", ("label_en",)), + xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("label_fr",)), + ], + ) + + def test_single_question_usage(self): + """Should include translation for search() items, edge case of single question""" + md = """ + | survey | | | | | | + | | type | name | label::en | label::fr | appearance | + | | select_one c1 | q1 | Question 1 | Chose 1 | search('my_file') | + | choices | | | | | + | | list_name | name | label::en | label::fr | + | | c1 | id | label_en | label_fr | + """ + self.assertPyxformXform( + md=md, + run_odk_validate=self.run_odk_validate, + xml__xpath_match=[ + self.xpath_body_select_search_appearance("q1"), + self.xpath_body_select_config_choice("q1", "c1", "id"), + xpc.model_instance_choices_itext("c1", ("id",)), + xpc.model_itext_choice_text_label_by_pos("en", "c1", ("label_en",)), + xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("label_fr",)), + ], + ) + + def test_additional_static_choices(self): + """Should include translation for search() items, when adding static choices""" + md = """ + | survey | | | | | | + | | type | name | label::en | label::fr | appearance | + | | select_one c1 | q1 | Question 1 | Chose 1 | search('my_file') | + | choices | | | | | + | | list_name | name | label::en | label::fr | + | | c1 | id | label_en | label_fr | + | | c1 | 0 | l0-e | l0-f | + | | c1 | 1 | l1-e | l1-f | """ self.assertPyxformXform( md=md, - run_odk_validate=True, + run_odk_validate=self.run_odk_validate, xml__xpath_match=[ - "/h:html/h:body/x:select1/x:item[./x:value/text()='na']", - xpc.model_itext_choice_text_label_by_pos("en", "c1", ("la-e",)), - xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("la-f",)), + self.xpath_body_select_search_appearance("q1"), + self.xpath_body_select_config_choice("q1", "c1", "id"), + xpc.model_instance_choices_itext("c1", ("id", "0", "1")), + xpc.model_itext_choice_text_label_by_pos( + "en", "c1", ("label_en", "l0-e", "l1-e") + ), + xpc.model_itext_choice_text_label_by_pos( + "fr", "c1", ("label_fr", "l0-f", "l1-f") + ), ], ) def test_name_clashes(self): """Should include translation for search() items, avoids any name clashes.""" md = """ - | survey | | | | | | - | | type | name | label::en | label::fr | appearance | - | | select_one c1-0 | c1-0 | Question 1 | Chose 1 | search('my_file') | + | survey | | | | | | + | | type | name | label::en | label::fr | appearance | + | | select_one c1-0 | c1-0 | Question 1 | Chose 1 | search('my_file') | | choices | | | | | | | list_name | name | label::en | label::fr | - | | c1-0 | na | la-e | la-f | + | | c1-0 | id | label_en | label_fr | + """ + self.assertPyxformXform( + md=md, + run_odk_validate=self.run_odk_validate, + xml__xpath_match=[ + self.xpath_body_select_search_appearance("c1-0"), + self.xpath_body_select_config_choice("c1-0", "c1-0", "id"), + xpc.model_instance_choices_itext("c1-0", ("id",)), + xpc.model_itext_choice_text_label_by_pos("en", "c1-0", ("label_en",)), + xpc.model_itext_choice_text_label_by_pos("fr", "c1-0", ("label_fr",)), + ], + ) + + def test_search_and_select_xlsx(self): + """Test to replace the old XLSX-based test fixture, 'search_and_select.xlsx'""" + md = """ + | survey | | | | | + | | type | name | label | appearance | + | | select_one fruits | fruit | Choose a fruit | search('fruits') | + | | note | note_fruit | The fruit ${fruit} pulled from csv | | + | choices | | | | + | | list_name | name | label | + | | fruits | name_key | name | """ self.assertPyxformXform( md=md, - run_odk_validate=True, + run_odk_validate=self.run_odk_validate, xml__xpath_match=[ - "/h:html/h:body/x:select1/x:item[./x:value/text()='na']", - xpc.model_itext_choice_text_label_by_pos("en", "c1-0", ("la-e",)), - xpc.model_itext_choice_text_label_by_pos("fr", "c1-0", ("la-f",)), + "/h:html/h:head/x:model[not(descendant::x:itext)]", + xpc.model_instance_choices_label("fruits", (("name_key", "name"),)), + """ + /h:html/h:body/x:select1[ + @ref='/test_name/fruit' + and @appearance="search('fruits')" + ]/x:item[ + ./x:value/text()='name_key' + and ./x:label/text()='name' + ] + """, + xpq.model_instance_bind("fruit", "string"), + xpq.model_instance_bind("note_fruit", "string"), + "/h:html/h:body/x:input[@ref='/test_name/note_fruit']", ], ) diff --git a/tests/xform_test_case/xlsform_spec_test.py b/tests/xform_test_case/xlsform_spec_test.py index 92d6d9421..61d08b647 100644 --- a/tests/xform_test_case/xlsform_spec_test.py +++ b/tests/xform_test_case/xlsform_spec_test.py @@ -54,9 +54,6 @@ def test_widgets(self): def test_pull_data(self): self.compare_xform(file_name="pull_data.xlsx") - def test_search_and_select(self): - self.compare_xform(file_name="search_and_select.xlsx") - def test_default_survey_sheet(self): self.compare_xform(file_name="survey_no_name.xlsx", set_default_name=False)