theme.test 17.5 KB
Newer Older
1 2 3 4 5 6 7 8
<?php

/**
 * @file
 * Tests for the theme API.
 */

/**
9
 * Unit tests for the Theme API.
10
 */
11
class ThemeUnitTest extends DrupalWebTestCase {
12
  public static function getInfo() {
13
    return array(
14
      'name' => 'Theme API',
15
      'description' => 'Test low-level theme functions.',
16
      'group' => 'Theme',
17 18 19
    );
  }

20 21 22 23 24
  function setUp() {
    parent::setUp('theme_test');
    theme_enable(array('test_theme'));
  }

25
  /**
26
   * Test function theme_get_suggestions() for SA-CORE-2009-003.
27
   */
28
  function testThemeSuggestions() {
29 30 31 32
    // Set the front page as something random otherwise the CLI
    // test runner fails.
    variable_set('site_frontpage', 'nobody-home');
    $args = array('node', '1', 'edit');
33
    $suggestions = theme_get_suggestions($args, 'page');
34
    $this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1', 'page__node__edit'), t('Found expected node edit page suggestions'));
35 36
    // Check attack vectors.
    $args = array('node', '\\1');
37
    $suggestions = theme_get_suggestions($args, 'page');
38
    $this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1'), t('Removed invalid \\ from suggestions'));
39
    $args = array('node', '1/');
40
    $suggestions = theme_get_suggestions($args, 'page');
41
    $this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1'), t('Removed invalid / from suggestions'));
42
    $args = array('node', "1\0");
43
    $suggestions = theme_get_suggestions($args, 'page');
44
    $this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1'), t('Removed invalid \\0 from suggestions'));
45 46 47 48 49
    // Define path with hyphens to be used to generate suggestions.
    $args = array('node', '1', 'hyphen-path');
    $result = array('page__node', 'page__node__%', 'page__node__1', 'page__node__hyphen_path');
    $suggestions = theme_get_suggestions($args, 'page');
    $this->assertEqual($suggestions, $result, t('Found expected page suggestions for paths containing hyphens.'));
50
  }
51

52
  /**
53 54
  * Preprocess functions for the base hook should run even for suggestion implementations.
  */
55 56
  function testPreprocessForSuggestions() {
    $this->drupalGet('theme-test/suggestion');
57
    $this->assertText('test_theme_breadcrumb__suggestion: 1', t('Theme hook suggestion ran with data available from a preprocess function for the base hook.'));
58
  }
59 60 61 62 63 64 65 66 67 68 69 70

  /**
   * Ensure page-front template suggestion is added when on front page.
   */
  function testFrontPageThemeSuggestion() {
    $q = $_GET['q'];
    // Set $_GET['q'] to node because theme_get_suggestions() will query it to
    // see if we are on the front page.
    $_GET['q'] = variable_get('site_frontpage', 'node');
    $suggestions = theme_get_suggestions(explode('/', $_GET['q']), 'page');
    // Set it back to not annoy the batch runner.
    $_GET['q'] = $q;
71
    $this->assertTrue(in_array('page__front', $suggestions), t('Front page template was suggested.'));
72
  }
73 74 75 76 77 78 79 80

  /**
   * Ensures theme hook_*_alter() implementations can run before anything is rendered.
   */
  function testAlter() {
    $this->drupalGet('theme-test/alter');
    $this->assertText('The altered data is test_theme_theme_test_alter_alter was invoked.', t('The theme was able to implement an alter hook during page building before anything was rendered.'));
  }
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

  /**
   * Ensures a theme's .info file is able to override a module CSS file from being added to the page.
   *
   * @see test_theme.info
   */
  function testCSSOverride() {
    // Reuse the same page as in testPreprocessForSuggestions(). We're testing
    // what is output to the HTML HEAD based on what is in a theme's .info file,
    // so it doesn't matter what page we get, as long as it is themed with the
    // test theme. First we test with CSS aggregation disabled.
    variable_set('preprocess_css', 0);
    $this->drupalGet('theme-test/suggestion');
    $this->assertNoText('system.base.css', t('The theme\'s .info file is able to override a module CSS file from being added to the page.'));

    // Also test with aggregation enabled, simply ensuring no PHP errors are
    // triggered during drupal_build_css_cache() when a source file doesn't
    // exist. Then allow remaining tests to continue with aggregation disabled
    // by default.
    variable_set('preprocess_css', 1);
    $this->drupalGet('theme-test/suggestion');
    variable_set('preprocess_css', 0);
  }
104 105 106 107 108 109 110 111 112

