LanguageSwitchingTest.php 22.4 KB
Newer Older
1
2
3
4
<?php

namespace Drupal\language\Tests;

5
6
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
7
use Drupal\menu_link_content\Entity\MenuLinkContent;
8
use Drupal\Core\Language\LanguageInterface;
9
10
11
12
use Drupal\simpletest\WebTestBase;

/**
 * Functional tests for the language switching feature.
13
14
 *
 * @group language
15
16
17
 */
class LanguageSwitchingTest extends WebTestBase {

18
19
20
21
22
  /**
   * Modules to enable.
   *
   * @var array
   */
23
  public static $modules = array('locale', 'locale_test', 'language', 'block', 'language_test', 'menu_ui');
24

25
  protected function setUp() {
26
    parent::setUp();
27

28
    // Create and log in user.
29
30
31
32
33
34
35
36
37
38
39
40
    $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer languages', 'access administration pages'));
    $this->drupalLogin($admin_user);
  }

  /**
   * Functional tests for the language switcher block.
   */
  function testLanguageBlock() {
    // Add language.
    $edit = array(
      'predefined_langcode' => 'fr',
    );
41
    $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
42

43
44
45
    // Set the native language name.
    $this->saveNativeLanguageName('fr', 'français');

46
47
    // Enable URL language detection and selection.
    $edit = array('language_interface[enabled][language-url]' => '1');
48
    $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
49

50
51
52
53
54
55
56
    // Enable the language switching block.
    $block = $this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, array(
      'id' => 'test_language_block',
      // Ensure a 2-byte UTF-8 sequence is in the tested output.
      'label' => $this->randomMachineName(8) . '×',
    ));

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
    $this->doTestLanguageBlockAuthenticated($block->label());
    $this->doTestLanguageBlockAnonymous($block->label());
  }

  /**
   * For authenticated users, the "active" class is set by JavaScript.
   *
   * @param string $block_label
   *   The label of the language switching block.
   *
   * @see testLanguageBlock()
   */
  protected function doTestLanguageBlockAuthenticated($block_label) {
    // Assert that the language switching block is displayed on the frontpage.
    $this->drupalGet('');
    $this->assertText($block_label, 'Language switcher block found.');

    // Assert that each list item and anchor element has the appropriate data-
    // attributes.
76
    list($language_switcher) = $this->xpath('//div[@id=:id]', array(':id' => 'block-test-language-block'));
77
78
    $list_items = array();
    $anchors = array();
79
    $labels = array();
80
81
82
83
84
85
86
87
88
89
90
    foreach ($language_switcher->ul->li as $list_item) {
      $classes = explode(" ", (string) $list_item['class']);
      list($langcode) = array_intersect($classes, array('en', 'fr'));
      $list_items[] = array(
        'langcode_class' => $langcode,
        'data-drupal-link-system-path' => (string) $list_item['data-drupal-link-system-path'],
      );
      $anchors[] = array(
        'hreflang' => (string) $list_item->a['hreflang'],
        'data-drupal-link-system-path' => (string) $list_item->a['data-drupal-link-system-path'],
      );
91
      $labels[] = (string) $list_item->a;
92
93
94
95
96
97
98
99
100
101
102
    }
    $expected_list_items = array(
      0 => array('langcode_class' => 'en', 'data-drupal-link-system-path' => 'user/2'),
      1 => array('langcode_class' => 'fr', 'data-drupal-link-system-path' => 'user/2'),
    );
    $this->assertIdentical($list_items, $expected_list_items, 'The list items have the correct attributes that will allow the drupal.active-link library to mark them as active.');
    $expected_anchors = array(
      0 => array('hreflang' => 'en', 'data-drupal-link-system-path' => 'user/2'),
      1 => array('hreflang' => 'fr', 'data-drupal-link-system-path' => 'user/2'),
    );
    $this->assertIdentical($anchors, $expected_anchors, 'The anchors have the correct attributes that will allow the drupal.active-link library to mark them as active.');
103
    $settings = $this->getDrupalSettings();
104
105
106
    $this->assertIdentical($settings['path']['currentPath'], 'user/2', 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
    $this->assertIdentical($settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
    $this->assertIdentical($settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
107
    $this->assertIdentical($labels, array('English', 'français'), 'The language links labels are in their own language on the language switcher block.');
108
109
110
111
112
113
114
115
116
117
118
119
120
  }

  /**
   * For anonymous users, the "active" class is set by PHP.
   *
   * @param string $block_label
   *   The label of the language switching block.
   *
   * @see testLanguageBlock()
   */
  protected function doTestLanguageBlockAnonymous($block_label) {
    $this->drupalLogout();

121
122
123
    // Assert that the language switching block is displayed on the frontpage
    // and ensure that the active class is added when query params are present.
    $this->drupalGet('', ['query' => ['foo' => 'bar']]);
124
    $this->assertText($block_label, 'Language switcher block found.');
125
126

    // Assert that only the current language is marked as active.
127
    list($language_switcher) = $this->xpath('//div[@id=:id]', array(':id' => 'block-test-language-block'));
128
129
130
131
132
133
134
135
    $links = array(
      'active' => array(),
      'inactive' => array(),
    );
    $anchors = array(
      'active' => array(),
      'inactive' => array(),
    );
136
    $labels = array();
137
138
139
    foreach ($language_switcher->ul->li as $link) {
      $classes = explode(" ", (string) $link['class']);
      list($langcode) = array_intersect($classes, array('en', 'fr'));
140
      if (in_array('is-active', $classes)) {
141
142
143
144
145
146
        $links['active'][] = $langcode;
      }
      else {
        $links['inactive'][] = $langcode;
      }
      $anchor_classes = explode(" ", (string) $link->a['class']);
147
      if (in_array('is-active', $anchor_classes)) {
148
149
150
151
152
        $anchors['active'][] = $langcode;
      }
      else {
        $anchors['inactive'][] = $langcode;
      }
153
      $labels[] = (string) $link->a;
154
    }
155
156
    $this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language list item is marked as active on the language switcher block.');
    $this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language anchor is marked as active on the language switcher block.');
157
    $this->assertIdentical($labels, array('English', 'français'), 'The language links labels are in their own language on the language switcher block.');
158
  }
159

160
  /**
161
   * Test language switcher links for domain based negotiation.
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
   */
  function testLanguageBlockWithDomain() {
    // Add the Italian language.
    ConfigurableLanguage::createFromLangcode('it')->save();

    // Rebuild the container so that the new language is picked up by services
    // that hold a list of languages.
    $this->rebuildContainer();

    $languages = $this->container->get('language_manager')->getLanguages();

    // Enable browser and URL language detection.
    $edit = array(
      'language_interface[enabled][language-url]' => TRUE,
      'language_interface[weight][language-url]' => -10,
    );
    $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));

    // Do not allow blank domain.
    $edit = array(
      'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
      'domain[en]' => '',
    );
    $this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
    $this->assertText(t('The domain may not be left blank for English'), 'The form does not allow blank domains.');

    // Change the domain for the Italian language.
    $edit = array(
      'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
      'domain[en]' => \Drupal::request()->getHost(),
      'domain[it]' => 'it.example.com',
    );
    $this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
    $this->assertText(t('The configuration options have been saved'), 'Domain configuration is saved.');

    // Enable the language switcher block.
    $this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, array('id' => 'test_language_block'));

    $this->drupalGet('');

    /** @var \Drupal\Core\Routing\UrlGenerator $generator */
    $generator = $this->container->get('url_generator');

205
    // Verify the English URL is correct
206
207
208
209
    list($english_link) = $this->xpath('//div[@id=:id]/ul/li/a[@hreflang=:hreflang]', array(
      ':id' => 'block-test-language-block',
      ':hreflang' => 'en',
    ));
210
    $english_url = $generator->generateFromRoute('entity.user.canonical', array('user' => 2), array('language' => $languages['en']));
211
212
    $this->assertEqual($english_url, (string) $english_link['href']);

213
    // Verify the Italian URL is correct
214
215
216
217
    list($italian_link) = $this->xpath('//div[@id=:id]/ul/li/a[@hreflang=:hreflang]', array(
      ':id' => 'block-test-language-block',
      ':hreflang' => 'it',
    ));
218
    $italian_url = $generator->generateFromRoute('entity.user.canonical', array('user' => 2), array('language' => $languages['it']));
219
220
221
    $this->assertEqual($italian_url, (string) $italian_link['href']);
  }

