You are here

advagg.test in Advanced CSS/JS Aggregation 7.2

Tests for advagg.module.

File

tests/advagg.test
View source
<?php

/**
 * @file
 * Tests for advagg.module.
 */

/**
 * @defgroup advagg_tests Advanced Aggregates Tests
 *
 * @{
 * Advanced Aggregates testing functionality.
 */

// Include all files in the project for simple sanity checking.
$files = file_scan_directory(drupal_get_path('module', 'advagg'), "/.*\\.(inc|module|install|php)\$/", array(
  'nomask' => '/(\\.\\.?|CVS|tpl\\.php)$/',
));
foreach ($files as $file) {
  include_once DRUPAL_ROOT . '/' . $file->uri;
}

/**
 * Resets static variables related to adding CSS or JS to a page.
 */
function advagg_test_reset_statics() {
  drupal_static_reset('drupal_add_css');
  drupal_static_reset('drupal_add_library');
  drupal_static_reset('drupal_get_library');
  drupal_static_reset('drupal_add_js');
  drupal_static_reset('drupal_add_js:jquery_added');
  drupal_static_reset('advagg_get_js');
}

/**
 * Generates a large CSS string.
 *
 * @param int $selector_count
 *   The number of selectors to generate.
 * @param int $denominator
 *   The max string length of the selector names.
 *
 * @return string
 *   Generated CSS string.
 */
function advagg_test_generate_selector_css($selector_count, $denominator = 5) {
  static $count = 0;
  $pool = array_merge(range('a', 'z'), range('A', 'Z'));
  $selector_count = 10000;
  $css = '';
  while ($selector_count > 0) {
    $rand_string = advagg_test_randon_string($selector_count % $denominator + 3, $pool);
    $css .= ".{$rand_string}, ";
    --$selector_count;
  }
  $css .= "#last{$count} {z-index: 2; margin-left: -1px; content: \" \"; display: table;}";
  ++$count;
  return $css;
}

/**
 * Generates random string.
 *
 * @param int $length
 *   How many characters will this string contain.
 * @param array $pool
 *   Array of characters to use.
 *
 * @return string
 *   Random string.
 */
function advagg_test_randon_string($length, array $pool) {
  $string = '';
  $count = count($pool);
  for ($i = 0; $i < $length; $i++) {
    $string .= $pool[mt_rand(0, $count - 1)];
  }
  return $string;
}

/**
 * Strip the codingStandardsIgnoreFile string from the input.
 *
 * @param string $input
 *   The input string.
 *
 * @return string
 *   The input string with codingStandardsIgnoreFile removed from it.
 */
function advagg_test_remove_sniffer_comments($input) {
  $string = "/* @codingStandardsIgnoreFile */\n";
  return str_replace($string, '', $input);
}

/**
 * Test the Drupal CSS system.
 */
class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase {

  /**
   * Store configured value for CSS preprocessing.
   *
   * @var bool
   */
  protected $preprocessCss = NULL;

  /**
   * Create user.
   *
   * @var object
   */
  protected $bigUser;

  /**
   * Theme settings.
   *
   * @var array
   */
  protected $themes;

  /**
   * Provide information to the UI for this test.
   */
  public static function getInfo() {
    return array(
      'name' => 'CSS',
      'description' => 'Tests adding various cascading stylesheets to the page.',
      'group' => 'AdvAgg',
    );
  }

  /**
   * Install the advagg module and include needed files.
   */
  public function setUp() {

    // Enable any modules required for the test. This should be an array of
    // module names.
    parent::setUp(array(
      'advagg',
      'php',
      'locale',
      'common_test',
      'menu_test',
      'color',
    ));

    // Include the advagg.module file.
    drupal_load('module', 'advagg');
    module_load_include('inc', 'advagg', 'advagg');

    // Set settings for testing.
    $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_force_https_path'] = FALSE;
    $GLOBALS['conf']['advagg_skip_file_create_url_inside_css'] = FALSE;

    // Disable CSS preprocessing.
    $this->preprocessCss = variable_get('preprocess_css', 0);
    variable_set('preprocess_css', 0);

    // Create users.
    $this->bigUser = $this
      ->drupalCreateUser(array(
      'administer themes',
    ));

    // This tests the color module in both Bartik and Garland.
    $this->themes = array(
      'bartik' => array(
        'palette_input' => 'palette[bg]',
        'scheme' => 'slate',
        'scheme_color' => '#3b3b3b',
      ),
    );
    theme_enable(array_keys($this->themes));

    // Reset drupal_add_css() before each test.
    advagg_test_reset_statics();
  }

  /**
   * Restore any variables we set.
   */
  public function tearDown() {

    // Restore configured value for CSS preprocessing.
    variable_set('preprocess_css', $this->preprocessCss);
    parent::tearDown();
  }

