Switch to UnittestJS.

This commit is contained in:
Tobie Langel 2008-12-11 18:01:31 +01:00
parent 1d617df4df
commit 52a781ae2a
11 changed files with 104 additions and 1239 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "vendor/unittest_js"]
path = vendor/unittest_js
url = git://github.com/tobie/unittest_js.git

View File

@ -45,6 +45,9 @@ From the root Prototype directory,
Check out the Prototype source with
$ git clone git://github.com/sstephenson/prototype.git
$ cd prototype
$ git submodule init
$ git submodule update
Find out how to contribute:
http://prototypejs.org/contribute

116
Rakefile
View File

@ -1,13 +1,14 @@
require 'rake'
require 'rake/packagetask'
PROTOTYPE_ROOT = File.expand_path(File.dirname(__FILE__))
PROTOTYPE_SRC_DIR = File.join(PROTOTYPE_ROOT, 'src')
PROTOTYPE_DIST_DIR = File.join(PROTOTYPE_ROOT, 'dist')
PROTOTYPE_PKG_DIR = File.join(PROTOTYPE_ROOT, 'pkg')
PROTOTYPE_TEST_DIR = File.join(PROTOTYPE_ROOT, 'test')
PROTOTYPE_TMP_DIR = File.join(PROTOTYPE_TEST_DIR, 'unit', 'tmp')
PROTOTYPE_VERSION = '1.6.0.3'
PROTOTYPE_ROOT = File.expand_path(File.dirname(__FILE__))
PROTOTYPE_SRC_DIR = File.join(PROTOTYPE_ROOT, 'src')
PROTOTYPE_DIST_DIR = File.join(PROTOTYPE_ROOT, 'dist')
PROTOTYPE_PKG_DIR = File.join(PROTOTYPE_ROOT, 'pkg')
PROTOTYPE_TEST_DIR = File.join(PROTOTYPE_ROOT, 'test')
PROTOTYPE_TEST_UNIT_DIR = File.join(PROTOTYPE_TEST_DIR, 'unit')
PROTOTYPE_TMP_DIR = File.join(PROTOTYPE_TEST_UNIT_DIR, 'tmp')
PROTOTYPE_VERSION = '1.6.0.3'
task :default => [:dist, :dist_helper, :package, :clean_package_source]
@ -47,46 +48,71 @@ Rake::PackageTask.new('prototype', PROTOTYPE_VERSION) do |package|
)
end
desc "Builds the distribution and the test suite, runs the tests and collects their results."
task :test => [:dist, :test_units]
require 'test/lib/jstest'
desc "Runs all the JavaScript unit tests and collects the results"
JavaScriptTestTask.new(:test_units => [:build_unit_tests]) do |t|
testcases = ENV['TESTCASES']
tests_to_run = ENV['TESTS'] && ENV['TESTS'].split(',')
browsers_to_test = ENV['BROWSERS'] && ENV['BROWSERS'].split(',')
t.mount("/dist")
t.mount("/test")
Dir.mkdir(PROTOTYPE_TMP_DIR) unless File.exist?(PROTOTYPE_TMP_DIR)
Dir["test/unit/tmp/*_test.html"].each do |file|
test_name = File.basename(file).sub("_test.html", "")
unless tests_to_run && !tests_to_run.include?(test_name)
t.run("/#{file}", testcases)
end
end
%w( safari firefox ie konqueror opera chrome ).each do |browser|
t.browser(browser.to_sym) unless browsers_to_test && !browsers_to_test.include?(browser)
end
end
task :build_unit_tests do
Dir[File.join('test', 'unit', '*_test.js')].each do |file|
PageBuilder.new(file, 'prototype.erb').render
end
end
task :clean_package_source do
rm_rf File.join(PROTOTYPE_PKG_DIR, "prototype-#{PROTOTYPE_VERSION}")
end
desc 'Generates an empty tmp directory for building tests.'
task :clean_tmp do
puts 'Generating an empty tmp directory for building tests.'
FileUtils.rm_rf(PROTOTYPE_TMP_DIR) if File.exist?(PROTOTYPE_TMP_DIR)
Dir.mkdir(PROTOTYPE_TMP_DIR)
task :test => ['test:build', 'test:run']
namespace :test do
desc 'Runs all the JavaScript unit tests and collects the results'
task :run => [:require] do
testcases = ENV['TESTCASES']
browsers_to_test = ENV['BROWSERS'] && ENV['BROWSERS'].split(',')
tests_to_run = ENV['TESTS'] && ENV['TESTS'].split(',')
runner = UnittestJS::WEBrickRunner::Runner.new(:test_dir => PROTOTYPE_TMP_DIR)
Dir[File.join(PROTOTYPE_TMP_DIR, '*_test.html')].each do |file|
file = File.basename(file)
test = file.sub('_test.html', '')
unless tests_to_run && !tests_to_run.include?(test)
runner.add_test(file, testcases)
end
end
UnittestJS::Browser::SUPPORTED.each do |browser|
unless browsers_to_test && !browsers_to_test.include?(browser)
runner.add_browser(browser.to_sym)
end
end
trap('INT') { runner.teardown; exit }
runner.run
end
task :build => [:clean, :dist] do
builder = UnittestJS::Builder::SuiteBuilder.new({
:input_dir => PROTOTYPE_TEST_UNIT_DIR,
:assets_dir => PROTOTYPE_DIST_DIR
})
selected_tests = (ENV['TESTS'] || '').split(',')
builder.collect(*selected_tests)
builder.render
end
task :clean => [:require] do
UnittestJS::Builder.empty_dir!(PROTOTYPE_TMP_DIR)
end
task :require do
lib = 'vendor/unittest_js/lib/unittest_js'
unless File.exists?(lib)
puts "\nYou'll need UnittestJS to run the tests. Just run:\n\n"
puts " $ git submodule init"
puts " $ git submodule update"
puts "\nand you should be all set.\n\n"
end
require lib
end
end
task :test_units do
puts '"rake test_units" is deprecated. Please use "rake test" instead.'
end
task :build_unit_tests do
puts '"rake test_units" is deprecated. Please use "rake test:build" instead.'
end
task :clean_tmp do
puts '"rake clean_tmp" is deprecated. Please use "rake test:clean" instead.'
end

