diff --git a/.travis.yml b/.travis.yml
index ada78945b6..4e296e8ae4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,7 +25,7 @@ before_script:
script:
- gulp copy
- npm run build
- - coverage run --source='hub,kpi,kobo' --omit='*/tests/*,*/migrations/*,*/management/commands/*' manage.py test
+ - coverage run --source='hub,kpi,kobo' --omit='*/tests/*,*/migrations/*,*/management/commands/*' pytest
- npm run test
after_script:
- coverage xml && python-codacy-coverage -r coverage.xml
diff --git a/dependencies/pip/external_services.txt b/dependencies/pip/external_services.txt
index 5c8c12f994..fe290abbd3 100644
--- a/dependencies/pip/external_services.txt
+++ b/dependencies/pip/external_services.txt
@@ -72,7 +72,7 @@ pygments==2.1.3
pymongo==3.3.0
pyopenssl==16.1.0
pyquery==1.4.0
-pytest-django==3.0.0
+pytest-django==3.1.2
pytest==3.0.3 # via pytest-django
python-dateutil==2.6.0
python-digest==1.7
diff --git a/dependencies/pip/requirements.txt b/dependencies/pip/requirements.txt
index a13fdce872..9505bfc1c6 100644
--- a/dependencies/pip/requirements.txt
+++ b/dependencies/pip/requirements.txt
@@ -70,7 +70,7 @@ pygments==2.1.3
pymongo==3.3.0
pyopenssl==16.1.0
pyquery==1.4.0
-pytest-django==3.0.0
+pytest-django==3.1.2
pytest==3.0.3 # via pytest-django
python-dateutil==2.6.0
python-digest==1.7
diff --git a/docker/run_tests.bash b/docker/run_tests.bash
index dea6329a8a..e03b24ceb8 100755
--- a/docker/run_tests.bash
+++ b/docker/run_tests.bash
@@ -3,5 +3,5 @@ set -e
source /etc/profile
-python manage.py test
+pytest
npm run test
diff --git a/kobo/settings.py b/kobo/settings.py
index e4d678da75..4e7a79198f 100644
--- a/kobo/settings.py
+++ b/kobo/settings.py
@@ -107,6 +107,12 @@
'hub.middleware.OtherFormBuilderRedirectMiddleware',
)
+# Warn developers to use `pytest` instead of `./manage.py test`
+class DoNotUseRunner(object):
+ def __init__(self, *args, **kwargs):
+ raise NotImplementedError('Please run tests with `pytest` instead')
+TEST_RUNNER = __name__ + '.DoNotUseRunner'
+
# used in kpi.models.sitewide_messages
MARKITUP_FILTER = ('markdown.markdown', {'safe_mode': False})
diff --git a/kpi/tests/test_asset_content.py b/kpi/tests/test_asset_content.py
index b4c0d0c099..73bdd9c13d 100644
--- a/kpi/tests/test_asset_content.py
+++ b/kpi/tests/test_asset_content.py
@@ -490,6 +490,47 @@ def kobomatrix_content_with_custom_fields():
return _content
+def reverse_str(s):
+ return ''.join(reversed(s))
+
+
+def kobomatrix_content_with_translations():
+ _content = kobomatrix_content()
+ for _s in ['survey', 'choices']:
+ _sheet = _content[_s]
+ for _row in _sheet:
+ try:
+ _label = _row['label']
+ except KeyError:
+ continue
+ _row['label::English'] = _label
+ _row['label::' + reverse_str('English')] = reverse_str(_label)
+ del _row['label']
+ return _content
+
+
+def kobomatrix_content_with_missing_translations():
+ _content = kobomatrix_content_with_translations()
+ for _s in ['survey', 'choices']:
+ _sheet = _content[_s]
+ found_first_translation = False
+ for _row in _sheet:
+ try:
+ _label = _row['label::English']
+ except KeyError:
+ continue
+ if not found_first_translation:
+ # leave the first translation alone
+ found_first_translation = True
+ continue
+ _row['label::' + reverse_str('English')] = None
+ return _content
+
+
+def _span_display_none(item):
+ return '{}'.format(item)
+
+
def test_kobomatrix_content():
content = _compile_asset_content(kobomatrix_content())
pattern = ['w7', 'w1', 'w2', 'w2', 'w2', '']
@@ -557,29 +598,83 @@ def test_kobomatrix_content():
'**Number**',
]
- def _span(item):
- return '{}'.format(item)
-
assert _labls[7:11] == ['##### Car',
- _span('car-Possess?'),
- _span('car-Necessary?'),
- _span('car-Number'),
+ _span_display_none('car-Possess?'),
+ _span_display_none('car-Necessary?'),
+ _span_display_none('car-Number'),
]
assert _labls[13:17] == ['##### Bike',
- _span('bike-Possess?'),
- _span('bike-Necessary?'),
- _span('bike-Number'),
+ _span_display_none('bike-Possess?'),
+ _span_display_none('bike-Necessary?'),
+ _span_display_none('bike-Number'),
]
assert _labls[19:23] == ['##### TV',
- _span('tv-Possess?'),
- _span('tv-Necessary?'),
- _span('tv-Number'),
+ _span_display_none('tv-Possess?'),
+ _span_display_none('tv-Necessary?'),
+ _span_display_none('tv-Number'),
]
assert _reqds == [None, False, False, False, False, None] + (
[None, False, True, True, True, None] * 3
)
+def test_kobomatrix_labels_with_translations():
+ content = _compile_asset_content(
+ kobomatrix_content_with_translations())
+ labels = [r.get('label', [None]) for r in content['survey']]
+
+ expected_labels = [
+ '**It\xe9ms**',
+ '**Possess?**',
+ '**Necessary?**',
+ '**Number**'
+ ]
+ assert labels[1:5] == [[l, reverse_str(l)] for l in expected_labels]
+
+ def _make_expected_labels(row):
+ labels = [['##### ' + row, '##### ' + reverse_str(row)]]
+ for col in ['Possess?', 'Necessary?', 'Number']:
+ labels.append([
+ _span_display_none(row.lower() + '-' + col),
+ _span_display_none(row.lower() + '-' + reverse_str(col))
+ ])
+ return labels
+
+ assert labels[7:11] == _make_expected_labels('Car')
+ assert labels[13:17] == _make_expected_labels('Bike')
+ assert labels[19:23] == _make_expected_labels('TV')
+
+
+def test_kobomatrix_labels_with_missing_translations():
+ content = _compile_asset_content(
+ kobomatrix_content_with_missing_translations())
+ labels = [r.get('label', [None]) for r in content['survey']]
+ assert labels[1:5] == [
+ ['**It\xe9ms**', '**sm\xe9tI**'],
+ ['**Possess?**', None],
+ ['**Necessary?**', None],
+ ['**Number**', None]
+ ]
+ assert labels[7:11] == [
+ ['##### Car', '##### raC'],
+ [_span_display_none('car-Possess?'), None],
+ [_span_display_none('car-Necessary?'), None],
+ [_span_display_none('car-Number'), None]
+ ]
+ assert labels[13:17] == [
+ ['##### Bike', None],
+ [_span_display_none('bike-Possess?'), None],
+ [_span_display_none('bike-Necessary?'), None],
+ [_span_display_none('bike-Number'), None]
+ ]
+ assert labels[19:23] == [
+ ['##### TV', None],
+ [_span_display_none('tv-Possess?'), None],
+ [_span_display_none('tv-Necessary?'), None],
+ [_span_display_none('tv-Number'), None]
+ ]
+
+
def test_xpath_fields_in_kobomatrix_are_preserved():
_content = kobomatrix_content_with_custom_fields()
(r0, r1, r2, r3, r4) = _content['survey']
diff --git a/kpi/utils/xlsform_preprocessors/kobomatrix_handler.py b/kpi/utils/xlsform_preprocessors/kobomatrix_handler.py
index c778ef0aa6..42dedebff0 100644
--- a/kpi/utils/xlsform_preprocessors/kobomatrix_handler.py
+++ b/kpi/utils/xlsform_preprocessors/kobomatrix_handler.py
@@ -112,7 +112,7 @@ def finish(self):
def _format_all_labels(self, labels, template):
return [
- template.format(_l) for _l in labels
+ template.format(_l) if _l is not None else None for _l in labels
]
def _header(self, name, items_label, cols,
@@ -173,13 +173,16 @@ def _make_row(col):
_appearance.append('horizontal-compact')
else:
_appearance.append('no-label')
+ _labels = []
+ for _label in col.get('label'):
+ if _label is not None:
+ _labels.append('-'.join([_item_name, _label]))
+ else:
+ _labels.append(None)
out = {'type': _type,
'name': '_'.join([_base_name, col['name']]),
'appearance': ' '.join(_appearance),
- 'label': self._format_all_labels([
- '-'.join([_item_name, _label])
- for _label in col.get('label')
- ], self.span_wrap),
+ 'label': self._format_all_labels(_labels, self.span_wrap),
'required': col.get('required', False),
}
for key in ['relevant', 'constraint', 'required']:
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000000..c28205b2db
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+testpaths = kobo kpi