  /**
   * Tests rendering the stylesheets.
   */
  public function testRenderFile() {
    foreach ($this->themes as $theme => $test_values) {
      variable_set('theme_default', $theme);
      $settings_path = 'admin/appearance/settings/' . $theme;
      $this
        ->drupalLogin($this->bigUser);
      $this
        ->drupalGet($settings_path);
      $this
        ->assertResponse(200);
      $edit['scheme'] = '';
      $edit[$test_values['palette_input']] = '#123456';
      $this
        ->drupalPost($settings_path, $edit, t('Save configuration'));

      // Reset drupal_add_css() before each test.
      $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE;
      $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE;
      advagg_test_reset_statics();

      // Add the css file.
      $stylesheets = variable_get('color_' . $theme . '_stylesheets', array());
      drupal_add_css($stylesheets[0]);
      $css = file_create_url($stylesheets[0]);

      // Get the render array.
      $full_css = advagg_get_css();
      $styles = drupal_render($full_css);
      $this
        ->assertTrue(strpos($styles, $css) !== FALSE, "Rendered CSS includes the added stylesheet ({$css}).");
    }

    // Reset drupal_add_css() before each test.
    advagg_test_reset_statics();

    // Add the css file.
    $css = drupal_get_path('module', 'simpletest') . '/simpletest.css';
    drupal_add_css($css);

    // Get the render array.
    $full_css = advagg_get_css();

    // Render the CSS.
    $styles = drupal_render($full_css);
    $this
      ->assertTrue(strpos($styles, $css) > 0, "Rendered CSS includes the added stylesheet ({$css}).");

    // Verify that newlines are properly added inside style tags.
    $query_string = variable_get('css_js_query_string', '0');
    $css_processed = "<style type=\"text/css\" media=\"all\">\n@import url(\"" . check_plain(file_create_url($css)) . "?" . $query_string . "\");\n</style>";
    $this
      ->assertEqual(trim($styles), $css_processed, 'Rendered CSS includes newlines inside style tags for JavaScript use.');

    //
    // Tests rendering an external stylesheet.
    advagg_test_reset_statics();

    // Add the css file.
    $css = 'http://example.com/style.css';
    drupal_add_css($css, 'external');

    // Get the render array.
    $full_css = advagg_get_css();

    // Render the CSS.
    $styles = drupal_render($full_css);

    // Stylesheet URL may be the href of a LINK tag or in an @import statement
    // of a STYLE tag.
    $this
      ->assertTrue(strpos($styles, 'href="' . $css) > 0 || strpos($styles, '@import url("' . $css . '")') > 0, 'Rendering an external CSS file.');

    //
    // Tests rendering inline stylesheets with preprocessing on.
    advagg_test_reset_statics();

    // Add the inline css.
    $css = 'body { padding: 0px; }';
    list($embed_prefix, $embed_suffix) = advagg_get_css_prefix_suffix();
    $css_preprocessed = '<style type="text/css" media="all">' . $embed_prefix . advagg_load_stylesheet_content($css, TRUE) . $embed_suffix . '</style>';
    drupal_add_css($css, array(
      'type' => 'inline',
    ));

    // Get the render array.
    $full_css = advagg_get_css();

    // Render the CSS.
    $styles = drupal_render($full_css);
    $this
      ->assertEqual(trim($styles), $css_preprocessed, 'Rendering preprocessed inline CSS adds it to the page.');

    //
    // Tests removing charset when rendering stylesheets with preprocessing on.
    advagg_test_reset_statics();
    $cases = array(
      array(
        'asset' => '@charset "UTF-8";html{font-family:"sans-serif";}',
        'expected' => 'html{font-family:"sans-serif";}',
      ),
      // This asset contains extra \n character.
      array(
        'asset' => "@charset 'UTF-8';\nhtml{font-family:'sans-serif';}",
        'expected' => "\nhtml{font-family:'sans-serif';}",
      ),
    );
    foreach ($cases as $case) {
      $this
        ->assertEqual($case['expected'], advagg_load_stylesheet_content($case['asset']), 'CSS optimizing correctly removes the charset declaration.');
    }

    //
    // Tests rendering inline stylesheets with preprocessing off.
    advagg_test_reset_statics();

    // Add the inline css.
    $css = 'body { padding: 0px; }';
    drupal_add_css($css, array(
      'type' => 'inline',
      'preprocess' => FALSE,
    ));

    // Get the render array.
    $full_css = advagg_get_css();

    // Render the CSS.
    $styles = drupal_render($full_css);
    $this
      ->assertTrue(strpos($styles, $css) > 0, 'Rendering non-preprocessed inline CSS adds it to the page.');

    //
    // Test CSS ordering.
    advagg_test_reset_statics();

    // A module CSS file.
    drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');

    // A few system CSS files, ordered in a strange way.
    $system_path = drupal_get_path('module', 'system');
    drupal_add_css($system_path . '/system.menus.css', array(
      'group' => CSS_SYSTEM,
    ));
    drupal_add_css($system_path . '/system.base.css', array(
      'group' => CSS_SYSTEM,
      'weight' => -10,
    ));
    drupal_add_css($system_path . '/system.theme.css', array(
      'group' => CSS_SYSTEM,
    ));
    $expected = array(
      $system_path . '/system.base.css',
      $system_path . '/system.menus.css',
      $system_path . '/system.theme.css',
      drupal_get_path('module', 'simpletest') . '/simpletest.css',
    );

    // Get the render array.
    $full_css = advagg_get_css();

    // Render the CSS.
    $styles = drupal_render($full_css);

    // Stylesheet URL may be the href of a LINK tag or in an @import statement
    // of a STYLE tag.
    if (preg_match_all('/(href="|url\\(")' . preg_quote($GLOBALS['base_url'] . '/', '/') . '([^?]+)\\?/', $styles, $matches)) {
      $result = $matches[2];
    }
    else {
      $result = array();
    }
    $this
      ->assertIdentical($result, $expected, 'The CSS files are in the expected order.');

    //
    // Test CSS override.
    advagg_test_reset_statics();
    $system = drupal_get_path('module', 'system');
    $simpletest = drupal_get_path('module', 'simpletest');
    drupal_add_css($system . '/system.base.css');
    drupal_add_css($simpletest . '/tests/system.base.css');

    // The dummy stylesheet should be the only one included.
    // Get the render array.
    $full_css = advagg_get_css();

    // Render the CSS.
    $styles = drupal_render($full_css);
    $this
      ->assert(strpos($styles, $simpletest . '/tests/system.base.css') !== FALSE, 'The overriding CSS file is output.');
    $this
      ->assert(strpos($styles, $system . '/system.base.css') === FALSE, 'The overridden CSS file is not output.');

    // The reset is needed here until this is fixed
    // https://www.drupal.org/node/1388546
    advagg_test_reset_statics();
    drupal_add_css($simpletest . '/tests/system.base.css');
    drupal_add_css($system . '/system.base.css');

    // Get the render array.
    $full_css = advagg_get_css();

    // Render the CSS.
    $styles = drupal_render($full_css);

    // The standard stylesheet should be the only one included.
    $this
      ->assert(strpos($styles, $system . '/system.base.css') !== FALSE, 'The overriding CSS file is output.');
    $this
      ->assert(strpos($styles, $simpletest . '/tests/system.base.css') === FALSE, 'The overridden CSS file is not output.');

    //
    //  Tests Locale module's CSS Alter to include RTL overrides.
    advagg_test_reset_statics();

    // Switch the language to a right to left language and add system.base.css.
    global $language;
    $language->direction = LANGUAGE_RTL;
    $path = drupal_get_path('module', 'system');
    drupal_add_css($path . '/system.base.css', array(
      'group' => CSS_SYSTEM,
    ));
    drupal_add_css($path . '/system.menus.css', array(
      'group' => CSS_SYSTEM,
    ));
    drupal_add_css($path . '/system.theme.css', array(
      'group' => CSS_SYSTEM,
    ));

    // Get the render array.
    $full_css = advagg_get_css();

    // Render the CSS.
    $styles = drupal_render($full_css);

    // Check to see if system.base-rtl.css was also added.
    $base_pos = strpos($styles, $path . '/system.base.css');
    $base_rtl_pos = strpos($styles, $path . '/system.base-rtl.css');
    $this
      ->assert($base_rtl_pos !== FALSE, 'CSS is alterable as right to left overrides are added.');
    $this
      ->assert($base_pos < $base_rtl_pos, 'system.base-rtl.css is added after system.base.css.');

    // Check to see if system.menus-rtl.css was also added.
    $menus_pos = strpos($styles, $path . '/system.menus.css');
    $menus_rtl_pos = strpos($styles, $path . '/system.menus-rtl.css');
    $this
      ->assert($menus_rtl_pos !== FALSE, 'CSS is alterable as right to left overrides are added.');
    $this
      ->assert($menus_pos < $menus_rtl_pos, 'system.menus-rtl.css is added after system.menus.css.');

    // Check to see if system.theme-rtl.css was also added.
    $theme_pos = strpos($styles, $path . '/system.theme.css');
    $theme_rtl_pos = strpos($styles, $path . '/system.theme-rtl.css');
    $this
      ->assert($theme_rtl_pos !== FALSE, 'CSS is alterable as right to left overrides are added.');
    $this
      ->assert($theme_pos < $theme_rtl_pos, 'system.theme-rtl.css is added after system.theme.css.');

    // Change the language back to left to right.
    $language->direction = LANGUAGE_LTR;

    //
    // Tests rendering inline stylesheets through a full page request.
    advagg_test_reset_statics();
    $css = 'body { font-size: 254px; }';

    // Inline CSS is minified unless 'preprocess' => FALSE is passed as a
    // drupal_add_css() option.
    $expected = 'body{font-size:254px;}';

    // Create a node, using the PHP filter that tests drupal_add_css().
    $php_format_id = 'php_code';
    $settings = array(
      'type' => 'page',
      'body' => array(
        LANGUAGE_NONE => array(
          array(
            'value' => t('This tests the inline CSS!') . "<?php drupal_add_css('{$css}', 'inline'); ?>",
            'format' => $php_format_id,
          ),
        ),
      ),
      'promote' => 1,
    );
    $node = $this
      ->drupalCreateNode($settings);

    // Fetch the page.
    $this
      ->drupalGet('node/' . $node->nid);
    $this
      ->assertRaw($expected, 'Inline stylesheets appear in the full page rendering.');

    //
    // Tests that the query string remains intact when adding CSS files that
    // have query string parameters.
    advagg_test_reset_statics();
    $this
      ->drupalGet('common-test/query-string');
    $query_string = variable_get('css_js_query_string', '0');
    $this
      ->assertRaw(drupal_get_path('module', 'node') . '/node.css?' . $query_string, 'Query string was appended correctly to css.');
    $this
      ->assertRaw(drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&amp;arg2=value2', 'Query string not escaped on a URI.');

    //
    // Make the tests below more robust by explicitly setting the default theme
    // and administrative theme that they expect.
    theme_enable(array(
      'bartik',
    ));
    variable_set('theme_default', 'bartik');
    variable_set('admin_theme', 'seven');

    // Test the theme callback when it is set to use an administrative theme.
    advagg_test_reset_statics();
    $this
      ->drupalGet('menu-test/theme-callback/use-admin-theme');
    $this
      ->assertText('Custom theme: seven. Actual theme: seven.', 'The administrative theme can be correctly set in a theme callback.');
    $this
      ->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page.");

    //
    // Test that the theme callback is properly inherited.
    advagg_test_reset_statics();
    $this
      ->drupalGet('menu-test/theme-callback/use-admin-theme/inheritance');
    $this
      ->assertText('Custom theme: seven. Actual theme: seven. Theme callback inheritance is being tested.', 'Theme callback inheritance correctly uses the administrative theme.');
    $this
      ->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page.");

    //
    // Test the theme callback when the site is in maintenance mode.
    advagg_test_reset_statics();
    variable_set('maintenance_mode', TRUE);

    // For a regular user, the fact that the site is in maintenance mode means
    // we expect the theme callback system to be bypassed entirely.
    $this
      ->drupalGet('menu-test/theme-callback/use-admin-theme');
    $this
      ->assertRaw('bartik/css/style.css', "The maintenance theme's CSS appears on the page.");

    // An administrator, however, should continue to see the requested theme.
    $admin_user = $this
      ->drupalCreateUser(array(
      'access site in maintenance mode',
    ));
    $this
      ->drupalLogin($admin_user);
    $this
      ->drupalGet('menu-test/theme-callback/use-admin-theme');
    $this
      ->assertText('Custom theme: seven. Actual theme: seven.', 'The theme callback system is correctly triggered for an administrator when the site is in maintenance mode.');
    $this
      ->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page.");
    variable_set('maintenance_mode', FALSE);

    //
    // Test the theme callback when it is set to use an optional theme.
    advagg_test_reset_statics();

    // Request a theme that is not enabled.
    $this
      ->drupalGet('menu-test/theme-callback/use-stark-theme');
    $this
      ->assertText('Custom theme: NONE. Actual theme: bartik.', 'The theme callback system falls back on the default theme when a theme that is not enabled is requested.');
    $this
      ->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page.");

    // Now enable the theme and request it again.
    theme_enable(array(
      'stark',
    ));
    $this
      ->drupalGet('menu-test/theme-callback/use-stark-theme');
    $this
      ->assertText('Custom theme: stark. Actual theme: stark.', 'The theme callback system uses an optional theme once it has been enabled.');
    $this
      ->assertRaw('stark/layout.css', "The optional theme's CSS appears on the page.");

    // Test the theme callback when it is set to use a theme that does not
    // exist.
    $this
      ->drupalGet('menu-test/theme-callback/use-fake-theme');
    $this
      ->assertText('Custom theme: NONE. Actual theme: bartik.', 'The theme callback system falls back on the default theme when a theme that does not exist is requested.');
    $this
      ->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page.");

    //
    // Test the theme callback when no theme is requested.
    advagg_test_reset_statics();
    $this
      ->drupalGet('menu-test/theme-callback/no-theme-requested');
    $this
      ->assertText('Custom theme: NONE. Actual theme: bartik.', 'The theme callback system falls back on the default theme when no theme is requested.');
    $this
      ->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page.");

    //
    // Test that hook_custom_theme() can control the theme of a page.
    advagg_test_reset_statics();

    // Trigger hook_custom_theme() to dynamically request the Stark theme for
    // the requested page.
    variable_set('menu_test_hook_custom_theme_name', 'stark');
    theme_enable(array(
      'stark',
    ));

    // Visit a page that does not implement a theme callback. The above request
    // should be honored.
    $this
      ->drupalGet('menu-test/no-theme-callback');
    $this
      ->assertText('Custom theme: stark. Actual theme: stark.', 'The result of hook_custom_theme() is used as the theme for the current page.');
    $this
      ->assertRaw('stark/layout.css', "The Stark theme's CSS appears on the page.");

    //
    // Test that the theme callback wins out over hook_custom_theme().
    advagg_test_reset_statics();

    // Trigger hook_custom_theme() to dynamically request the Stark theme for
    // the requested page.
    variable_set('menu_test_hook_custom_theme_name', 'stark');
    theme_enable(array(
      'stark',
    ));

    // The menu "theme callback" should take precedence over a value set in
    // hook_custom_theme().
    $this
      ->drupalGet('menu-test/theme-callback/use-admin-theme');
    $this
      ->assertText('Custom theme: seven. Actual theme: seven.', 'The result of hook_custom_theme() does not override what was set in a theme callback.');
    $this
      ->assertRaw('seven/style.css', "The Seven theme's CSS appears on the page.");

    //
    // Test css split file processing.
    // Generate a massive css file.
    $css_string = advagg_test_generate_selector_css(1000);
    $css_string .= '@media print {' . advagg_test_generate_selector_css(1000) . '}';
    $css_string .= advagg_test_generate_selector_css(1000);
    $css_string .= '@media screen {' . advagg_test_generate_selector_css(1000) . '}';
    $css_string .= advagg_test_generate_selector_css(1000);
    $css_string .= '@media print {' . advagg_test_generate_selector_css(1000) . '}';
    $css_string .= advagg_test_generate_selector_css(9000);
    $css_string .= '@media print {' . advagg_test_generate_selector_css(9000) . '}';
    $css_string .= advagg_test_generate_selector_css(9000);
    $css_string .= '@media screen {' . advagg_test_generate_selector_css(9000) . '}';
    $css_string .= '@media print {' . advagg_test_generate_selector_css(50) . '}';
    $css_string .= '@media screen {' . advagg_test_generate_selector_css(50) . '}';
    $css_string .= advagg_test_generate_selector_css(10);
    $css_string .= '@media print {' . advagg_test_generate_selector_css(50) . '}';
    $css_string .= '@media screen {' . advagg_test_generate_selector_css(50) . '}';
    $css_string .= '@media print {' . advagg_test_generate_selector_css(50) . '}';
    $css_string .= '@media screen {' . advagg_test_generate_selector_css(50) . '}';
    $css_string .= advagg_test_generate_selector_css(10);
    $css_string .= advagg_test_generate_selector_css(10);
    $css_string .= advagg_test_generate_selector_css(10);
    $css_string .= advagg_test_generate_selector_css(10);
    $css_string .= advagg_test_generate_selector_css(15000);
    $css_string .= '@media print {' . advagg_test_generate_selector_css(15000) . '}';
    $css_string .= advagg_test_generate_selector_css(10);
    $css_string .= advagg_test_generate_selector_css(10);
    $css_string .= advagg_test_generate_selector_css(10);
    $css_string .= advagg_test_generate_selector_css(10);
    $css_string .= advagg_test_generate_selector_css(10);
    $file_info = array(
      // Use a file that exists but isn't actually being used here.
      'data' => drupal_get_path('module', 'advagg') . '/tests/css_test_files/advagg.css',
    );
    $before_selector_count = advagg_count_css_selectors($css_string);

    // Split the huge css file.
    $parts = advagg_split_css_file($file_info, $css_string);
    $after = '';
    foreach ($parts as $part) {

      // Get written file.
      $after .= "\n" . (string) @advagg_file_get_contents($part['data']);

      // Cleanup.
      unlink($part['data']);
    }

    // Note that a diff of the text can not be used for this test. Counting
    // selectors is close enough for now.
    $after_selector_count = advagg_count_css_selectors($after);
    $this
      ->assertEqual($before_selector_count, $after_selector_count, t('Expected %before selectors, got %after.', array(
      '%before' => $before_selector_count,
      '%after' => $after_selector_count,
    )));

    //
    // Test css file processing.
    advagg_test_reset_statics();

    // Array of files to test living in 'advagg/tests/css_test_files/'.
    // - Original: name.css
    // - Unoptimized expected content: name.css.unoptimized.css
    // - Optimized expected content: name.css.optimized.css
    //
    // File. Tests: css_input_without_import.css.
    // - Stripped comments and white-space.
    // - Retain white-space in selectors. (http://drupal.org/node/472820)
    // - Retain pseudo-selectors. (http://drupal.org/node/460448)
    //
    // File. Tests: css_input_with_import.css.
    // - Proper URLs in imported files. (http://drupal.org/node/265719)
    // - A background image with relative paths, which must be rewritten.
    // - The rewritten background image path must also be passed through
    //   file_create_url(). (https://drupal.org/node/1961340)
    // - Imported files that are external (protocol-relative URL or not)
    //   should not be expanded. (https://drupal.org/node/2014851)
    //
    // File in sub-folder. Tests: css_subfolder/css_input_with_import.css.
    // - CSS import path interpreted. (https://drupal.org/node/1198904)
    // - Don't adjust data URIs (https://drupal.org/node/2142441)
    //
    // File. Tests: comment_hacks.css.
    // - Retain comment hacks.
    $testfiles = array(
      'css_input_without_import.css',
      'css_input_with_import.css',
      'css_subfolder/css_input_with_import.css',
      'comment_hacks.css',
    );
    $path = drupal_get_path('module', 'advagg') . '/tests/css_test_files';
    foreach ($testfiles as $file) {
      $file_path = $path . '/' . $file;
      $file_url = $GLOBALS['base_url'] . '/' . $file_path;
      $expected = advagg_file_get_contents($file_path . '.unoptimized.css');
      $unoptimized_output = advagg_load_stylesheet($file_path, FALSE);
      $this
        ->assertEqual($unoptimized_output, $expected, format_string('Unoptimized CSS file has expected contents (@file)', array(
        '@file' => $file,
      )));
      $expected = advagg_file_get_contents($file_path . '.optimized.css');
      $expected = advagg_test_remove_sniffer_comments($expected);
      $optimized_output = advagg_load_stylesheet($file_path, TRUE);
      $this
        ->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array(
        '@file' => $file,
      )));

      // Repeat the tests by accessing the stylesheets by URL.
      $expected = advagg_file_get_contents($file_path . '.unoptimized.css');
      $unoptimized_output_url = advagg_load_stylesheet($file_url, FALSE);
      $this
        ->assertEqual($unoptimized_output_url, $expected, format_string('Unoptimized CSS file (loaded from an URL) has expected contents (@file)', array(
        '@file' => $file,
      )));
      $expected = advagg_file_get_contents($file_path . '.optimized.css');
      $expected = advagg_test_remove_sniffer_comments($expected);
      $optimized_output_url = advagg_load_stylesheet($file_url, TRUE);
      $this
        ->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array(
        '@file' => $file,
      )));
    }

    // File. Tests: charset*.css
    // - Any @charaset declaration at the beginning of a file should be
    //   removed without breaking subsequent CSS.
    $testfiles = array(
      'charset.css',
      'charset_newline.css',
      'charset_sameline.css',
    );
    $path = drupal_get_path('module', 'advagg') . '/tests/css_test_files';
    foreach ($testfiles as $file) {
      $file_path = $path . '/' . $file;
      $file_url = $GLOBALS['base_url'] . '/' . $file_path;
      $expected = advagg_file_get_contents($file_path . '.optimized.css');
      $expected = advagg_test_remove_sniffer_comments($expected);
      $optimized_output = advagg_load_stylesheet($file_path, TRUE);
      $this
        ->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array(
        '@file' => $file,
      )));
      $expected = advagg_file_get_contents($file_path . '.optimized.css');
      $expected = advagg_test_remove_sniffer_comments($expected);
      $optimized_output_url = advagg_load_stylesheet($file_url, TRUE);
      $this
        ->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array(
        '@file' => $file,
      )));
    }

    // Set all to FALSE.
    $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_force_https_path'] = FALSE;
    $GLOBALS['conf']['advagg_skip_file_create_url_inside_css'] = FALSE;
    $settings_to_change = array(
      '' => '',
      'advagg_skip_file_create_url_inside_css' => TRUE,
      'advagg_convert_absolute_to_relative_path' => TRUE,
      'advagg_convert_absolute_to_protocol_relative_path' => TRUE,
      'advagg_force_https_path' => TRUE,
    );
    $advagg_path = drupal_get_path('module', 'advagg');
    $path = $advagg_path . '/tests/css_test_files';
    foreach ($settings_to_change as $name => $value) {
      $before = '';
      if (!empty($name)) {
        $before = variable_get($name, defined(strtoupper($name)) ? constant(strtoupper($name)) : NULL);
        $GLOBALS['conf'][$name] = $value;
      }

      // File. Tests: advagg.css
      // - Various url() tests.
      //   https://www.drupal.org/node/1514182
      //   https://www.drupal.org/node/1961340
      //   https://www.drupal.org/node/2362643
      //   https://www.drupal.org/node/2112067
      $testfiles = array(
        'advagg.css',
      );
      foreach ($testfiles as $testfile) {
        $base_url_before = $GLOBALS['base_url'];
        $GLOBALS['base_url'] = advagg_force_http_path($GLOBALS['base_url']);
        $aggregate_settings = array(
          'variables' => array(
            'is_https' => FALSE,
            'base_path' => $GLOBALS['base_path'] === '/checkout/' ? $GLOBALS['base_path'] : $GLOBALS['base_path'] . 'advagg_base_path_test/',
            'advagg_convert_absolute_to_relative_path' => $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'],
            'advagg_convert_absolute_to_protocol_relative_path' => $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'],
            'advagg_force_https_path' => $GLOBALS['conf']['advagg_force_https_path'],
            'advagg_skip_file_create_url_inside_css' => $GLOBALS['conf']['advagg_skip_file_create_url_inside_css'],
          ),
        );
        if (module_exists('cdn')) {
          $aggregate_settings['variables'][CDN_MODE_VARIABLE] = CDN_DISABLED;
          $aggregate_settings['variables'][CDN_STATUS_VARIABLE] = CDN_DISABLED;
        }
        $file_path = $path . '/' . $testfile;
        $files = array(
          'file' => $file_path,
          'external' => $GLOBALS['base_url'] . '/' . $file_path,
        );
        $expected = advagg_test_remove_sniffer_comments(advagg_file_get_contents($file_path . '.optimized.css'));
        foreach ($files as $type => $file) {
          $optimized_output = advagg_load_css_stylesheet($file, TRUE, $aggregate_settings);
          $mode = 0;
          if ($aggregate_settings['variables']['advagg_skip_file_create_url_inside_css'] && $type !== 'external') {
            $mode = "01";
            $optimized_output = str_replace($aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output);
          }
          elseif (!$aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'] && !$aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'] && !$aggregate_settings['variables']['advagg_force_https_path']) {
            $mode = 4;
            $optimized_output = str_replace('http://' . $_SERVER['HTTP_HOST'] . $aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output);
          }
          elseif ($aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path']) {
            $mode = 2;
            $optimized_output = str_replace('//' . $_SERVER['HTTP_HOST'] . $aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output);
          }
          elseif ($aggregate_settings['variables']['advagg_force_https_path']) {
            $mode = 3;
            $optimized_output = str_replace('https://' . $_SERVER['HTTP_HOST'] . $aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output);
          }
          else {
            $mode = 1;
            $optimized_output = str_replace($aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output);
          }
          $this
            ->assertEqual($optimized_output, $expected, format_string("Optimized CSS file has expected contents (@file). Setting tested: @name; value before: @before, value after: @after.<br>mode: @mode. <p>!replacements</p> <p><code>!debug</code></p>", array(
            '@file' => $file,
            '@name' => $name,
            '@before' => is_bool($before) || strlen((string) $before) == 0 ? strtoupper(var_export($before, TRUE)) : $before,
            '@after' => is_bool($value) || strlen((string) $value) == 0 ? strtoupper(var_export($value, TRUE)) : $value,
            '@mode' => $mode,
            '!replacements' => "1: {$aggregate_settings['variables']['base_path']}{$advagg_path}/ <br> 2: //{$_SERVER['HTTP_HOST']}{$aggregate_settings['variables']['base_path']}{$advagg_path}/ <br> 3: https://{$_SERVER['HTTP_HOST']}{$aggregate_settings['variables']['base_path']}{$advagg_path}/ <br> 4: http://{$_SERVER['HTTP_HOST']}{$aggregate_settings['variables']['base_path']}{$advagg_path}/",
            '!debug' => nl2br(str_replace(' ', '&nbsp;', $optimized_output)),
          )));
          $GLOBALS['base_url'] = $base_url_before;
        }
      }
      if (!empty($name)) {
        $GLOBALS['conf'][$name] = $before;
      }
    }
  }

}