  /**
   * Ensures a themes template is overrideable based on the 'template' filename.
   */
  function testTemplateOverride() {
    variable_set('theme_default', 'test_theme');
    $this->drupalGet('theme-test/template-test');
    $this->assertText('Success: Template overridden.', t('Template overridden by defined \'template\' filename.'));
  }
113
}
114

115 116 117 118 119 120
/**
 * Unit tests for theme_table().
 */
class ThemeTableUnitTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
121 122 123
      'name' => 'Theme Table',
      'description' => 'Tests built-in theme functions.',
      'group' => 'Theme',
124 125
    );
  }
126

127 128 129 130 131
  /**
   * Tableheader.js provides 'sticky' table headers, and is included by default.
   */
  function testThemeTableStickyHeaders() {
    $header = array('one', 'two', 'three');
132
    $rows = array(array(1,2,3), array(4,5,6), array(7,8,9));
133
    $this->content = theme('table', array('header' => $header, 'rows' => $rows));
134
    $js = drupal_add_js();
135 136
    $this->assertTrue(isset($js['misc/tableheader.js']), t('tableheader.js was included when $sticky = TRUE.'));
    $this->assertRaw('sticky-enabled',  t('Table has a class of sticky-enabled when $sticky = TRUE.'));
137 138 139 140 141 142 143 144
    drupal_static_reset('drupal_add_js');
  }

  /**
   * If $sticky is FALSE, no tableheader.js should be included.
   */
  function testThemeTableNoStickyHeaders() {
    $header = array('one', 'two', 'three');
145
    $rows = array(array(1,2,3), array(4,5,6), array(7,8,9));
146 147 148
    $attributes = array();
    $caption = NULL;
    $colgroups = array();
149
    $this->content = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => $attributes, 'caption' => $caption, 'colgroups' => $colgroups, 'sticky' => FALSE));
150
    $js = drupal_add_js();
151 152
    $this->assertFalse(isset($js['misc/tableheader.js']), t('tableheader.js was not included because $sticky = FALSE.'));
    $this->assertNoRaw('sticky-enabled',  t('Table does not have a class of sticky-enabled because $sticky = FALSE.'));
153 154
    drupal_static_reset('drupal_add_js');
  }
155 156

  /**
157 158
   * Tests that the table header is printed correctly even if there are no rows,
   * and that the empty text is displayed correctly.
159 160
   */
  function testThemeTableWithEmptyMessage() {
161 162 163 164 165 166 167
    $header = array(
      t('Header 1'),
      array(
        'data' => t('Header 2'),
        'colspan' => 2,
      ),
    );
168
    $this->content = theme('table', array('header' => $header, 'rows' => array(), 'empty' => t('No strings available.')));
169 170
    $this->assertRaw('<tr class="odd"><td colspan="3" class="empty message">No strings available.</td>', t('Correct colspan was set on empty message.'));
    $this->assertRaw('<thead><tr><th>Header 1</th>', t('Table header was printed.'));
171
  }
172

173
}
174 175

/**
176
 * Tests for common theme functions.
177
 */
178 179 180
class ThemeFunctionsTestCase extends DrupalWebTestCase {
  protected $profile = 'testing';

181 182
  public static function getInfo() {
    return array(
183 184
      'name' => 'Theme functions',
      'description' => 'Tests common theme functions.',
185 186 187 188 189
      'group' => 'Theme',
    );
  }

  /**
190
   * Tests theme_item_list().
191
   */
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
  function testItemList() {
    // Verify that empty variables produce no output.
    $variables = array();
    $expected = '';
    $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback generates no output.');

    $variables = array();
    $variables['title'] = 'Some title';
    $expected = '';
    $this->assertThemeOutput('item_list', $variables, $expected, 'Empty %callback with title generates no output.');

    // Verify nested item lists.
    $variables = array();
    $variables['title'] = 'Some title';
    $variables['attributes'] = array(
      'id' => 'parentlist',
    );
    $variables['items'] = array(
      'a',
      array(
        'data' => 'b',
        'children' => array(
          'c',
          // Nested children may use additional attributes.
          array(
            'data' => 'd',
            'class' => array('dee'),
          ),
          // Any string key is treated as child list attribute.
          'id' => 'childlist',
        ),
        // Any other keys are treated as item attributes.
        'id' => 'bee',
      ),
      array(
        'data' => 'e',
        'id' => 'E',
      ),
    );
    $inner = '<div class="item-list"><ul id="childlist">';
    $inner .= '<li class="odd first">c</li>';
    $inner .= '<li class="dee even last">d</li>';
    $inner .= '</ul></div>';

    $expected = '<div class="item-list">';
    $expected .= '<h3>Some title</h3>';
    $expected .= '<ul id="parentlist">';
    $expected .= '<li class="odd first">a</li>';
    $expected .= '<li id="bee" class="even">b' . $inner . '</li>';
    $expected .= '<li id="E" class="odd last">e</li>';
    $expected .= '</ul></div>';

    $this->assertThemeOutput('item_list', $variables, $expected);
  }

