Newer
Older
<?php
// $Id$
/**
* @file
* Tests for Locale module.
*
* The test file includes:
* - a functional test for the language configuration forms;
* - functional tests for the translation functionalities, including searching;
* - a functional test for the PO files import feature, including validation;
* - functional tests for translations and templates export feature;
* - functional tests for the uninstall process;
* - a functional test for the language switching feature;
* - a functional test for a user's ability to change their default language;
* - a functional test for configuring a different path alias per language;
* - a functional test for configuring a different path alias per language;
* - a functional test for multilingual support by content type and on nodes.
Angie Byron
committed
* - a functional test for multilingual fields.
/**
* Functional tests for the language configuration forms.
*/
class LocaleConfigurationTest extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
return array(
'name' => 'Language configuration',
'description' => 'Adds a new locale and tests changing its status and the default language.',
'group' => 'Locale',
);
}
function setUp() {
parent::setUp('locale');
}
/**
* Functional tests for adding, editing and deleting languages.
*/
function testLanguageConfiguration() {
global $base_url;
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
// Add predefined language.
$edit = array(
'langcode' => 'fr',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
$this->assertText('fr', 'Language added successfully.');
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
// Add custom language.
// Code for the language.
// The English name for the language.
$name = $this->randomName(16);
// The native name for the language.
$native = $this->randomName(16);
// The domain prefix.
$prefix = $langcode;
$edit = array(
'langcode' => $langcode,
'name' => $name,
'native' => $native,
'prefix' => $prefix,
'direction' => '0',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
$this->assertText($langcode, 'Language code found.');
$this->assertText($name, 'Name found.');
$this->assertText($native, 'Native found.');
$this->assertText($native, 'Test language added.');
// Check if we can change the default language.
Dries Buytaert
committed
$path = 'admin/config/regional/language';
$this->drupalGet($path);
$this->assertFieldChecked('edit-site-default-en', 'English is the default language.');
// Change the default language.
$edit = array(
'site_default' => $langcode,
);
$this->drupalPost(NULL, $edit, t('Save configuration'));
$this->assertNoFieldChecked('edit-site-default-en', 'Default language updated.');
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
// Ensure we can't delete the default language.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
$this->assertText(t('The default language cannot be deleted.'), 'Failed to delete the default language.');
// Check if we can disable a language.
$edit = array(
'enabled[en]' => FALSE,
);
$this->drupalPost($path, $edit, t('Save configuration'));
$this->assertNoFieldChecked('edit-enabled-en', 'Language disabled.');
// Set disabled language to be the default and ensure it is re-enabled.
$edit = array(
'site_default' => 'en',
);
$this->drupalPost(NULL, $edit, t('Save configuration'));
$this->assertFieldChecked('edit-enabled-en', 'Default language re-enabled.');
// Ensure 'edit' link works.
$this->clickLink(t('edit'));
$this->assertTitle(t('Edit language | Drupal'), 'Page title is "Edit language".');
// Edit a language.
$name = $this->randomName(16);
$edit = array(
'name' => $name,
);
$this->drupalPost('admin/config/regional/language/edit/' . $langcode, $edit, t('Save language'));
$this->assertRaw($name, 'The language has been updated.');
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
// Ensure 'delete' link works.
$this->drupalGet('admin/config/regional/language');
$this->clickLink(t('delete'));
$this->assertText(t('Are you sure you want to delete the language'), '"delete" link is correct.');
// Delete the language.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
// First test the 'cancel' link.
$this->clickLink(t('Cancel'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
$this->assertRaw($name, 'The language was not deleted.');
// Delete the language for real. This a confirm form, we do not need any
// fields changed.
$this->drupalPost('admin/config/regional/language/delete/' . $langcode, array(), t('Delete'));
// We need raw here because %locale will add HTML.
$this->assertRaw(t('The language %locale has been removed.', array('%locale' => $name)), 'The test language has been removed.');
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
// Verify that language is no longer found.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
$this->assertResponse(404, 'Language no longer found.');
// Ensure we can't delete the English language.
$this->drupalGet('admin/config/regional/language/delete/en');
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
$this->assertText(t('The English language cannot be deleted.'), 'Failed to delete English language.');
}
}
/**
* Functional test for string translation and validation.
*/
class LocaleTranslationFunctionalTest extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
return array(
'name' => 'String translate, search and validate',
'description' => 'Adds a new locale and translates its name. Checks the validation of translation strings and search results.',
'group' => 'Locale',
);
}
function setUp() {
parent::setUp('locale');
}
/**
* Adds a language and tests string translation by users with the appropriate permissions.
*/
function testStringTranslation() {
global $base_url;
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
// User to translate and delete string.
$translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
// Code for the language.
$langcode = 'xx';
// The English name for the language. This will be translated.
$name = $this->randomName(16);
// The native name for the language.
$native = $this->randomName(16);
// The domain prefix.
$prefix = $langcode;
// This is the language indicator on the translation search screen for
// untranslated strings. Copied straight from locale.inc.
$language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
// This will be the translation of $name.
$translation = $this->randomName(16);
// Add custom language.
$this->drupalLogin($admin_user);
Angie Byron
committed
$edit = array(
'langcode' => $langcode,
'name' => $name,
'native' => $native,
'prefix' => $prefix,
'direction' => '0',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
// Add string.
Dries Buytaert
committed
t($name, array(), array('langcode' => $langcode));
// Reset locale cache.
locale_reset();
$this->assertText($langcode, 'Language code found.');
$this->assertText($name, 'Name found.');
$this->assertText($native, 'Native found.');
// No t() here, we do not want to add this string to the database and it's
// surely not translated yet.
$this->assertText($native, 'Test language added.');
$this->drupalLogout();
// Search for the name and translate it.
$this->drupalLogin($translate_user);
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'all',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// assertText() seems to remove the input field where $name always could be
// found, so this is not a false assert. See how assertNoText succeeds
// later.
$this->assertText($name, 'Search found the name.');
$this->assertRaw($language_indicator, 'Name is untranslated.');
// Assume this is the only result, given the random name.
$this->clickLink(t('edit'));
// We save the lid from the path.
Angie Byron
committed
$matches = array();
Angie Byron
committed
preg_match('!admin/config/regional/translate/edit/(\d+)!', $this->getUrl(), $matches);
Angie Byron
committed
$lid = $matches[1];
// No t() here, it's surely not translated yet.
$this->assertText($name, 'name found on edit screen.');
$edit = array(
"translations[$langcode]" => $translation,
);
$this->drupalPost(NULL, $edit, t('Save translations'));
$this->assertText(t('The string has been saved.'), 'The string has been saved.');
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
$this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, 't() works.');
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// The indicator should not be here.
$this->assertNoRaw($language_indicator, 'String is translated.');
// Try to edit a non-existent string and ensure we're redirected correctly.
// Assuming we don't have 999,999 strings already.
$random_lid = 999999;
Dries Buytaert
committed
$this->drupalGet('admin/config/regional/translate/edit/' . $random_lid);
$this->assertText(t('String not found'), 'String not found.');
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
$this->drupalLogout();
// Delete the language.
$this->drupalLogin($admin_user);
Dries Buytaert
committed
$path = 'admin/config/regional/language/delete/' . $langcode;
// This a confirm form, we do not need any fields changed.
$this->drupalPost($path, array(), t('Delete'));
// We need raw here because %locale will add HTML.
$this->assertRaw(t('The language %locale has been removed.', array('%locale' => $name)), 'The test language has been removed.');
// Reload to remove $name.
$this->drupalGet($path);
$this->assertNoText($langcode, 'Language code not found.');
$this->assertNoText($name, 'Name not found.');
$this->assertNoText($native, 'Native not found.');
$this->drupalLogout();
// Delete the string.
$this->drupalLogin($translate_user);
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'all',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// Assume this is the only result, given the random name.
$this->clickLink(t('delete'));
$this->assertText(t('Are you sure you want to delete the string'), '"delete" link is correct.');
// Delete the string.
Dries Buytaert
committed
$path = 'admin/config/regional/translate/delete/' . $lid;
$this->drupalGet($path);
// First test the 'cancel' link.
$this->clickLink(t('Cancel'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
$this->assertRaw($name, 'The string was not deleted.');
// Delete the name string.
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/delete/' . $lid, array(), t('Delete'));
$this->assertText(t('The string has been removed.'), 'The string has been removed message.');
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText($name, 'Search now can not find the name.');
}
Dries Buytaert
committed
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/*
* Adds a language and checks that the JavaScript translation files are
* properly created and rebuilt on deletion.
*/
function testJavaScriptTranslation() {
$user = $this->drupalCreateUser(array('translate interface', 'administer languages', 'access administration pages'));
$this->drupalLogin($user);
$langcode = 'xx';
// The English name for the language. This will be translated.
$name = $this->randomName(16);
// The native name for the language.
$native = $this->randomName(16);
// The domain prefix.
$prefix = $langcode;
// Add custom language.
$edit = array(
'langcode' => $langcode,
'name' => $name,
'native' => $native,
'prefix' => $prefix,
'direction' => '0',
);
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
drupal_static_reset('language_list');
// Build the JavaScript translation file.
$this->drupalGet('admin/config/regional/translate/translate');
// Retrieve the id of the first string available in the {locales_source}
// table and translate it.
$query = db_select('locales_source', 'l');
$query->addExpression('min(l.lid)', 'lid');
$result = $query->condition('l.location', '%.js%', 'LIKE')
->condition('l.textgroup', 'default')
->execute();
$url = 'admin/config/regional/translate/edit/' . $result->fetchObject()->lid;
$edit = array('translations[' . $langcode . ']' => $this->randomName());
$this->drupalPost($url, $edit, t('Save translations'));
Dries Buytaert
committed
// Trigger JavaScript translation parsing and building.
require_once DRUPAL_ROOT . '/includes/locale.inc';
_locale_rebuild_js($langcode);
// Retrieve the JavaScript translation hash code for the custom language to
// check that the translation file has been properly built.
$file = db_select('languages', 'l')
->fields('l', array('javascript'))
->condition('language', $langcode)
->execute()
->fetchObject();
$js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/' . $langcode . '_' . $file->javascript . '.js';
$this->assertTrue($result = file_exists($js_file), 'JavaScript file created: ' . $result ? $js_file : t('not found'));
// Test JavaScript translation rebuilding.
file_unmanaged_delete($js_file);
$this->assertTrue($result = !file_exists($js_file), 'JavaScript file deleted: ' . $result ? $js_file : t('found'));
cache_clear_all();
_locale_rebuild_js($langcode);
$this->assertTrue($result = file_exists($js_file), 'JavaScript file rebuilt: ' . $result ? $js_file : t('not found'));
}
/**
* Tests the validation of the translation input.
*/
function testStringValidation() {
Dries Buytaert
committed
global $base_url;
// User to add language and strings.
Dries Buytaert
committed
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface'));
$this->drupalLogin($admin_user);
$langcode = 'xx';
Dries Buytaert
committed
// The English name for the language. This will be translated.
$name = $this->randomName(16);
// The native name for the language.
$native = $this->randomName(16);
// The domain prefix.
$prefix = $langcode;
Dries Buytaert
committed
// This is the language indicator on the translation search screen for
// untranslated strings. Copied straight from locale.inc.
$language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
// These will be the invalid translations of $name.
$key = $this->randomName(16);
$bad_translations[$key] = "<script>alert('xss');</script>" . $key;
$key = $this->randomName(16);
$bad_translations[$key] = '<img SRC="javascript:alert(\'xss\');">' . $key;
$key = $this->randomName(16);
$bad_translations[$key] = '<<SCRIPT>alert("xss");//<</SCRIPT>' . $key;
$key = $this->randomName(16);
$bad_translations[$key] = "<BODY ONLOAD=alert('xss')>" . $key;
Dries Buytaert
committed
// Add custom language.
$edit = array(
Dries Buytaert
committed
'langcode' => $langcode,
'name' => $name,
'native' => $native,
'prefix' => $prefix,
'direction' => '0',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
Dries Buytaert
committed
// Add string.
Dries Buytaert
committed
t($name, array(), array('langcode' => $langcode));
Dries Buytaert
committed
// Reset locale cache.
$search = array(
Dries Buytaert
committed
'string' => $name,
'language' => 'all',
'translation' => 'all',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// Find the edit path.
Dries Buytaert
committed
$content = $this->drupalGetContent();
$this->assertTrue(preg_match('@(admin/config/regional/translate/edit/[0-9]+)@', $content, $matches), 'Found the edit path.');
Dries Buytaert
committed
$path = $matches[0];
foreach ($bad_translations as $key => $translation) {
$edit = array(
Dries Buytaert
committed
"translations[$langcode]" => $translation,
);
$this->drupalPost($path, $edit, t('Save translations'));
// Check for a form error on the textarea.
$form_class = $this->xpath('//form[@id="locale-translate-edit-form"]//textarea/@class');
$this->assertNotIdentical(FALSE, strpos($form_class[0], 'error'), 'The string was rejected as unsafe.');
$this->assertNoText(t('The string has been saved.'), 'The string was not saved.');
Dries Buytaert
committed
}
}
/**
* Tests translation search form.
*/
function testStringSearch() {
global $base_url;
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
// User to translate and delete string.
$translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
// Code for the language.
$langcode = 'xx';
// The English name for the language. This will be translated.
$name = $this->randomName(16);
// The native name for the language.
$native = $this->randomName(16);
// The domain prefix.
$prefix = $langcode;
// This is the language indicator on the translation search screen for
// untranslated strings. Copied straight from locale.inc.
$language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
// This will be the translation of $name.
$translation = $this->randomName(16);
// Add custom language.
$this->drupalLogin($admin_user);
$edit = array(
'langcode' => $langcode,
'name' => $name,
'native' => $native,
'prefix' => $prefix,
'direction' => '0',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
// Add string.
Dries Buytaert
committed
t($name, array(), array('langcode' => $langcode));
// Reset locale cache.
locale_reset();
$this->drupalLogout();
// Search for the name.
$this->drupalLogin($translate_user);
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'all',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// assertText() seems to remove the input field where $name always could be
// found, so this is not a false assert. See how assertNoText succeeds
// later.
$this->assertText($name, 'Search found the string.');
// Ensure untranslated string doesn't appear if searching on 'only
// translated strings'.
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'translated',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), "Search didn't find the string.");
// Ensure untranslated string appears if searching on 'only untranslated
// strings'.
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'untranslated',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), 'Search found the string.');
// Add translation.
// Assume this is the only result, given the random name.
$this->clickLink(t('edit'));
// We save the lid from the path.
$matches = array();
Dries Buytaert
committed
preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches);
$lid = $matches[1];
$edit = array(
"translations[$langcode]" => $translation,
);
$this->drupalPost(NULL, $edit, t('Save translations'));
// Ensure translated string does appear if searching on 'only
// translated strings'.
$search = array(
'string' => $translation,
'language' => 'all',
'translation' => 'translated',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), 'Search found the translation.');
// Ensure translated source string doesn't appear if searching on 'only
// untranslated strings'.
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'untranslated',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), "Search didn't find the source string.");
// Ensure translated string doesn't appear if searching on 'only
// untranslated strings'.
$search = array(
'string' => $translation,
'language' => 'all',
'translation' => 'untranslated',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), "Search didn't find the translation.");
// Ensure translated string does appear if searching on the custom language.
$search = array(
'string' => $translation,
'language' => $langcode,
'translation' => 'all',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), 'Search found the translation.');
// Ensure translated string doesn't appear if searching on English.
$search = array(
'string' => $translation,
'language' => 'en',
'translation' => 'all',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), "Search didn't find the translation.");
// Search for a string that isn't in the system.
$unavailable_string = $this->randomName(16);
$search = array(
'string' => $unavailable_string,
'language' => 'all',
'translation' => 'all',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), "Search didn't find the invalid string.");
}
}
Dries Buytaert
committed
/**
* Functional tests for the import of translation files.
*/
class LocaleImportFunctionalTest extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
Dries Buytaert
committed
return array(
'name' => 'Translation import',
'description' => 'Tests the importation of locale files.',
'group' => 'Locale',
Dries Buytaert
committed
);
}
/**
* A user able to create languages and import translations.
*/
protected $admin_user = NULL;
function setUp() {
Angie Byron
committed
parent::setUp('locale', 'locale_test');
Dries Buytaert
committed
$this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
$this->drupalLogin($this->admin_user);
}
/**
* Test importation of standalone .po files.
*/
function testStandalonePoFile() {
// Try importing a .po file.
Dries Buytaert
committed
$this->importPoFile($this->getPoFile(), array(
Dries Buytaert
committed
'langcode' => 'fr',
Dries Buytaert
committed
));
Dries Buytaert
committed
// The import should automatically create the corresponding language.
$this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), 'The language has been automatically created.');
// The import should have created 7 strings.
$this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 7, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
Dries Buytaert
committed
Dries Buytaert
committed
// This import should have saved plural forms to have 2 variants.
$this->assert(db_query("SELECT plurals FROM {languages} WHERE language = 'fr'")->fetchField() == 2, 'Plural number initialized.');
Dries Buytaert
committed
// Ensure we were redirected correctly.
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate', array('absolute' => TRUE)), 'Correct page redirection.');
Dries Buytaert
committed
Dries Buytaert
committed
Angie Byron
committed
// Try importing a .po file with invalid tags in the default text group.
Dries Buytaert
committed
$this->importPoFile($this->getBadPoFile(), array(
Dries Buytaert
committed
'langcode' => 'fr',
Dries Buytaert
committed
));
// The import should have created 1 string and rejected 2.
$this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
Dries Buytaert
committed
$skip_message = format_plural(2, 'One translation string was skipped because it contains disallowed HTML.', '@count translation strings were skipped because they contain disallowed HTML.');
$this->assertRaw($skip_message, 'Unsafe strings were skipped.');
Dries Buytaert
committed
Dries Buytaert
committed
Angie Byron
committed
// Try importing a .po file with invalid tags in a non default text group.
Dries Buytaert
committed
$this->importPoFile($this->getBadPoFile(), array(
Angie Byron
committed
'langcode' => 'fr',
'group' => 'custom',
Dries Buytaert
committed
));
// The import should have created 3 strings.
$this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 3, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
Dries Buytaert
committed
// Try importing a .po file which doesn't exist.
$name = $this->randomName(16);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/import', array(
'langcode' => 'fr',
'files[file]' => $name,
'group' => 'custom',
), t('Import'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/import', array('absolute' => TRUE)), 'Correct page redirection.');
$this->assertText(t('File to import not found.'), 'File to import not found message.');
Dries Buytaert
committed
// Try importing a .po file with overriding strings, and ensure existing
// strings are kept.
Dries Buytaert
committed
$this->importPoFile($this->getOverwritePoFile(), array(
'langcode' => 'fr',
'mode' => 1, // Existing strings are kept, only new strings are added.
Dries Buytaert
committed
));
// The import should have created 1 string.
$this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
// Ensure string wasn't overwritten.
$search = array(
'string' => 'Montag',
'language' => 'fr',
'translation' => 'translated',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), 'String not overwritten by imported string.');
Dries Buytaert
committed
// This import should not have changed number of plural forms.
$this->assert(db_query("SELECT plurals FROM {languages} WHERE language = 'fr'")->fetchField() == 2, 'Plural numbers untouched.');
Dries Buytaert
committed
// Try importing a .po file with overriding strings, and ensure existing
// strings are overwritten.
Dries Buytaert
committed
$this->importPoFile($this->getOverwritePoFile(), array(
'langcode' => 'fr',
'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added.
Dries Buytaert
committed
));
// The import should have updated 2 strings.
$this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), 'The translation file was successfully imported.');
// Ensure string was overwritten.
$search = array(
'string' => 'Montag',
'language' => 'fr',
'translation' => 'translated',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), 'String overwritten by imported string.');
Dries Buytaert
committed
// This import should have changed number of plural forms.
$this->assert(db_query("SELECT plurals FROM {languages} WHERE language = 'fr'")->fetchField() == 3, 'Plural numbers changed.');
}
/**
* Test automatic importation of a module's translation files when a language
* is enabled.
*/
function testAutomaticModuleTranslationImportLanguageEnable() {
// Code for the language - manually set to match the test translation file.
$langcode = 'xx';
// The English name for the language.
$name = $this->randomName(16);
// The native name for the language.
$native = $this->randomName(16);
// The domain prefix.
$prefix = $langcode;
// Create a custom language.
$edit = array(
'langcode' => $langcode,
'name' => $name,
'native' => $native,
'prefix' => $prefix,
'direction' => '0',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
// Ensure the translation file was automatically imported when language was
// added.
$this->assertText(t('One translation file imported for the enabled modules.'), 'Language file automatically imported.');
// Ensure strings were successfully imported.
$search = array(
'string' => 'lundi',
'language' => $langcode,
'translation' => 'translated',
'group' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), 'String successfully imported.');
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
* Test automatic importation of a module's translation files when a language
* is enabled.
*/
function testLanguageContext() {
// Try importing a .po file.
$this->importPoFile($this->getPoFileWithContext(), array(
'langcode' => 'hr',
));
$this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', 'Long month name context is working.');
$this->assertIdentical(t('May', array(), array('langcode' => 'hr')), 'Svi.', 'Default context is working.');
Dries Buytaert
committed
}
/**
* Helper function: import a standalone .po file in a given language.
*
* @param $contents
* Contents of the .po file to import.
* @param $options
* Additional options to pass to the translation import form.
*/
function importPoFile($contents, array $options = array()) {
Dries Buytaert
committed
$name = tempnam(file_directory_path('temporary'), "po_") . '.po';
Dries Buytaert
committed
file_put_contents($name, $contents);
$options['files[file]'] = $name;
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
Dries Buytaert
committed
unlink($name);
}
Dries Buytaert
committed
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
/**
* Helper function that returns a proper .po file.
*/
function getPoFile() {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 6\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
msgid "Monday"
msgstr "lundi"
msgid "Tuesday"
msgstr "mardi"
msgid "Wednesday"
msgstr "mercredi"
msgid "Thursday"
msgstr "jeudi"
msgid "Friday"
msgstr "vendredi"
msgid "Saturday"
msgstr "samedi"
msgid "Sunday"
msgstr "dimanche"
Dries Buytaert
committed
EOF;
}
/**
* Helper function that returns a bad .po file.
Dries Buytaert
committed
*/
function getBadPoFile() {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 6\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
msgid "Save configuration"
msgstr "Enregistrer la configuration"
msgid "edit"
msgstr "modifier<img SRC="javascript:alert(\'xss\');">"
msgid "delete"
msgstr "supprimer<script>alert('xss');</script>"
Dries Buytaert
committed
EOF;
}
/**
* Helper function that returns a proper .po file, for testing overwriting
* existing translations.
*/
function getOverwritePoFile() {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 6\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
Dries Buytaert
committed
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
msgid "Monday"
msgstr "Montag"
msgid "Day"
msgstr "Jour"
EOF;
}
Dries Buytaert
committed
/**
* Helper function that returns a .po file with context.
*/
function getPoFileWithContext() {
// Croatian (code hr) is one the the languages that have a different
// form for the full name and the abbreviated name for the month May.
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 6\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
msgctxt "Long month name"
msgid "May"
msgstr "Svibanj"
msgid "May"
msgstr "Svi."
EOF;
}
}
/**
* Functional tests for the export of translation files.
*/
class LocaleExportFunctionalTest extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
return array(
'name' => 'Translation export',
'description' => 'Tests the exportation of locale files.',
'group' => 'Locale',
);
}
/**
* A user able to create languages and export translations.
*/
protected $admin_user = NULL;
function setUp() {
parent::setUp('locale', 'locale_test');
$this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
$this->drupalLogin($this->admin_user);
}
/**
* Test exportation of translations.
*/
function testExportTranslation() {
// First import some known translations.
// This will also automatically enable the 'fr' language.
Dries Buytaert
committed
$name = tempnam(file_directory_path('temporary'), "po_") . '.po';
file_put_contents($name, $this->getPoFile());
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/import', array(
'langcode' => 'fr',
'files[file]' => $name,
), t('Import'));
unlink($name);
// Get the French translations.
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/export', array(
'langcode' => 'fr',
), t('Export'));
// Ensure we have a translation file.
$this->assertRaw('# French translation of Drupal', 'Exported French translation file.');
// Ensure our imported translations exist in the file.
$this->assertRaw('msgstr "lundi"', 'French translations present in exported file.');
}
/**
* Test exportation of translation template file.
*/
function testExportTranslationTemplateFile() {
// Get the translation template file.
// There are two 'Export' buttons on this page, but it somehow works. It'd
// be better if we could use the submit button id like documented but that
// doesn't work.
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/export', array(), t('Export'));
// Ensure we have a translation file.
$this->assertRaw('# LANGUAGE translation of PROJECT', 'Exported translation template file.');
}
/**
* Helper function that returns a proper .po file.
*/
function getPoFile() {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 6\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
msgid "Monday"
msgstr "lundi"
EOF;
}
Dries Buytaert
committed
}
Angie Byron
committed
/**
* Tests for the st() function.
*/
class LocaleInstallTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'String translation using st()',
'description' => 'Tests that st() works like t().',
'group' => 'Locale',
);
}
function setUp() {
parent::setUp('locale');
// st() lives in install.inc, so ensure that it is loaded for all tests.
require_once DRUPAL_ROOT . '/includes/install.inc';
}
/**
* Verify that function signatures of t() and st() are equal.
*/
function testFunctionSignatures() {
$reflector_t = new ReflectionFunction('t');
$reflector_st = new ReflectionFunction('st');
$this->assertEqual($reflector_t->getParameters(), $reflector_st->getParameters(), 'Function signatures of t() and st() are equal.');
}
}
/**
* Locale uninstall with English UI functional test.
*/
class LocaleUninstallFunctionalTest extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
'name' => 'Locale uninstall (EN)',
'description' => 'Tests the uninstall process using the built-in UI language.',
'group' => 'Locale',