/**
 * Tests for the JavaScript system.
 */
class AdvAggJavaScriptTestCase extends DrupalWebTestCase {

  /**
   * Store configured value for JavaScript preprocessing.
   *
   * @var bool
   */
  protected $preprocessJs = NULL;

  /**
   * Provide information to the UI for this test.
   */
  public static function getInfo() {
    return array(
      'name' => 'JavaScript',
      'description' => 'Tests the JavaScript system.',
      'group' => 'AdvAgg',
    );
  }

  /**
   * Install the advagg module and include needed files.
   */
  public function setUp() {

    // Enable Locale and SimpleTest in the test environment.
    parent::setUp(array(
      'locale',
      'locale_test',
      'simpletest',
      'common_test',
      'form_test',
      'advagg',
      'color',
    ));

    // Include the advagg.module file.
    drupal_load('module', 'advagg');
    module_load_include('inc', 'advagg', 'advagg');

    // Disable preprocessing.
    $this->preprocessJs = variable_get('preprocess_js', 0);
    variable_set('preprocess_js', 0);

    // Reset before each test.
    advagg_test_reset_statics();

    // Set settings for testing.
    $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_force_https_path'] = FALSE;
    $GLOBALS['conf']['advagg_mod_js_footer'] = 0;
  }

  /**
   * Restore any variables we set.
   */
  public function tearDown() {

    // Restore configured value for JavaScript preprocessing.
    variable_set('preprocess_js', $this->preprocessJs);
    parent::tearDown();
  }

