1<?php 2 3/** 4 * @file 5 * Tests for common.inc functionality. 6 */ 7 8/** 9 * Tests for URL generation functions. 10 */ 11class DrupalAlterTestCase extends DrupalWebTestCase { 12 public static function getInfo() { 13 return array( 14 'name' => 'drupal_alter() tests', 15 'description' => 'Confirm that alteration of arguments passed to drupal_alter() works correctly.', 16 'group' => 'System', 17 ); 18 } 19 20 function setUp() { 21 parent::setUp('common_test'); 22 } 23 24 function testDrupalAlter() { 25 // This test depends on Bartik, so make sure that it is always the current 26 // active theme. 27 global $theme, $base_theme_info; 28 $theme = 'bartik'; 29 $base_theme_info = array(); 30 31 $array = array('foo' => 'bar'); 32 $entity = new stdClass(); 33 $entity->foo = 'bar'; 34 35 // Verify alteration of a single argument. 36 $array_copy = $array; 37 $array_expected = array('foo' => 'Drupal theme'); 38 drupal_alter('drupal_alter', $array_copy); 39 $this->assertEqual($array_copy, $array_expected, 'Single array was altered.'); 40 41 $entity_copy = clone $entity; 42 $entity_expected = clone $entity; 43 $entity_expected->foo = 'Drupal theme'; 44 drupal_alter('drupal_alter', $entity_copy); 45 $this->assertEqual($entity_copy, $entity_expected, 'Single object was altered.'); 46 47 // Verify alteration of multiple arguments. 48 $array_copy = $array; 49 $array_expected = array('foo' => 'Drupal theme'); 50 $entity_copy = clone $entity; 51 $entity_expected = clone $entity; 52 $entity_expected->foo = 'Drupal theme'; 53 $array2_copy = $array; 54 $array2_expected = array('foo' => 'Drupal theme'); 55 drupal_alter('drupal_alter', $array_copy, $entity_copy, $array2_copy); 56 $this->assertEqual($array_copy, $array_expected, 'First argument to drupal_alter() was altered.'); 57 $this->assertEqual($entity_copy, $entity_expected, 'Second argument to drupal_alter() was altered.'); 58 $this->assertEqual($array2_copy, $array2_expected, 'Third argument to drupal_alter() was altered.'); 59 60 // Verify alteration order when passing an array of types to drupal_alter(). 61 // common_test_module_implements_alter() places 'block' implementation after 62 // other modules. 63 $array_copy = $array; 64 $array_expected = array('foo' => 'Drupal block theme'); 65 drupal_alter(array('drupal_alter', 'drupal_alter_foo'), $array_copy); 66 $this->assertEqual($array_copy, $array_expected, 'hook_TYPE_alter() implementations ran in correct order.'); 67 } 68} 69 70/** 71 * Tests for URL generation functions. 72 * 73 * url() calls module_implements(), which may issue a db query, which requires 74 * inheriting from a web test case rather than a unit test case. 75 */ 76class CommonURLUnitTest extends DrupalWebTestCase { 77 public static function getInfo() { 78 return array( 79 'name' => 'URL generation unit tests', 80 'description' => 'Confirm that url(), drupal_get_query_parameters(), drupal_http_build_query(), and l() work correctly with various input.', 81 'group' => 'System', 82 ); 83 } 84 85 /** 86 * Confirm that invalid text given as $path is filtered. 87 */ 88 function testLXSS() { 89 $text = $this->randomName(); 90 $path = "<SCRIPT>alert('XSS')</SCRIPT>"; 91 $link = l($text, $path); 92 $sanitized_path = check_url(url($path)); 93 $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered', array('@path' => $path))); 94 } 95 96 /* 97 * Tests for active class in l() function. 98 */ 99 function testLActiveClass() { 100 $link = l($this->randomName(), $_GET['q']); 101 $this->assertTrue($this->hasClass($link, 'active'), format_string('Class @class is present on link to the current page', array('@class' => 'active'))); 102 } 103 104 /** 105 * Tests for custom class in l() function. 106 */ 107 function testLCustomClass() { 108 $class = $this->randomName(); 109 $link = l($this->randomName(), $_GET['q'], array('attributes' => array('class' => array($class)))); 110 $this->assertTrue($this->hasClass($link, $class), format_string('Custom class @class is present on link when requested', array('@class' => $class))); 111 $this->assertTrue($this->hasClass($link, 'active'), format_string('Class @class is present on link to the current page', array('@class' => 'active'))); 112 } 113 114 private function hasClass($link, $class) { 115 return preg_match('|class="([^\"\s]+\s+)*' . $class . '|', $link); 116 } 117 118 /** 119 * Test drupal_get_query_parameters(). 120 */ 121 function testDrupalGetQueryParameters() { 122 $original = array( 123 'a' => 1, 124 'b' => array( 125 'd' => 4, 126 'e' => array( 127 'f' => 5, 128 ), 129 ), 130 'c' => 3, 131 'q' => 'foo/bar', 132 ); 133 134 // Default arguments. 135 $result = $_GET; 136 unset($result['q']); 137 $this->assertEqual(drupal_get_query_parameters(), $result, "\$_GET['q'] was removed."); 138 139 // Default exclusion. 140 $result = $original; 141 unset($result['q']); 142 $this->assertEqual(drupal_get_query_parameters($original), $result, "'q' was removed."); 143 144 // First-level exclusion. 145 $result = $original; 146 unset($result['b']); 147 $this->assertEqual(drupal_get_query_parameters($original, array('b')), $result, "'b' was removed."); 148 149 // Second-level exclusion. 150 $result = $original; 151 unset($result['b']['d']); 152 $this->assertEqual(drupal_get_query_parameters($original, array('b[d]')), $result, "'b[d]' was removed."); 153 154 // Third-level exclusion. 155 $result = $original; 156 unset($result['b']['e']['f']); 157 $this->assertEqual(drupal_get_query_parameters($original, array('b[e][f]')), $result, "'b[e][f]' was removed."); 158 159 // Multiple exclusions. 160 $result = $original; 161 unset($result['a'], $result['b']['e'], $result['c']); 162 $this->assertEqual(drupal_get_query_parameters($original, array('a', 'b[e]', 'c')), $result, "'a', 'b[e]', 'c' were removed."); 163 } 164 165 /** 166 * Test drupal_http_build_query(). 167 */ 168 function testDrupalHttpBuildQuery() { 169 $this->assertEqual(drupal_http_build_query(array('a' => ' &#//+%20@۞')), 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'); 170 $this->assertEqual(drupal_http_build_query(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'); 171 $this->assertEqual(drupal_http_build_query(array('a' => '1', 'b' => '2', 'c' => '3')), 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'); 172 $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a%5Bb%5D=2&a%5Bc%5D=3&d=foo', 'Nested array was properly encoded.'); 173 } 174 175 /** 176 * Test drupal_parse_url(). 177 */ 178 function testDrupalParseUrl() { 179 // Relative URL. 180 $url = 'foo/bar?foo=bar&bar=baz&baz#foo'; 181 $result = array( 182 'path' => 'foo/bar', 183 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), 184 'fragment' => 'foo', 185 ); 186 $this->assertEqual(drupal_parse_url($url), $result, 'Relative URL parsed correctly.'); 187 188 // Relative URL that is known to confuse parse_url(). 189 $url = 'foo/bar:1'; 190 $result = array( 191 'path' => 'foo/bar:1', 192 'query' => array(), 193 'fragment' => '', 194 ); 195 $this->assertEqual(drupal_parse_url($url), $result, 'Relative URL parsed correctly.'); 196 197 // Absolute URL. 198 $url = '/foo/bar?foo=bar&bar=baz&baz#foo'; 199 $result = array( 200 'path' => '/foo/bar', 201 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), 202 'fragment' => 'foo', 203 ); 204 $this->assertEqual(drupal_parse_url($url), $result, 'Absolute URL parsed correctly.'); 205 206 // External URL testing. 207 $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; 208 209 // Test that drupal can recognize an absolute URL. Used to prevent attack vectors. 210 $this->assertTrue(url_is_external($url), 'Correctly identified an external URL.'); 211 212 // External URL without an explicit protocol. 213 $url = '//drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; 214 $this->assertTrue(url_is_external($url), 'Correctly identified an external URL without a protocol part.'); 215 216 // Internal URL starting with a slash. 217 $url = '/drupal.org'; 218 $this->assertFalse(url_is_external($url), 'Correctly identified an internal URL with a leading slash.'); 219 220 // Test the parsing of absolute URLs. 221 $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; 222 $result = array( 223 'path' => 'http://drupal.org/foo/bar', 224 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), 225 'fragment' => 'foo', 226 ); 227 $this->assertEqual(drupal_parse_url($url), $result, 'External URL parsed correctly.'); 228 229 // Verify proper parsing of URLs when clean URLs are disabled. 230 $result = array( 231 'path' => 'foo/bar', 232 'query' => array('bar' => 'baz'), 233 'fragment' => 'foo', 234 ); 235 // Non-clean URLs #1: Absolute URL generated by url(). 236 $url = $GLOBALS['base_url'] . '/?q=foo/bar&bar=baz#foo'; 237 $this->assertEqual(drupal_parse_url($url), $result, 'Absolute URL with clean URLs disabled parsed correctly.'); 238 239 // Non-clean URLs #2: Relative URL generated by url(). 240 $url = '?q=foo/bar&bar=baz#foo'; 241 $this->assertEqual(drupal_parse_url($url), $result, 'Relative URL with clean URLs disabled parsed correctly.'); 242 243 // Non-clean URLs #3: URL generated by url() on non-Apache webserver. 244 $url = 'index.php?q=foo/bar&bar=baz#foo'; 245 $this->assertEqual(drupal_parse_url($url), $result, 'Relative URL on non-Apache webserver with clean URLs disabled parsed correctly.'); 246 247 // Test that drupal_parse_url() does not allow spoofing a URL to force a malicious redirect. 248 $parts = drupal_parse_url('forged:http://cwe.mitre.org/data/definitions/601.html'); 249 $this->assertFalse(valid_url($parts['path'], TRUE), 'drupal_parse_url() correctly parsed a forged URL.'); 250 } 251 252 /** 253 * Test url() with/without query, with/without fragment, absolute on/off and 254 * assert all that works when clean URLs are on and off. 255 */ 256 function testUrl() { 257 global $base_url; 258 259 foreach (array(FALSE, TRUE) as $absolute) { 260 // Get the expected start of the path string. 261 $base = $absolute ? $base_url . '/' : base_path(); 262 $absolute_string = $absolute ? 'absolute' : NULL; 263 264 // Disable Clean URLs. 265 $GLOBALS['conf']['clean_url'] = 0; 266 267 $url = $base . '?q=node/123'; 268 $result = url('node/123', array('absolute' => $absolute)); 269 $this->assertEqual($url, $result, "$url == $result"); 270 271 $url = $base . '?q=node/123#foo'; 272 $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); 273 $this->assertEqual($url, $result, "$url == $result"); 274 275 $url = $base . '?q=node/123&foo'; 276 $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); 277 $this->assertEqual($url, $result, "$url == $result"); 278 279 $url = $base . '?q=node/123&foo=bar&bar=baz'; 280 $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); 281 $this->assertEqual($url, $result, "$url == $result"); 282 283 $url = $base . '?q=node/123&foo#bar'; 284 $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); 285 $this->assertEqual($url, $result, "$url == $result"); 286 287 $url = $base . '?q=node/123&foo#0'; 288 $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '0', 'absolute' => $absolute)); 289 $this->assertEqual($url, $result, "$url == $result"); 290 291 $url = $base . '?q=node/123&foo'; 292 $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '', 'absolute' => $absolute)); 293 $this->assertEqual($url, $result, "$url == $result"); 294 295 $url = $base; 296 $result = url('<front>', array('absolute' => $absolute)); 297 $this->assertEqual($url, $result, "$url == $result"); 298 299 // Enable Clean URLs. 300 $GLOBALS['conf']['clean_url'] = 1; 301 302 $url = $base . 'node/123'; 303 $result = url('node/123', array('absolute' => $absolute)); 304 $this->assertEqual($url, $result, "$url == $result"); 305 306 $url = $base . 'node/123#foo'; 307 $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); 308 $this->assertEqual($url, $result, "$url == $result"); 309 310 $url = $base . 'node/123?foo'; 311 $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); 312 $this->assertEqual($url, $result, "$url == $result"); 313 314 $url = $base . 'node/123?foo=bar&bar=baz'; 315 $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); 316 $this->assertEqual($url, $result, "$url == $result"); 317 318 $url = $base . 'node/123?foo#bar'; 319 $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); 320 $this->assertEqual($url, $result, "$url == $result"); 321 322 $url = $base; 323 $result = url('<front>', array('absolute' => $absolute)); 324 $this->assertEqual($url, $result, "$url == $result"); 325 } 326 } 327 328 /** 329 * Test external URL handling. 330 */ 331 function testExternalUrls() { 332 $test_url = 'http://drupal.org/'; 333 334 // Verify external URL can contain a fragment. 335 $url = $test_url . '#drupal'; 336 $result = url($url); 337 $this->assertEqual($url, $result, 'External URL with fragment works without a fragment in $options.'); 338 339 // Verify fragment can be overidden in an external URL. 340 $url = $test_url . '#drupal'; 341 $fragment = $this->randomName(10); 342 $result = url($url, array('fragment' => $fragment)); 343 $this->assertEqual($test_url . '#' . $fragment, $result, 'External URL fragment is overidden with a custom fragment in $options.'); 344 345 // Verify external URL can contain a query string. 346 $url = $test_url . '?drupal=awesome'; 347 $result = url($url); 348 $this->assertEqual($url, $result, 'External URL with query string works without a query string in $options.'); 349 350 // Verify external URL can be extended with a query string. 351 $url = $test_url; 352 $query = array($this->randomName(5) => $this->randomName(5)); 353 $result = url($url, array('query' => $query)); 354 $this->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, 'External URL can be extended with a query string in $options.'); 355 356 // Verify query string can be extended in an external URL. 357 $url = $test_url . '?drupal=awesome'; 358 $query = array($this->randomName(5) => $this->randomName(5)); 359 $result = url($url, array('query' => $query)); 360 $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'External URL query string can be extended with a custom query string in $options.'); 361 362 // Verify that an internal URL does not result in an external URL without 363 // protocol part. 364 $url = '/drupal.org'; 365 $result = url($url); 366 $this->assertTrue(strpos($result, '//') === FALSE, 'Internal URL does not turn into an external URL.'); 367 368 // Verify that an external URL without protocol part is recognized as such. 369 $url = '//drupal.org'; 370 $result = url($url); 371 $this->assertEqual($url, $result, 'External URL without protocol is not altered.'); 372 } 373} 374 375/** 376 * Web tests for URL generation functions. 377 */ 378class CommonURLWebTest extends DrupalWebTestCase { 379 public static function getInfo() { 380 return array( 381 'name' => 'URL generation web tests', 382 'description' => 'Confirm that URL-generating functions work correctly on specific site paths.', 383 'group' => 'System', 384 ); 385 } 386 387 function setUp() { 388 parent::setUp('common_test'); 389 } 390 391 /** 392 * Tests the url() function on internal paths which mimic external URLs. 393 */ 394 function testInternalPathMimicsExternal() { 395 // Ensure that calling url(current_path()) on "/http://example.com" (an 396 // internal path which mimics an external URL) always links to the internal 397 // path, not the external URL. This helps protect against external URL link 398 // injection vulnerabilities. 399 variable_set('common_test_link_to_current_path', TRUE); 400 $this->drupalGet('/http://example.com'); 401 $this->clickLink('link which should point to the current path'); 402 $this->assertUrl('/http://example.com'); 403 $this->assertText('link which should point to the current path'); 404 } 405} 406 407/** 408 * Tests url_is_external(). 409 */ 410class UrlIsExternalUnitTest extends DrupalUnitTestCase { 411 412 public static function getInfo() { 413 return array( 414 'name' => 'External URL checking', 415 'description' => 'Performs tests on url_is_external().', 416 'group' => 'System', 417 ); 418 } 419 420 /** 421 * Tests if each URL is external or not. 422 */ 423 function testUrlIsExternal() { 424 foreach ($this->examples() as $path => $expected) { 425 $this->assertIdentical(url_is_external($path), $expected, $path); 426 } 427 } 428 429 /** 430 * Provides data for testUrlIsExternal(). 431 * 432 * @return array 433 * An array of test data, keyed by a path, with the expected value where 434 * TRUE is external, and FALSE is not external. 435 */ 436 protected function examples() { 437 return array( 438 // Simple external URLs. 439 'http://example.com' => TRUE, 440 'https://example.com' => TRUE, 441 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo' => TRUE, 442 '//drupal.org' => TRUE, 443 // Some browsers ignore or strip leading control characters. 444 "\x00//www.example.com" => TRUE, 445 "\x08//www.example.com" => TRUE, 446 "\x1F//www.example.com" => TRUE, 447 "\n//www.example.com" => TRUE, 448 // JSON supports decoding directly from UTF-8 code points. 449 json_decode('"\u00AD"') . "//www.example.com" => TRUE, 450 json_decode('"\u200E"') . "//www.example.com" => TRUE, 451 json_decode('"\uE0020"') . "//www.example.com" => TRUE, 452 json_decode('"\uE000"') . "//www.example.com" => TRUE, 453 // Backslashes should be normalized to forward. 454 '\\\\example.com' => TRUE, 455 // Local URLs. 456 'node' => FALSE, 457 '/system/ajax' => FALSE, 458 '?q=foo:bar' => FALSE, 459 'node/edit:me' => FALSE, 460 '/drupal.org' => FALSE, 461 '<front>' => FALSE, 462 ); 463 } 464} 465 466/** 467 * Tests for check_plain(), filter_xss(), format_string(), and check_url(). 468 */ 469class CommonXssUnitTest extends DrupalUnitTestCase { 470 471 public static function getInfo() { 472 return array( 473 'name' => 'String filtering tests', 474 'description' => 'Confirm that check_plain(), filter_xss(), format_string() and check_url() work correctly, including invalid multi-byte sequences.', 475 'group' => 'System', 476 ); 477 } 478 479 /** 480 * Check that invalid multi-byte sequences are rejected. 481 */ 482 function testInvalidMultiByte() { 483 // Ignore PHP 5.3+ invalid multibyte sequence warning. 484 $text = @check_plain("Foo\xC0barbaz"); 485 $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "Foo\xC0barbaz"'); 486 // Ignore PHP 5.3+ invalid multibyte sequence warning. 487 $text = @check_plain("\xc2\""); 488 $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "\xc2\""'); 489 $text = check_plain("Fooÿñ"); 490 $this->assertEqual($text, "Fooÿñ", 'check_plain() accepts valid sequence "Fooÿñ"'); 491 $text = filter_xss("Foo\xC0barbaz"); 492 $this->assertEqual($text, '', 'filter_xss() rejects invalid sequence "Foo\xC0barbaz"'); 493 $text = filter_xss("Fooÿñ"); 494 $this->assertEqual($text, "Fooÿñ", 'filter_xss() accepts valid sequence Fooÿñ'); 495 } 496 497 /** 498 * Check that special characters are escaped. 499 */ 500 function testEscaping() { 501 $text = check_plain("<script>"); 502 $this->assertEqual($text, '<script>', 'check_plain() escapes <script>'); 503 $text = check_plain('<>&"\''); 504 $this->assertEqual($text, '<>&"'', 'check_plain() escapes reserved HTML characters.'); 505 } 506 507 /** 508 * Test t() and format_string() replacement functionality. 509 */ 510 function testFormatStringAndT() { 511 foreach (array('format_string', 't') as $function) { 512 $text = $function('Simple text'); 513 $this->assertEqual($text, 'Simple text', $function . ' leaves simple text alone.'); 514 $text = $function('Escaped text: @value', array('@value' => '<script>')); 515 $this->assertEqual($text, 'Escaped text: <script>', $function . ' replaces and escapes string.'); 516 $text = $function('Placeholder text: %value', array('%value' => '<script>')); 517 $this->assertEqual($text, 'Placeholder text: <em class="placeholder"><script></em>', $function . ' replaces, escapes and themes string.'); 518 $text = $function('Verbatim text: !value', array('!value' => '<script>')); 519 $this->assertEqual($text, 'Verbatim text: <script>', $function . ' replaces verbatim string as-is.'); 520 } 521 } 522 523 /** 524 * Check that harmful protocols are stripped. 525 */ 526 function testBadProtocolStripping() { 527 // Ensure that check_url() strips out harmful protocols, and encodes for 528 // HTML. Ensure drupal_strip_dangerous_protocols() can be used to return a 529 // plain-text string stripped of harmful protocols. 530 $url = 'javascript:http://www.example.com/?x=1&y=2'; 531 $expected_plain = 'http://www.example.com/?x=1&y=2'; 532 $expected_html = 'http://www.example.com/?x=1&y=2'; 533 $this->assertIdentical(check_url($url), $expected_html, 'check_url() filters a URL and encodes it for HTML.'); 534 $this->assertIdentical(drupal_strip_dangerous_protocols($url), $expected_plain, 'drupal_strip_dangerous_protocols() filters a URL and returns plain text.'); 535 } 536} 537 538/** 539 * Tests file size parsing and formatting functions. 540 */ 541class CommonSizeTestCase extends DrupalUnitTestCase { 542 protected $exact_test_cases; 543 protected $rounded_test_cases; 544 545 public static function getInfo() { 546 return array( 547 'name' => 'Size parsing test', 548 'description' => 'Parse a predefined amount of bytes and compare the output with the expected value.', 549 'group' => 'System' 550 ); 551 } 552 553 function setUp() { 554 $kb = DRUPAL_KILOBYTE; 555 $this->exact_test_cases = array( 556 '1 byte' => 1, 557 '1 KB' => $kb, 558 '1 MB' => $kb * $kb, 559 '1 GB' => $kb * $kb * $kb, 560 '1 TB' => $kb * $kb * $kb * $kb, 561 '1 PB' => $kb * $kb * $kb * $kb * $kb, 562 '1 EB' => $kb * $kb * $kb * $kb * $kb * $kb, 563 '1 ZB' => $kb * $kb * $kb * $kb * $kb * $kb * $kb, 564 '1 YB' => $kb * $kb * $kb * $kb * $kb * $kb * $kb * $kb, 565 ); 566 $this->rounded_test_cases = array( 567 '2 bytes' => 2, 568 '1 MB' => ($kb * $kb) - 1, // rounded to 1 MB (not 1000 or 1024 kilobyte!) 569 round(3623651 / ($this->exact_test_cases['1 MB']), 2) . ' MB' => 3623651, // megabytes 570 round(67234178751368124 / ($this->exact_test_cases['1 PB']), 2) . ' PB' => 67234178751368124, // petabytes 571 round(235346823821125814962843827 / ($this->exact_test_cases['1 YB']), 2) . ' YB' => 235346823821125814962843827, // yottabytes 572 ); 573 parent::setUp(); 574 } 575 576 /** 577 * Check that format_size() returns the expected string. 578 */ 579 function testCommonFormatSize() { 580 foreach (array($this->exact_test_cases, $this->rounded_test_cases) as $test_cases) { 581 foreach ($test_cases as $expected => $input) { 582 $this->assertEqual( 583 ($result = format_size($input, NULL)), 584 $expected, 585 $expected . ' == ' . $result . ' (' . $input . ' bytes)' 586 ); 587 } 588 } 589 } 590 591 /** 592 * Check that parse_size() returns the proper byte sizes. 593 */ 594 function testCommonParseSize() { 595 foreach ($this->exact_test_cases as $string => $size) { 596 $this->assertEqual( 597 $parsed_size = parse_size($string), 598 $size, 599 $size . ' == ' . $parsed_size . ' (' . $string . ')' 600 ); 601 } 602 603 // Some custom parsing tests 604 $string = '23476892 bytes'; 605 $this->assertEqual( 606 ($parsed_size = parse_size($string)), 607 $size = 23476892, 608 $string . ' == ' . $parsed_size . ' bytes' 609 ); 610 $string = '76MRandomStringThatShouldBeIgnoredByParseSize.'; // 76 MB 611 $this->assertEqual( 612 $parsed_size = parse_size($string), 613 $size = 79691776, 614 $string . ' == ' . $parsed_size . ' bytes' 615 ); 616 $string = '76.24 Giggabyte'; // Misspeld text -> 76.24 GB 617 $this->assertEqual( 618 $parsed_size = parse_size($string), 619 $size = 81862076662, 620 $string . ' == ' . $parsed_size . ' bytes' 621 ); 622 } 623 624 /** 625 * Cross-test parse_size() and format_size(). 626 */ 627 function testCommonParseSizeFormatSize() { 628 foreach ($this->exact_test_cases as $size) { 629 $this->assertEqual( 630 $size, 631 ($parsed_size = parse_size($string = format_size($size, NULL))), 632 $size . ' == ' . $parsed_size . ' (' . $string . ')' 633 ); 634 } 635 } 636} 637 638/** 639 * Test drupal_explode_tags() and drupal_implode_tags(). 640 */ 641class DrupalTagsHandlingTestCase extends DrupalUnitTestCase { 642 var $validTags = array( 643 'Drupal' => 'Drupal', 644 'Drupal with some spaces' => 'Drupal with some spaces', 645 '"Legendary Drupal mascot of doom: ""Druplicon"""' => 'Legendary Drupal mascot of doom: "Druplicon"', 646 '"Drupal, although it rhymes with sloopal, is as awesome as a troopal!"' => 'Drupal, although it rhymes with sloopal, is as awesome as a troopal!', 647 ); 648 649 public static function getInfo() { 650 return array( 651 'name' => 'Drupal tags handling', 652 'description' => "Performs tests on Drupal's handling of tags, both explosion and implosion tactics used.", 653 'group' => 'System' 654 ); 655 } 656 657 /** 658 * Explode a series of tags. 659 */ 660 function testDrupalExplodeTags() { 661 $string = implode(', ', array_keys($this->validTags)); 662 $tags = drupal_explode_tags($string); 663 $this->assertTags($tags); 664 } 665 666 /** 667 * Implode a series of tags. 668 */ 669 function testDrupalImplodeTags() { 670 $tags = array_values($this->validTags); 671 // Let's explode and implode to our heart's content. 672 for ($i = 0; $i < 10; $i++) { 673 $string = drupal_implode_tags($tags); 674 $tags = drupal_explode_tags($string); 675 } 676 $this->assertTags($tags); 677 } 678 679 /** 680 * Helper function: asserts that the ending array of tags is what we wanted. 681 */ 682 function assertTags($tags) { 683 $original = $this->validTags; 684 foreach ($tags as $tag) { 685 $key = array_search($tag, $original); 686 $this->assertTrue($key, format_string('Make sure tag %tag shows up in the final tags array (originally %original)', array('%tag' => $tag, '%original' => $key))); 687 unset($original[$key]); 688 } 689 foreach ($original as $leftover) { 690 $this->fail(format_string('Leftover tag %leftover was left over.', array('%leftover' => $leftover))); 691 } 692 } 693} 694 695/** 696 * Test the Drupal CSS system. 697 */ 698class CascadingStylesheetsTestCase extends DrupalWebTestCase { 699 public static function getInfo() { 700 return array( 701 'name' => 'Cascading stylesheets', 702 'description' => 'Tests adding various cascading stylesheets to the page.', 703 'group' => 'System', 704 ); 705 } 706 707 function setUp() { 708 parent::setUp('php', 'locale', 'common_test'); 709 // Reset drupal_add_css() before each test. 710 drupal_static_reset('drupal_add_css'); 711 } 712 713 /** 714 * Check default stylesheets as empty. 715 */ 716 function testDefault() { 717 $this->assertEqual(array(), drupal_add_css(), 'Default CSS is empty.'); 718 } 719 720 /** 721 * Test that stylesheets in module .info files are loaded. 722 */ 723 function testModuleInfo() { 724 $this->drupalGet(''); 725 726 // Verify common_test.css in a STYLE media="all" tag. 727 $elements = $this->xpath('//style[@media=:media and contains(text(), :filename)]', array( 728 ':media' => 'all', 729 ':filename' => 'tests/common_test.css', 730 )); 731 $this->assertTrue(count($elements), "Stylesheet with media 'all' in module .info file found."); 732 733 // Verify common_test.print.css in a STYLE media="print" tag. 734 $elements = $this->xpath('//style[@media=:media and contains(text(), :filename)]', array( 735 ':media' => 'print', 736 ':filename' => 'tests/common_test.print.css', 737 )); 738 $this->assertTrue(count($elements), "Stylesheet with media 'print' in module .info file found."); 739 } 740 741 /** 742 * Tests adding a file stylesheet. 743 */ 744 function testAddFile() { 745 $path = drupal_get_path('module', 'simpletest') . '/simpletest.css'; 746 $css = drupal_add_css($path); 747 $this->assertEqual($css[$path]['data'], $path, 'Adding a CSS file caches it properly.'); 748 } 749 750 /** 751 * Tests adding an external stylesheet. 752 */ 753 function testAddExternal() { 754 $path = 'http://example.com/style.css'; 755 $css = drupal_add_css($path, 'external'); 756 $this->assertEqual($css[$path]['type'], 'external', 'Adding an external CSS file caches it properly.'); 757 } 758 759 /** 760 * Makes sure that reseting the CSS empties the cache. 761 */ 762 function testReset() { 763 drupal_static_reset('drupal_add_css'); 764 $this->assertEqual(array(), drupal_add_css(), 'Resetting the CSS empties the cache.'); 765 } 766 767 /** 768 * Tests rendering the stylesheets. 769 */ 770 function testRenderFile() { 771 $css = drupal_get_path('module', 'simpletest') . '/simpletest.css'; 772 drupal_add_css($css); 773 $styles = drupal_get_css(); 774 $this->assertTrue(strpos($styles, $css) > 0, 'Rendered CSS includes the added stylesheet.'); 775 // Verify that newlines are properly added inside style tags. 776 $query_string = variable_get('css_js_query_string', '0'); 777 $css_processed = "<style type=\"text/css\" media=\"all\">\n@import url(\"" . check_plain(file_create_url($css)) . "?" . $query_string ."\");\n</style>"; 778 $this->assertEqual(trim($styles), $css_processed, 'Rendered CSS includes newlines inside style tags for JavaScript use.'); 779 } 780 781 /** 782 * Tests rendering an external stylesheet. 783 */ 784 function testRenderExternal() { 785 $css = 'http://example.com/style.css'; 786 drupal_add_css($css, 'external'); 787 $styles = drupal_get_css(); 788 // Stylesheet URL may be the href of a LINK tag or in an @import statement 789 // of a STYLE tag. 790 $this->assertTrue(strpos($styles, 'href="' . $css) > 0 || strpos($styles, '@import url("' . $css . '")') > 0, 'Rendering an external CSS file.'); 791 } 792 793 /** 794 * Tests rendering inline stylesheets with preprocessing on. 795 */ 796 function testRenderInlinePreprocess() { 797 $css = 'body { padding: 0px; }'; 798 $css_preprocessed = '<style type="text/css" media="all">' . "\n<!--/*--><![CDATA[/*><!--*/\n" . drupal_load_stylesheet_content($css, TRUE) . "\n/*]]>*/-->\n" . '</style>'; 799 drupal_add_css($css, array('type' => 'inline')); 800 $styles = drupal_get_css(); 801 $this->assertEqual(trim($styles), $css_preprocessed, 'Rendering preprocessed inline CSS adds it to the page.'); 802 } 803 804 /** 805 * Tests removing charset when rendering stylesheets with preprocessing on. 806 */ 807 function testRenderRemoveCharsetPreprocess() { 808 $cases = array( 809 array( 810 'asset' => '@charset "UTF-8";html{font-family:"sans-serif";}', 811 'expected' => 'html{font-family:"sans-serif";}', 812 ), 813 // This asset contains extra \n character. 814 array( 815 'asset' => "@charset 'UTF-8';\nhtml{font-family:'sans-serif';}", 816 'expected' => "\nhtml{font-family:'sans-serif';}", 817 ), 818 ); 819 820 foreach ($cases as $case) { 821 $this->assertEqual( 822 $case['expected'], 823 drupal_load_stylesheet_content($case['asset']), 824 'CSS optimizing correctly removes the charset declaration.' 825 ); 826 } 827 } 828 829 /** 830 * Tests rendering inline stylesheets with preprocessing off. 831 */ 832 function testRenderInlineNoPreprocess() { 833 $css = 'body { padding: 0px; }'; 834 drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE)); 835 $styles = drupal_get_css(); 836 $this->assertTrue(strpos($styles, $css) > 0, 'Rendering non-preprocessed inline CSS adds it to the page.'); 837 } 838 839 /** 840 * Tests rendering inline stylesheets through a full page request. 841 */ 842 function testRenderInlineFullPage() { 843 $css = 'body { font-size: 254px; }'; 844 // Inline CSS is minified unless 'preprocess' => FALSE is passed as a 845 // drupal_add_css() option. 846 $expected = 'body{font-size:254px;}'; 847 848 // Create a node, using the PHP filter that tests drupal_add_css(). 849 $php_format_id = 'php_code'; 850 $settings = array( 851 'type' => 'page', 852 'body' => array( 853 LANGUAGE_NONE => array( 854 array( 855 'value' => t('This tests the inline CSS!') . "<?php drupal_add_css('$css', 'inline'); ?>", 856 'format' => $php_format_id, 857 ), 858 ), 859 ), 860 'promote' => 1, 861 ); 862 $node = $this->drupalCreateNode($settings); 863 864 // Fetch the page. 865 $this->drupalGet('node/' . $node->nid); 866 $this->assertRaw($expected, 'Inline stylesheets appear in the full page rendering.'); 867 } 868 869 /** 870 * Test CSS ordering. 871 */ 872 function testRenderOrder() { 873 // A module CSS file. 874 drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css'); 875 // A few system CSS files, ordered in a strange way. 876 $system_path = drupal_get_path('module', 'system'); 877 drupal_add_css($system_path . '/system.menus.css', array('group' => CSS_SYSTEM)); 878 drupal_add_css($system_path . '/system.base.css', array('group' => CSS_SYSTEM, 'weight' => -10)); 879 drupal_add_css($system_path . '/system.theme.css', array('group' => CSS_SYSTEM)); 880 881 $expected = array( 882 $system_path . '/system.base.css', 883 $system_path . '/system.menus.css', 884 $system_path . '/system.theme.css', 885 drupal_get_path('module', 'simpletest') . '/simpletest.css', 886 ); 887 888 889 $styles = drupal_get_css(); 890 // Stylesheet URL may be the href of a LINK tag or in an @import statement 891 // of a STYLE tag. 892 if (preg_match_all('/(href="|url\(")' . preg_quote($GLOBALS['base_url'] . '/', '/') . '([^?]+)\?/', $styles, $matches)) { 893 $result = $matches[2]; 894 } 895 else { 896 $result = array(); 897 } 898 899 $this->assertIdentical($result, $expected, 'The CSS files are in the expected order.'); 900 } 901 902 /** 903 * Test CSS override. 904 */ 905 function testRenderOverride() { 906 $system = drupal_get_path('module', 'system'); 907 $simpletest = drupal_get_path('module', 'simpletest'); 908 909 drupal_add_css($system . '/system.base.css'); 910 drupal_add_css($simpletest . '/tests/system.base.css'); 911 912 // The dummy stylesheet should be the only one included. 913 $styles = drupal_get_css(); 914 $this->assert(strpos($styles, $simpletest . '/tests/system.base.css') !== FALSE, 'The overriding CSS file is output.'); 915 $this->assert(strpos($styles, $system . '/system.base.css') === FALSE, 'The overridden CSS file is not output.'); 916 917 drupal_add_css($simpletest . '/tests/system.base.css'); 918 drupal_add_css($system . '/system.base.css'); 919 920 // The standard stylesheet should be the only one included. 921 $styles = drupal_get_css(); 922 $this->assert(strpos($styles, $system . '/system.base.css') !== FALSE, 'The overriding CSS file is output.'); 923 $this->assert(strpos($styles, $simpletest . '/tests/system.base.css') === FALSE, 'The overridden CSS file is not output.'); 924 } 925 926 /** 927 * Tests Locale module's CSS Alter to include RTL overrides. 928 */ 929 function testAlter() { 930 // Switch the language to a right to left language and add system.base.css. 931 global $language; 932 $language->direction = LANGUAGE_RTL; 933 $path = drupal_get_path('module', 'system'); 934 drupal_add_css($path . '/system.base.css'); 935 936 // Check to see if system.base-rtl.css was also added. 937 $styles = drupal_get_css(); 938 $this->assert(strpos($styles, $path . '/system.base-rtl.css') !== FALSE, 'CSS is alterable as right to left overrides are added.'); 939 940 // Change the language back to left to right. 941 $language->direction = LANGUAGE_LTR; 942 } 943 944 /** 945 * Tests that the query string remains intact when adding CSS files that have 946 * query string parameters. 947 */ 948 function testAddCssFileWithQueryString() { 949 $this->drupalGet('common-test/query-string'); 950 $query_string = variable_get('css_js_query_string', '0'); 951 $this->assertRaw(drupal_get_path('module', 'node') . '/node.css?' . $query_string, 'Query string was appended correctly to css.'); 952 $this->assertRaw(drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&arg2=value2', 'Query string not escaped on a URI.'); 953 } 954} 955 956/** 957 * Test for cleaning HTML identifiers. 958 */ 959class DrupalHTMLIdentifierTestCase extends DrupalUnitTestCase { 960 public static function getInfo() { 961 return array( 962 'name' => 'HTML identifiers', 963 'description' => 'Test the functions drupal_html_class(), drupal_html_id() and drupal_clean_css_identifier() for expected behavior', 964 'group' => 'System', 965 ); 966 } 967 968 /** 969 * Tests that drupal_clean_css_identifier() cleans the identifier properly. 970 */ 971 function testDrupalCleanCSSIdentifier() { 972 // Verify that no valid ASCII characters are stripped from the identifier. 973 $identifier = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789'; 974 $this->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, 'Verify valid ASCII characters pass through.'); 975 976 // Verify that valid UTF-8 characters are not stripped from the identifier. 977 $identifier = '¡¢£¤¥'; 978 $this->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, 'Verify valid UTF-8 characters pass through.'); 979 980 // Verify that invalid characters (including non-breaking space) are stripped from the identifier. 981 $this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', 'Strip invalid characters.'); 982 983 // Verify that double underscores are replaced in the identifier by default. 984 $identifier = 'css__identifier__with__double__underscores'; 985 $expected = 'css--identifier--with--double--underscores'; 986 $this->assertIdentical(drupal_clean_css_identifier($identifier), $expected, 'Verify double underscores are replaced with double hyphens by default.'); 987 988 // Verify that double underscores are preserved in the identifier if the 989 // variable allow_css_double_underscores is set to TRUE. 990 $this->setAllowCSSDoubleUnderscores(TRUE); 991 $this->assertIdentical(drupal_clean_css_identifier($identifier), $identifier, 'Verify double underscores are preserved if the allow_css_double_underscores set to TRUE.'); 992 993 // To avoid affecting other test cases, set the variable 994 // allow_css_double_underscores to FALSE which is the default value. 995 $this->setAllowCSSDoubleUnderscores(FALSE); 996 } 997 998 /** 999 * Set the variable allow_css_double_underscores and reset the cache. 1000 * 1001 * @param $value bool 1002 * A new value to be set to allow_css_double_underscores. 1003 */ 1004 function setAllowCSSDoubleUnderscores($value) { 1005 $GLOBALS['conf']['allow_css_double_underscores'] = $value; 1006 drupal_static_reset('drupal_clean_css_identifier:allow_css_double_underscores'); 1007 } 1008 1009 /** 1010 * Tests that drupal_html_class() cleans the class name properly. 1011 */ 1012 function testDrupalHTMLClass() { 1013 // Verify Drupal coding standards are enforced. 1014 $this->assertIdentical(drupal_html_class('CLASS NAME_[Ü]'), 'class-name--ü', 'Enforce Drupal coding standards.'); 1015 } 1016 1017 /** 1018 * Tests that drupal_html_id() cleans the ID properly. 1019 */ 1020 function testDrupalHTMLId() { 1021 // Verify that letters, digits, and hyphens are not stripped from the ID. 1022 $id = 'abcdefghijklmnopqrstuvwxyz-0123456789'; 1023 $this->assertIdentical(drupal_html_id($id), $id, 'Verify valid characters pass through.'); 1024 1025 // Verify that invalid characters are stripped from the ID. 1026 $this->assertIdentical(drupal_html_id('invalid,./:@\\^`{Üidentifier'), 'invalididentifier', 'Strip invalid characters.'); 1027 1028 // Verify Drupal coding standards are enforced. 1029 $this->assertIdentical(drupal_html_id('ID NAME_[1]'), 'id-name-1', 'Enforce Drupal coding standards.'); 1030 1031 // Reset the static cache so we can ensure the unique id count is at zero. 1032 drupal_static_reset('drupal_html_id'); 1033 1034 // Clean up IDs with invalid starting characters. 1035 $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id', 'Test the uniqueness of IDs #1.'); 1036 $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--2', 'Test the uniqueness of IDs #2.'); 1037 $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--3', 'Test the uniqueness of IDs #3.'); 1038 } 1039} 1040 1041/** 1042 * CSS Unit Tests. 1043 */ 1044class CascadingStylesheetsUnitTest extends DrupalUnitTestCase { 1045 public static function getInfo() { 1046 return array( 1047 'name' => 'CSS Unit Tests', 1048 'description' => 'Unit tests on CSS functions like aggregation.', 1049 'group' => 'System', 1050 ); 1051 } 1052 1053 /** 1054 * Tests basic CSS loading with and without optimization via drupal_load_stylesheet(). 1055 * 1056 * Known tests: 1057 * - Retain white-space in selectors. (https://drupal.org/node/472820) 1058 * - Proper URLs in imported files. (https://drupal.org/node/265719) 1059 * - Retain pseudo-selectors. (https://drupal.org/node/460448) 1060 * - Don't adjust data URIs. (https://drupal.org/node/2142441) 1061 * - Files imported from external URLs. (https://drupal.org/node/2014851) 1062 */ 1063 function testLoadCssBasic() { 1064 // Array of files to test living in 'simpletest/files/css_test_files/'. 1065 // - Original: name.css 1066 // - Unoptimized expected content: name.css.unoptimized.css 1067 // - Optimized expected content: name.css.optimized.css 1068 $testfiles = array( 1069 'css_input_without_import.css', 1070 'css_input_with_import.css', 1071 'css_subfolder/css_input_with_import.css', 1072 'comment_hacks.css', 1073 'quotes.css', 1074 ); 1075 $path = drupal_get_path('module', 'simpletest') . '/files/css_test_files'; 1076 foreach ($testfiles as $file) { 1077 $file_path = $path . '/' . $file; 1078 $file_url = $GLOBALS['base_url'] . '/' . $file_path; 1079 1080 $expected = file_get_contents($file_path . '.unoptimized.css'); 1081 $unoptimized_output = drupal_load_stylesheet($file_path, FALSE); 1082 $this->assertEqual($unoptimized_output, $expected, format_string('Unoptimized CSS file has expected contents (@file)', array('@file' => $file))); 1083 1084 $expected = file_get_contents($file_path . '.optimized.css'); 1085 $optimized_output = drupal_load_stylesheet($file_path, TRUE); 1086 $this->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array('@file' => $file))); 1087 1088 // Repeat the tests by accessing the stylesheets by URL. 1089 $expected = file_get_contents($file_path . '.unoptimized.css'); 1090 $unoptimized_output_url = drupal_load_stylesheet($file_url, FALSE); 1091 $this->assertEqual($unoptimized_output_url, $expected, format_string('Unoptimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); 1092 1093 $expected = file_get_contents($file_path . '.optimized.css'); 1094 $optimized_output_url = drupal_load_stylesheet($file_url, TRUE); 1095 $this->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); 1096 } 1097 } 1098} 1099 1100/** 1101 * Test drupal_http_request(). 1102 */ 1103class DrupalHTTPRequestTestCase extends DrupalWebTestCase { 1104 public static function getInfo() { 1105 return array( 1106 'name' => 'Drupal HTTP request', 1107 'description' => "Performs tests on Drupal's HTTP request mechanism.", 1108 'group' => 'System' 1109 ); 1110 } 1111 1112 function setUp() { 1113 parent::setUp('system_test', 'locale'); 1114 } 1115 1116 function testDrupalHTTPRequest() { 1117 global $is_https; 1118 1119 // Parse URL schema. 1120 $missing_scheme = drupal_http_request('example.com/path'); 1121 $this->assertEqual($missing_scheme->code, -1002, 'Returned with "-1002" error code.'); 1122 $this->assertEqual($missing_scheme->error, 'missing schema', 'Returned with "missing schema" error message.'); 1123 1124 $unable_to_parse = drupal_http_request('http:///path'); 1125 $this->assertEqual($unable_to_parse->code, -1001, 'Returned with "-1001" error code.'); 1126 $this->assertEqual($unable_to_parse->error, 'unable to parse URL', 'Returned with "unable to parse URL" error message.'); 1127 1128 // Fetch page and check that the data parameter works with both array and string. 1129 $data_array = array($this->randomName() => $this->randomString() . ' "\''); 1130 $data_string = drupal_http_build_query($data_array); 1131 $result = drupal_http_request(url('node', array('absolute' => TRUE)), array('data' => $data_array)); 1132 $this->assertEqual($result->code, 200, 'Fetched page successfully.'); 1133 $this->assertTrue(substr($result->request, -strlen($data_string)) === $data_string, 'Request ends with URL-encoded data when drupal_http_request() called using array.'); 1134 $result = drupal_http_request(url('node', array('absolute' => TRUE)), array('data' => $data_string)); 1135 $this->assertTrue(substr($result->request, -strlen($data_string)) === $data_string, 'Request ends with URL-encoded data when drupal_http_request() called using string.'); 1136 1137 $this->drupalSetContent($result->data); 1138 $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), 'Site title matches.'); 1139 1140 // Test that code and status message is returned. 1141 $result = drupal_http_request(url('pagedoesnotexist', array('absolute' => TRUE))); 1142 $this->assertTrue(!empty($result->protocol), 'Result protocol is returned.'); 1143 $this->assertEqual($result->code, '404', 'Result code is 404'); 1144 $this->assertEqual($result->status_message, 'Not Found', 'Result status message is "Not Found"'); 1145 1146 // Skip the timeout tests when the testing environment is HTTPS because 1147 // stream_set_timeout() does not work for SSL connections. 1148 // @link http://bugs.php.net/bug.php?id=47929 1149 if (!$is_https) { 1150 // Test that timeout is respected. The test machine is expected to be able 1151 // to make the connection (i.e. complete the fsockopen()) in 2 seconds and 1152 // return within a total of 5 seconds. If the test machine is extremely 1153 // slow, the test will fail. fsockopen() has been seen to time out in 1154 // slightly less than the specified timeout, so allow a little slack on 1155 // the minimum expected time (i.e. 1.8 instead of 2). 1156 timer_start(__METHOD__); 1157 $result = drupal_http_request(url('system-test/sleep/10', array('absolute' => TRUE)), array('timeout' => 2)); 1158 $time = timer_read(__METHOD__) / 1000; 1159 $this->assertTrue(1.8 < $time && $time < 5, format_string('Request timed out (%time seconds).', array('%time' => $time))); 1160 $this->assertTrue($result->error, 'An error message was returned.'); 1161 $this->assertEqual($result->code, HTTP_REQUEST_TIMEOUT, 'Proper error code was returned.'); 1162 } 1163 } 1164 1165 function testDrupalHTTPRequestBasicAuth() { 1166 $username = $this->randomName(); 1167 $password = $this->randomName(); 1168 $url = url('system-test/auth', array('absolute' => TRUE)); 1169 1170 $auth = str_replace('://', '://' . $username . ':' . $password . '@', $url); 1171 $result = drupal_http_request($auth); 1172 1173 $this->drupalSetContent($result->data); 1174 $this->assertRaw($username, 'Username is passed correctly.'); 1175 $this->assertRaw($password, 'Password is passed correctly.'); 1176 } 1177 1178 function testDrupalHTTPRequestRedirect() { 1179 $redirect_301 = drupal_http_request(url('system-test/redirect/301', array('absolute' => TRUE)), array('max_redirects' => 1)); 1180 $this->assertEqual($redirect_301->redirect_code, 301, 'drupal_http_request follows the 301 redirect.'); 1181 1182 $redirect_301 = drupal_http_request(url('system-test/redirect/301', array('absolute' => TRUE)), array('max_redirects' => 0)); 1183 $this->assertFalse(isset($redirect_301->redirect_code), 'drupal_http_request does not follow 301 redirect if max_redirects = 0.'); 1184 1185 $redirect_invalid = drupal_http_request(url('system-test/redirect-noscheme', array('absolute' => TRUE)), array('max_redirects' => 1)); 1186 $this->assertEqual($redirect_invalid->code, -1002, format_string('301 redirect to invalid URL returned with error code !error.', array('!error' => $redirect_invalid->error))); 1187 $this->assertEqual($redirect_invalid->error, 'missing schema', format_string('301 redirect to invalid URL returned with error message "!error".', array('!error' => $redirect_invalid->error))); 1188 1189 $redirect_invalid = drupal_http_request(url('system-test/redirect-noparse', array('absolute' => TRUE)), array('max_redirects' => 1)); 1190 $this->assertEqual($redirect_invalid->code, -1001, format_string('301 redirect to invalid URL returned with error message code "!error".', array('!error' => $redirect_invalid->error))); 1191 $this->assertEqual($redirect_invalid->error, 'unable to parse URL', format_string('301 redirect to invalid URL returned with error message "!error".', array('!error' => $redirect_invalid->error))); 1192 1193 $redirect_invalid = drupal_http_request(url('system-test/redirect-invalid-scheme', array('absolute' => TRUE)), array('max_redirects' => 1)); 1194 $this->assertEqual($redirect_invalid->code, -1003, format_string('301 redirect to invalid URL returned with error code !error.', array('!error' => $redirect_invalid->error))); 1195 $this->assertEqual($redirect_invalid->error, 'invalid schema ftp', format_string('301 redirect to invalid URL returned with error message "!error".', array('!error' => $redirect_invalid->error))); 1196 1197 $redirect_302 = drupal_http_request(url('system-test/redirect/302', array('absolute' => TRUE)), array('max_redirects' => 1)); 1198 $this->assertEqual($redirect_302->redirect_code, 302, 'drupal_http_request follows the 302 redirect.'); 1199 1200 $redirect_302 = drupal_http_request(url('system-test/redirect/302', array('absolute' => TRUE)), array('max_redirects' => 0)); 1201 $this->assertFalse(isset($redirect_302->redirect_code), 'drupal_http_request does not follow 302 redirect if $retry = 0.'); 1202 1203 $redirect_307 = drupal_http_request(url('system-test/redirect/307', array('absolute' => TRUE)), array('max_redirects' => 1)); 1204 $this->assertEqual($redirect_307->redirect_code, 307, 'drupal_http_request follows the 307 redirect.'); 1205 1206 $redirect_307 = drupal_http_request(url('system-test/redirect/307', array('absolute' => TRUE)), array('max_redirects' => 0)); 1207 $this->assertFalse(isset($redirect_307->redirect_code), 'drupal_http_request does not follow 307 redirect if max_redirects = 0.'); 1208 1209 $multiple_redirect_final_url = url('system-test/multiple-redirects/0', array('absolute' => TRUE)); 1210 $multiple_redirect_1 = drupal_http_request(url('system-test/multiple-redirects/1', array('absolute' => TRUE)), array('max_redirects' => 1)); 1211 $this->assertEqual($multiple_redirect_1->redirect_url, $multiple_redirect_final_url, 'redirect_url contains the final redirection location after 1 redirect.'); 1212 1213 $multiple_redirect_3 = drupal_http_request(url('system-test/multiple-redirects/3', array('absolute' => TRUE)), array('max_redirects' => 3)); 1214 $this->assertEqual($multiple_redirect_3->redirect_url, $multiple_redirect_final_url, 'redirect_url contains the final redirection location after 3 redirects.'); 1215 } 1216 1217 /** 1218 * Tests Content-language headers generated by Drupal. 1219 */ 1220 function testDrupalHTTPRequestHeaders() { 1221 // Check the default header. 1222 $request = drupal_http_request(url('<front>', array('absolute' => TRUE))); 1223 $this->assertEqual($request->headers['content-language'], 'en', 'Content-Language HTTP header is English.'); 1224 1225 // Add German language and set as default. 1226 locale_add_language('de', 'German', 'Deutsch', LANGUAGE_LTR, '', '', TRUE, TRUE); 1227 1228 // Request front page and check for matching Content-Language. 1229 $request = drupal_http_request(url('<front>', array('absolute' => TRUE))); 1230 $this->assertEqual($request->headers['content-language'], 'de', 'Content-Language HTTP header is German.'); 1231 } 1232} 1233 1234/** 1235 * Tests parsing of the HTTP response status line. 1236 */ 1237class DrupalHTTPResponseStatusLineTest extends DrupalUnitTestCase { 1238 public static function getInfo() { 1239 return array( 1240 'name' => 'Drupal HTTP request response status parsing', 1241 'description' => 'Perform unit tests on _drupal_parse_response_status().', 1242 'group' => 'System', 1243 ); 1244 } 1245 1246 /** 1247 * Tests parsing HTTP response status line. 1248 */ 1249 public function testStatusLine() { 1250 // Grab the big array of test data from statusLineData(). 1251 $data = $this->statusLineData(); 1252 foreach($data as $test_case) { 1253 $test_data = array_shift($test_case); 1254 $expected = array_shift($test_case); 1255 1256 $outcome = _drupal_parse_response_status($test_data); 1257 1258 foreach(array_keys($expected) as $key) { 1259 $this->assertIdentical($outcome[$key], $expected[$key]); 1260 } 1261 } 1262 } 1263 1264 /** 1265 * Data provider for testStatusLine(). 1266 * 1267 * @return array 1268 * Test data. 1269 */ 1270 protected function statusLineData() { 1271 return array( 1272 array( 1273 'HTTP/1.1 200 OK', 1274 array( 1275 'http_version' => 'HTTP/1.1', 1276 'response_code' => '200', 1277 'reason_phrase' => 'OK', 1278 ), 1279 ), 1280 // Data set with no reason phrase. 1281 array( 1282 'HTTP/1.1 200', 1283 array( 1284 'http_version' => 'HTTP/1.1', 1285 'response_code' => '200', 1286 'reason_phrase' => '', 1287 ), 1288 ), 1289 // Arbitrary strings. 1290 array( 1291 'version code multi word explanation', 1292 array( 1293 'http_version' => 'version', 1294 'response_code' => 'code', 1295 'reason_phrase' => 'multi word explanation', 1296 ), 1297 ), 1298 ); 1299 } 1300} 1301 1302/** 1303 * Testing drupal_add_region_content and drupal_get_region_content. 1304 */ 1305class DrupalSetContentTestCase extends DrupalWebTestCase { 1306 public static function getInfo() { 1307 return array( 1308 'name' => 'Drupal set/get regions', 1309 'description' => 'Performs tests on setting and retrieiving content from theme regions.', 1310 'group' => 'System' 1311 ); 1312 } 1313 1314 1315 /** 1316 * Test setting and retrieving content for theme regions. 1317 */ 1318 function testRegions() { 1319 global $theme_key; 1320 1321 $block_regions = system_region_list($theme_key, REGIONS_ALL, FALSE); 1322 $delimiter = $this->randomName(32); 1323 $values = array(); 1324 // Set some random content for each region available. 1325 foreach ($block_regions as $region) { 1326 $first_chunk = $this->randomName(32); 1327 drupal_add_region_content($region, $first_chunk); 1328 $second_chunk = $this->randomName(32); 1329 drupal_add_region_content($region, $second_chunk); 1330 // Store the expected result for a drupal_get_region_content call for this region. 1331 $values[$region] = $first_chunk . $delimiter . $second_chunk; 1332 } 1333 1334 // Ensure drupal_get_region_content returns expected results when fetching all regions. 1335 $content = drupal_get_region_content(NULL, $delimiter); 1336 foreach ($content as $region => $region_content) { 1337 $this->assertEqual($region_content, $values[$region], format_string('@region region text verified when fetching all regions', array('@region' => $region))); 1338 } 1339 1340 // Ensure drupal_get_region_content returns expected results when fetching a single region. 1341 foreach ($block_regions as $region) { 1342 $region_content = drupal_get_region_content($region, $delimiter); 1343 $this->assertEqual($region_content, $values[$region], format_string('@region region text verified when fetching single region.', array('@region' => $region))); 1344 } 1345 } 1346} 1347 1348/** 1349 * Testing drupal_goto and hook_drupal_goto_alter(). 1350 */ 1351class DrupalGotoTest extends DrupalWebTestCase { 1352 public static function getInfo() { 1353 return array( 1354 'name' => 'Drupal goto', 1355 'description' => 'Performs tests on the drupal_goto function and hook_drupal_goto_alter', 1356 'group' => 'System' 1357 ); 1358 } 1359 1360 function setUp() { 1361 parent::setUp('common_test'); 1362 } 1363 1364 /** 1365 * Test drupal_goto(). 1366 */ 1367 function testDrupalGoto() { 1368 $this->drupalGet('common-test/drupal_goto/redirect'); 1369 $headers = $this->drupalGetHeaders(TRUE); 1370 list(, $status) = explode(' ', $headers[0][':status'], 3); 1371 $this->assertEqual($status, 302, 'Expected response code was sent.'); 1372 $this->assertText('drupal_goto', 'Drupal goto redirect succeeded.'); 1373 $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('absolute' => TRUE)), 'Drupal goto redirected to expected URL.'); 1374 1375 $this->drupalGet('common-test/drupal_goto/redirect_advanced'); 1376 $headers = $this->drupalGetHeaders(TRUE); 1377 list(, $status) = explode(' ', $headers[0][':status'], 3); 1378 $this->assertEqual($status, 301, 'Expected response code was sent.'); 1379 $this->assertText('drupal_goto', 'Drupal goto redirect succeeded.'); 1380 $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('query' => array('foo' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to expected URL.'); 1381 1382 // Test that calling drupal_goto() on the current path is not dangerous. 1383 variable_set('common_test_redirect_current_path', TRUE); 1384 $this->drupalGet('', array('query' => array('q' => 'http://www.example.com/'))); 1385 $headers = $this->drupalGetHeaders(TRUE); 1386 list(, $status) = explode(' ', $headers[0][':status'], 3); 1387 $this->assertEqual($status, 302, 'Expected response code was sent.'); 1388 $this->assertNotEqual($this->getUrl(), 'http://www.example.com/', 'Drupal goto did not redirect to external URL.'); 1389 $this->assertTrue(strpos($this->getUrl(), url('<front>', array('absolute' => TRUE))) === 0, 'Drupal redirected to itself.'); 1390 variable_del('common_test_redirect_current_path'); 1391 // Test that drupal_goto() respects ?destination=xxx. Use an complicated URL 1392 // to test that the path is encoded and decoded properly. 1393 $destination = 'common-test/drupal_goto/destination?foo=%2525&bar=123'; 1394 $this->drupalGet('common-test/drupal_goto/redirect', array('query' => array('destination' => $destination))); 1395 $this->assertText('drupal_goto', 'Drupal goto redirect with destination succeeded.'); 1396 $this->assertEqual($this->getUrl(), url('common-test/drupal_goto/destination', array('query' => array('foo' => '%25', 'bar' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to given query string destination.'); 1397 } 1398 1399 /** 1400 * Test hook_drupal_goto_alter(). 1401 */ 1402 function testDrupalGotoAlter() { 1403 $this->drupalGet('common-test/drupal_goto/redirect_fail'); 1404 1405 $this->assertNoText(t("Drupal goto failed to stop program"), "Drupal goto stopped program."); 1406 $this->assertNoText('drupal_goto_fail', "Drupal goto redirect failed."); 1407 } 1408 1409 /** 1410 * Test drupal_get_destination(). 1411 */ 1412 function testDrupalGetDestination() { 1413 $query = $this->randomName(10); 1414 1415 // Verify that a 'destination' query string is used as destination. 1416 $this->drupalGet('common-test/destination', array('query' => array('destination' => $query))); 1417 $this->assertText('The destination: ' . $query, 'The given query string destination is determined as destination.'); 1418 1419 // Verify that the current path is used as destination. 1420 $this->drupalGet('common-test/destination', array('query' => array($query => NULL))); 1421 $url = 'common-test/destination?' . $query; 1422 $this->assertText('The destination: ' . $url, 'The current path is determined as destination.'); 1423 } 1424} 1425 1426/** 1427 * Tests for the JavaScript system. 1428 */ 1429class JavaScriptTestCase extends DrupalWebTestCase { 1430 /** 1431 * Store configured value for JavaScript preprocessing. 1432 */ 1433 protected $preprocess_js = NULL; 1434 1435 public static function getInfo() { 1436 return array( 1437 'name' => 'JavaScript', 1438 'description' => 'Tests the JavaScript system.', 1439 'group' => 'System' 1440 ); 1441 } 1442 1443 function setUp() { 1444 // Enable Locale and SimpleTest in the test environment. 1445 parent::setUp('locale', 'simpletest', 'common_test'); 1446 1447 // Disable preprocessing 1448 $this->preprocess_js = variable_get('preprocess_js', 0); 1449 variable_set('preprocess_js', 0); 1450 1451 // Reset drupal_add_js() and drupal_add_library() statics before each test. 1452 drupal_static_reset('drupal_add_js'); 1453 drupal_static_reset('drupal_add_library'); 1454 } 1455 1456 function tearDown() { 1457 // Restore configured value for JavaScript preprocessing. 1458 variable_set('preprocess_js', $this->preprocess_js); 1459 parent::tearDown(); 1460 } 1461 1462 /** 1463 * Test default JavaScript is empty. 1464 */ 1465 function testDefault() { 1466 $this->assertEqual(array(), drupal_add_js(), 'Default JavaScript is empty.'); 1467 } 1468 1469 /** 1470 * Test adding a JavaScript file. 1471 */ 1472 function testAddFile() { 1473 $javascript = drupal_add_js('misc/collapse.js'); 1474 $this->assertTrue(array_key_exists('misc/jquery.js', $javascript), 'jQuery is added when a file is added.'); 1475 $this->assertTrue(array_key_exists('misc/drupal.js', $javascript), 'Drupal.js is added when file is added.'); 1476 $this->assertTrue(array_key_exists('misc/collapse.js', $javascript), 'JavaScript files are correctly added.'); 1477 $this->assertEqual(base_path(), $javascript['settings']['data'][0]['basePath'], 'Base path JavaScript setting is correctly set.'); 1478 url('', array('prefix' => &$prefix)); 1479 $this->assertEqual(empty($prefix) ? '' : $prefix, $javascript['settings']['data'][1]['pathPrefix'], 'Path prefix JavaScript setting is correctly set.'); 1480 } 1481 1482 /** 1483 * Test adding settings. 1484 */ 1485 function testAddSetting() { 1486 $javascript = drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting'); 1487 $this->assertEqual(280342800, $javascript['settings']['data'][2]['dries'], 'JavaScript setting is set correctly.'); 1488 $this->assertEqual('rocks', $javascript['settings']['data'][2]['drupal'], 'The other JavaScript setting is set correctly.'); 1489 } 1490 1491 /** 1492 * Tests adding an external JavaScript File. 1493 */ 1494 function testAddExternal() { 1495 $path = 'http://example.com/script.js'; 1496 $javascript = drupal_add_js($path, 'external'); 1497 $this->assertTrue(array_key_exists('http://example.com/script.js', $javascript), 'Added an external JavaScript file.'); 1498 } 1499 1500 /** 1501 * Test drupal_get_js() for JavaScript settings. 1502 */ 1503 function testHeaderSetting() { 1504 // Only the second of these two entries should appear in Drupal.settings. 1505 drupal_add_js(array('commonTest' => 'commonTestShouldNotAppear'), 'setting'); 1506 drupal_add_js(array('commonTest' => 'commonTestShouldAppear'), 'setting'); 1507 // All three of these entries should appear in Drupal.settings. 1508 drupal_add_js(array('commonTestArray' => array('commonTestValue0')), 'setting'); 1509 drupal_add_js(array('commonTestArray' => array('commonTestValue1')), 'setting'); 1510 drupal_add_js(array('commonTestArray' => array('commonTestValue2')), 'setting'); 1511 // Only the second of these two entries should appear in Drupal.settings. 1512 drupal_add_js(array('commonTestArray' => array('key' => 'commonTestOldValue')), 'setting'); 1513 drupal_add_js(array('commonTestArray' => array('key' => 'commonTestNewValue')), 'setting'); 1514 1515 $javascript = drupal_get_js('header'); 1516 $this->assertTrue(strpos($javascript, 'basePath') > 0, 'Rendered JavaScript header returns basePath setting.'); 1517 $this->assertTrue(strpos($javascript, 'misc/jquery.js') > 0, 'Rendered JavaScript header includes jQuery.'); 1518 $this->assertTrue(strpos($javascript, 'pathPrefix') > 0, 'Rendered JavaScript header returns pathPrefix setting.'); 1519 1520 // Test whether drupal_add_js can be used to override a previous setting. 1521 $this->assertTrue(strpos($javascript, 'commonTestShouldAppear') > 0, 'Rendered JavaScript header returns custom setting.'); 1522 $this->assertTrue(strpos($javascript, 'commonTestShouldNotAppear') === FALSE, 'drupal_add_js() correctly overrides a custom setting.'); 1523 1524 // Test whether drupal_add_js can be used to add numerically indexed values 1525 // to an array. 1526 $array_values_appear = strpos($javascript, 'commonTestValue0') > 0 && strpos($javascript, 'commonTestValue1') > 0 && strpos($javascript, 'commonTestValue2') > 0; 1527 $this->assertTrue($array_values_appear, 'drupal_add_js() correctly adds settings to the end of an indexed array.'); 1528 1529 // Test whether drupal_add_js can be used to override the entry for an 1530 // existing key in an associative array. 1531 $associative_array_override = strpos($javascript, 'commonTestNewValue') > 0 && strpos($javascript, 'commonTestOldValue') === FALSE; 1532 $this->assertTrue($associative_array_override, 'drupal_add_js() correctly overrides settings within an associative array.'); 1533 } 1534 1535 /** 1536 * Test to see if resetting the JavaScript empties the cache. 1537 */ 1538 function testReset() { 1539 drupal_add_js('misc/collapse.js'); 1540 drupal_static_reset('drupal_add_js'); 1541 $this->assertEqual(array(), drupal_add_js(), 'Resetting the JavaScript correctly empties the cache.'); 1542 } 1543 1544 /** 1545 * Test adding inline scripts. 1546 */ 1547 function testAddInline() { 1548 $inline = 'jQuery(function () { });'; 1549 $javascript = drupal_add_js($inline, array('type' => 'inline', 'scope' => 'footer')); 1550 $this->assertTrue(array_key_exists('misc/jquery.js', $javascript), 'jQuery is added when inline scripts are added.'); 1551 $data = end($javascript); 1552 $this->assertEqual($inline, $data['data'], 'Inline JavaScript is correctly added to the footer.'); 1553 } 1554 1555 /** 1556 * Test rendering an external JavaScript file. 1557 */ 1558 function testRenderExternal() { 1559 $external = 'http://example.com/example.js'; 1560 drupal_add_js($external, 'external'); 1561 $javascript = drupal_get_js(); 1562 // Local files have a base_path() prefix, external files should not. 1563 $this->assertTrue(strpos($javascript, 'src="' . $external) > 0, 'Rendering an external JavaScript file.'); 1564 } 1565 1566 /** 1567 * Test drupal_get_js() with a footer scope. 1568 */ 1569 function testFooterHTML() { 1570 $inline = 'jQuery(function () { });'; 1571 drupal_add_js($inline, array('type' => 'inline', 'scope' => 'footer')); 1572 $javascript = drupal_get_js('footer'); 1573 $this->assertTrue(strpos($javascript, $inline) > 0, 'Rendered JavaScript footer returns the inline code.'); 1574 } 1575 1576 /** 1577 * Test the 'javascript_always_use_jquery' variable. 1578 */ 1579 function testJavaScriptAlwaysUseJQuery() { 1580 // The default front page of the site should use jQuery and other standard 1581 // scripts and settings. 1582 $this->drupalGet(''); 1583 $this->assertRaw('misc/jquery.js', 'Default behavior: The front page of the site includes jquery.js.'); 1584 $this->assertRaw('misc/drupal.js', 'Default behavior: The front page of the site includes drupal.js.'); 1585 $this->assertRaw('Drupal.settings', 'Default behavior: The front page of the site includes Drupal settings.'); 1586 $this->assertRaw('basePath', 'Default behavior: The front page of the site includes the basePath Drupal setting.'); 1587 1588 // The default front page should not use jQuery and other standard scripts 1589 // and settings when the 'javascript_always_use_jquery' variable is set to 1590 // FALSE. 1591 variable_set('javascript_always_use_jquery', FALSE); 1592 $this->drupalGet(''); 1593 $this->assertNoRaw('misc/jquery.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include jquery.js.'); 1594 $this->assertNoRaw('misc/drupal.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include drupal.js.'); 1595 $this->assertNoRaw('Drupal.settings', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include Drupal settings.'); 1596 $this->assertNoRaw('basePath', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include the basePath Drupal setting.'); 1597 variable_del('javascript_always_use_jquery'); 1598 1599 // When only settings have been added via drupal_add_js(), drupal_get_js() 1600 // should still return jQuery and other standard scripts and settings. 1601 $this->resetStaticVariables(); 1602 drupal_add_js(array('testJavaScriptSetting' => 'test'), 'setting'); 1603 $javascript = drupal_get_js(); 1604 $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes jquery.js.'); 1605 $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes drupal.js.'); 1606 $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes Drupal.settings.'); 1607 $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the basePath Drupal setting.'); 1608 $this->assertTrue(strpos($javascript, 'testJavaScriptSetting') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the added Drupal settings.'); 1609 1610 // When only settings have been added via drupal_add_js() and the 1611 // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js() 1612 // should not return jQuery and other standard scripts and settings, nor 1613 // should it return the requested settings (since they cannot actually be 1614 // addded to the page without jQuery). 1615 $this->resetStaticVariables(); 1616 variable_set('javascript_always_use_jquery', FALSE); 1617 drupal_add_js(array('testJavaScriptSetting' => 'test'), 'setting'); 1618 $javascript = drupal_get_js(); 1619 $this->assertTrue(strpos($javascript, 'misc/jquery.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include jquery.js.'); 1620 $this->assertTrue(strpos($javascript, 'misc/drupal.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include drupal.js.'); 1621 $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include Drupal.settings.'); 1622 $this->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the basePath Drupal setting.'); 1623 $this->assertTrue(strpos($javascript, 'testJavaScriptSetting') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the added Drupal settings.'); 1624 variable_del('javascript_always_use_jquery'); 1625 1626 // When a regular file has been added via drupal_add_js(), drupal_get_js() 1627 // should return jQuery and other standard scripts and settings. 1628 $this->resetStaticVariables(); 1629 drupal_add_js('misc/collapse.js'); 1630 $javascript = drupal_get_js(); 1631 $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes jquery.js.'); 1632 $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal.js.'); 1633 $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.'); 1634 $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.'); 1635 $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.'); 1636 1637 // When a regular file has been added via drupal_add_js() and the 1638 // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js() 1639 // should still return jQuery and other standard scripts and settings 1640 // (since the file is assumed to require jQuery by default). 1641 $this->resetStaticVariables(); 1642 variable_set('javascript_always_use_jquery', FALSE); 1643 drupal_add_js('misc/collapse.js'); 1644 $javascript = drupal_get_js(); 1645 $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes jquery.js.'); 1646 $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal.js.'); 1647 $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.'); 1648 $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.'); 1649 $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.'); 1650 variable_del('javascript_always_use_jquery'); 1651 1652 // When a file that does not require jQuery has been added via 1653 // drupal_add_js(), drupal_get_js() should still return jQuery and other 1654 // standard scripts and settings by default. 1655 $this->resetStaticVariables(); 1656 drupal_add_js('misc/collapse.js', array('requires_jquery' => FALSE)); 1657 $javascript = drupal_get_js(); 1658 $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes jquery.js.'); 1659 $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes drupal.js.'); 1660 $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes Drupal.settings.'); 1661 $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the basePath Drupal setting.'); 1662 $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.'); 1663 1664 // When a file that does not require jQuery has been added via 1665 // drupal_add_js() and the 'javascript_always_use_jquery' variable is set 1666 // to FALSE, drupal_get_js() should not return jQuery and other standard 1667 // scripts and setting, but it should still return the requested file. 1668 $this->resetStaticVariables(); 1669 variable_set('javascript_always_use_jquery', FALSE); 1670 drupal_add_js('misc/collapse.js', array('requires_jquery' => FALSE)); 1671 $javascript = drupal_get_js(); 1672 $this->assertTrue(strpos($javascript, 'misc/jquery.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include jquery.js.'); 1673 $this->assertTrue(strpos($javascript, 'misc/drupal.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include drupal.js.'); 1674 $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include Drupal.settings.'); 1675 $this->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include the basePath Drupal setting.'); 1676 $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.'); 1677 variable_del('javascript_always_use_jquery'); 1678 1679 // When 'javascript_always_use_jquery' is set to FALSE and a file that does 1680 // not require jQuery is added, followed by one that does, drupal_get_js() 1681 // should return jQuery and other standard scripts and settings, in 1682 // addition to both of the requested files. 1683 $this->resetStaticVariables(); 1684 variable_set('javascript_always_use_jquery', FALSE); 1685 drupal_add_js('misc/collapse.js', array('requires_jquery' => FALSE)); 1686 drupal_add_js('misc/ajax.js'); 1687 $javascript = drupal_get_js(); 1688 $this->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes jquery.js.'); 1689 $this->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes drupal.js.'); 1690 $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes Drupal.settings.'); 1691 $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the basePath Drupal setting.'); 1692 $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the first custom file.'); 1693 $this->assertTrue(strpos($javascript, 'misc/ajax.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the second custom file.'); 1694 variable_del('javascript_always_use_jquery'); 1695 } 1696 1697 /** 1698 * Test drupal_add_js() sets preproccess to false when cache is set to false. 1699 */ 1700 function testNoCache() { 1701 $javascript = drupal_add_js('misc/collapse.js', array('cache' => FALSE)); 1702 $this->assertFalse($javascript['misc/collapse.js']['preprocess'], 'Setting cache to FALSE sets proprocess to FALSE when adding JavaScript.'); 1703 } 1704 1705 /** 1706 * Test adding a JavaScript file with a different group. 1707 */ 1708 function testDifferentGroup() { 1709 $javascript = drupal_add_js('misc/collapse.js', array('group' => JS_THEME)); 1710 $this->assertEqual($javascript['misc/collapse.js']['group'], JS_THEME, 'Adding a JavaScript file with a different group caches the given group.'); 1711 } 1712 1713 /** 1714 * Test adding a JavaScript file with a different weight. 1715 */ 1716 function testDifferentWeight() { 1717 $javascript = drupal_add_js('misc/collapse.js', array('weight' => 2)); 1718 $this->assertEqual($javascript['misc/collapse.js']['weight'], 2, 'Adding a JavaScript file with a different weight caches the given weight.'); 1719 } 1720 1721 /** 1722 * Tests JavaScript aggregation when files are added to a different scope. 1723 */ 1724 function testAggregationOrder() { 1725 // Enable JavaScript aggregation. 1726 variable_set('preprocess_js', 1); 1727 drupal_static_reset('drupal_add_js'); 1728 1729 // Add two JavaScript files to the current request and build the cache. 1730 drupal_add_js('misc/ajax.js'); 1731 drupal_add_js('misc/autocomplete.js'); 1732 1733 $js_items = drupal_add_js(); 1734 drupal_build_js_cache(array( 1735 'misc/ajax.js' => $js_items['misc/ajax.js'], 1736 'misc/autocomplete.js' => $js_items['misc/autocomplete.js'] 1737 )); 1738 1739 // Store the expected key for the first item in the cache. 1740 $cache = array_keys(variable_get('drupal_js_cache_files', array())); 1741 $expected_key = $cache[0]; 1742 1743 // Reset variables and add a file in a different scope first. 1744 variable_del('drupal_js_cache_files'); 1745 drupal_static_reset('drupal_add_js'); 1746 drupal_add_js('some/custom/javascript_file.js', array('scope' => 'footer')); 1747 drupal_add_js('misc/ajax.js'); 1748 drupal_add_js('misc/autocomplete.js'); 1749 1750 // Rebuild the cache. 1751 $js_items = drupal_add_js(); 1752 drupal_build_js_cache(array( 1753 'misc/ajax.js' => $js_items['misc/ajax.js'], 1754 'misc/autocomplete.js' => $js_items['misc/autocomplete.js'] 1755 )); 1756 1757 // Compare the expected key for the first file to the current one. 1758 $cache = array_keys(variable_get('drupal_js_cache_files', array())); 1759 $key = $cache[0]; 1760 $this->assertEqual($key, $expected_key, 'JavaScript aggregation is not affected by ordering in different scopes.'); 1761 } 1762 1763 /** 1764 * Test JavaScript ordering. 1765 */ 1766 function testRenderOrder() { 1767 // Add a bunch of JavaScript in strange ordering. 1768 drupal_add_js('(function($){alert("Weight 5 #1");})(jQuery);', array('type' => 'inline', 'scope' => 'footer', 'weight' => 5)); 1769 drupal_add_js('(function($){alert("Weight 0 #1");})(jQuery);', array('type' => 'inline', 'scope' => 'footer')); 1770 drupal_add_js('(function($){alert("Weight 0 #2");})(jQuery);', array('type' => 'inline', 'scope' => 'footer')); 1771 drupal_add_js('(function($){alert("Weight -8 #1");})(jQuery);', array('type' => 'inline', 'scope' => 'footer', 'weight' => -8)); 1772 drupal_add_js('(function($){alert("Weight -8 #2");})(jQuery);', array('type' => 'inline', 'scope' => 'footer', 'weight' => -8)); 1773 drupal_add_js('(function($){alert("Weight -8 #3");})(jQuery);', array('type' => 'inline', 'scope' => 'footer', 'weight' => -8)); 1774 drupal_add_js('http://example.com/example.js?Weight -5 #1', array('type' => 'external', 'scope' => 'footer', 'weight' => -5)); 1775 drupal_add_js('(function($){alert("Weight -8 #4");})(jQuery);', array('type' => 'inline', 'scope' => 'footer', 'weight' => -8)); 1776 drupal_add_js('(function($){alert("Weight 5 #2");})(jQuery);', array('type' => 'inline', 'scope' => 'footer', 'weight' => 5)); 1777 drupal_add_js('(function($){alert("Weight 0 #3");})(jQuery);', array('type' => 'inline', 'scope' => 'footer')); 1778 1779 // Construct the expected result from the regex. 1780 $expected = array( 1781 "-8 #1", 1782 "-8 #2", 1783 "-8 #3", 1784 "-8 #4", 1785 "-5 #1", // The external script. 1786 "0 #1", 1787 "0 #2", 1788 "0 #3", 1789 "5 #1", 1790 "5 #2", 1791 ); 1792 1793 // Retrieve the rendered JavaScript and test against the regex. 1794 $js = drupal_get_js('footer'); 1795 $matches = array(); 1796 if (preg_match_all('/Weight\s([-0-9]+\s[#0-9]+)/', $js, $matches)) { 1797 $result = $matches[1]; 1798 } 1799 else { 1800 $result = array(); 1801 } 1802 $this->assertIdentical($result, $expected, 'JavaScript is added in the expected weight order.'); 1803 } 1804 1805 /** 1806 * Test rendering the JavaScript with a file's weight above jQuery's. 1807 */ 1808 function testRenderDifferentWeight() { 1809 // JavaScript files are sorted first by group, then by the 'every_page' 1810 // flag, then by weight (see drupal_sort_css_js()), so to test the effect of 1811 // weight, we need the other two options to be the same. 1812 drupal_add_js('misc/collapse.js', array('group' => JS_LIBRARY, 'every_page' => TRUE, 'weight' => -21)); 1813 $javascript = drupal_get_js(); 1814 $this->assertTrue(strpos($javascript, 'misc/collapse.js') < strpos($javascript, 'misc/jquery.js'), 'Rendering a JavaScript file above jQuery.'); 1815 } 1816 1817 /** 1818 * Test altering a JavaScript's weight via hook_js_alter(). 1819 * 1820 * @see simpletest_js_alter() 1821 */ 1822 function testAlter() { 1823 // Add both tableselect.js and simpletest.js, with a larger weight on SimpleTest. 1824 drupal_add_js('misc/tableselect.js'); 1825 drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', array('weight' => 9999)); 1826 1827 // Render the JavaScript, testing if simpletest.js was altered to be before 1828 // tableselect.js. See simpletest_js_alter() to see where this alteration 1829 // takes place. 1830 $javascript = drupal_get_js(); 1831 $this->assertTrue(strpos($javascript, 'simpletest.js') < strpos($javascript, 'misc/tableselect.js'), 'Altering JavaScript weight through the alter hook.'); 1832 } 1833 1834 /** 1835 * Adds a library to the page and tests for both its JavaScript and its CSS. 1836 */ 1837 function testLibraryRender() { 1838 $result = drupal_add_library('system', 'farbtastic'); 1839 $this->assertTrue($result !== FALSE, 'Library was added without errors.'); 1840 $scripts = drupal_get_js(); 1841 $styles = drupal_get_css(); 1842 $this->assertTrue(strpos($scripts, 'misc/farbtastic/farbtastic.js'), 'JavaScript of library was added to the page.'); 1843 $this->assertTrue(strpos($styles, 'misc/farbtastic/farbtastic.css'), 'Stylesheet of library was added to the page.'); 1844 } 1845 1846 /** 1847 * Adds a JavaScript library to the page and alters it. 1848 * 1849 * @see common_test_library_alter() 1850 */ 1851 function testLibraryAlter() { 1852 // Verify that common_test altered the title of Farbtastic. 1853 $library = drupal_get_library('system', 'farbtastic'); 1854 $this->assertEqual($library['title'], 'Farbtastic: Altered Library', 'Registered libraries were altered.'); 1855 1856 // common_test_library_alter() also added a dependency on jQuery Form. 1857 drupal_add_library('system', 'farbtastic'); 1858 $scripts = drupal_get_js(); 1859 $this->assertTrue(strpos($scripts, 'misc/jquery.form.js'), 'Altered library dependencies are added to the page.'); 1860 } 1861 1862 /** 1863 * Tests that multiple modules can implement the same library. 1864 * 1865 * @see common_test_library() 1866 */ 1867 function testLibraryNameConflicts() { 1868 $farbtastic = drupal_get_library('common_test', 'farbtastic'); 1869 $this->assertEqual($farbtastic['title'], 'Custom Farbtastic Library', 'Alternative libraries can be added to the page.'); 1870 } 1871 1872 /** 1873 * Tests non-existing libraries. 1874 */ 1875 function testLibraryUnknown() { 1876 $result = drupal_get_library('unknown', 'unknown'); 1877 $this->assertFalse($result, 'Unknown library returned FALSE.'); 1878 drupal_static_reset('drupal_get_library'); 1879 1880 $result = drupal_add_library('unknown', 'unknown'); 1881 $this->assertFalse($result, 'Unknown library returned FALSE.'); 1882 $scripts = drupal_get_js(); 1883 $this->assertTrue(strpos($scripts, 'unknown') === FALSE, 'Unknown library was not added to the page.'); 1884 } 1885 1886 /** 1887 * Tests the addition of libraries through the #attached['library'] property. 1888 */ 1889 function testAttachedLibrary() { 1890 $element['#attached']['library'][] = array('system', 'farbtastic'); 1891 drupal_render($element); 1892 $scripts = drupal_get_js(); 1893 $this->assertTrue(strpos($scripts, 'misc/farbtastic/farbtastic.js'), 'The attached_library property adds the additional libraries.'); 1894 } 1895 1896 /** 1897 * Tests retrieval of libraries via drupal_get_library(). 1898 */ 1899 function testGetLibrary() { 1900 // Retrieve all libraries registered by a module. 1901 $libraries = drupal_get_library('common_test'); 1902 $this->assertTrue(isset($libraries['farbtastic']), 'Retrieved all module libraries.'); 1903 // Retrieve all libraries for a module not implementing hook_library(). 1904 // Note: This test installs Locale module. 1905 $libraries = drupal_get_library('locale'); 1906 $this->assertEqual($libraries, array(), 'Retrieving libraries from a module not implementing hook_library() returns an emtpy array.'); 1907 1908 // Retrieve a specific library by module and name. 1909 $farbtastic = drupal_get_library('common_test', 'farbtastic'); 1910 $this->assertEqual($farbtastic['version'], '5.3', 'Retrieved a single library.'); 1911 // Retrieve a non-existing library by module and name. 1912 $farbtastic = drupal_get_library('common_test', 'foo'); 1913 $this->assertIdentical($farbtastic, FALSE, 'Retrieving a non-existing library returns FALSE.'); 1914 } 1915 1916 /** 1917 * Tests that the query string remains intact when adding JavaScript files 1918 * that have query string parameters. 1919 */ 1920 function testAddJsFileWithQueryString() { 1921 $this->drupalGet('common-test/query-string'); 1922 $query_string = variable_get('css_js_query_string', '0'); 1923 $this->assertRaw(drupal_get_path('module', 'node') . '/node.js?' . $query_string, 'Query string was appended correctly to js.'); 1924 } 1925 1926 /** 1927 * Resets static variables related to adding JavaScript to a page. 1928 */ 1929 function resetStaticVariables() { 1930 drupal_static_reset('drupal_add_js'); 1931 drupal_static_reset('drupal_add_library'); 1932 drupal_static_reset('drupal_get_library'); 1933 } 1934} 1935 1936/** 1937 * Tests for drupal_render(). 1938 */ 1939class DrupalRenderTestCase extends DrupalWebTestCase { 1940 public static function getInfo() { 1941 return array( 1942 'name' => 'drupal_render()', 1943 'description' => 'Performs functional tests on drupal_render().', 1944 'group' => 'System', 1945 ); 1946 } 1947 1948 function setUp() { 1949 parent::setUp('common_test'); 1950 } 1951 1952 /** 1953 * Tests the output drupal_render() for some elementary input values. 1954 */ 1955 function testDrupalRenderBasics() { 1956 $types = array( 1957 array( 1958 'name' => 'null', 1959 'value' => NULL, 1960 'expected' => '', 1961 ), 1962 array( 1963 'name' => 'no value', 1964 'expected' => '', 1965 ), 1966 array( 1967 'name' => 'empty string', 1968 'value' => '', 1969 'expected' => '', 1970 ), 1971 array( 1972 'name' => 'no access', 1973 'value' => array( 1974 '#markup' => 'foo', 1975 '#access' => FALSE, 1976 ), 1977 'expected' => '', 1978 ), 1979 array( 1980 'name' => 'previously printed', 1981 'value' => array( 1982 '#markup' => 'foo', 1983 '#printed' => TRUE, 1984 ), 1985 'expected' => '', 1986 ), 1987 array( 1988 'name' => 'printed in prerender', 1989 'value' => array( 1990 '#markup' => 'foo', 1991 '#pre_render' => array('common_test_drupal_render_printing_pre_render'), 1992 ), 1993 'expected' => '', 1994 ), 1995 array( 1996 'name' => 'basic renderable array', 1997 'value' => array('#markup' => 'foo'), 1998 'expected' => 'foo', 1999 ), 2000 ); 2001 foreach($types as $type) { 2002 $this->assertIdentical(drupal_render($type['value']), $type['expected'], '"' . $type['name'] . '" input rendered correctly by drupal_render().'); 2003 } 2004 } 2005 2006 /** 2007 * Test sorting by weight. 2008 */ 2009 function testDrupalRenderSorting() { 2010 $first = $this->randomName(); 2011 $second = $this->randomName(); 2012 // Build an array with '#weight' set for each element. 2013 $elements = array( 2014 'second' => array( 2015 '#weight' => 10, 2016 '#markup' => $second, 2017 ), 2018 'first' => array( 2019 '#weight' => 0, 2020 '#markup' => $first, 2021 ), 2022 ); 2023 $output = drupal_render($elements); 2024 2025 // The lowest weight element should appear last in $output. 2026 $this->assertTrue(strpos($output, $second) > strpos($output, $first), 'Elements were sorted correctly by weight.'); 2027 2028 // Confirm that the $elements array has '#sorted' set to TRUE. 2029 $this->assertTrue($elements['#sorted'], "'#sorted' => TRUE was added to the array"); 2030 2031 // Pass $elements through element_children() and ensure it remains 2032 // sorted in the correct order. drupal_render() will return an empty string 2033 // if used on the same array in the same request. 2034 $children = element_children($elements); 2035 $this->assertTrue(array_shift($children) == 'first', 'Child found in the correct order.'); 2036 $this->assertTrue(array_shift($children) == 'second', 'Child found in the correct order.'); 2037 2038 2039 // The same array structure again, but with #sorted set to TRUE. 2040 $elements = array( 2041 'second' => array( 2042 '#weight' => 10, 2043 '#markup' => $second, 2044 ), 2045 'first' => array( 2046 '#weight' => 0, 2047 '#markup' => $first, 2048 ), 2049 '#sorted' => TRUE, 2050 ); 2051 $output = drupal_render($elements); 2052 2053 // The elements should appear in output in the same order as the array. 2054 $this->assertTrue(strpos($output, $second) < strpos($output, $first), 'Elements were not sorted.'); 2055 2056 // The order of children with same weight should be preserved. 2057 $element_mixed_weight = array( 2058 'child5' => array('#weight' => 10), 2059 'child3' => array('#weight' => -10), 2060 'child1' => array(), 2061 'child4' => array('#weight' => 10), 2062 'child2' => array(), 2063 'child6' => array('#weight' => 10), 2064 'child9' => array(), 2065 'child8' => array('#weight' => 10), 2066 'child7' => array(), 2067 ); 2068 2069 $expected = array( 2070 'child3', 2071 'child1', 2072 'child2', 2073 'child9', 2074 'child7', 2075 'child5', 2076 'child4', 2077 'child6', 2078 'child8', 2079 ); 2080 $this->assertEqual($expected, element_children($element_mixed_weight, TRUE), 'Order of elements with the same weight is preserved.'); 2081 } 2082 2083 /** 2084 * Test #attached functionality in children elements. 2085 */ 2086 function testDrupalRenderChildrenAttached() { 2087 // The cache system is turned off for POST requests. 2088 $request_method = $_SERVER['REQUEST_METHOD']; 2089 $_SERVER['REQUEST_METHOD'] = 'GET'; 2090 2091 // Create an element with a child and subchild. Each element loads a 2092 // different JavaScript file using #attached. 2093 $parent_js = drupal_get_path('module', 'user') . '/user.js'; 2094 $child_js = drupal_get_path('module', 'forum') . '/forum.js'; 2095 $subchild_js = drupal_get_path('module', 'book') . '/book.js'; 2096 $element = array( 2097 '#type' => 'fieldset', 2098 '#cache' => array( 2099 'keys' => array('simpletest', 'drupal_render', 'children_attached'), 2100 ), 2101 '#attached' => array('js' => array($parent_js)), 2102 '#title' => 'Parent', 2103 ); 2104 $element['child'] = array( 2105 '#type' => 'fieldset', 2106 '#attached' => array('js' => array($child_js)), 2107 '#title' => 'Child', 2108 ); 2109 $element['child']['subchild'] = array( 2110 '#attached' => array('js' => array($subchild_js)), 2111 '#markup' => 'Subchild', 2112 ); 2113 2114 // Render the element and verify the presence of #attached JavaScript. 2115 drupal_render($element); 2116 $scripts = drupal_get_js(); 2117 $this->assertTrue(strpos($scripts, $parent_js), 'The element #attached JavaScript was included.'); 2118 $this->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included.'); 2119 $this->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included.'); 2120 2121 // Load the element from cache and verify the presence of the #attached 2122 // JavaScript. 2123 drupal_static_reset('drupal_add_js'); 2124 $this->assertTrue(drupal_render_cache_get($element), 'The element was retrieved from cache.'); 2125 $scripts = drupal_get_js(); 2126 $this->assertTrue(strpos($scripts, $parent_js), 'The element #attached JavaScript was included when loading from cache.'); 2127 $this->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included when loading from cache.'); 2128 $this->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included when loading from cache.'); 2129 2130 $_SERVER['REQUEST_METHOD'] = $request_method; 2131 } 2132 2133 /** 2134 * Test passing arguments to the theme function. 2135 */ 2136 function testDrupalRenderThemeArguments() { 2137 $element = array( 2138 '#theme' => 'common_test_foo', 2139 ); 2140 // Test that defaults work. 2141 $this->assertEqual(drupal_render($element), 'foobar', 'Defaults work'); 2142 $element = array( 2143 '#theme' => 'common_test_foo', 2144 '#foo' => $this->randomName(), 2145 '#bar' => $this->randomName(), 2146 ); 2147 // Test that passing arguments to the theme function works. 2148 $this->assertEqual(drupal_render($element), $element['#foo'] . $element['#bar'], 'Passing arguments to theme functions works'); 2149 } 2150 2151 /** 2152 * Test rendering form elements without passing through form_builder(). 2153 */ 2154 function testDrupalRenderFormElements() { 2155 // Define a series of form elements. 2156 $element = array( 2157 '#type' => 'button', 2158 '#value' => $this->randomName(), 2159 ); 2160 $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'submit')); 2161 2162 $element = array( 2163 '#type' => 'textfield', 2164 '#title' => $this->randomName(), 2165 '#value' => $this->randomName(), 2166 ); 2167 $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'text')); 2168 2169 $element = array( 2170 '#type' => 'password', 2171 '#title' => $this->randomName(), 2172 ); 2173 $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'password')); 2174 2175 $element = array( 2176 '#type' => 'textarea', 2177 '#title' => $this->randomName(), 2178 '#value' => $this->randomName(), 2179 ); 2180 $this->assertRenderedElement($element, '//textarea'); 2181 2182 $element = array( 2183 '#type' => 'radio', 2184 '#title' => $this->randomName(), 2185 '#value' => FALSE, 2186 ); 2187 $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'radio')); 2188 2189 $element = array( 2190 '#type' => 'checkbox', 2191 '#title' => $this->randomName(), 2192 ); 2193 $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'checkbox')); 2194 2195 $element = array( 2196 '#type' => 'select', 2197 '#title' => $this->randomName(), 2198 '#options' => array( 2199 0 => $this->randomName(), 2200 1 => $this->randomName(), 2201 ), 2202 ); 2203 $this->assertRenderedElement($element, '//select'); 2204 2205 $element = array( 2206 '#type' => 'file', 2207 '#title' => $this->randomName(), 2208 ); 2209 $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'file')); 2210 2211 $element = array( 2212 '#type' => 'item', 2213 '#title' => $this->randomName(), 2214 '#markup' => $this->randomName(), 2215 ); 2216 $this->assertRenderedElement($element, '//div[contains(@class, :class) and contains(., :markup)]/label[contains(., :label)]', array( 2217 ':class' => 'form-type-item', 2218 ':markup' => $element['#markup'], 2219 ':label' => $element['#title'], 2220 )); 2221 2222 $element = array( 2223 '#type' => 'hidden', 2224 '#title' => $this->randomName(), 2225 '#value' => $this->randomName(), 2226 ); 2227 $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'hidden')); 2228 2229 $element = array( 2230 '#type' => 'link', 2231 '#title' => $this->randomName(), 2232 '#href' => $this->randomName(), 2233 '#options' => array( 2234 'absolute' => TRUE, 2235 ), 2236 ); 2237 $this->assertRenderedElement($element, '//a[@href=:href and contains(., :title)]', array( 2238 ':href' => url($element['#href'], array('absolute' => TRUE)), 2239 ':title' => $element['#title'], 2240 )); 2241 2242 $element = array( 2243 '#type' => 'fieldset', 2244 '#title' => $this->randomName(), 2245 ); 2246 $this->assertRenderedElement($element, '//fieldset/legend[contains(., :title)]', array( 2247 ':title' => $element['#title'], 2248 )); 2249 2250 $element['item'] = array( 2251 '#type' => 'item', 2252 '#title' => $this->randomName(), 2253 '#markup' => $this->randomName(), 2254 ); 2255 $this->assertRenderedElement($element, '//fieldset/div/div[contains(@class, :class) and contains(., :markup)]', array( 2256 ':class' => 'form-type-item', 2257 ':markup' => $element['item']['#markup'], 2258 )); 2259 } 2260 2261 protected function assertRenderedElement(array $element, $xpath, array $xpath_args = array()) { 2262 $original_element = $element; 2263 $this->drupalSetContent(drupal_render($element)); 2264 $this->verbose('<pre>' . check_plain(var_export($original_element, TRUE)) . '</pre>' 2265 . '<pre>' . check_plain(var_export($element, TRUE)) . '</pre>' 2266 . '<hr />' . $this->drupalGetContent() 2267 ); 2268 2269 // @see DrupalWebTestCase::xpath() 2270 $xpath = $this->buildXPathQuery($xpath, $xpath_args); 2271 $element += array('#value' => NULL); 2272 $this->assertFieldByXPath($xpath, $element['#value'], format_string('#type @type was properly rendered.', array( 2273 '@type' => var_export($element['#type'], TRUE), 2274 ))); 2275 } 2276 2277 /** 2278 * Tests caching of render items. 2279 */ 2280 function testDrupalRenderCache() { 2281 // Force a request via GET. 2282 $request_method = $_SERVER['REQUEST_METHOD']; 2283 $_SERVER['REQUEST_METHOD'] = 'GET'; 2284 // Create an empty element. 2285 $test_element = array( 2286 '#cache' => array( 2287 'cid' => 'render_cache_test', 2288 ), 2289 '#markup' => '', 2290 ); 2291 2292 // Render the element and confirm that it goes through the rendering 2293 // process (which will set $element['#printed']). 2294 $element = $test_element; 2295 drupal_render($element); 2296 $this->assertTrue(isset($element['#printed']), 'No cache hit'); 2297 2298 // Render the element again and confirm that it is retrieved from the cache 2299 // instead (so $element['#printed'] will not be set). 2300 $element = $test_element; 2301 drupal_render($element); 2302 $this->assertFalse(isset($element['#printed']), 'Cache hit'); 2303 2304 // Test that user 1 does not share the cache with other users who have the 2305 // same roles, even when DRUPAL_CACHE_PER_ROLE is used. 2306 $user1 = user_load(1); 2307 $first_authenticated_user = $this->drupalCreateUser(); 2308 $second_authenticated_user = $this->drupalCreateUser(); 2309 $user1->roles = array_intersect_key($user1->roles, array(DRUPAL_AUTHENTICATED_RID => TRUE)); 2310 user_save($user1); 2311 // Load all the accounts again, to make sure we have complete account 2312 // objects. 2313 $user1 = user_load(1); 2314 $first_authenticated_user = user_load($first_authenticated_user->uid); 2315 $second_authenticated_user = user_load($second_authenticated_user->uid); 2316 $this->assertEqual($user1->roles, $first_authenticated_user->roles, 'User 1 has the same roles as an authenticated user.'); 2317 // Impersonate user 1 and render content that only user 1 should have 2318 // permission to see. 2319 $original_user = $GLOBALS['user']; 2320 $original_session_state = drupal_save_session(); 2321 drupal_save_session(FALSE); 2322 $GLOBALS['user'] = $user1; 2323 $test_element = array( 2324 '#cache' => array( 2325 'keys' => array('test'), 2326 'granularity' => DRUPAL_CACHE_PER_ROLE, 2327 ), 2328 ); 2329 $element = $test_element; 2330 $element['#markup'] = 'content for user 1'; 2331 $output = drupal_render($element); 2332 $this->assertEqual($output, 'content for user 1'); 2333 // Verify the cache is working by rendering the same element but with 2334 // different markup passed in; the result should be the same. 2335 $element = $test_element; 2336 $element['#markup'] = 'should not be used'; 2337 $output = drupal_render($element); 2338 $this->assertEqual($output, 'content for user 1'); 2339 // Verify that the first authenticated user does not see the same content 2340 // as user 1. 2341 $GLOBALS['user'] = $first_authenticated_user; 2342 $element = $test_element; 2343 $element['#markup'] = 'content for authenticated users'; 2344 $output = drupal_render($element); 2345 $this->assertEqual($output, 'content for authenticated users'); 2346 // Verify that the second authenticated user shares the cache with the 2347 // first authenticated user. 2348 $GLOBALS['user'] = $second_authenticated_user; 2349 $element = $test_element; 2350 $element['#markup'] = 'should not be used'; 2351 $output = drupal_render($element); 2352 $this->assertEqual($output, 'content for authenticated users'); 2353 // Restore the original logged-in user. 2354 $GLOBALS['user'] = $original_user; 2355 drupal_save_session($original_session_state); 2356 2357 // Restore the previous request method. 2358 $_SERVER['REQUEST_METHOD'] = $request_method; 2359 } 2360} 2361 2362/** 2363 * Test for valid_url(). 2364 */ 2365class ValidUrlTestCase extends DrupalUnitTestCase { 2366 public static function getInfo() { 2367 return array( 2368 'name' => 'Valid URL', 2369 'description' => "Performs tests on Drupal's valid URL function.", 2370 'group' => 'System' 2371 ); 2372 } 2373 2374 /** 2375 * Test valid absolute URLs. 2376 */ 2377 function testValidAbsolute() { 2378 $url_schemes = array('http', 'https', 'ftp'); 2379 $valid_absolute_urls = array( 2380 'example.com', 2381 'www.example.com', 2382 'ex-ample.com', 2383 '3xampl3.com', 2384 'example.com/paren(the)sis', 2385 'example.com/index.html#pagetop', 2386 'example.com:8080', 2387 'subdomain.example.com', 2388 'example.com/index.php?q=node', 2389 'example.com/index.php?q=node¶m=false', 2390 'user@www.example.com', 2391 'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop', 2392 '127.0.0.1', 2393 'example.org?', 2394 'john%20doe:secret:foo@example.org/', 2395 'example.org/~,$\'*;', 2396 'caf%C3%A9.example.org', 2397 '[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html', 2398 ); 2399 2400 foreach ($url_schemes as $scheme) { 2401 foreach ($valid_absolute_urls as $url) { 2402 $test_url = $scheme . '://' . $url; 2403 $valid_url = valid_url($test_url, TRUE); 2404 $this->assertTrue($valid_url, format_string('@url is a valid url.', array('@url' => $test_url))); 2405 } 2406 } 2407 } 2408 2409 /** 2410 * Test invalid absolute URLs. 2411 */ 2412 function testInvalidAbsolute() { 2413 $url_schemes = array('http', 'https', 'ftp'); 2414 $invalid_ablosule_urls = array( 2415 '', 2416 'ex!ample.com', 2417 'ex%ample.com', 2418 ); 2419 2420 foreach ($url_schemes as $scheme) { 2421 foreach ($invalid_ablosule_urls as $url) { 2422 $test_url = $scheme . '://' . $url; 2423 $valid_url = valid_url($test_url, TRUE); 2424 $this->assertFalse($valid_url, format_string('@url is NOT a valid url.', array('@url' => $test_url))); 2425 } 2426 } 2427 } 2428 2429 /** 2430 * Test valid relative URLs. 2431 */ 2432 function testValidRelative() { 2433 $valid_relative_urls = array( 2434 'paren(the)sis', 2435 'index.html#pagetop', 2436 'index.php?q=node', 2437 'index.php?q=node¶m=false', 2438 'login.php?do=login&style=%23#pagetop', 2439 ); 2440 2441 foreach (array('', '/') as $front) { 2442 foreach ($valid_relative_urls as $url) { 2443 $test_url = $front . $url; 2444 $valid_url = valid_url($test_url); 2445 $this->assertTrue($valid_url, format_string('@url is a valid url.', array('@url' => $test_url))); 2446 } 2447 } 2448 } 2449 2450 /** 2451 * Test invalid relative URLs. 2452 */ 2453 function testInvalidRelative() { 2454 $invalid_relative_urls = array( 2455 'ex^mple', 2456 'example<>', 2457 'ex%ample', 2458 ); 2459 2460 foreach (array('', '/') as $front) { 2461 foreach ($invalid_relative_urls as $url) { 2462 $test_url = $front . $url; 2463 $valid_url = valid_url($test_url); 2464 $this->assertFALSE($valid_url, format_string('@url is NOT a valid url.', array('@url' => $test_url))); 2465 } 2466 } 2467 } 2468} 2469 2470/** 2471 * Tests for CRUD API functions. 2472 */ 2473class DrupalDataApiTest extends DrupalWebTestCase { 2474 public static function getInfo() { 2475 return array( 2476 'name' => 'Data API functions', 2477 'description' => 'Tests the performance of CRUD APIs.', 2478 'group' => 'System', 2479 ); 2480 } 2481 2482 function setUp() { 2483 parent::setUp('database_test'); 2484 } 2485 2486 /** 2487 * Test the drupal_write_record() API function. 2488 */ 2489 function testDrupalWriteRecord() { 2490 // Insert a record - no columns allow NULL values. 2491 $person = new stdClass(); 2492 $person->name = 'John'; 2493 $person->unknown_column = 123; 2494 $insert_result = drupal_write_record('test', $person); 2495 $this->assertTrue($insert_result == SAVED_NEW, 'Correct value returned when a record is inserted with drupal_write_record() for a table with a single-field primary key.'); 2496 $this->assertTrue(isset($person->id), 'Primary key is set on record created with drupal_write_record().'); 2497 $this->assertIdentical($person->age, 0, 'Age field set to default value.'); 2498 $this->assertIdentical($person->job, 'Undefined', 'Job field set to default value.'); 2499 2500 // Verify that the record was inserted. 2501 $result = db_query("SELECT * FROM {test} WHERE id = :id", array(':id' => $person->id))->fetchObject(); 2502 $this->assertIdentical($result->name, 'John', 'Name field set.'); 2503 $this->assertIdentical($result->age, '0', 'Age field set to default value.'); 2504 $this->assertIdentical($result->job, 'Undefined', 'Job field set to default value.'); 2505 $this->assertFalse(isset($result->unknown_column), 'Unknown column was ignored.'); 2506 2507 // Update the newly created record. 2508 $person->name = 'Peter'; 2509 $person->age = 27; 2510 $person->job = NULL; 2511 $update_result = drupal_write_record('test', $person, array('id')); 2512 $this->assertTrue($update_result == SAVED_UPDATED, 'Correct value returned when a record updated with drupal_write_record() for table with single-field primary key.'); 2513 2514 // Verify that the record was updated. 2515 $result = db_query("SELECT * FROM {test} WHERE id = :id", array(':id' => $person->id))->fetchObject(); 2516 $this->assertIdentical($result->name, 'Peter', 'Name field set.'); 2517 $this->assertIdentical($result->age, '27', 'Age field set.'); 2518 $this->assertIdentical($result->job, '', 'Job field set and cast to string.'); 2519 2520 // Try to insert NULL in columns that does not allow this. 2521 $person = new stdClass(); 2522 $person->name = 'Ringo'; 2523 $person->age = NULL; 2524 $person->job = NULL; 2525 $insert_result = drupal_write_record('test', $person); 2526 $this->assertTrue(isset($person->id), 'Primary key is set on record created with drupal_write_record().'); 2527 $result = db_query("SELECT * FROM {test} WHERE id = :id", array(':id' => $person->id))->fetchObject(); 2528 $this->assertIdentical($result->name, 'Ringo', 'Name field set.'); 2529 $this->assertIdentical($result->age, '0', 'Age field set.'); 2530 $this->assertIdentical($result->job, '', 'Job field set.'); 2531 2532 // Insert a record - the "age" column allows NULL. 2533 $person = new stdClass(); 2534 $person->name = 'Paul'; 2535 $person->age = NULL; 2536 $insert_result = drupal_write_record('test_null', $person); 2537 $this->assertTrue(isset($person->id), 'Primary key is set on record created with drupal_write_record().'); 2538 $result = db_query("SELECT * FROM {test_null} WHERE id = :id", array(':id' => $person->id))->fetchObject(); 2539 $this->assertIdentical($result->name, 'Paul', 'Name field set.'); 2540 $this->assertIdentical($result->age, NULL, 'Age field set.'); 2541 2542 // Insert a record - do not specify the value of a column that allows NULL. 2543 $person = new stdClass(); 2544 $person->name = 'Meredith'; 2545 $insert_result = drupal_write_record('test_null', $person); 2546 $this->assertTrue(isset($person->id), 'Primary key is set on record created with drupal_write_record().'); 2547 $this->assertIdentical($person->age, 0, 'Age field set to default value.'); 2548 $result = db_query("SELECT * FROM {test_null} WHERE id = :id", array(':id' => $person->id))->fetchObject(); 2549 $this->assertIdentical($result->name, 'Meredith', 'Name field set.'); 2550 $this->assertIdentical($result->age, '0', 'Age field set to default value.'); 2551 2552 // Update the newly created record. 2553 $person->name = 'Mary'; 2554 $person->age = NULL; 2555 $update_result = drupal_write_record('test_null', $person, array('id')); 2556 $result = db_query("SELECT * FROM {test_null} WHERE id = :id", array(':id' => $person->id))->fetchObject(); 2557 $this->assertIdentical($result->name, 'Mary', 'Name field set.'); 2558 $this->assertIdentical($result->age, NULL, 'Age field set.'); 2559 2560 // Insert a record - the "data" column should be serialized. 2561 $person = new stdClass(); 2562 $person->name = 'Dave'; 2563 $update_result = drupal_write_record('test_serialized', $person); 2564 $result = db_query("SELECT * FROM {test_serialized} WHERE id = :id", array(':id' => $person->id))->fetchObject(); 2565 $this->assertIdentical($result->name, 'Dave', 'Name field set.'); 2566 $this->assertIdentical($result->info, NULL, 'Info field set.'); 2567 2568 $person->info = array(); 2569 $update_result = drupal_write_record('test_serialized', $person, array('id')); 2570 $result = db_query("SELECT * FROM {test_serialized} WHERE id = :id", array(':id' => $person->id))->fetchObject(); 2571 $this->assertIdentical(unserialize($result->info), array(), 'Info field updated.'); 2572 2573 // Update the serialized record. 2574 $data = array('foo' => 'bar', 1 => 2, 'empty' => '', 'null' => NULL); 2575 $person->info = $data; 2576 $update_result = drupal_write_record('test_serialized', $person, array('id')); 2577 $result = db_query("SELECT * FROM {test_serialized} WHERE id = :id", array(':id' => $person->id))->fetchObject(); 2578 $this->assertIdentical(unserialize($result->info), $data, 'Info field updated.'); 2579 2580 // Run an update query where no field values are changed. The database 2581 // layer should return zero for number of affected rows, but 2582 // db_write_record() should still return SAVED_UPDATED. 2583 $update_result = drupal_write_record('test_null', $person, array('id')); 2584 $this->assertTrue($update_result == SAVED_UPDATED, 'Correct value returned when a valid update is run without changing any values.'); 2585 2586 // Insert an object record for a table with a multi-field primary key. 2587 $node_access = new stdClass(); 2588 $node_access->nid = mt_rand(); 2589 $node_access->gid = mt_rand(); 2590 $node_access->realm = $this->randomName(); 2591 $insert_result = drupal_write_record('node_access', $node_access); 2592 $this->assertTrue($insert_result == SAVED_NEW, 'Correct value returned when a record is inserted with drupal_write_record() for a table with a multi-field primary key.'); 2593 2594 // Update the record. 2595 $update_result = drupal_write_record('node_access', $node_access, array('nid', 'gid', 'realm')); 2596 $this->assertTrue($update_result == SAVED_UPDATED, 'Correct value returned when a record is updated with drupal_write_record() for a table with a multi-field primary key.'); 2597 } 2598 2599} 2600 2601/** 2602 * Tests Simpletest error and exception collector. 2603 */ 2604class DrupalErrorCollectionUnitTest extends DrupalWebTestCase { 2605 2606 /** 2607 * Errors triggered during the test. 2608 * 2609 * Errors are intercepted by the overriden implementation 2610 * of DrupalWebTestCase::error below. 2611 * 2612 * @var Array 2613 */ 2614 protected $collectedErrors = array(); 2615 2616 public static function getInfo() { 2617 return array( 2618 'name' => 'SimpleTest error collector', 2619 'description' => 'Performs tests on the Simpletest error and exception collector.', 2620 'group' => 'SimpleTest', 2621 ); 2622 } 2623 2624 function setUp() { 2625 parent::setUp('system_test', 'error_test'); 2626 } 2627 2628 /** 2629 * Test that simpletest collects errors from the tested site. 2630 */ 2631 function testErrorCollect() { 2632 $this->collectedErrors = array(); 2633 $this->drupalGet('error-test/generate-warnings-with-report'); 2634 $this->assertEqual(count($this->collectedErrors), 3, 'Three errors were collected'); 2635 2636 if (count($this->collectedErrors) == 3) { 2637 $this->assertError($this->collectedErrors[0], 'Notice', 'error_test_generate_warnings()', 'error_test.module', 'Object of class stdClass could not be converted to int'); 2638 $this->assertError($this->collectedErrors[1], 'Warning', 'error_test_generate_warnings()', 'error_test.module', \PHP_VERSION_ID < 80000 ? 'Invalid argument supplied for foreach()' : 'foreach() argument must be of type array|object, string given'); 2639 $this->assertError($this->collectedErrors[2], 'User warning', 'error_test_generate_warnings()', 'error_test.module', 'Drupal is awesome'); 2640 } 2641 else { 2642 // Give back the errors to the log report. 2643 foreach ($this->collectedErrors as $error) { 2644 parent::error($error['message'], $error['group'], $error['caller']); 2645 } 2646 } 2647 } 2648 2649 /** 2650 * Error handler that collects errors in an array. 2651 * 2652 * This test class is trying to verify that simpletest correctly sees errors 2653 * and warnings. However, it can't generate errors and warnings that 2654 * propagate up to the testing framework itself, or these tests would always 2655 * fail. So, this special copy of error() doesn't propagate the errors up 2656 * the class hierarchy. It just stuffs them into a protected collectedErrors 2657 * array for various assertions to inspect. 2658 */ 2659 protected function error($message = '', $group = 'Other', array $caller = NULL) { 2660 // Due to a WTF elsewhere, simpletest treats debug() and verbose() 2661 // messages as if they were an 'error'. But, we don't want to collect 2662 // those here. This function just wants to collect the real errors (PHP 2663 // notices, PHP fatal errors, etc.), and let all the 'errors' from the 2664 // 'User notice' group bubble up to the parent classes to be handled (and 2665 // eventually displayed) as normal. 2666 if ($group == 'User notice') { 2667 parent::error($message, $group, $caller); 2668 } 2669 // Everything else should be collected but not propagated. 2670 else { 2671 $this->collectedErrors[] = array( 2672 'message' => $message, 2673 'group' => $group, 2674 'caller' => $caller 2675 ); 2676 } 2677 } 2678 2679 /** 2680 * Assert that a collected error matches what we are expecting. 2681 */ 2682 function assertError($error, $group, $function, $file, $message = NULL) { 2683 $this->assertEqual($error['group'], $group, format_string("Group was %group", array('%group' => $group))); 2684 $this->assertEqual($error['caller']['function'], $function, format_string("Function was %function", array('%function' => $function))); 2685 $this->assertEqual(drupal_basename($error['caller']['file']), $file, format_string("File was %file", array('%file' => $file))); 2686 if (isset($message)) { 2687 $this->assertEqual($error['message'], $message, format_string("Message was %message", array('%message' => $message))); 2688 } 2689 } 2690} 2691 2692/** 2693 * Test the drupal_parse_info_file() API function. 2694 */ 2695class ParseInfoFilesTestCase extends DrupalUnitTestCase { 2696 public static function getInfo() { 2697 return array( 2698 'name' => 'Parsing .info files', 2699 'description' => 'Tests parsing .info files.', 2700 'group' => 'System', 2701 ); 2702 } 2703 2704 /** 2705 * Parse an example .info file an verify the results. 2706 */ 2707 function testParseInfoFile() { 2708 $info_values = drupal_parse_info_file(drupal_get_path('module', 'simpletest') . '/tests/common_test_info.txt'); 2709 $this->assertEqual($info_values['simple_string'], 'A simple string', 'Simple string value was parsed correctly.', 'System'); 2710 $this->assertEqual($info_values['simple_constant'], WATCHDOG_INFO, 'Constant value was parsed correctly.', 'System'); 2711 $this->assertEqual($info_values['double_colon'], 'dummyClassName::', 'Value containing double-colon was parsed correctly.', 'System'); 2712 } 2713} 2714 2715/** 2716 * Tests for the drupal_system_listing() function. 2717 */ 2718class DrupalSystemListingTestCase extends DrupalWebTestCase { 2719 /** 2720 * Use the testing profile; this is needed for testDirectoryPrecedence(). 2721 */ 2722 protected $profile = 'testing'; 2723 2724 public static function getInfo() { 2725 return array( 2726 'name' => 'Drupal system listing', 2727 'description' => 'Tests the mechanism for scanning system directories in drupal_system_listing().', 2728 'group' => 'System', 2729 ); 2730 } 2731 2732 /** 2733 * Test that files in different directories take precedence as expected. 2734 */ 2735 function testDirectoryPrecedence() { 2736 // Define the module files we will search for, and the directory precedence 2737 // we expect. 2738 $expected_directories = array( 2739 // When the copy of the module in the profile directory is incompatible 2740 // with Drupal core, the copy in the core modules directory takes 2741 // precedence. 2742 'drupal_system_listing_incompatible_test' => array( 2743 'modules/simpletest/tests', 2744 'profiles/testing/modules', 2745 ), 2746 // When both copies of the module are compatible with Drupal core, the 2747 // copy in the profile directory takes precedence. 2748 'drupal_system_listing_compatible_test' => array( 2749 'profiles/testing/modules', 2750 'modules/simpletest/tests', 2751 ), 2752 ); 2753 2754 // This test relies on two versions of the same module existing in 2755 // different places in the filesystem. Without that, the test has no 2756 // meaning, so assert their presence first. 2757 foreach ($expected_directories as $module => $directories) { 2758 foreach ($directories as $directory) { 2759 $filename = "$directory/$module/$module.module"; 2760 $this->assertTrue(file_exists(DRUPAL_ROOT . '/' . $filename), format_string('@filename exists.', array('@filename' => $filename))); 2761 } 2762 } 2763 2764 // Now scan the directories and check that the files take precedence as 2765 // expected. 2766 $files = drupal_system_listing('/\.module$/', 'modules', 'name', 1); 2767 foreach ($expected_directories as $module => $directories) { 2768 $expected_directory = array_shift($directories); 2769 $expected_filename = "$expected_directory/$module/$module.module"; 2770 $this->assertEqual($files[$module]->uri, $expected_filename, format_string('Module @module was found at @filename.', array('@module' => $module, '@filename' => $expected_filename))); 2771 } 2772 } 2773} 2774 2775/** 2776 * Tests for the format_date() function. 2777 */ 2778class FormatDateUnitTest extends DrupalWebTestCase { 2779 2780 /** 2781 * Arbitrary langcode for a custom language. 2782 */ 2783 const LANGCODE = 'xx'; 2784 2785 public static function getInfo() { 2786 return array( 2787 'name' => 'Format date', 2788 'description' => 'Test the format_date() function.', 2789 'group' => 'System', 2790 ); 2791 } 2792 2793 function setUp() { 2794 parent::setUp('locale'); 2795 variable_set('configurable_timezones', 1); 2796 variable_set('date_format_long', 'l, j. F Y - G:i'); 2797 variable_set('date_format_medium', 'j. F Y - G:i'); 2798 variable_set('date_format_short', 'Y M j - g:ia'); 2799 variable_set('locale_custom_strings_' . self::LANGCODE, array( 2800 '' => array('Sunday' => 'domingo'), 2801 'Long month name' => array('March' => 'marzo'), 2802 )); 2803 $this->refreshVariables(); 2804 } 2805 2806 /** 2807 * Test admin-defined formats in format_date(). 2808 */ 2809 function testAdminDefinedFormatDate() { 2810 // Create an admin user. 2811 $this->admin_user = $this->drupalCreateUser(array('administer site configuration')); 2812 $this->drupalLogin($this->admin_user); 2813 2814 // Add new date format. 2815 $admin_date_format = 'j M y'; 2816 $edit = array('date_format' => $admin_date_format); 2817 $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, 'Add format'); 2818 2819 // Add a new date format which just differs in the case. 2820 $admin_date_format_uppercase = 'j M Y'; 2821 $edit = array('date_format' => $admin_date_format_uppercase); 2822 $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); 2823 $this->assertText(t('Custom date format added.')); 2824 2825 // Add new date type. 2826 $edit = array( 2827 'date_type' => 'Example Style', 2828 'machine_name' => 'example_style', 2829 'date_format' => $admin_date_format, 2830 ); 2831 $this->drupalPost('admin/config/regional/date-time/types/add', $edit, 'Add date type'); 2832 2833 // Add a second date format with a different case than the first. 2834 $edit = array( 2835 'machine_name' => 'example_style_uppercase', 2836 'date_type' => 'Example Style Uppercase', 2837 'date_format' => $admin_date_format_uppercase, 2838 ); 2839 $this->drupalPost('admin/config/regional/date-time/types/add', $edit, t('Add date type')); 2840 $this->assertText(t('New date type added successfully.')); 2841 2842 $timestamp = strtotime('2007-03-10T00:00:00+00:00'); 2843 $this->assertIdentical(format_date($timestamp, 'example_style', '', 'America/Los_Angeles'), '9 Mar 07', 'Test format_date() using an admin-defined date type.'); 2844 $this->assertIdentical(format_date($timestamp, 'example_style_uppercase', '', 'America/Los_Angeles'), '9 Mar 2007', 'Test format_date() using an admin-defined date type with different case.'); 2845 $this->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'medium'), 'Test format_date() defaulting to medium when $type not found.'); 2846 } 2847 2848 /** 2849 * Tests for the format_date() function. 2850 */ 2851 function testFormatDate() { 2852 global $user, $language; 2853 2854 $timestamp = strtotime('2007-03-26T00:00:00+00:00'); 2855 $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test all parameters.'); 2856 $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test translated format.'); 2857 $this->assertIdentical(format_date($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'l, 25-Mar-07 17:00:00 PDT', 'Test an escaped format string.'); 2858 $this->assertIdentical(format_date($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\domingo, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash character.'); 2859 $this->assertIdentical(format_date($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\l, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash followed by escaped format string.'); 2860 $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London', 'en'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.'); 2861 2862 // Create an admin user and add Spanish language. 2863 $admin_user = $this->drupalCreateUser(array('administer languages')); 2864 $this->drupalLogin($admin_user); 2865 $edit = array( 2866 'langcode' => self::LANGCODE, 2867 'name' => self::LANGCODE, 2868 'native' => self::LANGCODE, 2869 'direction' => LANGUAGE_LTR, 2870 'prefix' => self::LANGCODE, 2871 ); 2872 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); 2873 2874 // Create a test user to carry out the tests. 2875 $test_user = $this->drupalCreateUser(); 2876 $this->drupalLogin($test_user); 2877 $edit = array('language' => self::LANGCODE, 'mail' => $test_user->mail, 'timezone' => 'America/Los_Angeles'); 2878 $this->drupalPost('user/' . $test_user->uid . '/edit', $edit, t('Save')); 2879 2880 // Disable session saving as we are about to modify the global $user. 2881 drupal_save_session(FALSE); 2882 // Save the original user and language and then replace it with the test user and language. 2883 $real_user = $user; 2884 $user = user_load($test_user->uid, TRUE); 2885 $real_language = $language->language; 2886 $language->language = $user->language; 2887 // Simulate a Drupal bootstrap with the logged-in user. 2888 date_default_timezone_set(drupal_get_user_timezone()); 2889 2890 $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test a different language.'); 2891 $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.'); 2892 $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T'), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test custom date format.'); 2893 $this->assertIdentical(format_date($timestamp, 'long'), 'domingo, 25. marzo 2007 - 17:00', 'Test long date format.'); 2894 $this->assertIdentical(format_date($timestamp, 'medium'), '25. marzo 2007 - 17:00', 'Test medium date format.'); 2895 $this->assertIdentical(format_date($timestamp, 'short'), '2007 Mar 25 - 5:00pm', 'Test short date format.'); 2896 $this->assertIdentical(format_date($timestamp), '25. marzo 2007 - 17:00', 'Test default date format.'); 2897 2898 // Restore the original user and language, and enable session saving. 2899 $user = $real_user; 2900 $language->language = $real_language; 2901 // Restore default time zone. 2902 date_default_timezone_set(drupal_get_user_timezone()); 2903 drupal_save_session(TRUE); 2904 } 2905} 2906 2907/** 2908 * Tests for the format_date() function. 2909 */ 2910class DrupalAttributesUnitTest extends DrupalUnitTestCase { 2911 public static function getInfo() { 2912 return array( 2913 'name' => 'HTML Attributes', 2914 'description' => 'Perform unit tests on the drupal_attributes() function.', 2915 'group' => 'System', 2916 ); 2917 } 2918 2919 /** 2920 * Tests that drupal_html_class() cleans the class name properly. 2921 */ 2922 function testDrupalAttributes() { 2923 // Verify that special characters are HTML encoded. 2924 $this->assertIdentical(drupal_attributes(array('title' => '&"\'<>')), ' title="&"'<>"', 'HTML encode attribute values.'); 2925 2926 // Verify multi-value attributes are concatenated with spaces. 2927 $attributes = array('class' => array('first', 'last')); 2928 $this->assertIdentical(drupal_attributes(array('class' => array('first', 'last'))), ' class="first last"', 'Concatenate multi-value attributes.'); 2929 2930 // Verify empty attribute values are rendered. 2931 $this->assertIdentical(drupal_attributes(array('alt' => '')), ' alt=""', 'Empty attribute value #1.'); 2932 $this->assertIdentical(drupal_attributes(array('alt' => NULL)), ' alt=""', 'Empty attribute value #2.'); 2933 2934 // Verify multiple attributes are rendered. 2935 $attributes = array( 2936 'id' => 'id-test', 2937 'class' => array('first', 'last'), 2938 'alt' => 'Alternate', 2939 ); 2940 $this->assertIdentical(drupal_attributes($attributes), ' id="id-test" class="first last" alt="Alternate"', 'Multiple attributes.'); 2941 2942 // Verify empty attributes array is rendered. 2943 $this->assertIdentical(drupal_attributes(array()), '', 'Empty attributes array.'); 2944 } 2945} 2946 2947/** 2948 * Tests converting PHP variables to JSON strings and back. 2949 */ 2950class DrupalJSONTest extends DrupalUnitTestCase { 2951 public static function getInfo() { 2952 return array( 2953 'name' => 'JSON', 2954 'description' => 'Perform unit tests on the drupal_json_encode() and drupal_json_decode() functions.', 2955 'group' => 'System', 2956 ); 2957 } 2958 2959 /** 2960 * Tests converting PHP variables to JSON strings and back. 2961 */ 2962 function testJSON() { 2963 // Setup a string with the full ASCII table. 2964 // @todo: Add tests for non-ASCII characters and Unicode. 2965 $str = ''; 2966 for ($i=0; $i < 128; $i++) { 2967 $str .= chr($i); 2968 } 2969 // Characters that must be escaped. 2970 // We check for unescaped " separately. 2971 $html_unsafe = array('<', '>', '\'', '&'); 2972 // The following are the encoded forms of: < > ' & " 2973 $html_unsafe_escaped = array('\u003C', '\u003E', '\u0027', '\u0026', '\u0022'); 2974 2975 // Verify there aren't character encoding problems with the source string. 2976 $this->assertIdentical(strlen($str), 128, 'A string with the full ASCII table has the correct length.'); 2977 foreach ($html_unsafe as $char) { 2978 $this->assertTrue(strpos($str, $char) > 0, format_string('A string with the full ASCII table includes @s.', array('@s' => $char))); 2979 } 2980 2981 // Verify that JSON encoding produces a string with all of the characters. 2982 $json = drupal_json_encode($str); 2983 $this->assertTrue(strlen($json) > strlen($str), 'A JSON encoded string is larger than the source string.'); 2984 2985 // The first and last characters should be ", and no others. 2986 $this->assertTrue($json[0] == '"', 'A JSON encoded string begins with ".'); 2987 $this->assertTrue($json[strlen($json) - 1] == '"', 'A JSON encoded string ends with ".'); 2988 $this->assertTrue(substr_count($json, '"') == 2, 'A JSON encoded string contains exactly two ".'); 2989 2990 // Verify that encoding/decoding is reversible. 2991 $json_decoded = drupal_json_decode($json); 2992 $this->assertIdentical($str, $json_decoded, 'Encoding a string to JSON and decoding back results in the original string.'); 2993 2994 // Verify reversibility for structured data. Also verify that necessary 2995 // characters are escaped. 2996 $source = array(TRUE, FALSE, 0, 1, '0', '1', $str, array('key1' => $str, 'key2' => array('nested' => TRUE))); 2997 $json = drupal_json_encode($source); 2998 foreach ($html_unsafe as $char) { 2999 $this->assertTrue(strpos($json, $char) === FALSE, format_string('A JSON encoded string does not contain @s.', array('@s' => $char))); 3000 } 3001 // Verify that JSON encoding escapes the HTML unsafe characters 3002 foreach ($html_unsafe_escaped as $char) { 3003 $this->assertTrue(strpos($json, $char) > 0, format_string('A JSON encoded string contains @s.', array('@s' => $char))); 3004 } 3005 $json_decoded = drupal_json_decode($json); 3006 $this->assertNotIdentical($source, $json, 'An array encoded in JSON is not identical to the source.'); 3007 $this->assertIdentical($source, $json_decoded, 'Encoding structured data to JSON and decoding back results in the original data.'); 3008 } 3009} 3010 3011/** 3012 * Tests for RDF namespaces XML serialization. 3013 */ 3014class DrupalGetRdfNamespacesTestCase extends DrupalWebTestCase { 3015 public static function getInfo() { 3016 return array( 3017 'name' => 'RDF namespaces XML serialization tests', 3018 'description' => 'Confirm that the serialization of RDF namespaces via drupal_get_rdf_namespaces() is output and parsed correctly in the XHTML document.', 3019 'group' => 'System', 3020 ); 3021 } 3022 3023 function setUp() { 3024 parent::setUp('rdf', 'rdf_test'); 3025 } 3026 3027 /** 3028 * Test RDF namespaces. 3029 */ 3030 function testGetRdfNamespaces() { 3031 // Fetches the front page and extracts XML namespaces. 3032 $this->drupalGet(''); 3033 $xml = new SimpleXMLElement($this->content); 3034 $ns = $xml->getDocNamespaces(); 3035 3036 $this->assertEqual($ns['rdfs'], 'http://www.w3.org/2000/01/rdf-schema#', 'A prefix declared once is displayed.'); 3037 $this->assertEqual($ns['foaf'], 'http://xmlns.com/foaf/0.1/', 'The same prefix declared in several implementations of hook_rdf_namespaces() is valid as long as all the namespaces are the same.'); 3038 $this->assertEqual($ns['foaf1'], 'http://xmlns.com/foaf/0.1/', 'Two prefixes can be assigned the same namespace.'); 3039 $this->assertTrue(!isset($ns['dc']), 'A prefix with conflicting namespaces is discarded.'); 3040 } 3041} 3042 3043/** 3044 * Basic tests for drupal_add_feed(). 3045 */ 3046class DrupalAddFeedTestCase extends DrupalWebTestCase { 3047 public static function getInfo() { 3048 return array( 3049 'name' => 'drupal_add_feed() tests', 3050 'description' => 'Make sure that drupal_add_feed() works correctly with various constructs.', 3051 'group' => 'System', 3052 ); 3053 } 3054 3055 /** 3056 * Test drupal_add_feed() with paths, URLs, and titles. 3057 */ 3058 function testBasicFeedAddNoTitle() { 3059 $path = $this->randomName(12); 3060 $external_url = 'http://' . $this->randomName(12) . '/' . $this->randomName(12); 3061 $fully_qualified_local_url = url($this->randomName(12), array('absolute' => TRUE)); 3062 3063 $path_for_title = $this->randomName(12); 3064 $external_for_title = 'http://' . $this->randomName(12) . '/' . $this->randomName(12); 3065 $fully_qualified_for_title = url($this->randomName(12), array('absolute' => TRUE)); 3066 3067 // Possible permutations of drupal_add_feed() to test. 3068 // - 'input_url': the path passed to drupal_add_feed(), 3069 // - 'output_url': the expected URL to be found in the header. 3070 // - 'title' == the title of the feed as passed into drupal_add_feed(). 3071 $urls = array( 3072 'path without title' => array( 3073 'input_url' => $path, 3074 'output_url' => url($path, array('absolute' => TRUE)), 3075 'title' => '', 3076 ), 3077 'external URL without title' => array( 3078 'input_url' => $external_url, 3079 'output_url' => $external_url, 3080 'title' => '', 3081 ), 3082 'local URL without title' => array( 3083 'input_url' => $fully_qualified_local_url, 3084 'output_url' => $fully_qualified_local_url, 3085 'title' => '', 3086 ), 3087 'path with title' => array( 3088 'input_url' => $path_for_title, 3089 'output_url' => url($path_for_title, array('absolute' => TRUE)), 3090 'title' => $this->randomName(12), 3091 ), 3092 'external URL with title' => array( 3093 'input_url' => $external_for_title, 3094 'output_url' => $external_for_title, 3095 'title' => $this->randomName(12), 3096 ), 3097 'local URL with title' => array( 3098 'input_url' => $fully_qualified_for_title, 3099 'output_url' => $fully_qualified_for_title, 3100 'title' => $this->randomName(12), 3101 ), 3102 ); 3103 3104 foreach ($urls as $description => $feed_info) { 3105 drupal_add_feed($feed_info['input_url'], $feed_info['title']); 3106 } 3107 3108 $this->drupalSetContent(drupal_get_html_head()); 3109 foreach ($urls as $description => $feed_info) { 3110 $this->assertPattern($this->urlToRSSLinkPattern($feed_info['output_url'], $feed_info['title']), format_string('Found correct feed header for %description', array('%description' => $description))); 3111 } 3112 } 3113 3114 /** 3115 * Create a pattern representing the RSS feed in the page. 3116 */ 3117 function urlToRSSLinkPattern($url, $title = '') { 3118 // Escape any regular expression characters in the URL ('?' is the worst). 3119 $url = preg_replace('/([+?.*])/', '[$0]', $url); 3120 $generated_pattern = '%<link +rel="alternate" +type="application/rss.xml" +title="' . $title . '" +href="' . $url . '" */>%'; 3121 return $generated_pattern; 3122 } 3123} 3124 3125/** 3126 * Test for theme_feed_icon(). 3127 */ 3128class FeedIconTest extends DrupalWebTestCase { 3129 3130 public static function getInfo() { 3131 return array( 3132 'name' => 'Feed icon', 3133 'description' => 'Check escaping of theme_feed_icon()', 3134 'group' => 'System', 3135 ); 3136 } 3137 3138 /** 3139 * Check that special characters are correctly escaped. Test for issue #1211668. 3140 */ 3141 function testFeedIconEscaping() { 3142 $variables = array(); 3143 $variables['url'] = 'node'; 3144 $variables['title'] = '<>&"\''; 3145 $text = theme_feed_icon($variables); 3146 preg_match('/title="(.*?)"/', $text, $matches); 3147 $this->assertEqual($matches[1], 'Subscribe to &"'', 'theme_feed_icon() escapes reserved HTML characters.'); 3148 } 3149 3150} 3151 3152/** 3153 * Test array diff functions. 3154 */ 3155class ArrayDiffUnitTest extends DrupalUnitTestCase { 3156 3157 /** 3158 * Array to use for testing. 3159 * 3160 * @var array 3161 */ 3162 protected $array1; 3163 3164 /** 3165 * Array to use for testing. 3166 * 3167 * @var array 3168 */ 3169 protected $array2; 3170 3171 public static function getInfo() { 3172 return array( 3173 'name' => 'Array differences', 3174 'description' => 'Performs tests on drupal_array_diff_assoc_recursive().', 3175 'group' => 'System', 3176 ); 3177 } 3178 3179 function setUp() { 3180 parent::setUp(); 3181 3182 $this->array1 = array( 3183 'same' => 'yes', 3184 'different' => 'no', 3185 'array_empty_diff' => array(), 3186 'null' => NULL, 3187 'int_diff' => 1, 3188 'array_diff' => array('same' => 'same', 'array' => array('same' => 'same')), 3189 'array_compared_to_string' => array('value'), 3190 'string_compared_to_array' => 'value', 3191 'new' => 'new', 3192 ); 3193 $this->array2 = array( 3194 'same' => 'yes', 3195 'different' => 'yes', 3196 'array_empty_diff' => array(), 3197 'null' => NULL, 3198 'int_diff' => '1', 3199 'array_diff' => array('same' => 'different', 'array' => array('same' => 'same')), 3200 'array_compared_to_string' => 'value', 3201 'string_compared_to_array' => array('value'), 3202 ); 3203 } 3204 3205 3206 /** 3207 * Tests drupal_array_diff_assoc_recursive(). 3208 */ 3209 public function testArrayDiffAssocRecursive() { 3210 $expected = array( 3211 'different' => 'no', 3212 'int_diff' => 1, 3213 // The 'array' key should not be returned, as it's the same. 3214 'array_diff' => array('same' => 'same'), 3215 'array_compared_to_string' => array('value'), 3216 'string_compared_to_array' => 'value', 3217 'new' => 'new', 3218 ); 3219 3220 $this->assertIdentical(drupal_array_diff_assoc_recursive($this->array1, $this->array2), $expected); 3221 } 3222} 3223 3224/** 3225 * Tests the functionality of drupal_get_query_array(). 3226 */ 3227class DrupalGetQueryArrayTestCase extends DrupalWebTestCase { 3228 3229 public static function getInfo() { 3230 return array( 3231 'name' => 'Query parsing using drupal_get_query_array()', 3232 'description' => 'Tests that drupal_get_query_array() correctly parses query parameters.', 3233 'group' => 'System', 3234 ); 3235 } 3236 3237 /** 3238 * Tests that drupal_get_query_array() correctly explodes query parameters. 3239 */ 3240 public function testDrupalGetQueryArray() { 3241 $url = "http://my.site.com/somepath?foo=/content/folder[@name='foo']/folder[@name='bar']"; 3242 $parsed = parse_url($url); 3243 $result = drupal_get_query_array($parsed['query']); 3244 $this->assertEqual($result['foo'], "/content/folder[@name='foo']/folder[@name='bar']", 'drupal_get_query_array() should only explode parameters on the first equals sign.'); 3245 } 3246 3247} 3248 3249/** 3250 * Test for block_interest_cohort. 3251 */ 3252class BlockInterestCohortTest extends DrupalWebTestCase { 3253 3254 public static function getInfo() { 3255 return array( 3256 'name' => 'Block Interest Cohort', 3257 'description' => 'Check Permissions-Policy header to disable Google FLoC', 3258 'group' => 'System', 3259 ); 3260 } 3261 3262 function setUp() { 3263 parent::setUp('common_test'); 3264 } 3265 3266 /** 3267 * Tests that FLoC is blocked by default. 3268 */ 3269 function testDefaultBlocking() { 3270 $this->drupalGet('node'); 3271 $this->assertEqual($this->drupalGetHeader('Permissions-Policy'), 'interest-cohort=()', 'FLoC is blocked by default.'); 3272 } 3273 3274 /** 3275 * Tests that an existing interest-cohort policy is not overwritten. 3276 */ 3277 function testExistingInterestCohortPolicy() { 3278 $this->drupalGet('common-test/existing_interest_cohort_policy'); 3279 $this->assertEqual($this->drupalGetHeader('Permissions-Policy'), 'interest-cohort=*', 'Existing interest-cohort policy is not overwritten.'); 3280 } 3281 3282 /** 3283 * Tests that an existing header is appended to correctly. 3284 */ 3285 function testExistingPolicyHeader() { 3286 $this->drupalGet('common-test/existing_permissions_policy_header'); 3287 $this->assertTrue((strpos($this->drupalGetHeader('Permissions-Policy'), 'geolocation=(),interest-cohort=()') !== FALSE), 'The existing header is appended to.'); 3288 } 3289 3290 /** 3291 * Tests that FLoC blocking can be disabled. 3292 */ 3293 function testDisableBlocking() { 3294 variable_set('block_interest_cohort', FALSE); 3295 $this->drupalGet('node'); 3296 $this->assertFalse($this->drupalGetHeader('Permissions-Policy'), 'FLoC blocking can be disabled.'); 3297 } 3298 3299} 3300