test_i18n.py 9.88 KB
Newer Older
1 2 3 4 5
"""
Tests for validate Internationalization and Module i18n service.
"""
import mock
import gettext
6
from unittest import skip
Steve Strassmann committed
7
from django.contrib.auth.models import User
8
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
9
from contentstore.tests.utils import AjaxEnabledTestClient
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
from xmodule.modulestore.django import ModuleI18nService
from django.utils import translation
from django.utils.translation import get_language
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
from contentstore.views.preview import _preview_module_system


class FakeTranslations(ModuleI18nService):
    """A test GNUTranslations class that takes a map of msg -> translations."""

    def __init__(self, translations):  # pylint: disable=super-init-not-called
        self.translations = translations

    def ugettext(self, msgid):
        """
        Mock override for ugettext translation operation
        """
        return self.translations.get(msgid, msgid)

    @staticmethod
    def translator(locales_map):  # pylint: disable=method-hidden
        """Build mock translator for the given locales.
        Returns a mock gettext.translation function that uses
        individual TestTranslations to translate in the given locales.
        :param locales_map: A map from locale name to a translations map.
                            {
                             'es': {'Hi': 'Hola', 'Bye': 'Adios'},
                             'zh': {'Hi': 'Ni Hao', 'Bye': 'Zaijian'}
                            }
        """
        def _translation(domain, localedir=None, languages=None):  # pylint: disable=unused-argument
            """
            return gettext.translation for given language
            """
            if languages:
                language = languages[0]
                if language in locales_map:
                    return FakeTranslations(locales_map[language])
            return gettext.NullTranslations()
        return _translation


class TestModuleI18nService(ModuleStoreTestCase):
    """ Test ModuleI18nService """

    def setUp(self):
        """ Setting up tests """
        super(TestModuleI18nService, self).setUp()
        self.test_language = 'dummy language'
        self.request = mock.Mock()
        self.course = CourseFactory.create()
        self.field_data = mock.Mock()
        self.descriptor = ItemFactory(category="pure", parent=self.course)
        self.runtime = _preview_module_system(
            self.request,
            self.descriptor,
            self.field_data,
        )
68
        self.addCleanup(translation.deactivate)
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

    def get_module_i18n_service(self, descriptor):
        """
        return the module i18n service.
        """
        i18n_service = self.runtime.service(descriptor, 'i18n')
        self.assertIsNotNone(i18n_service)
        self.assertIsInstance(i18n_service, ModuleI18nService)
        return i18n_service

    def test_django_service_translation_works(self):
        """
        Test django translation service works fine.
        """

84
        class wrap_ugettext_with_xyz(object):  # pylint: disable=invalid-name
85
            """
86 87
            A context manager function that just adds 'XYZ ' to the front
            of all strings of the module ugettext function.
88 89
            """

90 91 92 93 94 95 96 97 98 99 100 101 102
            def __init__(self, module):
                self.module = module
                self.old_ugettext = module.ugettext

            def __enter__(self):
                def new_ugettext(*args, **kwargs):
                    """ custom function """
                    output = self.old_ugettext(*args, **kwargs)
                    return "XYZ " + output
                self.module.ugettext = new_ugettext

            def __exit__(self, _type, _value, _traceback):
                self.module.ugettext = self.old_ugettext
103 104 105 106

        i18n_service = self.get_module_i18n_service(self.descriptor)

        # Activate french, so that if the fr files haven't been loaded, they will be loaded now.
107 108
        with translation.override("fr"):
            french_translation = translation.trans_real._active.value  # pylint: disable=protected-access
109

110 111 112
            # wrap the ugettext functions so that 'XYZ ' will prefix each translation
            with wrap_ugettext_with_xyz(french_translation):
                self.assertEqual(i18n_service.ugettext(self.test_language), 'XYZ dummy language')
113