  /**
   * Test the 'javascript_always_use_jquery' variable.
   */
  public function testJavaScriptAlwaysUsejQuery() {

    // The default front page of the site should use jQuery and other standard
    // scripts and settings.
    advagg_test_reset_statics();
    $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_force_https_path'] = FALSE;
    $GLOBALS['conf']['advagg_mod_js_footer'] = 0;
    $this
      ->drupalGet('');
    $this
      ->assertRaw('misc/jquery.js', 'Default behavior: The front page of the site includes jquery.js.');
    $this
      ->assertRaw('misc/drupal.js', 'Default behavior: The front page of the site includes drupal.js.');
    $this
      ->assertRaw('Drupal.settings', 'Default behavior: The front page of the site includes Drupal settings.');
    $this
      ->assertRaw('basePath', 'Default behavior: The front page of the site includes the basePath Drupal setting.');

    //
    // The default front page should not use jQuery and other standard scripts
    // and settings when the 'javascript_always_use_jquery' variable is set to
    // FALSE.
    advagg_test_reset_statics();
    variable_set('javascript_always_use_jquery', FALSE);
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $this
      ->drupalGet('');
    $this
      ->assertNoRaw('misc/jquery.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include jquery.js.');
    $this
      ->assertNoRaw('misc/drupal.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include drupal.js.');
    $this
      ->assertNoRaw('Drupal.settings', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include Drupal settings.');
    $this
      ->assertNoRaw('basePath', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include the basePath Drupal setting.');
    variable_del('javascript_always_use_jquery');

    //
    // When only settings have been added via drupal_add_js(), drupal_get_js()
    // should still return jQuery and other standard scripts and settings.
    advagg_test_reset_statics();
    drupal_add_js(array(
      'testJavaScriptSetting' => 'test',
    ), 'setting');
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $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.');
    $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.');
    $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.');
    $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.');
    $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.');

    //
    // When only settings have been added via drupal_add_js() and the
    // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js()
    // should not return jQuery and other standard scripts and settings, nor
    // should it return the requested settings (since they cannot actually be
    // added to the page without jQuery).
    advagg_test_reset_statics();
    variable_set('javascript_always_use_jquery', FALSE);
    drupal_add_js(array(
      'testJavaScriptSetting' => 'test',
    ), 'setting');
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $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.');
    $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.');
    $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.');
    $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.');
    $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.');
    variable_del('javascript_always_use_jquery');

    //
    // When a regular file has been added via drupal_add_js(), drupal_get_js()
    // should return jQuery and other standard scripts and settings.
    advagg_test_reset_statics();
    drupal_add_js('misc/collapse.js');
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $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.');
    $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.');
    $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.');
    $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.');
    $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.');

    //
    // When a regular file has been added via drupal_add_js() and the
    // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js()
    // should still return jQuery and other standard scripts and settings
    // (since the file is assumed to require jQuery by default).
    advagg_test_reset_statics();
    variable_set('javascript_always_use_jquery', FALSE);
    drupal_add_js('misc/collapse.js');
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $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.');
    $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.');
    $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.');
    $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.');
    $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.');
    variable_del('javascript_always_use_jquery');

    //
    // When a file that does not require jQuery has been added via
    // drupal_add_js(), drupal_get_js() should still return jQuery and other
    // standard scripts and settings by default.
    advagg_test_reset_statics();
    drupal_add_js('misc/collapse.js', array(
      'requires_jquery' => FALSE,
    ));
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $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.');
    $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.');
    $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.');
    $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.');
    $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.');

    //
    // When a file that does not require jQuery has been added via
    // drupal_add_js() and the 'javascript_always_use_jquery' variable is set
    // to FALSE, drupal_get_js() should not return jQuery and other standard
    // scripts and setting, but it should still return the requested file.
    advagg_test_reset_statics();
    variable_set('javascript_always_use_jquery', FALSE);
    drupal_add_js('misc/collapse.js', array(
      'requires_jquery' => FALSE,
    ));
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $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.');
    $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.');
    $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.');
    $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.');
    $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.');
    variable_del('javascript_always_use_jquery');

    //
    // When 'javascript_always_use_jquery' is set to FALSE and a file that does
    // not require jQuery is added, followed by one that does, drupal_get_js()
    // should return jQuery and other standard scripts and settings, in
    // addition to both of the requested files.
    advagg_test_reset_statics();
    variable_set('javascript_always_use_jquery', FALSE);
    drupal_add_js('misc/collapse.js', array(
      'requires_jquery' => FALSE,
    ));
    drupal_add_js('misc/ajax.js');
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $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.');
    $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.');
    $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.');
    $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.');
    $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.');
    $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.');
    variable_del('javascript_always_use_jquery');

    //
    // Tests JavaScript aggregation when files are added to a different scope.
    advagg_test_reset_statics();

    // Enable JavaScript aggregation.
    variable_set('preprocess_js', 1);

    // Add two JavaScript files to the current request and build the cache.
    drupal_add_js('misc/ajax.js');
    drupal_add_js('misc/autocomplete.js');
    $js_items = drupal_add_js();
    drupal_build_js_cache(array(
      'misc/ajax.js' => $js_items['misc/ajax.js'],
      'misc/autocomplete.js' => $js_items['misc/autocomplete.js'],
    ));

    // Store the expected key for the first item in the cache.
    $cache = array_keys(variable_get('drupal_js_cache_files', array()));
    $expected_key = $cache[0];

    // Reset variables and add a file in a different scope first.
    variable_del('drupal_js_cache_files');
    advagg_test_reset_statics();
    drupal_add_js('some/custom/javascript_file.js', array(
      'scope' => 'footer',
    ));
    drupal_add_js('misc/ajax.js');
    drupal_add_js('misc/autocomplete.js');

    // Rebuild the cache.
    $js_items = drupal_add_js();
    drupal_build_js_cache(array(
      'misc/ajax.js' => $js_items['misc/ajax.js'],
      'misc/autocomplete.js' => $js_items['misc/autocomplete.js'],
    ));

    // Compare the expected key for the first file to the current one.
    $cache = array_keys(variable_get('drupal_js_cache_files', array()));
    $key = $cache[0];
    $this
      ->assertEqual($key, $expected_key, 'JavaScript aggregation is not affected by ordering in different scopes.');
    variable_set('preprocess_js', 0);

    //
    // Test JavaScript ordering.
    advagg_test_reset_statics();

    // Add a bunch of JavaScript in strange ordering.
    drupal_add_js('(function($){alert("Weight 5 #1");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => 5,
    ));
    drupal_add_js('(function($){alert("Weight 0 #1");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
    ));
    drupal_add_js('(function($){alert("Weight 0 #2");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
    ));
    drupal_add_js('(function($){alert("Weight -8 #1");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => -8,
    ));
    drupal_add_js('(function($){alert("Weight -8 #2");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => -8,
    ));
    drupal_add_js('(function($){alert("Weight -8 #3");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => -8,
    ));
    drupal_add_js('http://example.com/example.js?Weight -5 #1', array(
      'type' => 'external',
      'scope' => 'footer',
      'weight' => -5,
    ));
    drupal_add_js('(function($){alert("Weight -8 #4");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => -8,
    ));
    drupal_add_js('(function($){alert("Weight 5 #2");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => 5,
    ));
    drupal_add_js('(function($){alert("Weight 0 #3");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
    ));

    // Construct the expected result from the regex.
    $expected = array(
      "-8 #1",
      "-8 #2",
      "-8 #3",
      "-8 #4",
      // -5 #1: The external script.
      "-5 #1",
      "0 #1",
      "0 #2",
      "0 #3",
      "5 #1",
      "5 #2",
    );

    // Retrieve the rendered JavaScript and test against the regex.
    $render_array = advagg_get_js('footer');
    $js = drupal_render($render_array);
    $matches = array();
    if (preg_match_all('/Weight\\s([-0-9]+\\s[#0-9]+)/', $js, $matches)) {
      $result = $matches[1];
    }
    else {
      $result = array();
    }
    $this
      ->assertIdentical($result, $expected, 'JavaScript is added in the expected weight order.');

    //
    // Test default JavaScript is empty.
    advagg_test_reset_statics();
    $this
      ->assertEqual(array(), drupal_add_js(), 'Default JavaScript is empty.');

    //
    // Test drupal_get_js() for JavaScript settings.
    advagg_test_reset_statics();

    // Only the second of these two entries should appear in Drupal.settings.
    drupal_add_js(array(
      'commonTest' => 'commonTestShouldNotAppear',
    ), 'setting');
    drupal_add_js(array(
      'commonTest' => 'commonTestShouldAppear',
    ), 'setting');

    // All three of these entries should appear in Drupal.settings.
    drupal_add_js(array(
      'commonTestArray' => array(
        'commonTestValue0',
      ),
    ), 'setting');
    drupal_add_js(array(
      'commonTestArray' => array(
        'commonTestValue1',
      ),
    ), 'setting');
    drupal_add_js(array(
      'commonTestArray' => array(
        'commonTestValue2',
      ),
    ), 'setting');

    // Only the second of these two entries should appear in Drupal.settings.
    drupal_add_js(array(
      'commonTestArray' => array(
        'key' => 'commonTestOldValue',
      ),
    ), 'setting');
    drupal_add_js(array(
      'commonTestArray' => array(
        'key' => 'commonTestNewValue',
      ),
    ), 'setting');
    $render_array = advagg_get_js('header');
    $javascript = drupal_render($render_array);
    $this
      ->assertTrue(strpos($javascript, 'basePath') > 0, 'Rendered JavaScript header returns basePath setting.');
    $this
      ->assertTrue(strpos($javascript, 'misc/jquery.js') > 0, 'Rendered JavaScript header includes jQuery.');
    $this
      ->assertTrue(strpos($javascript, 'pathPrefix') > 0, 'Rendered JavaScript header returns pathPrefix setting.');

    // Test whether drupal_add_js can be used to override a previous setting.
    $this
      ->assertTrue(strpos($javascript, 'commonTestShouldAppear') > 0, 'Rendered JavaScript header returns custom setting.');
    $this
      ->assertTrue(strpos($javascript, 'commonTestShouldNotAppear') === FALSE, 'drupal_add_js() correctly overrides a custom setting.');

    // Test whether drupal_add_js can be used to add numerically indexed values
    // to an array.
    $array_values_appear = strpos($javascript, 'commonTestValue0') > 0 && strpos($javascript, 'commonTestValue1') > 0 && strpos($javascript, 'commonTestValue2') > 0;
    $this
      ->assertTrue($array_values_appear, 'drupal_add_js() correctly adds settings to the end of an indexed array.');

    // Test whether drupal_add_js can be used to override the entry for an
    // existing key in an associative array.
    $associative_array_override = strpos($javascript, 'commonTestNewValue') > 0 && strpos($javascript, 'commonTestOldValue') === FALSE;
    $this
      ->assertTrue($associative_array_override, 'drupal_add_js() correctly overrides settings within an associative array.');

    //
    // Test rendering an external JavaScript file.
    advagg_test_reset_statics();
    $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_force_https_path'] = FALSE;
    $external = 'http://example.com/example.js';
    drupal_add_js($external, 'external');
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);

    // Local files have a base_path() prefix, external files should not.
    $this
      ->assertTrue(strpos($javascript, 'src="' . $external) > 0, 'Rendering an external JavaScript file.');

    //
    // Test drupal_get_js() with a footer scope.
    advagg_test_reset_statics();
    $inline = 'jQuery(function () { });';
    drupal_add_js($inline, array(
      'type' => 'inline',
      'scope' => 'footer',
    ));
    $render_array = advagg_get_js('footer');
    $javascript = drupal_render($render_array);
    $this
      ->assertTrue(strpos($javascript, $inline) > 0, 'Rendered JavaScript footer returns the inline code.');

    //
    // Ensures that vertical-tabs.js is included before collapse.js.
    advagg_test_reset_statics();
    $this
      ->drupalGet('form_test/vertical-tabs');
    $position1 = strpos($this->content, 'misc/vertical-tabs.js');
    $position2 = strpos($this->content, 'misc/collapse.js');
    $this
      ->assertTrue($position1 !== FALSE && $position2 !== FALSE && $position1 < $position2, 'vertical-tabs.js is included before collapse.js');

    //
    // Test rendering the JavaScript with a file's weight above jQuery's.
    advagg_test_reset_statics();

    // JavaScript files are sorted first by group, then by the 'every_page'
    // flag, then by weight (see drupal_sort_css_js()), so to test the effect of
    // weight, we need the other two options to be the same.
    drupal_add_js('misc/collapse.js', array(
      'group' => JS_LIBRARY,
      'every_page' => TRUE,
      'weight' => -21,
    ));
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $this
      ->assertTrue(strpos($javascript, 'misc/collapse.js') < strpos($javascript, 'misc/jquery.js'), 'Rendering a JavaScript file above jQuery.');

    //
    // Test altering a JavaScript's weight via hook_js_alter().
    advagg_test_reset_statics();

    // Add both tableselect.js and simpletest.js, with a larger weight on
    // SimpleTest.
    drupal_add_js('misc/tableselect.js');
    drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', array(
      'weight' => 9999,
    ));

    // Render the JavaScript, testing if simpletest.js was altered to be before
    // tableselect.js. See simpletest_js_alter() to see where this alteration
    // takes place.
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $this
      ->assertTrue(strpos($javascript, 'simpletest.js') < strpos($javascript, 'misc/tableselect.js'), 'Altering JavaScript weight through the alter hook.');

    //
    // Adds a library to the page and tests for both its JavaScript and its CSS.
    advagg_test_reset_statics();
    $result = drupal_add_library('system', 'farbtastic');
    $this
      ->assertTrue($result !== FALSE, 'Library was added without errors.');
    $render_array = advagg_get_js();
    $scripts = drupal_render($render_array);
    $render_array = advagg_get_css();
    $styles = drupal_render($render_array);
    $this
      ->assertTrue(strpos($scripts, 'misc/farbtastic/farbtastic.js'), 'JavaScript of library was added to the page.');
    $this
      ->assertTrue(strpos($styles, 'misc/farbtastic/farbtastic.css'), 'Stylesheet of library was added to the page.');

    //
    // Adds a JavaScript library to the page and alters it.
    advagg_test_reset_statics();

    // Verify that common_test altered the title of Farbtastic.
    $library = drupal_get_library('system', 'farbtastic');
    $this
      ->assertEqual($library['title'], 'Farbtastic: Altered Library', 'Registered libraries were altered.');

    // common_test_library_alter() also added a dependency on jQuery Form.
    drupal_add_library('system', 'farbtastic');
    $render_array = advagg_get_js();
    $scripts = drupal_render($render_array);
    $this
      ->assertTrue(strpos($scripts, 'misc/jquery.form.js'), 'Altered library dependencies are added to the page.');

    //
    // Tests non-existing libraries.
    advagg_test_reset_statics();
    $result = drupal_get_library('unknown', 'unknown');
    $this
      ->assertFalse($result, 'Unknown library returned FALSE.');
    advagg_test_reset_statics();
    $result = drupal_add_library('unknown', 'unknown');
    $this
      ->assertFalse($result, 'Unknown library returned FALSE.');
    $render_array = advagg_get_js();
    $scripts = drupal_render($render_array);
    $this
      ->assertTrue(strpos($scripts, 'unknown') === FALSE, 'Unknown library was not added to the page.');

    //
    // Tests the addition of libraries through the #attached['library']
    // property.
    advagg_test_reset_statics();
    $element['#attached']['library'][] = array(
      'system',
      'farbtastic',
    );
    drupal_render($element);
    $render_array = advagg_get_js();
    $scripts = drupal_render($render_array);
    $this
      ->assertTrue(strpos($scripts, 'misc/farbtastic/farbtastic.js'), 'The attached_library property adds the additional libraries.');

    //
    // Test #attached functionality in children elements.
    advagg_test_reset_statics();

    // The cache system is turned off for POST requests.
    $request_method = $_SERVER['REQUEST_METHOD'];
    $_SERVER['REQUEST_METHOD'] = 'GET';

    // Create an element with a child and subchild.  Each element loads a
    // different JavaScript file using #attached.
    $parent_js = drupal_get_path('module', 'user') . '/user.js';
    $child_js = drupal_get_path('module', 'color') . '/color.js';
    $subchild_js = drupal_get_path('module', 'book') . '/book.js';
    $element = array(
      '#type' => 'fieldset',
      '#cache' => array(
        'keys' => array(
          'simpletest',
          'drupal_render',
          'children_attached',
        ),
      ),
      '#attached' => array(
        'js' => array(
          $parent_js,
        ),
      ),
      '#title' => 'Parent',
    );
    $element['child'] = array(
      '#type' => 'fieldset',
      '#attached' => array(
        'js' => array(
          $child_js,
        ),
      ),
      '#title' => 'Child',
    );
    $element['child']['subchild'] = array(
      '#attached' => array(
        'js' => array(
          $subchild_js,
        ),
      ),
      '#markup' => 'Subchild',
    );

    // Render the element and verify the presence of #attached JavaScript.
    drupal_render($element);
    $render_array = advagg_get_js();
    $scripts = drupal_render($render_array);
    $this
      ->assertTrue(strpos($scripts, $parent_js), 'The element #attached JavaScript was included.');
    $this
      ->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included.');
    $this
      ->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included.');

    // Load the element from cache and verify the presence of the #attached
    // JavaScript.
    advagg_test_reset_statics();
    $this
      ->assertTrue(drupal_render_cache_get($element), 'The element was retrieved from cache.');
    $render_array = advagg_get_js();
    $scripts = drupal_render($render_array);
    $this
      ->assertTrue(strpos($scripts, $parent_js), 'The element #attached JavaScript was included when loading from cache.');
    $this
      ->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included when loading from cache.');
    $this
      ->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included when loading from cache.');
    $_SERVER['REQUEST_METHOD'] = $request_method;

    //
    // Tests the localisation of JavaScript libraries. Verifies that the
    // datepicker can be localized.
    advagg_test_reset_statics();
    drupal_add_library('system', 'ui.datepicker');
    $GLOBALS['conf']['advagg_mod_js_footer'] = 0;
    $render_array = advagg_get_js();
    $javascript = drupal_render($render_array);
    $this
      ->assertTrue(strpos($javascript, 'locale.datepicker.js'), 'locale.datepicker.js added to scripts.');

    //
    // Functional tests for JavaScript parsing for translatable strings.
    // Tests parsing js files for translatable strings.
    advagg_test_reset_statics();
    $filename = drupal_get_path('module', 'locale_test') . '/locale_test.js';

    // Parse the file to look for source strings.
    _locale_parse_js_file($filename);

    // Get all of the source strings that were found.
    $source_strings = db_select('locales_source', 's')
      ->fields('s', array(
      'source',
      'context',
    ))
      ->condition('s.location', $filename)
      ->execute()
      ->fetchAllKeyed();

    // List of all strings that should be in the file.
    $test_strings = array(
      "Standard Call t" => '',
      "Whitespace Call t" => '',
      "Single Quote t" => '',
      "Single Quote \\'Escaped\\' t" => '',
      "Single Quote Concat strings t" => '',
      "Double Quote t" => '',
      "Double Quote \\\"Escaped\\\" t" => '',
      "Double Quote Concat strings t" => '',
      "Context !key Args t" => "Context string",
      "Context Unquoted t" => "Context string unquoted",
      "Context Single Quoted t" => "Context string single quoted",
      "Context Double Quoted t" => "Context string double quoted",
      "Standard Call plural" => '',
      "Standard Call @count plural" => '',
      "Whitespace Call plural" => '',
      "Whitespace Call @count plural" => '',
      "Single Quote plural" => '',
      "Single Quote @count plural" => '',
      "Single Quote \\'Escaped\\' plural" => '',
      "Single Quote \\'Escaped\\' @count plural" => '',
      "Double Quote plural" => '',
      "Double Quote @count plural" => '',
      "Double Quote \\\"Escaped\\\" plural" => '',
      "Double Quote \\\"Escaped\\\" @count plural" => '',
      "Context !key Args plural" => "Context string",
      "Context !key Args @count plural" => "Context string",
      "Context Unquoted plural" => "Context string unquoted",
      "Context Unquoted @count plural" => "Context string unquoted",
      "Context Single Quoted plural" => "Context string single quoted",
      "Context Single Quoted @count plural" => "Context string single quoted",
      "Context Double Quoted plural" => "Context string double quoted",
      "Context Double Quoted @count plural" => "Context string double quoted",
    );

    // Assert that all strings were found properly.
    foreach ($test_strings as $str => $context) {
      $args = array(
        '%source' => $str,
        '%context' => $context,
      );

      // Make sure that the string was found in the file.
      $this
        ->assertTrue(isset($source_strings[$str]), format_string('Found source string: %source', $args));

      // Make sure that the proper context was matched.
      $this
        ->assertTrue(isset($source_strings[$str]) && $source_strings[$str] === $context, strlen($context) > 0 ? format_string('Context for %source is %context', $args) : format_string('Context for %source is blank', $args));
    }
    $this
      ->assertEqual(count($source_strings), count($test_strings), 'Found correct number of source strings.');

    //
    // Adds a language and checks that the JavaScript translation files are
    // properly created and rebuilt on deletion.
    $user = $this
      ->drupalCreateUser(array(
      'translate interface',
      'administer languages',
      'access administration pages',
    ));
    $this
      ->drupalLogin($user);
    $langcode = 'xx';

    // The English name for the language. This will be translated.
    $name = $this
      ->randomName(16);

    // The native name for the language.
    $native = $this
      ->randomName(16);

    // The domain prefix.
    $prefix = $langcode;

    // Add custom language.
    $edit = array(
      'langcode' => $langcode,
      'name' => $name,
      'native' => $native,
      'prefix' => $prefix,
      'direction' => '0',
    );
    $this
      ->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
    drupal_static_reset('language_list');

    // Build the JavaScript translation file.
    $this
      ->drupalGet('admin/config/regional/translate/translate');

    // Retrieve the id of the first string available in the {locales_source}
    // table and translate it.
    $query = db_select('locales_source', 'l');
    $query
      ->addExpression('min(l.lid)', 'lid');
    $result = $query
      ->condition('l.location', '%.js%', 'LIKE')
      ->condition('l.textgroup', 'default')
      ->execute();
    $url = 'admin/config/regional/translate/edit/' . $result
      ->fetchObject()->lid;
    $edit = array(
      'translations[' . $langcode . ']' => $this
        ->randomName(),
    );
    $this
      ->drupalPost($url, $edit, t('Save translations'));

    // Trigger JavaScript translation parsing and building.
    require_once DRUPAL_ROOT . '/includes/locale.inc';
    _locale_rebuild_js($langcode);

    // Retrieve the JavaScript translation hash code for the custom language to
    // check that the translation file has been properly built.
    $file = db_select('languages', 'l')
      ->fields('l', array(
      'javascript',
    ))
      ->condition('language', $langcode)
      ->execute()
      ->fetchObject();
    $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/' . $langcode . '_' . $file->javascript . '.js';
    $this
      ->assertTrue($result = file_exists($js_file), format_string('JavaScript file created: %file', array(
      '%file' => $result ? $js_file : 'not found',
    )));

    // Test JavaScript translation rebuilding.
    file_unmanaged_delete($js_file);
    $this
      ->assertTrue($result = !file_exists($js_file), format_string('JavaScript file deleted: %file', array(
      '%file' => $result ? $js_file : 'found',
    )));
    cache_clear_all();
    _locale_rebuild_js($langcode);
    $this
      ->assertTrue($result = file_exists($js_file), format_string('JavaScript file rebuilt: %file', array(
      '%file' => $result ? $js_file : 'not found',
    )));
  }

}