  /**
   * Asserts themed output.
   *
   * @param $callback
   *   The name of the theme function to invoke; e.g. 'links' for theme_links().
   * @param $variables
   *   An array of variables to pass to the theme function.
   * @param $expected
   *   The expected themed output string.
   * @param $message
   *   (optional) An assertion message.
   */
  protected function assertThemeOutput($callback, array $variables = array(), $expected, $message = '') {
    $output = theme($callback, $variables);
    $this->verbose('Variables:' . '<pre>' .  check_plain(var_export($variables, TRUE)) . '</pre>'
      . '<hr />' . 'Result:' . '<pre>' .  check_plain(var_export($output, TRUE)) . '</pre>'
      . '<hr />' . 'Expected:' . '<pre>' .  check_plain(var_export($expected, TRUE)) . '</pre>'
      . '<hr />' . $output
    );
    if (!$message) {
      $message = '%callback rendered correctly.';
    }
    $message = t($message, array('%callback' => 'theme_' . $callback . '()'));
    $this->assertIdentical($output, $expected, $message);
271 272
  }
}
273

274 275 276
/**
 * Unit tests for theme_links().
 */
277
class ThemeLinksTest extends DrupalWebTestCase {
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
  public static function getInfo() {
    return array(
      'name' => 'Links',
      'description' => 'Test the theme_links() function and rendering groups of links.',
      'group' => 'Theme',
    );
  }

  /**
   * Test the use of drupal_pre_render_links() on a nested array of links.
   */
  function testDrupalPreRenderLinks() {
    // Define the base array to be rendered, containing a variety of different
    // kinds of links.
    $base_array = array(
      '#theme' => 'links',
      '#pre_render' => array('drupal_pre_render_links'),
      '#links' => array(
        'parent_link' => array(
          'title' => 'Parent link original',
          'href' => 'parent-link-original',
        ),
      ),
      'first_child' => array(
        '#theme' => 'links',
        '#links' => array(
          // This should be rendered if 'first_child' is rendered separately,
          // but ignored if the parent is being rendered (since it duplicates
          // one of the parent's links).
          'parent_link' => array(
            'title' => 'Parent link copy',
            'href' => 'parent-link-copy',
          ),
          // This should always be rendered.
          'first_child_link' => array(
            'title' => 'First child link',
            'href' => 'first-child-link',
          ),
        ),
      ),
      // This should always be rendered as part of the parent.
      'second_child' => array(
        '#theme' => 'links',
        '#links' => array(
          'second_child_link' => array(
            'title' => 'Second child link',
            'href' => 'second-child-link',
          ),
        ),
      ),
      // This should never be rendered, since the user does not have access to
      // it.
      'third_child' => array(
        '#theme' => 'links',
        '#links' => array(
          'third_child_link' => array(
            'title' => 'Third child link',
            'href' => 'third-child-link',
          ),
        ),
        '#access' => FALSE,
      ),
    );

    // Start with a fresh copy of the base array, and try rendering the entire
    // thing. We expect a single <ul> with appropriate links contained within
    // it.
    $render_array = $base_array;
    $html = drupal_render($render_array);
    $dom = new DOMDocument();
    $dom->loadHTML($html);
    $this->assertEqual($dom->getElementsByTagName('ul')->length, 1, t('One "ul" tag found in the rendered HTML.'));
    $list_elements = $dom->getElementsByTagName('li');
    $this->assertEqual($list_elements->length, 3, t('Three "li" tags found in the rendered HTML.'));
    $this->assertEqual($list_elements->item(0)->nodeValue, 'Parent link original', t('First expected link found.'));
    $this->assertEqual($list_elements->item(1)->nodeValue, 'First child link', t('Second expected link found.'));
    $this->assertEqual($list_elements->item(2)->nodeValue, 'Second child link', t('Third expected link found.'));
    $this->assertIdentical(strpos($html, 'Parent link copy'), FALSE, t('"Parent link copy" link not found.'));
    $this->assertIdentical(strpos($html, 'Third child link'), FALSE, t('"Third child link" link not found.'));

    // Now render 'first_child', followed by the rest of the links, and make
    // sure we get two separate <ul>'s with the appropriate links contained
    // within each.
    $render_array = $base_array;
    $child_html = drupal_render($render_array['first_child']);
    $parent_html = drupal_render($render_array);
    // First check the child HTML.
    $dom = new DOMDocument();
    $dom->loadHTML($child_html);
    $this->assertEqual($dom->getElementsByTagName('ul')->length, 1, t('One "ul" tag found in the rendered child HTML.'));
    $list_elements = $dom->getElementsByTagName('li');
    $this->assertEqual($list_elements->length, 2, t('Two "li" tags found in the rendered child HTML.'));
    $this->assertEqual($list_elements->item(0)->nodeValue, 'Parent link copy', t('First expected link found.'));
    $this->assertEqual($list_elements->item(1)->nodeValue, 'First child link', t('Second expected link found.'));
    // Then check the parent HTML.
    $dom = new DOMDocument();
    $dom->loadHTML($parent_html);
    $this->assertEqual($dom->getElementsByTagName('ul')->length, 1, t('One "ul" tag found in the rendered parent HTML.'));
    $list_elements = $dom->getElementsByTagName('li');
    $this->assertEqual($list_elements->length, 2, t('Two "li" tags found in the rendered parent HTML.'));
    $this->assertEqual($list_elements->item(0)->nodeValue, 'Parent link original', t('First expected link found.'));
    $this->assertEqual($list_elements->item(1)->nodeValue, 'Second child link', t('Second expected link found.'));
    $this->assertIdentical(strpos($parent_html, 'First child link'), FALSE, t('"First child link" link not found.'));
    $this->assertIdentical(strpos($parent_html, 'Third child link'), FALSE, t('"Third child link" link not found.'));
  }
}

