Index: misc/drupal.js =================================================================== RCS file: /cvs/drupal/drupal/misc/drupal.js,v retrieving revision 1.65 diff -u -p -r1.65 drupal.js --- misc/drupal.js 10 Mar 2010 15:14:38 -0000 1.65 +++ misc/drupal.js 19 Apr 2010 05:38:35 -0000 @@ -1,6 +1,6 @@ // $Id: drupal.js,v 1.65 2010/03/10 15:14:38 dries Exp $ -var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'locale': {} }; +var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'locale': {}, 'testCases': {} }; // Allow other JavaScript libraries to use $. jQuery.noConflict(); Index: modules/simpletest/simpletest.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v retrieving revision 1.91 diff -u -p -r1.91 simpletest.module --- modules/simpletest/simpletest.module 30 Mar 2010 07:17:19 -0000 1.91 +++ modules/simpletest/simpletest.module 19 Apr 2010 05:38:35 -0000 @@ -42,6 +42,12 @@ function simpletest_menu() { 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, ); + $items['admin/config/development/testing/js'] = array( + 'title' => 'JavaScript', + 'page callback' => 'simpletest_javascript_tests', + 'access arguments' => array('administer unit tests'), + 'file' => 'simpletest.pages.inc', + ); $items['admin/config/development/testing/settings'] = array( 'title' => 'Settings', 'page callback' => 'drupal_get_form', Index: modules/simpletest/simpletest.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.pages.inc,v retrieving revision 1.28 diff -u -p -r1.28 simpletest.pages.inc --- modules/simpletest/simpletest.pages.inc 13 Apr 2010 15:23:03 -0000 1.28 +++ modules/simpletest/simpletest.pages.inc 19 Apr 2010 05:38:35 -0000 @@ -58,6 +58,29 @@ function simpletest_test_form($form) { } /** + * Run JavaScript tests. + */ +function simpletest_javascript_tests() { + $files = array(); + drupal_add_js(drupal_get_path('module', 'simpletest') . '/test.js'); + + foreach (module_list() as $module) { + $directory = drupal_get_path('module', $module); + $directories[$module] = $directory; + if (file_exists("$directory/$module.test.js") && is_file("$directory/$module.test.js")) { + drupal_add_js("$directory/$module.test.js"); + } + if (file_exists("$directory/tests") && is_dir("$directory/tests")) { + foreach (file_scan_directory("$directory/tests", '/.*?\.test\.js/') as $file) { + drupal_add_js($file->uri); + } + } + } + drupal_add_js(drupal_get_path('module', 'simpletest') . '/testrunner.js'); + return t('Testing...') . '
'; +} + +/** * Returns HTML for a test list generated by simpletest_test_form() into a table. * * @param $variables Index: modules/simpletest/test.js =================================================================== RCS file: modules/simpletest/test.js diff -N modules/simpletest/test.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/test.js 19 Apr 2010 05:38:35 -0000 @@ -0,0 +1,97 @@ +// $Id$ + +(function ($) { + +Drupal.testCase = function() {}; + +Drupal.testCase.prototype.messages = []; + +Drupal.testCase.prototype.iframe = function() { + // We need to re-establish a hold on the iframe object here. + iframeElement = $('#drupal-test-iframe').get(0); + + // Get the document object of the iframe window. + // See http://xkr.us/articles/dom/iframe-document/. + var iframeDocument = (iframeElement.contentWindow || iframeElement.contentDocument); + if (iframeDocument.document) { + iframeDocument = iframeDocument.document; + } + + this.iframe = iframeDocument; +} + +Drupal.testCase.prototype.drupalGet = function(path) { + this.iframe.location.replace(path); +}; + +Drupal.testCase.prototype.drupalPost = function(path, postdata, submit) { + this.iframe.location.replace(path); + $.each(postdata, function(key, value) { + var field = $('#edit-' + key.replace(/_/, '-')); + this.assertLength(field, 1, Drupal.t('Make sure the "@key" field was found.', {'@key': key})); + field.val(value); + this.assertEqual(field.val(), value, Drupal.t('Make sure the "@key" field could be set properly to "@value".', {'@key': key, '@value': value})); + }); + var submit = $('input:submit[value=' + submit + ']'); + this.assertLength(submit, 1, Drupal.t('Make sure the submit button was found.')); + submit.parents('form:first').submit(); +}; + +Drupal.testCase.prototype.assert = function(bool, message, group) { + if (group == null) { + group = Drupal.t('Other'); + } + this.messages.push({'result': bool, 'message': message, 'group': group}); +}; + +Drupal.testCase.prototype.assertLength = function(jq, length, message, group) { + if (message == null) { + message = Drupal.t('Make sure that exactly @count jquery objects matched the selector.', {'@count': length}); + } + this.assert(jq.length == length, message, group); +}; + +Drupal.testCase.prototype.assertTrue = function(result, message, group) { + if (message == null) { + message = Drupal.t('Make sure "@result" evaluates to true.', {'@result': result}); + } + this.assert(result == true, message, group); +}; + +Drupal.testCase.prototype.assertFalse = function(result, message, group) { + if (message == null) { + message = Drupal.t('Make sure "@result" evaluates to false.', {'@result': result}); + } + this.assert(result == false, message, group); +}; + +Drupal.testCase.prototype.assertNull = function(result, message, group) { + if (message == null) { + message = Drupal.t('Make sure "@result" is null.', {'@result': result}); + } + this.assert(typeof result == 'null', message, group); +}; + +Drupal.testCase.prototype.assertNotNull = function(result, message, group) { + if (message == null) { + message = Drupal.t('Make sure "@result" is not null.', {'@result': result}); + } + this.assert(typeof result != 'null', message, group); +}; + +Drupal.testCase.prototype.assertEqual = function(first, second, message, group) { + if (message == null) { + message = Drupal.t('Make sure %first is equal to %second.', {'%first': first, '%second': second}); + } + this.assert(first == second, message, group); +}; + +Drupal.testCase.prototype.assertNotEqual = function(first, second, message, group) { + if (message == null) { + message = Drupal.t('Make sure %first is not equal to %second.', {'%first': first, '%second': second}); + } + this.assert(first != second, message, group); + +}; + +})(jQuery); Index: modules/simpletest/testrunner.js =================================================================== RCS file: modules/simpletest/testrunner.js diff -N modules/simpletest/testrunner.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/testrunner.js 19 Apr 2010 05:38:35 -0000 @@ -0,0 +1,45 @@ +// $Id$ + +(function($) { + +Drupal.behaviors.runTests = { + 'attach': function(context, settings) { + if (!this.ran) { + var self = this; + this.ran = true; + $.each(Drupal.testCases, function(key, testCase) { + $('#drupal-test-iframe').remove(); + $('body').append($('').load(function() { + $('iframe').css({'width': '1px', 'height': '1px'}); + if (testCase.dependencies != null) { + $.each(testCase.dependencies, function(index, dependency) { + if (index < testCase.dependencies.length - 1) { + $.getScript(settings.basePath + dependency); + } + }); + $.getScript(settings.basePath + testCase.dependencies[testCase.dependencies.length - 1], function() { + self.test(testCase); + }); + } + else { + self.test(testCase); + } + })); + }); + } + }, + 'test': function(testCase) { + testCase.iframe(); + if (typeof testCase.setUp == 'function') { + testCase.setUp(); + } + testCase.run(); + if (typeof testCase.tearDown == 'function') { + testCase.tearDown(); + } + console.log(testCase.messages); + }, + 'ran': false +}; + +})(jQuery); Index: modules/simpletest/tests/drupal.test.js =================================================================== RCS file: modules/simpletest/tests/drupal.test.js diff -N modules/simpletest/tests/drupal.test.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/tests/drupal.test.js 19 Apr 2010 05:38:35 -0000 @@ -0,0 +1,140 @@ +// $Id$ + +/** + * @file + * Tests for drupal.js + */ + +(function($) { + +/** + * Test the Drupal.checkPlain function. + */ +Drupal.testCases.checkPlain = new Drupal.testCase(); + +Drupal.testCases.checkPlain.getInfo = function() { + return { + name: 'Check plain', + description: 'Tests the Drupal.checkPlain() JavaScript function for properly escaping HTML.', + group: 'System' + }; +}; + +Drupal.testCases.checkPlain.run = function() { + // Test basic strings. + this.assertEqual(Drupal.checkPlain('test'), 'test', Drupal.t("Nothing gets replaced that doesn't need to be replaced with their escaped equivalent.")); + this.assertEqual(Drupal.checkPlain('"test'), '"test', Drupal.t('Quotes are replaced with their escaped equivalent.')); + this.assertEqual(Drupal.checkPlain('Test&1'), 'Test&1', Drupal.t('Ampersands are replaced with their escaped equivalent.')); + this.assertEqual(Drupal.checkPlain('Test>test'), 'Test>test', Drupal.t('Greater-than signs are replaced with their escaped equivalent.')); + this.assertEqual(Drupal.checkPlain('TestStuff'), '<tagname property="value">Stuff</tagname>', Drupal.t('Full HTML tags are replaced with their escaped equivalent.')); + this.assertEqual(Drupal.checkPlain('Test "&".'), 'Test "&".', Drupal.t('A string with both quotes and ampersands replaces those entities with their escaped equivalents.')); +}; + +/** + * Tests Drupal.t(). + */ +Drupal.testCases.t = new Drupal.testCase(); + +Drupal.testCases.t.getInfo = function() { + return { + name: Drupal.t('Translation'), + description: Drupal.t('Tests the basic translation functionality of the Drupal.t() function, including the proper handling of variable strings.'), + group: Drupal.t('System') + }; +}; + +Drupal.testCases.t.setUp = function() { + this.originalLocale = Drupal.locale; + Drupal.locale = { + 'strings': { + 'Translation 1': '1 noitalsnarT', + 'Translation with a @placeholder': '@placeholder a with Translation', + 'Translation with another %placeholder': '%placeholder in another translation', + 'Literal !placeholder': 'A literal !placeholder', + 'Test unspecified placeholder': 'Unspecified placeholder test' + } + }; +}; + +Drupal.testCases.t.tearDown = function() { + Drupal.locale = this.originalLocale; +}; + +Drupal.testCases.t.run = function() { + var html = 'Apples & Oranges'; + var escaped = '<tag attribute="value">Apples & Oranges</tag>'; + + // Test placeholders. + this.assertEqual(Drupal.t('Hello world! @html', {'@html': html}), 'Hello world! ' + escaped, Drupal.t('The "@" placeholder escapes the variable.')); + this.assertEqual(Drupal.t('Hello world! %html', {'%html': html}), 'Hello world! ' + escaped + '', Drupal.t('The "%" placeholder escapes the variable and themes it as a placeholder.')); + this.assertEqual(Drupal.t('Hello world! !html', {'!html': html}), 'Hello world! ' + html, Drupal.t('The "!" placeholder passes the variable through as-is.')); + this.assertEqual(Drupal.t('Hello world! html', {'html': html}), 'Hello world! ' + escaped + '', Drupal.t('Other placeholders act as "%" placeholders do.')); + + // Test actual translations. + this.assertEqual(Drupal.t('Translation 1'), '1 noitalsnarT', Drupal.t('Basic translations work.')); + this.assertEqual(Drupal.t('Translation with a @placeholder', {'@placeholder': ''}), '<script>alert("xss")</script> a with Translation', Drupal.t('Translations with the "@" placeholder work.')); + this.assertEqual(Drupal.t('Translation with another %placeholder', {'%placeholder': ''}), '<script>alert("xss")</script> in another translation', Drupal.t('Translations with the "%" placeholder work.')); + this.assertEqual(Drupal.t('Literal !placeholder', {'!placeholder': ''}), 'A literal ', Drupal.t('Translations with the "!" placeholder work.')); + this.assertEqual(Drupal.t('Test unspecified placeholder', {'placeholder': ''}), 'Unspecified <script>alert("xss")</script> test', Drupal.t('Translations with unspecified placeholders work.')); +}; + +/** + * Tests Drupal.attachBehaviors() and Drupal.detachBehaviors(). + */ +Drupal.testCases.behaviors = new Drupal.testCase(); + +Drupal.testCases.behaviors.getInfo = function() { + return { + name: 'JavaScript behaviors', + description: 'Tests the functionality of Drupal behaviors to make sure it allows JavaScript files to attach and detach behaviors in different contexts.', + group: 'System' + }; +}; + +Drupal.testCases.behaviors.setUp = function() { + this.originalBehaviors = Drupal.behaviors; + var attachIndex = 0; + var detachIndex = 0; + var self = this; + Drupal.behaviors = { + testBehavior: { + attach: function(context, settings) { + attachIndex++; + self.assertEqual(context, 'Attach context ' + attachIndex, Drupal.t('Attach context matches passed context.')); + self.assertEqual(settings, 'Attach settings ' + attachIndex, Drupal.t('Attach settings match passed settings.')); + }, + detach: function(context, settings) { + detachIndex++; + self.assertEqual(context, 'Detach context ' + detachIndex, Drupal.t('Detach context matches passed context.')); + self.assertEqual(settings, 'Detach settings ' + detachIndex, Drupal.t('Detach settings match passed settings.')); + } + } + }; +}; + +Drupal.testCases.behaviors.tearDown = function() { + Drupal.behaviors = this.originalBehaviors; +}; + +Drupal.testCases.behaviors.run = function() { + // Test attaching behaviors. + Drupal.attachBehaviors('Attach context 1', 'Attach settings 1'); + + // Test attaching behaviors again. + Drupal.attachBehaviors('Attach context 2', 'Attach settings 2'); + + // Test detaching behaviors. + Drupal.detachBehaviors('Detach context 1', 'Detach settings 1'); + + // Try detaching behaviors again. + Drupal.detachBehaviors('Detach context 2', 'Detach settings 2'); +}; + +})(jQuery);