View File

@ -1,50 +0,0 @@
body, div, p, h1, h2, h3, ul, ol, span, a, table, td, form, img, li {
font-family: sans-serif;
}
body {
font-size:0.8em;
}
#log {
padding-bottom: 1em;
border-bottom: 2px solid #000;
margin-bottom: 2em;
}
.logsummary {
margin-top: 1em;
margin-bottom: 1em;
padding: 1ex;
border: 1px solid #000;
font-weight: bold;
}
.logtable {
width:100%;
border-collapse: collapse;
border: 1px dotted #666;
}
.logtable td, .logtable th {
text-align: left;
padding: 3px 8px;
border: 1px dotted #666;
}
.logtable .passed {
background-color: #cfc;
}
.logtable .failed, .logtable .error {
background-color: #fcc;
}
.logtable td div.action_buttons {
display: inline;
}
.logtable td div.action_buttons input {
margin: 0 5px;
font-size: 10px;
}

View File

@ -1,562 +0,0 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// experimental, Firefox-only
Event.simulateMouse = function(element, eventName) {
var options = Object.extend({
pointerX: 0,
pointerY: 0,
buttons: 0
}, arguments[2] || {});
var oEvent = document.createEvent("MouseEvents");
oEvent.initMouseEvent(eventName, true, true, document.defaultView,
options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
false, false, false, false, 0, $(element));
if(this.mark) Element.remove(this.mark);
var style = 'position: absolute; width: 5px; height: 5px;' +
'top: #{pointerY}px; left: #{pointerX}px;'.interpolate(options) +
'border-top: 1px solid red; border-left: 1px solid red;'
this.mark = new Element('div', { style: style });
this.mark.appendChild(document.createTextNode(" "));
document.body.appendChild(this.mark);
if(this.step)
alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
$(element).dispatchEvent(oEvent);
};
// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
// You need to downgrade to 1.0.4 for now to get this working
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
Event.simulateKey = function(element, eventName) {
var options = Object.extend({
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
keyCode: 0,
charCode: 0
}, arguments[2] || {});
var oEvent = document.createEvent("KeyEvents");
oEvent.initKeyEvent(eventName, true, true, window,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
options.keyCode, options.charCode );
$(element).dispatchEvent(oEvent);
};
Event.simulateKeys = function(element, command) {
for(var i=0; i<command.length; i++) {
Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
}
};
var Test = {
Unit: {
inspect: Object.inspect // security exception workaround
}
};
Test.Unit.Logger = Class.create({
initialize: function(element) {
this.element = $(element);
if (this.element) this._createLogTable();
this.tbody = $(this.element.getElementsByTagName('tbody')[0]);
},
start: function(testName) {
if (!this.element) return;
this.tbody.insert('<tr><td>' + testName + '</td><td></td><td></td></tr>');
},
setStatus: function(status) {
this.getLastLogLine().addClassName(status);
$(this.getLastLogLine().getElementsByTagName('td')[1]).update(status);
},
finish: function(status, summary) {
if (!this.element) return;
this.setStatus(status);
this.message(summary);
},
message: function(message) {
if (!this.element) return;
this.getMessageCell().update(this._toHTML(message));
},
summary: function(summary) {
if (!this.element) return;
var div = $(this.element.getElementsByTagName('div')[0]);
div.update(this._toHTML(summary));
},
getLastLogLine: function() {
//return this.element.descendants('tr').last();
var trs = this.element.getElementsByTagName('tr');
return $(trs[trs.length - 1]);
},
getMessageCell: function() {
return this.getLastLogLine().down('td', 2);
var tds = this.getLastLogLine().getElementsByTagName('td');
return $(tds[2]);
},
_createLogTable: function() {
var html = '<div class="logsummary">running...</div>' +
'<table class="logtable">' +
'<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
'<tbody class="loglines"></tbody>' +
'</table>';
this.element.update(html);
},
appendActionButtons: function(actions) {
actions = $H(actions);
if (!actions.any()) return;
var div = new Element("div", {className: 'action_buttons'});
actions.inject(div, function(container, action) {
var button = new Element("input").setValue(action.key).observe("click", action.value);
button.type = "button";
return container.insert(button);
});
this.getMessageCell().insert(div);
},
_toHTML: function(txt) {
return txt.escapeHTML().replace(/\n/g,"<br />");
}
});
Test.Unit.Runner = Class.create({
initialize: function(testcases) {
var options = this.options = Object.extend({
testLog: 'testlog'
}, arguments[1] || {});
options.resultsURL = this.queryParams.resultsURL;
this.tests = this.getTests(testcases);
this.currentTest = 0;
Event.observe(window, "load", function() {
this.logger = new Test.Unit.Logger($(options.testLog));
this.runTests.bind(this).delay(0.1);
}.bind(this));
},
queryParams: window.location.search.parseQuery(),
getTests: function(testcases) {
var tests, options = this.options;
if (this.queryParams.tests) tests = this.queryParams.tests.split(',');
else if (options.tests) tests = options.tests;
else if (options.test) tests = [option.test];
else tests = Object.keys(testcases).grep(/^test/);
return tests.map(function(test) {
if (testcases[test])
return new Test.Unit.Testcase(test, testcases[test], testcases.setup, testcases.teardown);
}).compact();
},
getResult: function() {
var results = {
tests: this.tests.length,
assertions: 0,
failures: 0,
errors: 0
};
return this.tests.inject(results, function(results, test) {
results.assertions += test.assertions;
results.failures += test.failures;
results.errors += test.errors;
return results;
});
},
postResults: function() {
if (this.options.resultsURL) {
new Ajax.Request(this.options.resultsURL,
{ method: 'get', parameters: this.getResult(), asynchronous: false });
}
},
runTests: function() {
var test = this.tests[this.currentTest], actions;
if (!test) return this.finish();
if (!test.isWaiting) this.logger.start(test.name);
test.run();
if(test.isWaiting) {
this.logger.message("Waiting for " + test.timeToWait + "ms");
setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
return;
}
this.logger.finish(test.status(), test.summary());
if (actions = test.actions) this.logger.appendActionButtons(actions);
this.currentTest++;
// tail recursive, hopefully the browser will skip the stackframe
this.runTests();
},
finish: function() {
this.postResults();
this.logger.summary(this.summary());
},
summary: function() {
return '#{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors'
.interpolate(this.getResult());
}
});
Test.Unit.MessageTemplate = Class.create({
initialize: function(string) {
var parts = [];
(string || '').scan(/(?=[^\\])\?|(?:\\\?|[^\?])+/, function(part) {
parts.push(part[0]);
});
this.parts = parts;
},
evaluate: function(params) {
return this.parts.map(function(part) {
return part == '?' ? Test.Unit.inspect(params.shift()) : part.replace(/\\\?/, '?');
}).join('');
}
});
Test.Unit.Assertions = {
buildMessage: function(message, template) {
var args = $A(arguments).slice(2);
return (message ? message + '\n' : '') + new Test.Unit.MessageTemplate(template).evaluate(args);
},
flunk: function(message) {
this.assertBlock(message || 'Flunked', function() { return false });
},
assertBlock: function(message, block) {
try {
block.call(this) ? this.pass() : this.fail(message);
} catch(e) { this.error(e) }
},
assert: function(expression, message) {
message = this.buildMessage(message || 'assert', 'got <?>', expression);
this.assertBlock(message, function() { return expression });
},
assertEqual: function(expected, actual, message) {
message = this.buildMessage(message || 'assertEqual', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() { return expected == actual });
},
assertNotEqual: function(expected, actual, message) {
message = this.buildMessage(message || 'assertNotEqual', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() { return expected != actual });
},
assertEnumEqual: function(expected, actual, message) {
expected = $A(expected);
actual = $A(actual);
message = this.buildMessage(message || 'assertEnumEqual', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() {
return expected.length == actual.length && expected.zip(actual).all(function(pair) { return pair[0] == pair[1] });
});
},
assertEnumNotEqual: function(expected, actual, message) {
expected = $A(expected);
actual = $A(actual);
message = this.buildMessage(message || 'assertEnumNotEqual', '<?> was the same as <?>', expected, actual);
this.assertBlock(message, function() {
return expected.length != actual.length || expected.zip(actual).any(function(pair) { return pair[0] != pair[1] });
});
},
assertHashEqual: function(expected, actual, message) {
expected = $H(expected);
actual = $H(actual);
var expected_array = expected.toArray().sort(), actual_array = actual.toArray().sort();
message = this.buildMessage(message || 'assertHashEqual', 'expected <?>, actual: <?>', expected, actual);
// from now we recursively zip & compare nested arrays
var block = function() {
return expected_array.length == actual_array.length &&
expected_array.zip(actual_array).all(function(pair) {
return pair.all(Object.isArray) ?
pair[0].zip(pair[1]).all(arguments.callee) : pair[0] == pair[1];
});
};
this.assertBlock(message, block);
},
assertHashNotEqual: function(expected, actual, message) {
expected = $H(expected);
actual = $H(actual);
var expected_array = expected.toArray().sort(), actual_array = actual.toArray().sort();
message = this.buildMessage(message || 'assertHashNotEqual', '<?> was the same as <?>', expected, actual);
// from now we recursively zip & compare nested arrays
var block = function() {
return !(expected_array.length == actual_array.length &&
expected_array.zip(actual_array).all(function(pair) {
return pair.all(Object.isArray) ?
pair[0].zip(pair[1]).all(arguments.callee) : pair[0] == pair[1];
}));
};
this.assertBlock(message, block);
},
assertIdentical: function(expected, actual, message) {
message = this.buildMessage(message || 'assertIdentical', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() { return expected === actual });
},
assertNotIdentical: function(expected, actual, message) {
message = this.buildMessage(message || 'assertNotIdentical', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() { return expected !== actual });
},
assertNull: function(obj, message) {
message = this.buildMessage(message || 'assertNull', 'got <?>', obj);
this.assertBlock(message, function() { return obj === null });
},
assertNotNull: function(obj, message) {
message = this.buildMessage(message || 'assertNotNull', 'got <?>', obj);
this.assertBlock(message, function() { return obj !== null });
},
assertUndefined: function(obj, message) {
message = this.buildMessage(message || 'assertUndefined', 'got <?>', obj);
this.assertBlock(message, function() { return typeof obj == "undefined" });
},
assertNotUndefined: function(obj, message) {
message = this.buildMessage(message || 'assertNotUndefined', 'got <?>', obj);
this.assertBlock(message, function() { return typeof obj != "undefined" });
},
assertNullOrUndefined: function(obj, message) {
message = this.buildMessage(message || 'assertNullOrUndefined', 'got <?>', obj);
this.assertBlock(message, function() { return obj == null });
},
assertNotNullOrUndefined: function(obj, message) {
message = this.buildMessage(message || 'assertNotNullOrUndefined', 'got <?>', obj);
this.assertBlock(message, function() { return obj != null });
},
assertMatch: function(expected, actual, message) {
message = this.buildMessage(message || 'assertMatch', 'regex <?> did not match <?>', expected, actual);
this.assertBlock(message, function() { return new RegExp(expected).exec(actual) });
},
assertNoMatch: function(expected, actual, message) {
message = this.buildMessage(message || 'assertNoMatch', 'regex <?> matched <?>', expected, actual);
this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)) });
},
assertHidden: function(element, message) {
message = this.buildMessage(message || 'assertHidden', '? isn\'t hidden.', element);
this.assertBlock(message, function() { return element.style.display == 'none' });
},
assertInstanceOf: function(expected, actual, message) {
message = this.buildMessage(message || 'assertInstanceOf', '<?> was not an instance of the expected type', actual);
this.assertBlock(message, function() { return actual instanceof expected });
},
assertNotInstanceOf: function(expected, actual, message) {
message = this.buildMessage(message || 'assertNotInstanceOf', '<?> was an instance of the expected type', actual);
this.assertBlock(message, function() { return !(actual instanceof expected) });
},
assertRespondsTo: function(method, obj, message) {
message = this.buildMessage(message || 'assertRespondsTo', 'object doesn\'t respond to <?>', method);
this.assertBlock(message, function() { return (method in obj && typeof obj[method] == 'function') });
},
assertRaise: function(exceptionName, method, message) {
message = this.buildMessage(message || 'assertRaise', '<?> exception expected but none was raised', exceptionName);
var block = function() {
try {
method();
return false;
} catch(e) {
if (e.name == exceptionName) return true;
else throw e;
}
};
this.assertBlock(message, block);
},
assertNothingRaised: function(method, message) {
try {
method();
this.assert(true, "Expected nothing to be thrown");
} catch(e) {
message = this.buildMessage(message || 'assertNothingRaised', '<?> was thrown when nothing was expected.', e);
this.flunk(message);
}
},
_isVisible: function(element) {
element = $(element);
if(!element.parentNode) return true;
this.assertNotNull(element);
if(element.style && Element.getStyle(element, 'display') == 'none')
return false;
return arguments.callee.call(this, element.parentNode);
},
assertVisible: function(element, message) {
message = this.buildMessage(message, '? was not visible.', element);
this.assertBlock(message, function() { return this._isVisible(element) });
},
assertNotVisible: function(element, message) {
message = this.buildMessage(message, '? was not hidden and didn\'t have a hidden parent either.', element);
this.assertBlock(message, function() { return !this._isVisible(element) });
},
assertElementsMatch: function() {
var message, pass = true, expressions = $A(arguments), elements = $A(expressions.shift());
if (elements.length != expressions.length) {
message = this.buildMessage('assertElementsMatch', 'size mismatch: ? elements, ? expressions (?).', elements.length, expressions.length, expressions);
this.flunk(message);
pass = false;
}
elements.zip(expressions).all(function(pair, index) {
var element = $(pair.first()), expression = pair.last();
if (element.match(expression)) return true;
message = this.buildMessage('assertElementsMatch', 'In index <?>: expected <?> but got ?', index, expression, element);
this.flunk(message);
pass = false;
}.bind(this))
if (pass) this.assert(true, "Expected all elements to match.");
},
assertElementMatches: function(element, expression, message) {
this.assertElementsMatch([element], expression);
}
};
Test.Unit.Testcase = Class.create(Test.Unit.Assertions, {
initialize: function(name, test, setup, teardown) {
this.name = name;
this.test = test || Prototype.emptyFunction;
this.setup = setup || Prototype.emptyFunction;
this.teardown = teardown || Prototype.emptyFunction;
this.messages = [];
this.actions = {};
},
isWaiting: false,
timeToWait: 1000,
assertions: 0,
failures: 0,
errors: 0,
isRunningFromRake: window.location.port == 4711,
wait: function(time, nextPart) {
this.isWaiting = true;
this.test = nextPart;
this.timeToWait = time;
},
run: function(rethrow) {
try {
try {
if (!this.isWaiting) this.setup();
this.isWaiting = false;
this.test();
} finally {
if(!this.isWaiting) {
this.teardown();
}
}
}
catch(e) {
if (rethrow) throw e;
this.error(e, this);
}
},
summary: function() {
var msg = '#{assertions} assertions, #{failures} failures, #{errors} errors\n';
return msg.interpolate(this) + this.messages.join("\n");
},
pass: function() {
this.assertions++;
},
fail: function(message) {
this.failures++;
var line = "";
try {
throw new Error("stack");
} catch(e){
line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1];
}
this.messages.push("Failure: " + message + (line ? " Line #" + line : ""));
},
info: function(message) {
this.messages.push("Info: " + message);
},
error: function(error, test) {
this.errors++;
this.actions['retry with throw'] = function() { test.run(true) };
this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) + ")");
},
status: function() {
if (this.failures > 0) return 'failed';
if (this.errors > 0) return 'error';
return 'passed';
},
benchmark: function(operation, iterations) {
var startAt = new Date();
(iterations || 1).times(operation);
var timeTaken = ((new Date())-startAt);
this.info((arguments[2] || 'Operation') + ' finished ' +
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
return timeTaken;
}
});