/**
 * Unit tests for the Theme API.
 */
class AdvAggThemeTestCase extends AJAXTestCase {

  /**
   * Provide information to the UI for this test.
   */
  public static function getInfo() {
    return array(
      'name' => 'Theme API and AJAX',
      'description' => 'Test low-level theme functions and AJAX framework functions and commands.',
      'group' => 'AdvAgg',
    );
  }

  /**
   * Install the advagg module and include needed files.
   */
  public function setUp() {
    parent::setUp(array(
      'advagg',
    ));

    // Include the advagg.module file.
    drupal_load('module', 'advagg');
    module_load_include('inc', 'advagg', 'advagg');
    module_enable(array(
      'theme_test',
    ));
    theme_enable(array(
      'test_theme',
    ));

    // Reset before each test.
    advagg_test_reset_statics();

    // Set settings for testing.
    $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE;
    $GLOBALS['conf']['advagg_force_https_path'] = FALSE;
  }

  /**
   * Check theme and ajax functions and commands.
   */
  public function testCssOverride() {

    // Ensures a theme's .info file is able to override a module CSS file from
    // being added to the page.
    advagg_test_reset_statics();

    // 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.
    $GLOBALS['conf']['preprocess_css'] = 0;
    variable_set('preprocess_css', 0);
    $this
      ->drupalGet('theme-test/suggestion');
    $this
      ->assertNoText('system.base.css', '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);

    //
    // Test ajax and js settings.
    advagg_test_reset_statics();
    $commands = $this
      ->drupalGetAJAX('ajax-test/render');

    // Verify that there is a command to load settings added with
    // drupal_add_js().
    $expected = array(
      'command' => 'settings',
      'settings' => array(
        'basePath' => base_path(),
        'ajax' => 'test',
      ),
    );
    $this
      ->assertCommand($commands, $expected, t('ajax_render() loads settings added with drupal_add_js().'));

    // Verify that Ajax settings are loaded for #type 'link'.
    $this
      ->drupalGet('ajax-test/link');
    $settings = $this
      ->drupalGetSettings();
    $this
      ->assertEqual($settings['ajax']['ajax-link']['url'], url('filter/tips'));
    $this
      ->assertEqual($settings['ajax']['ajax-link']['wrapper'], 'block-system-main');

    //
    // Test behavior of ajax_render_error().
    // Reset before each test.
    advagg_test_reset_statics();

    // Verify default error message.
    $commands = $this
      ->drupalGetAJAX('ajax-test/render-error');
    $expected = array(
      'command' => 'alert',
      'text' => t('An error occurred while handling the request: The server received invalid input.'),
    );
    $this
      ->assertCommand($commands, $expected, t('ajax_render_error() invokes alert command.'));

    // Verify custom error message.
    $edit = array(
      'message' => 'Custom error message.',
    );
    $commands = $this
      ->drupalGetAJAX('ajax-test/render-error', array(
      'query' => $edit,
    ));
    $expected = array(
      'command' => 'alert',
      'text' => $edit['message'],
    );
    $this
      ->assertCommand($commands, $expected, t('Custom error message is output.'));

    //
    // Test that new JavaScript and CSS files added during an AJAX request are
    // returned.
    // Reset before each test.
    advagg_test_reset_statics();
    $expected = array(
      'setting_name' => 'ajax_forms_test_lazy_load_form_submit',
      'setting_value' => 'executed',
      'css' => drupal_get_path('module', 'system') . '/system.admin.css',
      'js' => drupal_get_path('module', 'system') . '/system.js',
    );

    // @todo D8: Add a drupal_css_defaults() helper function.
    $expected_css_render_array = advagg_get_css(array(
      $expected['css'] => array(
        'type' => 'file',
        'group' => CSS_DEFAULT,
        'weight' => 0,
        'every_page' => FALSE,
        'media' => 'all',
        'preprocess' => TRUE,
        'data' => $expected['css'],
        'browsers' => array(
          'IE' => TRUE,
          '!IE' => TRUE,
        ),
      ),
    ), TRUE);
    $expected_css_html = drupal_render($expected_css_render_array);
    $expected_js_render_array = advagg_get_js('header', array(
      $expected['js'] => drupal_js_defaults($expected['js']),
    ), TRUE);
    $expected_js_html = drupal_render($expected_js_render_array);

    // Get the base page.
    $this
      ->drupalGet('ajax_forms_test_lazy_load_form');
    $original_settings = $this
      ->drupalGetSettings();
    $original_css = $original_settings['ajaxPageState']['css'];
    $original_js = $original_settings['ajaxPageState']['js'];

    // Verify that the base page doesn't have the settings and files that are to
    // be lazy loaded as part of the next requests.
    $this
      ->assertTrue(!isset($original_settings[$expected['setting_name']]), t('Page originally lacks the %setting, as expected.', array(
      '%setting' => $expected['setting_name'],
    )));
    $this
      ->assertTrue(!isset($original_settings[$expected['css']]), t('Page originally lacks the %css file, as expected.', array(
      '%css' => $expected['css'],
    )));
    $this
      ->assertTrue(!isset($original_settings[$expected['js']]), t('Page originally lacks the %js file, as expected.', array(
      '%js' => $expected['js'],
    )));

    // Submit the AJAX request without triggering files getting added.
    $commands = $this
      ->drupalPostAJAX(NULL, array(
      'add_files' => FALSE,
    ), array(
      'op' => t('Submit'),
    ));
    $new_settings = $this
      ->drupalGetSettings();

    // Verify the setting was not added when not expected.
    $this
      ->assertTrue(!isset($new_settings['setting_name']), t('Page still lacks the %setting, as expected.', array(
      '%setting' => $expected['setting_name'],
    )));

    // Verify a settings command does not add CSS or scripts to Drupal.settings
    // and no command inserts the corresponding tags on the page.
    $found_settings_command = FALSE;
    $found_markup_command = FALSE;
    foreach ($commands as $command) {
      if ($command['command'] == 'settings' && (array_key_exists('css', $command['settings']['ajaxPageState']) || array_key_exists('js', $command['settings']['ajaxPageState']))) {
        $found_settings_command = TRUE;
      }
      if (isset($command['data']) && ($command['data'] == $expected_js_html || $command['data'] == $expected_css_html)) {
        $found_markup_command = TRUE;
      }
    }
    $this
      ->assertFalse($found_settings_command, t('Page state still lacks the %css and %js files, as expected.', array(
      '%css' => $expected['css'],
      '%js' => $expected['js'],
    )));
    $this
      ->assertFalse($found_markup_command, t('Page still lacks the %css and %js files, as expected.', array(
      '%css' => $expected['css'],
      '%js' => $expected['js'],
    )));

    // Submit the AJAX request and trigger adding files.
    $commands = $this
      ->drupalPostAJAX(NULL, array(
      'add_files' => TRUE,
    ), array(
      'op' => t('Submit'),
    ));
    $new_settings = $this
      ->drupalGetSettings();
    $new_css = $new_settings['ajaxPageState']['css'];
    $new_js = $new_settings['ajaxPageState']['js'];

    // Verify the expected setting was added.
    $this
      ->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], t('Page now has the %setting.', array(
      '%setting' => $expected['setting_name'],
    )));