222
223
224
225
226
227
228
229
  /**
   * Test active class on links when switching languages.
   */
  function testLanguageLinkActiveClass() {
    // Add language.
    $edit = array(
      'predefined_langcode' => 'fr',
    );
230
    $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
231
232
233

    // Enable URL language detection and selection.
    $edit = array('language_interface[enabled][language-url]' => '1');
234
    $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
235

236
237
238
239
    $this->doTestLanguageLinkActiveClassAuthenticated();
    $this->doTestLanguageLinkActiveClassAnonymous();
  }

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
  /**
   * Check the path-admin class, as same as on default language.
   */
  function testLanguageBodyClass() {
    $searched_class = 'path-admin';

    // Add language.
    $edit = array(
      'predefined_langcode' => 'fr',
    );
    $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));

    // Enable URL language detection and selection.
    $edit = array('language_interface[enabled][language-url]' => '1');
    $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));

256
    // Check if the default (English) admin/config page has the right class.
257
258
259
260
    $this->drupalGet('admin/config');
    $class = $this->xpath('//body[contains(@class, :class)]', array(':class' => $searched_class));
    $this->assertTrue(isset($class[0]), t('The path-admin class appears on default language.'));

261
    // Check if the French admin/config page has the right class.
262
263
264
    $this->drupalGet('fr/admin/config');
    $class = $this->xpath('//body[contains(@class, :class)]', array(':class' => $searched_class));
    $this->assertTrue(isset($class[0]), t('The path-admin class same as on default language.'));
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280

    // The testing profile sets the user/login page as the frontpage. That
    // redirects authenticated users to their profile page, so check with an
    // anonymous user instead.
    $this->drupalLogout();

    // Check if the default (English) frontpage has the right class.
    $this->drupalGet('<front>');
    $class = $this->xpath('//body[contains(@class, :class)]', array(':class' => 'path-frontpage'));
    $this->assertTrue(isset($class[0]), 'path-frontpage class found on the body tag');

    // Check if the French frontpage has the right class.
    $this->drupalGet('fr');
    $class = $this->xpath('//body[contains(@class, :class)]', array(':class' => 'path-frontpage'));
    $this->assertTrue(isset($class[0]), 'path-frontpage class found on the body tag with french as the active language');