View File

@ -1,504 +0,0 @@
require 'rake/tasklib'
require 'thread'
require 'webrick'
require 'fileutils'
include FileUtils
require 'erb'
class Browser
def supported?; true; end
def setup ; end
def open(url) ; end
def teardown ; end
def host
require 'rbconfig'
Config::CONFIG['host']
end
def macos?
host.include?('darwin')
end
def windows?
host.include?('mswin')
end
def linux?
host.include?('linux')
end
def applescript(script)
raise "Can't run AppleScript on #{host}" unless macos?
system "osascript -e '#{script}' 2>&1 >/dev/null"
end
end
class FirefoxBrowser < Browser
def initialize(path=File.join(ENV['ProgramFiles'] || 'c:\Program Files', '\Mozilla Firefox\firefox.exe'))
@path = path
end
def visit(url)
system("open -a Firefox '#{url}'") if macos?
system("#{@path} #{url}") if windows?
system("firefox #{url}") if linux?
end
def to_s
"Firefox"
end
end
class ChromeBrowser < Browser
def initialize(path = nil)
@path = path || File.join(
ENV['UserPath'] || "C:/Documents and Settings/Administrator",
"Local Settings",
"Application Data",
"Google",
"Chrome",
"Application",
"chrome.exe"
)
end
def supported?
windows?
end
def visit(url)
system("#{@path} #{url}")
end
def to_s
"Chrome"
end
end
class SafariBrowser < Browser
def supported?
macos?
end
def setup
applescript('tell application "Safari" to make new document')
end
def visit(url)
applescript('tell application "Safari" to set URL of front document to "' + url + '"')
end
def teardown
#applescript('tell application "Safari" to close front document')
end
def to_s
"Safari"
end
end
class IEBrowser < Browser
def setup
require 'win32ole' if windows?
end
def supported?
windows?
end
def visit(url)
if windows?
ie = WIN32OLE.new('InternetExplorer.Application')
ie.visible = true
ie.Navigate(url)
sleep 0.01 while ie.Busy || ie.ReadyState != 4
end
end
def to_s
"Internet Explorer"
end
end
class KonquerorBrowser < Browser
@@configDir = File.join((ENV['HOME'] || ''), '.kde', 'share', 'config')
@@globalConfig = File.join(@@configDir, 'kdeglobals')
@@konquerorConfig = File.join(@@configDir, 'konquerorrc')
def supported?
linux?
end
# Forces KDE's default browser to be Konqueror during the tests, and forces
# Konqueror to open external URL requests in new tabs instead of a new
# window.
def setup
cd @@configDir, :verbose => false do
copy @@globalConfig, "#{@@globalConfig}.bak", :preserve => true, :verbose => false
copy @@konquerorConfig, "#{@@konquerorConfig}.bak", :preserve => true, :verbose => false
# Too lazy to write it in Ruby... Is sed dependency so bad?
system "sed -ri /^BrowserApplication=/d '#{@@globalConfig}'"
system "sed -ri /^KonquerorTabforExternalURL=/s:false:true: '#{@@konquerorConfig}'"
end
end
def teardown
cd @@configDir, :verbose => false do
copy "#{@@globalConfig}.bak", @@globalConfig, :preserve => true, :verbose => false
copy "#{@@konquerorConfig}.bak", @@konquerorConfig, :preserve => true, :verbose => false
end
end
def visit(url)
system("kfmclient openURL #{url}")
end
def to_s
"Konqueror"
end
end
class OperaBrowser < Browser
def initialize(path='c:\Program Files\Opera\Opera.exe')
@path = path
end
def setup
if windows?
puts %{
MAJOR ANNOYANCE on Windows.
You have to shut down Opera manually after each test
for the script to proceed.
Any suggestions on fixing this is GREATLY appreciated!
Thank you for your understanding.
}
end
end
def visit(url)
applescript('tell application "Opera" to GetURL "' + url + '"') if macos?
system("#{@path} #{url}") if windows?
system("opera #{url}") if linux?
end
def to_s
"Opera"
end
end
# shut up, webrick :-)
class ::WEBrick::HTTPServer
def access_log(config, req, res)
# nop
end
end
class ::WEBrick::BasicLog
def log(level, data)
# nop
end
end
class WEBrick::HTTPResponse
alias send send_response
def send_response(socket)
send(socket) unless fail_silently?
end
def fail_silently?
@fail_silently
end
def fail_silently
@fail_silently = true
end
end
class WEBrick::HTTPRequest
def to_json
headers = []
each { |k, v| headers.push "#{k.inspect}: #{v.inspect}" }
headers = "{" << headers.join(', ') << "}"
%({ "headers": #{headers}, "body": #{body.inspect}, "method": #{request_method.inspect} })
end
end
class WEBrick::HTTPServlet::AbstractServlet
def prevent_caching(res)
res['ETag'] = nil
res['Last-Modified'] = Time.now + 100**4
res['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
res['Pragma'] = 'no-cache'
res['Expires'] = Time.now - 100**4
end
end
class BasicServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
prevent_caching(res)
res['Content-Type'] = "text/plain"
req.query.each do |k, v|
res[k] = v unless k == 'responseBody'
end
res.body = req.query["responseBody"]
raise WEBrick::HTTPStatus::OK
end
def do_POST(req, res)
do_GET(req, res)
end
end
class SlowServlet < BasicServlet
def do_GET(req, res)
sleep(2)
super
end
end
class DownServlet < BasicServlet
def do_GET(req, res)
res.fail_silently
end
end
class InspectionServlet < BasicServlet
def do_GET(req, res)
prevent_caching(res)
res['Content-Type'] = "application/json"
res.body = req.to_json
raise WEBrick::HTTPStatus::OK
end
end
class NonCachingFileHandler < WEBrick::HTTPServlet::FileHandler
def do_GET(req, res)
super
set_default_content_type(res, req.path)
prevent_caching(res)
end
def set_default_content_type(res, path)
res['Content-Type'] = case path
when /\.js$/ then 'text/javascript'
when /\.html$/ then 'text/html'
when /\.css$/ then 'text/css'
else 'text/plain'
end
end
end
class JavaScriptTestTask < ::Rake::TaskLib
def initialize(name=:test)
@name = name
@tests = []
@browsers = []
@queue = Queue.new
@server = WEBrick::HTTPServer.new(:Port => 4711) # TODO: make port configurable
@server.mount_proc("/results") do |req, res|
@queue.push(req)
res.body = "OK"
end
@server.mount("/response", BasicServlet)
@server.mount("/slow", SlowServlet)
@server.mount("/down", DownServlet)
@server.mount("/inspect", InspectionServlet)
yield self if block_given?
define
end
def define
task @name do
trap("INT") { @server.shutdown; exit }
t = Thread.new { @server.start }
# run all combinations of browsers and tests
@browsers.each do |browser|
if browser.supported?
t0 = Time.now
test_suite_results = TestSuiteResults.new
browser.setup
puts "\nStarted tests in #{browser}."
@tests.each do |test|
browser.visit(get_url(test))
results = TestResults.new(@queue.pop.query, test[:url])
print results
test_suite_results << results
end
print "\nFinished in #{Time.now - t0} seconds."
print test_suite_results
browser.teardown
else
puts "\nSkipping #{browser}, not supported on this OS."
end
end
@server.shutdown
t.join
end
end
def get_url(test)
params = "resultsURL=http://localhost:4711/results&t=" + ("%.6f" % Time.now.to_f)
params << "&tests=#{test[:testcases]}" unless test[:testcases] == :all
"http://localhost:4711#{test[:url]}?#{params}"
end
def mount(path, dir=nil)
dir = Dir.pwd + path unless dir
# don't cache anything in our tests
@server.mount(path, NonCachingFileHandler, dir)
end
# test should be specified as a hash of the form
# {:url => "url", :testcases => "testFoo,testBar"}.
# specifying :testcases is optional
def run(url, testcases = :all)
@tests << { :url => url, :testcases => testcases }
end
def browser(browser)
browser =
case(browser)
when :firefox
FirefoxBrowser.new
when :safari
SafariBrowser.new
when :ie
IEBrowser.new
when :konqueror
KonquerorBrowser.new
when :opera
OperaBrowser.new
when :chrome
ChromeBrowser.new
else
browser
end
@browsers<<browser
end
end
class TestResults
attr_reader :tests, :assertions, :failures, :errors, :filename
def initialize(query, filename)
@tests = query['tests'].to_i
@assertions = query['assertions'].to_i
@failures = query['failures'].to_i
@errors = query['errors'].to_i
@filename = filename
end
def error?
@errors > 0
end
def failure?
@failures > 0
end
def to_s
return "E" if error?
return "F" if failure?
"."
end
end
class TestSuiteResults
def initialize
@tests = 0
@assertions = 0
@failures = 0
@errors = 0
@error_files = []
@failure_files = []
end
def <<(result)
@tests += result.tests
@assertions += result.assertions
@failures += result.failures
@errors += result.errors
@error_files.push(result.filename) if result.error?
@failure_files.push(result.filename) if result.failure?
end
def error?
@errors > 0
end
def failure?
@failures > 0
end
def to_s
str = ""
str << "\n Failures: #{@failure_files.join(', ')}" if failure?
str << "\n Errors: #{@error_files.join(', ')}" if error?
"#{str}\n#{summary}\n\n"
end
def summary
"#{@tests} tests, #{@assertions} assertions, #{@failures} failures, #{@errors} errors."
end
end
class PageBuilder
UNITTEST_DIR = File.expand_path('test')
FIXTURES_DIR = File.join(UNITTEST_DIR, 'unit', 'fixtures')
TMP_DIR = File.join(UNITTEST_DIR, 'unit', 'tmp')
TEMPLATES_DIR = File.join(UNITTEST_DIR, 'lib', 'templates')
def initialize(filename, template = 'default.erb')
@filename = filename
@template = File.join(self.class::TEMPLATES_DIR, template)
@js_filename = File.basename(@filename)
@basename = @js_filename.sub('_test.js', '')
end
def html_fixtures
content = ""
file = File.join(FIXTURES_DIR, "#{@basename}.html")
File.open(file).each { |l| content << l } if File.exists?(file)
content
end
def external_fixtures(extension)
filename = "#{@basename}.#{extension}"
File.exists?(File.join(FIXTURES_DIR, filename)) ? filename : nil
end
def render
@title = @basename.gsub('_', ' ').strip.capitalize
@html_fixtures = html_fixtures
@js_fixtures_filename = external_fixtures('js')
@css_fixtures_filename = external_fixtures('css')
File.open(destination, 'w+') do |file|
file << ERB.new(IO.read(@template), nil, '%').result(binding)
end
end
def destination
name_file(:ext => 'html')
end
def name_file(options = {})
prefix = options[:prefix] ? "#{options[:prefix]}_" : ""
suffix = options[:suffix] ? "_#{options[:suffix]}" : ""
ext = options[:ext] ? options[:ext] : "js"
filename = File.basename(@filename, '.js')
File.join(TMP_DIR, "#{prefix}#{filename}#{suffix}.#{ext}")
end
end

View File

@ -1,35 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Unit test file | <%= @title %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../../dist/prototype.js" type="text/javascript"></script>
<script src="../../lib/assets/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../../lib/assets/test.css" type="text/css" />
<% if @css_fixtures_filename %>
<link rel="stylesheet" href="../fixtures/<%= @css_fixtures_filename %>" type="text/css" charset="utf-8" />
<% end %>
<% if @js_fixtures_filename %>
<script src="../fixtures/<%= @js_fixtures_filename %>" type="text/javascript" charset="utf-8"></script>
<% end %>
<script src="../<%= @js_filename %>" type="text/javascript"></script>
</head>
<body>
<h1>Unit test file</h1>
<h2><%= @title %></h2>
<!-- This file is programmatically generated. Do not attempt to modify it. Instead, modify <%= @fixtures_filename %> -->
<!-- Log output start -->
<div id="testlog"></div>
<!-- Log output end -->
<!-- HTML Fixtures start -->
<%= @html_fixtures %>
<!-- HTML Fixtures end -->
</body>
</html>

View File

@ -1,42 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file | <%= @title %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/javascript" charset="utf-8">
var eventResults = {};
var originalElement = window.Element;
</script>
<script src="../../../dist/prototype.js" type="text/javascript"></script>
<script src="../../lib/assets/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../../lib/assets/test.css" type="text/css" />
<% if @css_fixtures_filename %>
<link rel="stylesheet" href="../fixtures/<%= @css_fixtures_filename %>" type="text/css" charset="utf-8" />
<% end %>
<% if @js_fixtures_filename %>
<script src="../fixtures/<%= @js_fixtures_filename %>" type="text/javascript" charset="utf-8"></script>
<% end %>
<script src="../<%= @js_filename %>" type="text/javascript"></script>
</head>
<body>
<h1>Prototype Unit test file</h1>
<h2><%= @title %></h2>
<!-- This file is programmatically generated. Do not attempt to modify it. Instead, modify <%= @fixtures_filename %> -->
<!-- Log output start -->
<div id="testlog"></div>
<!-- Log output end -->
<!-- HTML Fixtures start -->
<%= @html_fixtures %>
<!-- HTML Fixtures end -->
</body>
</html>
<script type="text/javascript" charset="utf-8">
eventResults.endOfDocument = true;
</script>

View File

@ -313,7 +313,7 @@ new Test.Unit.Runner({
// with empty action attribute
request = $("ffe").request({ method: 'post' });
this.assert(request.url.include("unit/tmp/form_test.html"),
this.assert(request.url.include("/tmp/form_test.html"),
'wrong default action for form element with empty action attribute');
},

View File

@ -0,0 +1,25 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Unit test file | <%= title %> | <%= template_name %> template | <%= timestamp %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/javascript" charset="utf-8">
var eventResults = {};
var originalElement = window.Element;
</script>
<%= lib_files %>
<%= css_fixtures %>
<%= js_fixtures %>
<%= test_file %>
</head>
<body>
<div id="testlog"></div>
<%= html_fixtures %>
</body>
</html>
<script type="text/javascript" charset="utf-8">
eventResults.endOfDocument = true;
</script>

1
vendor/unittest_js vendored Submodule

@ -0,0 +1 @@
Subproject commit d0d28f58f127796c9cf916bd4f1c95f90b9a3fdc