    // Verify the expected CSS file was added, both to Drupal.settings, and as
    // an AJAX command for inclusion into the HTML.
    $this
      ->assertEqual($new_css, $original_css + array(
      $expected['css'] => 1,
    ), t('Page state now has the %css file.', array(
      '%css' => $expected['css'],
    )));
    $this
      ->assertCommand($commands, array(
      'data' => $expected_css_html,
    ), t('Page now has the %css file.', array(
      '%css' => $expected['css'],
    )));

    // Verify the expected JS file was added, both to Drupal.settings, and as
    // an AJAX command for inclusion into the HTML. By testing for an exact HTML
    // string containing the SCRIPT tag, we also ensure that unexpected
    // JavaScript code, such as a jQuery.extend() that would potentially clobber
    // rather than properly merge settings, didn't accidentally get added.
    $this
      ->assertEqual($new_js, $original_js + array(
      $expected['js'] => 1,
    ), t('Page state now has the %js file.', array(
      '%js' => $expected['js'],
    )));
    $this
      ->assertCommand($commands, array(
      'data' => $expected_js_html,
    ), t('Page now has the %js file.', array(
      '%js' => $expected['js'],
    )));

    //
    // Tests that overridden CSS files are not added during lazy load.
    // Reset before each test.
    advagg_test_reset_statics();

