'JavaScript', 'description' => 'Tests the JavaScript system.', 'group' => 'Common', ); } function setUp() { parent::setUp(); // There are dependencies in drupal_get_js() on the theme layer so we need // to initialize it. drupal_theme_initialize(); // Disable preprocessing $config = \Drupal::config('system.performance'); $this->preprocess_js = $config->get('js.preprocess'); $config->set('js.preprocess', 0); $config->save(); // Reset drupal_add_js() and drupal_add_library() statics before each test. drupal_static_reset('drupal_add_js'); drupal_static_reset('drupal_add_library'); } function tearDown() { // Restore configured value for JavaScript preprocessing. $config = \Drupal::config('system.performance'); $config->set('js.preprocess', $this->preprocess_js); $config->save(); parent::tearDown(); } /** * Tests that default JavaScript is empty. */ function testDefault() { $this->assertEqual(array(), drupal_add_js(), 'Default JavaScript is empty.'); } /** * Tests adding a JavaScript file. */ function testAddFile() { $javascript = drupal_add_js('core/misc/collapse.js'); $this->assertTrue(array_key_exists('core/misc/collapse.js', $javascript), 'JavaScript files are correctly added.'); } /** * Tests adding settings. */ function testAddSetting() { // Add a file in order to test default settings. drupal_add_library('system', 'drupalSettings'); $javascript = drupal_add_js(); $last_settings = reset($javascript['settings']['data']); $this->assertTrue(array_key_exists('currentPath', $last_settings), 'The current path JavaScript setting is set correctly.'); $javascript = drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting'); $last_settings = end($javascript['settings']['data']); $this->assertEqual(280342800, $last_settings['dries'], 'JavaScript setting is set correctly.'); $this->assertEqual('rocks', $last_settings['drupal'], 'The other JavaScript setting is set correctly.'); } /** * Tests adding an external JavaScript File. */ function testAddExternal() { $path = 'http://example.com/script.js'; $javascript = drupal_add_js($path, 'external'); $this->assertTrue(array_key_exists('http://example.com/script.js', $javascript), 'Added an external JavaScript file.'); } /** * Tests adding JavaScript files with additional attributes. */ function testAttributes() { $default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0'; drupal_add_library('system', 'drupal'); drupal_add_js('http://example.com/script.js', array('attributes' => array('defer' => 'defer'))); drupal_add_js('core/misc/collapse.js', array('attributes' => array('defer' => 'defer'))); $javascript = drupal_get_js(); $expected_1 = ''; $expected_2 = ''; $this->assertTrue(strpos($javascript, $expected_1) > 0, 'Rendered external JavaScript with correct defer attribute.'); $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered internal JavaScript with correct defer attribute.'); } /** * Tests that attributes are maintained when JS aggregation is enabled. */ function testAggregatedAttributes() { // Enable aggregation. \Drupal::config('system.performance')->set('js.preprocess', 1)->save(); $default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0'; drupal_add_library('system', 'drupal'); drupal_add_js('http://example.com/script.js', array('attributes' => array('defer' => 'defer'))); drupal_add_js('core/misc/collapse.js', array('attributes' => array('defer' => 'defer'))); $javascript = drupal_get_js(); $expected_1 = ''; $expected_2 = ''; $this->assertTrue(strpos($javascript, $expected_1) > 0, 'Rendered external JavaScript with correct defer attribute with aggregation enabled.'); $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered internal JavaScript with correct defer attribute with aggregation enabled.'); } /** * Tests drupal_get_js() for JavaScript settings. */ function testHeaderSetting() { drupal_add_library('system', 'drupalSettings'); $javascript = drupal_get_js('header'); $this->assertTrue(strpos($javascript, 'basePath') > 0, 'Rendered JavaScript header returns basePath setting.'); $this->assertTrue(strpos($javascript, 'scriptPath') > 0, 'Rendered JavaScript header returns scriptPath setting.'); $this->assertTrue(strpos($javascript, 'pathPrefix') > 0, 'Rendered JavaScript header returns pathPrefix setting.'); $this->assertTrue(strpos($javascript, 'currentPath') > 0, 'Rendered JavaScript header returns currentPath setting.'); // Only the second of these two entries should appear in drupalSettings. drupal_add_js(array('commonTest' => 'commonTestShouldNotAppear'), 'setting'); drupal_add_js(array('commonTest' => 'commonTestShouldAppear'), 'setting'); // Only the second of these entries should appear in drupalSettings. drupal_add_js(array('commonTestJsArrayLiteral' => array('commonTestJsArrayLiteralOldValue')), 'setting'); drupal_add_js(array('commonTestJsArrayLiteral' => array('commonTestJsArrayLiteralNewValue')), 'setting'); // Only the second of these two entries should appear in drupalSettings. drupal_add_js(array('commonTestJsObjectLiteral' => array('key' => 'commonTestJsObjectLiteralOldValue')), 'setting'); drupal_add_js(array('commonTestJsObjectLiteral' => array('key' => 'commonTestJsObjectLiteralNewValue')), 'setting'); // Real world test case: multiple elements in a render array are adding the // same (or nearly the same) JavaScript settings. When merged, they should // contain all settings and not duplicate some settings. $settings_one = array('moduleName' => array('ui' => array('button A', 'button B'), 'magical flag' => 3.14159265359)); drupal_add_js(array('commonTestRealWorldIdentical' => $settings_one), 'setting'); drupal_add_js(array('commonTestRealWorldIdentical' => $settings_one), 'setting'); $settings_two = array('moduleName' => array('ui' => array('button A', 'button B'), 'magical flag' => 3.14159265359, 'thingiesOnPage' => array('id1' => array()))); drupal_add_js(array('commonTestRealWorldAlmostIdentical' => $settings_two), 'setting'); $settings_two = array('moduleName' => array('ui' => array('button C', 'button D'), 'magical flag' => 3.14, 'thingiesOnPage' => array('id2' => array()))); drupal_add_js(array('commonTestRealWorldAlmostIdentical' => $settings_two), 'setting'); $javascript = drupal_get_js('header'); // 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 and override a JavaScript // array literal (an indexed PHP array) values. $array_override = strpos($javascript, 'commonTestJsArrayLiteralNewValue') > 0 && strpos($javascript, 'commonTestJsArrayLiteralOldValue') === FALSE; $this->assertTrue($array_override, 'drupal_add_js() correctly overrides settings within an array literal (indexed array).'); // Test whether drupal_add_js can be used to add and override a JavaScript // object literal (an associate PHP array) values. $associative_array_override = strpos($javascript, 'commonTestJsObjectLiteralNewValue') > 0 && strpos($javascript, 'commonTestJsObjectLiteralOldValue') === FALSE; $this->assertTrue($associative_array_override, 'drupal_add_js() correctly overrides settings within an object literal (associative array).'); // Parse the generated drupalSettings ' . "\n"; $this->assertTrue(strpos($javascript, $expected_1) > 0, 'Rendered JavaScript within downlevel-hidden conditional comments.'); $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered JavaScript within downlevel-revealed conditional comments.'); } /** * Tests JavaScript versioning. */ function testVersionQueryString() { drupal_add_library('system', 'drupal'); drupal_add_js('core/misc/collapse.js', array('version' => 'foo')); drupal_add_js('core/misc/ajax.js', array('version' => 'bar')); $javascript = drupal_get_js(); $this->assertTrue(strpos($javascript, 'core/misc/collapse.js?v=foo') > 0 && strpos($javascript, 'core/misc/ajax.js?v=bar') > 0 , 'JavaScript version identifiers correctly appended to URLs'); } /** * Tests JavaScript grouping and aggregation. */ function testAggregation() { $default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0'; // To optimize aggregation, items with the 'every_page' option are ordered // ahead of ones without. The order of JavaScript execution must be the // same regardless of whether aggregation is enabled, so ensure this // expected order, first with aggregation off. drupal_add_library('system', 'drupal'); drupal_add_js('core/misc/ajax.js'); drupal_add_js('core/misc/collapse.js', array('every_page' => TRUE)); drupal_add_js('core/misc/autocomplete.js'); drupal_add_js('core/misc/batch.js', array('every_page' => TRUE)); $javascript = drupal_get_js(); $expected = implode("\n", array( '', '', '', '', )); $this->assertTrue(strpos($javascript, $expected) > 0, 'Unaggregated JavaScript is added in the expected group order.'); // Now ensure that with aggregation on, one file is made for the // 'every_page' files, and one file is made for the others. drupal_static_reset('drupal_add_js'); $config = \Drupal::config('system.performance'); $config->set('js.preprocess', 1); $config->save(); drupal_add_library('system', 'drupal'); drupal_add_js('core/misc/ajax.js'); drupal_add_js('core/misc/collapse.js', array('every_page' => TRUE)); drupal_add_js('core/misc/autocomplete.js'); drupal_add_js('core/misc/batch.js', array('every_page' => TRUE)); $js_items = drupal_add_js(); $javascript = drupal_get_js(); $expected = implode("\n", array( '', '', )); $this->assertTrue(strpos($javascript, $expected) !== FALSE, 'JavaScript is aggregated in the expected groups and order.'); } /** * Tests JavaScript aggregation when files are added to a different scope. */ function testAggregationOrder() { // Enable JavaScript aggregation. \Drupal::config('system.performance')->set('js.preprocess', 1)->save(); drupal_static_reset('drupal_add_js'); // Add two JavaScript files to the current request and build the cache. drupal_add_library('system', 'drupal'); drupal_add_js('core/misc/ajax.js'); drupal_add_js('core/misc/autocomplete.js'); $js_items = drupal_add_js(); $scripts_html = array( '#type' => 'scripts', '#items' => array( 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] ) ); drupal_render($scripts_html); // Store the expected key for the first item in the cache. $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array()); $expected_key = $cache[0]; // Reset variables and add a file in a different scope first. \Drupal::state()->delete('system.js_cache_files'); drupal_static_reset('drupal_add_js'); drupal_add_library('system', 'drupal'); drupal_add_js('some/custom/javascript_file.js', array('scope' => 'footer')); drupal_add_js('core/misc/ajax.js'); drupal_add_js('core/misc/autocomplete.js'); // Rebuild the cache. $js_items = drupal_add_js(); $scripts_html = array( '#type' => 'scripts', '#items' => array( 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] ) ); drupal_render($scripts_html); // Compare the expected key for the first file to the current one. $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array()); $key = $cache[0]; $this->assertEqual($key, $expected_key, 'JavaScript aggregation is not affected by ordering in different scopes.'); } /** * Tests JavaScript ordering. */ function testRenderOrder() { // 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. "0 #1", "0 #2", "0 #3", "5 #1", "5 #2", ); // Retrieve the rendered JavaScript and test against the regex. $js = drupal_get_js('footer'); $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.'); } /** * Tests rendering the JavaScript with a file's weight above jQuery's. */ function testRenderDifferentWeight() { // 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_library('system', 'jquery'); drupal_add_js('core/misc/collapse.js', array('group' => JS_LIBRARY, 'every_page' => TRUE, 'weight' => -21)); $javascript = drupal_get_js(); $this->assertTrue(strpos($javascript, 'core/misc/collapse.js') < strpos($javascript, 'core/assets/vendor/jquery/jquery.js'), 'Rendering a JavaScript file above jQuery.'); } /** * Tests altering a JavaScript's weight via hook_js_alter(). * * @see simpletest_js_alter() */ function testAlter() { // Add both tableselect.js and simpletest.js, with a larger weight on SimpleTest. drupal_add_js('core/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. $javascript = drupal_get_js(); $this->assertTrue(strpos($javascript, 'simpletest.js') < strpos($javascript, 'core/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. */ function testLibraryRender() { $result = drupal_add_library('system', 'jquery.farbtastic'); $this->assertTrue($result !== FALSE, 'Library was added without errors.'); $scripts = drupal_get_js(); $styles = drupal_get_css(); $this->assertTrue(strpos($scripts, 'core/assets/vendor/farbtastic/farbtastic.js'), 'JavaScript of library was added to the page.'); $this->assertTrue(strpos($styles, 'core/assets/vendor/farbtastic/farbtastic.css'), 'Stylesheet of library was added to the page.'); drupal_add_library('common_test', 'shorthand.plugin'); $path = drupal_get_path('module', 'common_test') . '/js/shorthand.js?v=0.8.3.37'; $scripts = drupal_get_js(); $this->assertTrue(strpos($scripts, $path), 'JavaScript specified in hook_library_info() using shorthand format (without any options) was added to the page.'); $this->assertEqual(substr_count($scripts, 'shorthand.js'), 1, 'Shorthand JavaScript file only added once.'); } /** * Adds a JavaScript library to the page and alters it. * * @see common_test_library_info_alter() */ function testLibraryAlter() { // Verify that common_test altered the title of Farbtastic. $library = drupal_get_library('system', 'jquery.farbtastic'); $this->assertEqual($library['title'], 'Farbtastic: Altered Library', 'Registered libraries were altered.'); // common_test_library_info_alter() also added a dependency on jQuery Form. drupal_add_library('system', 'jquery.farbtastic'); $scripts = drupal_get_js(); $this->assertTrue(strpos($scripts, 'core/assets/vendor/jquery-form/jquery.form.js'), 'Altered library dependencies are added to the page.'); } /** * Tests that multiple modules can implement the same library. * * @see common_test_library_info() */ function testLibraryNameConflicts() { $farbtastic = drupal_get_library('common_test', 'jquery.farbtastic'); $this->assertEqual($farbtastic['title'], 'Custom Farbtastic Library', 'Alternative libraries can be added to the page.'); } /** * Tests non-existing libraries. */ function testLibraryUnknown() { $result = drupal_get_library('unknown', 'unknown'); $this->assertFalse($result, 'Unknown library returned FALSE.'); drupal_static_reset('drupal_get_library'); $result = drupal_add_library('unknown', 'unknown'); $this->assertFalse($result, 'Unknown library returned FALSE.'); $scripts = drupal_get_js(); $this->assertTrue(strpos($scripts, 'unknown') === FALSE, 'Unknown library was not added to the page.'); } /** * Tests the addition of libraries through the #attached['library'] property. */ function testAttachedLibrary() { $element['#attached']['library'][] = array('system', 'jquery.farbtastic'); drupal_render($element); $scripts = drupal_get_js(); $this->assertTrue(strpos($scripts, 'core/assets/vendor/farbtastic/farbtastic.js'), 'The attached_library property adds the additional libraries.'); } /** * Tests retrieval of libraries via drupal_get_library(). */ function testGetLibrary() { // Retrieve all libraries registered by a module. $libraries = drupal_get_library('common_test'); $this->assertTrue(isset($libraries['jquery.farbtastic']), 'Retrieved all module libraries.'); // Retrieve all libraries for a module not implementing hook_library_info(). // Note: This test installs language module. $libraries = drupal_get_library('dblog'); $this->assertEqual($libraries, array(), 'Retrieving libraries from a module not implementing hook_library_info() returns an emtpy array.'); // Retrieve a specific library by module and name. $farbtastic = drupal_get_library('common_test', 'jquery.farbtastic'); $this->assertEqual($farbtastic['version'], '5.3', 'Retrieved a single library.'); // Retrieve a non-existing library by module and name. $farbtastic = drupal_get_library('common_test', 'foo'); $this->assertIdentical($farbtastic, FALSE, 'Retrieving a non-existing library returns FALSE.'); } /** * Tests JavaScript files that have querystrings attached get added right. */ function testAddJsFileWithQueryString() { $js = drupal_get_path('module', 'node') . '/node.js'; drupal_add_js($js); $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0'; $scripts = drupal_get_js(); $this->assertTrue(strpos($scripts, $js . '?' . $query_string), 'Query string was appended correctly to JS.'); } /** * Calculates the aggregated file URI of a group of JavaScript assets. * * @param array $js_assets * A group of JavaScript assets. * @return string * A file URI. * * @see testAggregation() * @see testAggregationOrder() */ protected function calculateAggregateFilename($js_assets) { $data = ''; foreach ($js_assets as $js_asset) { $data .= file_get_contents($js_asset['data']) . ";\n"; } return file_create_url('public://js/js_' . Crypt::hashBase64($data) . '.js'); } }