281
282
  }

283
284
285
286
287
288
289
290
291
  /**
   * For authenticated users, the "active" class is set by JavaScript.
   *
   * @see testLanguageLinkActiveClass()
   */
  protected function doTestLanguageLinkActiveClassAuthenticated() {
    $function_name = '#type link';
    $path = 'language_test/type-link-active-class';

292
    // Test links generated by the link generator on an English page.
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
    $current_language = 'English';
    $this->drupalGet($path);

    // Language code 'none' link should be active.
    $langcode = 'none';
    $links = $this->xpath('//a[@id = :id and @data-drupal-link-system-path = :path]', array(':id' => 'no_lang_link', ':path' => $path));
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Language code 'en' link should be active.
    $langcode = 'en';
    $links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'en_link', ':lang' => 'en', ':path' => $path));
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Language code 'fr' link should not be active.
    $langcode = 'fr';
    $links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'fr_link', ':lang' => 'fr', ':path' => $path));
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to NOT mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Verify that drupalSettings contains the correct values.
312
    $settings = $this->getDrupalSettings();
313
314
315
316
    $this->assertIdentical($settings['path']['currentPath'], $path, 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
    $this->assertIdentical($settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
    $this->assertIdentical($settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');

317
    // Test links generated by the link generator on a French page.
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
    $current_language = 'French';
    $this->drupalGet('fr/language_test/type-link-active-class');

    // Language code 'none' link should be active.
    $langcode = 'none';
    $links = $this->xpath('//a[@id = :id and @data-drupal-link-system-path = :path]', array(':id' => 'no_lang_link', ':path' => $path));
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Language code 'en' link should not be active.
    $langcode = 'en';
    $links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'en_link', ':lang' => 'en', ':path' => $path));
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to NOT mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Language code 'fr' link should be active.
    $langcode = 'fr';
    $links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'fr_link', ':lang' => 'fr', ':path' => $path));
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Verify that drupalSettings contains the correct values.
337
    $settings = $this->getDrupalSettings();