    // The test theme overrides system.base.css without an implementation,
    // thereby removing it.
    theme_enable(array(
      'test_theme',
    ));
    variable_set('theme_default', 'test_theme');

    // This gets the form, and emulates an Ajax submission on it, including
    // adding markup to the HEAD and BODY for any lazy loaded JS/CSS files.
    $this
      ->drupalPostAJAX('ajax_forms_test_lazy_load_form', array(
      'add_files' => TRUE,
    ), array(
      'op' => t('Submit'),
    ));

    // Verify that the resulting HTML does not load the overridden CSS file.
    // We add a "?" to the assertion, because Drupal.settings may include
    // information about the file; we only really care about whether it appears
    // in a LINK or STYLE tag, for which Drupal always adds a query string for
    // cache control.
    $this
      ->assertNoText('system.base.css?', 'Ajax lazy loading does not add overridden CSS files.');

    //
    // Test the various Ajax Commands.
    // Reset before each test.
    advagg_test_reset_statics();
    $form_path = 'ajax_forms_test_ajax_commands_form';
    $web_user = $this
      ->drupalCreateUser(array(
      'access content',
    ));
    $this
      ->drupalLogin($web_user);
    $edit = array();

    // Tests the 'after' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX 'After': Click to put something after the div"),
    ));
    $expected = array(
      'command' => 'insert',
      'method' => 'after',
      'data' => 'This will be placed after',
    );
    $this
      ->assertCommand($commands, $expected, "'after' AJAX command issued with correct data");

    // Tests the 'alert' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX 'Alert': Click to alert"),
    ));
    $expected = array(
      'command' => 'alert',
      'text' => 'Alert',
    );
    $this
      ->assertCommand($commands, $expected, "'alert' AJAX Command issued with correct text");

    // Tests the 'append' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX 'Append': Click to append something"),
    ));
    $expected = array(
      'command' => 'insert',
      'method' => 'append',
      'data' => 'Appended text',
    );
    $this
      ->assertCommand($commands, $expected, "'append' AJAX command issued with correct data");

    // Tests the 'before' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX 'before': Click to put something before the div"),
    ));
    $expected = array(
      'command' => 'insert',
      'method' => 'before',
      'data' => 'Before text',
    );
    $this
      ->assertCommand($commands, $expected, "'before' AJAX command issued with correct data");

    // Tests the 'changed' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX changed: Click to mark div changed."),
    ));
    $expected = array(
      'command' => 'changed',
      'selector' => '#changed_div',
    );
    $this
      ->assertCommand($commands, $expected, "'changed' AJAX command issued with correct selector");

    // Tests the 'changed' command using the second argument.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX changed: Click to mark div changed with asterisk."),
    ));
    $expected = array(
      'command' => 'changed',
      'selector' => '#changed_div',
      'asterisk' => '#changed_div_mark_this',
    );
    $this
      ->assertCommand($commands, $expected, "'changed' AJAX command (with asterisk) issued with correct selector");

    // Tests the 'css' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("Set the '#box' div to be blue."),
    ));
    $expected = array(
      'command' => 'css',
      'selector' => '#css_div',
      'argument' => array(
        'background-color' => 'blue',
      ),
    );
    $this
      ->assertCommand($commands, $expected, "'css' AJAX command issued with correct selector");

    // Tests the 'data' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX data command: Issue command."),
    ));
    $expected = array(
      'command' => 'data',
      'name' => 'testkey',
      'value' => 'testvalue',
    );
    $this
      ->assertCommand($commands, $expected, "'data' AJAX command issued with correct key and value");

    // Tests the 'invoke' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX invoke command: Invoke addClass() method."),
    ));
    $expected = array(
      'command' => 'invoke',
      'method' => 'addClass',
      'arguments' => array(
        'error',
      ),
    );
    $this
      ->assertCommand($commands, $expected, "'invoke' AJAX command issued with correct method and argument");

    // Tests the 'html' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX html: Replace the HTML in a selector."),
    ));
    $expected = array(
      'command' => 'insert',
      'method' => 'html',
      'data' => 'replacement text',
    );
    $this
      ->assertCommand($commands, $expected, "'html' AJAX command issued with correct data");

    // Tests the 'insert' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX insert: Let client insert based on #ajax['method']."),
    ));
    $expected = array(
      'command' => 'insert',
      'data' => 'insert replacement text',
    );
    $this
      ->assertCommand($commands, $expected, "'insert' AJAX command issued with correct data");

    // Tests the 'prepend' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX 'prepend': Click to prepend something"),
    ));
    $expected = array(
      'command' => 'insert',
      'method' => 'prepend',
      'data' => 'prepended text',
    );
    $this
      ->assertCommand($commands, $expected, "'prepend' AJAX command issued with correct data");

    // Tests the 'remove' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX 'remove': Click to remove text"),
    ));
    $expected = array(
      'command' => 'remove',
      'selector' => '#remove_text',
    );
    $this
      ->assertCommand($commands, $expected, "'remove' AJAX command issued with correct command and selector");

    // Tests the 'restripe' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX 'restripe' command"),
    ));
    $expected = array(
      'command' => 'restripe',
      'selector' => '#restripe_table',
    );
    $this
      ->assertCommand($commands, $expected, "'restripe' AJAX command issued with correct selector");

    // Tests the 'settings' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX 'settings' command"),
    ));
    $expected = array(
      'command' => 'settings',
      'settings' => array(
        'ajax_forms_test' => array(
          'foo' => 42,
        ),
      ),
    );
    $this
      ->assertCommand($commands, $expected, "'settings' AJAX command issued with correct data");

    // Tests the 'add_css' command.
    $commands = $this
      ->drupalPostAJAX($form_path, $edit, array(
      'op' => t("AJAX 'add_css' command"),
    ));
    $expected = array(
      'command' => 'add_css',
      'data' => 'my/file.css',
    );
    $this
      ->assertCommand($commands, $expected, "'add_css' AJAX command issued with correct data");
  }

}

/**
 * @} End of "defgroup advagg_tests".
 */

Functions

Namesort descending Description
advagg_test_generate_selector_css Generates a large CSS string.
advagg_test_randon_string Generates random string.
advagg_test_remove_sniffer_comments Strip the codingStandardsIgnoreFile string from the input.
advagg_test_reset_statics Resets static variables related to adding CSS or JS to a page.

Classes

Namesort descending Description
AdvAggCascadingStylesheetsTestCase Test the Drupal CSS system.
AdvAggJavaScriptTestCase Tests for the JavaScript system.
AdvAggThemeTestCase Unit tests for the Theme API.