385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
/**
 * Functional test for initialization of the theme system in hook_init().
 */
class ThemeHookInitUnitTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Theme initialization in hook_init()',
      'description' => 'Tests that the theme system can be correctly initialized in hook_init().',
      'group' => 'Theme',
    );
  }

  function setUp() {
    parent::setUp('theme_test');
  }

  /**
   * Test that the theme system can generate output when called by hook_init().
   */
  function testThemeInitializationHookInit() {
    $this->drupalGet('theme-test/hook-init');
406
    $this->assertRaw('Themed output generated in hook_init()', t('Themed output generated in hook_init() correctly appears on the page.'));
407
    $this->assertRaw('bartik/css/style.css', t("The default theme's CSS appears on the page when the theme system is initialized in hook_init()."));
408 409
  }
}
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436

/**
 * Tests autocompletion not loading registry.
 */
class ThemeFastTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Theme fast initialization',
      'description' => 'Test that autocompletion does not load the registry.',
      'group' => 'Theme'
    );
  }

  function setUp() {
    parent::setUp('theme_test');
    $this->account = $this->drupalCreateUser(array('access user profiles'));
  }

  /**
   * Tests access to user autocompletion and verify the correct results.
   */
  function testUserAutocomplete() {
    $this->drupalLogin($this->account);
    $this->drupalGet('user/autocomplete/' . $this->account->name);
    $this->assertText('registry not initialized', t('The registry was not initialized'));
  }
}
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462

/**
 * Unit tests for theme_html_tag().
 */
class ThemeHtmlTag extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Theme HTML Tag',
      'description' => 'Tests theme_html_tag() built-in theme functions.',
      'group' => 'Theme',
    );
  }

  /**
   * Test function theme_html_tag()
   */
  function testThemeHtmlTag() {
    // Test auto-closure meta tag generation
    $tag['element'] = array('#tag' => 'meta', '#attributes' => array('name' => 'description', 'content' => 'Drupal test'));
    $this->assertEqual('<meta name="description" content="Drupal test" />'."\n", theme_html_tag($tag), t('Test auto-closure meta tag generation.'));

    // Test title tag generation
    $tag['element'] = array('#tag' => 'title', '#value' => 'title test');
    $this->assertEqual('<title>title test</title>'."\n", theme_html_tag($tag), t('Test title tag generation.'));
  }
}