338
339
340
341
342
343
344
345
346
347
348
    $this->assertIdentical($settings['path']['currentPath'], $path, 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
    $this->assertIdentical($settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
    $this->assertIdentical($settings['path']['currentLanguage'], 'fr', 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
  }

  /**
   * For anonymous users, the "active" class is set by PHP.
   *
   * @see testLanguageLinkActiveClass()
   */
  protected function doTestLanguageLinkActiveClassAnonymous() {
349
    $function_name = '#type link';
350
351

    $this->drupalLogout();
352

353
    // Test links generated by the link generator on an English page.
354
    $current_language = 'English';
355
    $this->drupalGet('language_test/type-link-active-class');
356
357
358

    // Language code 'none' link should be active.
    $langcode = 'none';
359
    $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'no_lang_link', ':class' => 'is-active'));
360
361
362
363
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Language code 'en' link should be active.
    $langcode = 'en';
364
    $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'en_link', ':class' => 'is-active'));
365
366
367
368
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Language code 'fr' link should not be active.
    $langcode = 'fr';
369
    $links = $this->xpath('//a[@id = :id and not(contains(@class, :class))]', array(':id' => 'fr_link', ':class' => 'is-active'));
370
371
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is NOT marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

372
    // Test links generated by the link generator on a French page.
373
    $current_language = 'French';
374
    $this->drupalGet('fr/language_test/type-link-active-class');
375
376
377

    // Language code 'none' link should be active.
    $langcode = 'none';
378
    $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'no_lang_link', ':class' => 'is-active'));
379
380
381
382
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Language code 'en' link should not be active.
    $langcode = 'en';
383
    $links = $this->xpath('//a[@id = :id and not(contains(@class, :class))]', array(':id' => 'en_link', ':class' => 'is-active'));
384
385
386
387
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is NOT marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));

    // Language code 'fr' link should be active.
    $langcode = 'fr';
388
    $links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'fr_link', ':class' => 'is-active'));
389
390
391
    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
  }

392
  /**
393
   * Tests language switcher links for session based negotiation.
394
   */
395
  public function testLanguageSessionSwitchLinks() {
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
    // Add language.
    $edit = array(
      'predefined_langcode' => 'fr',
    );
    $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));

    // Enable session language detection and selection.
    $edit = array(
      'language_interface[enabled][language-url]' => FALSE,
      'language_interface[enabled][language-session]' => TRUE,
    );
    $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));

    // Enable the language switching block.
    $this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, array(
      'id' => 'test_language_block',
    ));

    // Enable the main menu block.
    $this->drupalPlaceBlock('system_menu_block:main', array(
      'id' => 'test_menu',
    ));

    // Add a link to the homepage.
    $link = MenuLinkContent::create([
      'title' => 'Home',
      'menu_name' => 'main',
      'bundle' => 'menu_link_content',
      'link' => [['uri' => 'entity:user/2']],
    ]);
    $link->save();

    // Go to the homepage.
    $this->drupalGet('');
    // Click on the French link.
    $this->clickLink(t('French'));
    // There should be a query parameter to set the session language.
    $this->assertUrl('user/2', ['query' => ['language' => 'fr']]);
    // Click on the 'Home' Link.
    $this->clickLink(t('Home'));
    // There should be no query parameter.
    $this->assertUrl('user/2');
    // Click on the French link.
    $this->clickLink(t('French'));
    // There should be no query parameter.
    $this->assertUrl('user/2');
  }

444
445
446
447
448
449
450
451
452
453
454
455
456
  /**
   * Saves the native name of a language entity in configuration as a label.
   *
   * @param string $langcode
   *   The language code of the language.
   * @param string $label
   *   The native name of the language.
   */
  protected function saveNativeLanguageName($langcode, $label) {
    \Drupal::service('language.config_factory_override')
      ->getOverride($langcode, 'language.entity.' . $langcode)->set('label', $label)->save();
  }

457
}