114 115
            # Check that the old ugettext has been put back into place
            self.assertEqual(i18n_service.ugettext(self.test_language), 'dummy language')
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161

    @mock.patch('django.utils.translation.ugettext', mock.Mock(return_value='XYZ-TEST-LANGUAGE'))
    def test_django_translator_in_use_with_empty_block(self):
        """
        Test: Django default translator should in use if we have an empty block
        """
        i18n_service = ModuleI18nService(None)
        self.assertEqual(i18n_service.ugettext(self.test_language), 'XYZ-TEST-LANGUAGE')

    @mock.patch('django.utils.translation.ugettext', mock.Mock(return_value='XYZ-TEST-LANGUAGE'))
    def test_message_catalog_translations(self):
        """
        Test: Message catalog from FakeTranslation should return required translations.
        """
        _translator = FakeTranslations.translator(
            {
                'es': {'Hello': 'es-hello-world'},
                'fr': {'Hello': 'fr-hello-world'},
            },
        )
        localedir = '/translations'
        translation.activate("es")
        with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir,
                                                                        languages=[get_language()])):
            i18n_service = self.get_module_i18n_service(self.descriptor)
            self.assertEqual(i18n_service.ugettext('Hello'), 'es-hello-world')

        translation.activate("ar")
        with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir,
                                                                        languages=[get_language()])):
            i18n_service = self.get_module_i18n_service(self.descriptor)
            self.assertEqual(i18n_service.ugettext('Hello'), 'Hello')
            self.assertNotEqual(i18n_service.ugettext('Hello'), 'fr-hello-world')
            self.assertNotEqual(i18n_service.ugettext('Hello'), 'es-hello-world')

        translation.activate("fr")
        with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir,
                                                                        languages=[get_language()])):
            i18n_service = self.get_module_i18n_service(self.descriptor)
            self.assertEqual(i18n_service.ugettext('Hello'), 'fr-hello-world')

    def test_i18n_service_callable(self):
        """
        Test: i18n service should be callable in studio.
        """
        self.assertTrue(callable(self.runtime._services.get('i18n')))  # pylint: disable=protected-access
162

163

164
class InternationalizationTest(ModuleStoreTestCase):
Steve Strassmann committed
165 166 167 168
    """
    Tests to validate Internationalization.
    """

169 170
    CREATE_USER = False

Steve Strassmann committed
171 172 173 174 175 176 177 178
    def setUp(self):
        """
        These tests need a user in the DB so that the django Test Client
        can log them in.
        They inherit from the ModuleStoreTestCase class so that the mongodb collection
        will be cleared out before each test case execution and deleted
        afterwards.
        """
179
        super(InternationalizationTest, self).setUp()
180

Steve Strassmann committed
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
        self.uname = 'testuser'
        self.email = 'test+courses@edx.org'
        self.password = 'foo'

        # Create the use so we can log them in.
        self.user = User.objects.create_user(self.uname, self.email, self.password)

        # Note that we do not actually need to do anything
        # for registration if we directly mark them active.
        self.user.is_active = True
        # Staff has access to view all courses
        self.user.is_staff = True
        self.user.save()

        self.course_data = {
            'org': 'MITx',
            'number': '999',
            'display_name': 'Robot Super Course',
199
        }
Steve Strassmann committed
200 201 202

    def test_course_plain_english(self):
        """Test viewing the index page with no courses"""
203
        self.client = AjaxEnabledTestClient()
Steve Strassmann committed
204
        self.client.login(username=self.uname, password=self.password)
205

206
        resp = self.client.get_html('/home/')
Steve Strassmann committed
207
        self.assertContains(resp,
208
                            '<h1 class="page-header">Studio Home</h1>',
209 210
                            status_code=200,
                            html=True)
Steve Strassmann committed
211

212 213
    def test_course_explicit_english(self):
        """Test viewing the index page with no courses"""
214
        self.client = AjaxEnabledTestClient()
215
        self.client.login(username=self.uname, password=self.password)
216

217
        resp = self.client.get_html(
218
            '/home/',
219 220
            {},
            HTTP_ACCEPT_LANGUAGE='en',
221
        )
222 223

        self.assertContains(resp,
224
                            '<h1 class="page-header">Studio Home</h1>',
225 226
                            status_code=200,
                            html=True)
Steve Strassmann committed
227 228 229 230 231 232

    # ****
    # NOTE:
    # ****
    #
    # This test will break when we replace this fake 'test' language
233 234
    # with actual Esperanto. This test will need to be updated with
    # actual Esperanto at that time.
235
    # Test temporarily disable since it depends on creation of dummy strings
236
    @skip
237
    def test_course_with_accents(self):
Steve Strassmann committed
238
        """Test viewing the index page with no courses"""
239
        self.client = AjaxEnabledTestClient()
Steve Strassmann committed
240
        self.client.login(username=self.uname, password=self.password)
241

242
        resp = self.client.get_html(
243
            '/home/',
244 245 246
            {},
            HTTP_ACCEPT_LANGUAGE='eo'
        )
Steve Strassmann committed
247

David Baumgold committed
248 249 250 251 252
        TEST_STRING = (
            u'<h1 class="title-1">'
            u'My \xc7\xf6\xfcrs\xe9s L#'
            u'</h1>'
        )
253

Steve Strassmann committed
254 255 256
        self.assertContains(resp,
                            TEST_STRING,
                            status_code=200,
257
                            html=True)