Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial import.

  • Loading branch information...
commit d0d28f58f127796c9cf916bd4f1c95f90b9a3fdc 1 parent ef159c1
@tobie authored
Showing with 6,456 additions and 0 deletions.
  1. +16 −0 LICENSE
  2. +4,545 −0 assets/prototype.js
  3. +50 −0 assets/unittest.css
  4. +615 −0 assets/unittest.js
  5. +1 −0  assets/unittest_transport.js
  6. BIN  lib/.DS_Store
  7. +19 −0 lib/unittest_js.rb
  8. BIN  lib/unittest_js/.DS_Store
  9. +11 −0 lib/unittest_js/browsers.rb
  10. +40 −0 lib/unittest_js/browsers/abstract.rb
  11. +29 −0 lib/unittest_js/browsers/chrome.rb
  12. +19 −0 lib/unittest_js/browsers/firefox.rb
  13. +26 −0 lib/unittest_js/browsers/ie.rb
  14. +41 −0 lib/unittest_js/browsers/konqueror.rb
  15. +32 −0 lib/unittest_js/browsers/opera.rb
  16. +25 −0 lib/unittest_js/browsers/safari.rb
  17. +19 −0 lib/unittest_js/builder.rb
  18. +36 −0 lib/unittest_js/builder/assets_handler.rb
  19. +69 −0 lib/unittest_js/builder/builder_options.rb
  20. +59 −0 lib/unittest_js/builder/fixtures.rb
  21. +13 −0 lib/unittest_js/builder/html_helper.rb
  22. +9 −0 lib/unittest_js/builder/missing_template_error.rb
  23. +38 −0 lib/unittest_js/builder/suite_builder.rb
  24. +56 −0 lib/unittest_js/builder/template.rb
  25. +90 −0 lib/unittest_js/builder/test_builder.rb
  26. +78 −0 lib/unittest_js/dir_pathname.rb
  27. +18 −0 lib/unittest_js/webrick_runner.rb
  28. BIN  lib/unittest_js/webrick_runner/.DS_Store
  29. +92 −0 lib/unittest_js/webrick_runner/runner.rb
  30. +43 −0 lib/unittest_js/webrick_runner/runner_options.rb
  31. +16 −0 lib/unittest_js/webrick_runner/servlets.rb
  32. +24 −0 lib/unittest_js/webrick_runner/servlets/basic.rb
  33. +12 −0 lib/unittest_js/webrick_runner/servlets/down.rb
  34. +23 −0 lib/unittest_js/webrick_runner/servlets/file_handler.rb
  35. +15 −0 lib/unittest_js/webrick_runner/servlets/inspection.rb
  36. +22 −0 lib/unittest_js/webrick_runner/servlets/result.rb
  37. +13 −0 lib/unittest_js/webrick_runner/servlets/slow.rb
  38. +31 −0 lib/unittest_js/webrick_runner/suite.rb
  39. +44 −0 lib/unittest_js/webrick_runner/suite_results.rb
  40. +35 −0 lib/unittest_js/webrick_runner/test.rb
  41. +42 −0 lib/unittest_js/webrick_runner/test_results.rb
  42. +45 −0 lib/unittest_js/webrick_runner/webrick_helper.rb
  43. +20 −0 templates/default.erb
  44. +25 −0 templates/doctypes.txt
View
16 LICENSE
@@ -0,0 +1,16 @@
+Copyright (c) 2008 Tobie Langel
+
+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 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.
View
4,545 assets/prototype.js
4,545 additions, 0 deletions not shown
View
50 assets/unittest.css
@@ -0,0 +1,50 @@
+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
615 assets/unittest.js
@@ -0,0 +1,615 @@
+// 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(id) {
+ if (typeof id === 'string')
+ id = document.getElementById(id);
+ this.element = id;
+ if (this.element) this._createLogTable();
+ this.tbody = this.element.getElementsByTagName('tbody')[0];
+ },
+
+ start: function(testName) {
+ if (!this.element) return;
+ var tr = document.createElement('tr');
+ var first = document.createElement('td');
+ first.appendChild(document.createTextNode(testName));
+ tr.appendChild(first);
+ tr.appendChild(document.createElement('td'));
+ tr.appendChild(document.createElement('td'));
+ this.tbody.appendChild(tr);
+ },
+
+ setStatus: function(status) {
+ this.getLastLogLine().className = status;
+ this.getLastLogLine().getElementsByTagName('td')[1].innerHTML = status;
+ },
+
+ finish: function(status, summary) {
+ if (!this.element) return;
+ this.setStatus(status);
+ this.message(summary);
+ },
+
+ message: function(message) {
+ if (!this.element) return;
+ this.getMessageCell().innerHTML = this._toHTML(message);
+ },
+
+ summary: function(summary) {
+ if (!this.element) return;
+ this.element.getElementsByTagName('div')[0].innerHTML = this._toHTML(summary);
+ },
+
+ getLastLogLine: function() {
+ var trs = this.element.getElementsByTagName('tr');
+ return trs[trs.length - 1];
+ },
+
+ getMessageCell: function() {
+ return this.getLastLogLine().getElementsByTagName('td')[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.innerHTML = 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.runners = [];
+Test.Unit.run = true;
+Test.Unit.AutoRunner = {
+ run: function() {
+ if (!Test.Unit.run) return;
+ Test.Unit.run = false;
+ for (var i=0; i < Test.Unit.runners.length; i++) {
+ Test.Unit.runners[i].run();
+ };
+ }
+};
+Event.observe(window, "load", Test.Unit.AutoRunner.run);
+
+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;
+ Test.Unit.runners.push(this);
+ },
+
+ run: function() {
+ this.logger = new Test.Unit.Logger(this.options.testLog);
+ this.runTests.bind(this).delay(0.1);
+ },
+
+ 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 (window.postUnittestResults) {
+ window.postUnittestResults(this.getResult());
+ } else 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 = (function() {
+ var MessageTemplate = Test.Unit.MessageTemplate;
+
+ function buildMessage(message, template) {
+ var args = Array.prototype.slice.call(arguments, 2);
+ return (message ? message + '\n' : '') + new MessageTemplate(template).evaluate(args);
+ }
+
+ function flunk(message) {
+ this.assertBlock(message || 'Flunked', function() { return false });
+ }
+
+ function assertBlock(message, block) {
+ try {
+ block.call(this) ? this.pass() : this.fail(message);
+ } catch(e) { this.error(e) }
+ }
+
+ function assert(expression, message) {
+ message = buildMessage(message || 'assert', 'got <?>', expression);
+ this.assertBlock(message, function() { return expression });
+ }
+
+ function assertEqual(expected, actual, message) {
+ message = buildMessage(message || 'assertEqual', 'expected: <?>, actual: <?>', expected, actual);
+ this.assertBlock(message, function() { return expected == actual });
+ }
+
+ function assertNotEqual(expected, actual, message) {
+ message = buildMessage(message || 'assertNotEqual', 'expected: <?>, actual: <?>', expected, actual);
+ this.assertBlock(message, function() { return expected != actual });
+ }
+
+ function assertEnumEqual(expected, actual, message) {
+ expected = $A(expected);
+ actual = $A(actual);
+ message = 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] });
+ });
+ }
+
+ function assertEnumNotEqual(expected, actual, message) {
+ expected = $A(expected);
+ actual = $A(actual);
+ message = 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] });
+ });
+ }
+
+ function assertPairEqual(pair) {
+ return pair.all(Object.isArray) ?
+ pair[0].zip(pair[1]).all(assertPairEqual) : pair[0] == pair[1];
+ }
+
+ function assertHashEqual(expected, actual, message) {
+ expected = $H(expected);
+ actual = $H(actual);
+ var expected_array = expected.toArray().sort(), actual_array = actual.toArray().sort();
+ message = buildMessage(message || 'assertHashEqual', 'expected: <?>, actual: <?>', expected, actual);
+ // from now we recursively zip & compare nested arrays
+ function block() {
+ return expected_array.length == actual_array.length &&
+ expected_array.zip(actual_array).all(assertPairEqual);
+ }
+ this.assertBlock(message, block);
+ }
+
+ function assertHashNotEqual(expected, actual, message) {
+ expected = $H(expected);
+ actual = $H(actual);
+ var expected_array = expected.toArray().sort(), actual_array = actual.toArray().sort();
+ message = buildMessage(message || 'assertHashNotEqual', '<?> was the same as <?>', expected, actual);
+ // from now we recursively zip & compare nested arrays
+ function block() {
+ return !(expected_array.length == actual_array.length &&
+ expected_array.zip(actual_array).all(assertPairEqual));
+ };
+ this.assertBlock(message, block);
+ }
+
+ function assertIdentical(expected, actual, message) {
+ message = buildMessage(message || 'assertIdentical', 'expected: <?>, actual: <?>', expected, actual);
+ this.assertBlock(message, function() { return expected === actual });
+ }
+
+ function assertNotIdentical(expected, actual, message) {
+ message = buildMessage(message || 'assertNotIdentical', 'expected: <?>, actual: <?>', expected, actual);
+ this.assertBlock(message, function() { return expected !== actual });
+ }
+
+ function assertNull(obj, message) {
+ message = buildMessage(message || 'assertNull', 'got <?>', obj);
+ this.assertBlock(message, function() { return obj === null });
+ }
+
+ function assertNotNull(obj, message) {
+ message = buildMessage(message || 'assertNotNull', 'got <?>', obj);
+ this.assertBlock(message, function() { return obj !== null });
+ }
+
+ function assertUndefined(obj, message) {
+ message = buildMessage(message || 'assertUndefined', 'got <?>', obj);
+ this.assertBlock(message, function() { return typeof obj == "undefined" });
+ }
+
+ function assertNotUndefined(obj, message) {
+ message = buildMessage(message || 'assertNotUndefined', 'got <?>', obj);
+ this.assertBlock(message, function() { return typeof obj != "undefined" });
+ }
+
+ function assertNullOrUndefined(obj, message) {
+ message = buildMessage(message || 'assertNullOrUndefined', 'got <?>', obj);
+ this.assertBlock(message, function() { return obj == null });
+ }
+
+ function assertNotNullOrUndefined(obj, message) {
+ message = buildMessage(message || 'assertNotNullOrUndefined', 'got <?>', obj);
+ this.assertBlock(message, function() { return obj != null });
+ }
+
+ function assertMatch(expected, actual, message) {
+ message = buildMessage(message || 'assertMatch', 'regex <?> did not match <?>', expected, actual);
+ this.assertBlock(message, function() { return new RegExp(expected).exec(actual) });
+ }
+
+ function assertNoMatch(expected, actual, message) {
+ message = buildMessage(message || 'assertNoMatch', 'regex <?> matched <?>', expected, actual);
+ this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)) });
+ }
+
+ function assertHidden(element, message) {
+ message = buildMessage(message || 'assertHidden', '? isn\'t hidden.', element);
+ this.assertBlock(message, function() { return element.style.display == 'none' });
+ }
+
+ function assertInstanceOf(expected, actual, message) {
+ message = buildMessage(message || 'assertInstanceOf', '<?> was not an instance of the expected type', actual);
+ this.assertBlock(message, function() { return actual instanceof expected });
+ }
+
+ function assertNotInstanceOf(expected, actual, message) {
+ message = buildMessage(message || 'assertNotInstanceOf', '<?> was an instance of the expected type', actual);
+ this.assertBlock(message, function() { return !(actual instanceof expected) });
+ }
+
+ function assertRespondsTo(method, obj, message) {
+ message = buildMessage(message || 'assertRespondsTo', 'object doesn\'t respond to <?>', method);
+ this.assertBlock(message, function() { return (method in obj && typeof obj[method] == 'function') });
+ }
+
+ function assertRaise(exceptionName, method, message) {
+ message = 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);
+ }
+
+ function assertNothingRaised(method, message) {
+ try {
+ method();
+ this.assert(true, "Expected nothing to be thrown");
+ } catch(e) {
+ message = buildMessage(message || 'assertNothingRaised', '<?> was thrown when nothing was expected.', e);
+ this.flunk(message);
+ }
+ }
+
+ function isVisible(element) {
+ element = $(element);
+ if(!element.parentNode) return true;
+ this.assertNotNull(element);
+ if(element.style && Element.getStyle(element, 'display') == 'none')
+ return false;
+ return isVisible.call(this, element.parentNode);
+ }
+
+ function assertVisible(element, message) {
+ message = buildMessage(message, '? was not visible.', element);
+ this.assertBlock(message, function() { return isVisible.call(this, element) });
+ }
+
+ function assertNotVisible(element, message) {
+ message = buildMessage(message, '? was not hidden and didn\'t have a hidden parent either.', element);
+ this.assertBlock(message, function() { return !isVisible.call(this, element) });
+ }
+
+ function assertElementsMatch() {
+ var message, pass = true, expressions = $A(arguments), elements = $A(expressions.shift());
+ if (elements.length != expressions.length) {
+ message = 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 = 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.");
+ }
+
+ function assertElementMatches(element, expression, message) {
+ this.assertElementsMatch([element], expression);
+ }
+
+ return {
+ buildMessage: buildMessage,
+ flunk: flunk,
+ assertBlock: assertBlock,
+ assert: assert,
+ assertEqual: assertEqual,
+ assertNotEqual: assertNotEqual,
+ assertEnumEqual: assertEnumEqual,
+ assertEnumNotEqual: assertEnumNotEqual,
+ assertHashEqual: assertHashEqual,
+ assertHashNotEqual: assertHashNotEqual,
+ assertIdentical: assertIdentical,
+ assertNotIdentical: assertNotIdentical,
+ assertNull: assertNull,
+ assertNotNull: assertNotNull,
+ assertUndefined: assertUndefined,
+ assertNotUndefined: assertNotUndefined,
+ assertNullOrUndefined: assertNullOrUndefined,
+ assertNotNullOrUndefined: assertNotNullOrUndefined,
+ assertMatch: assertMatch,
+ assertNoMatch: assertNoMatch,
+ assertHidden: assertHidden,
+ assertInstanceOf: assertInstanceOf,
+ assertNotInstanceOf: assertNotInstanceOf,
+ assertRespondsTo: assertRespondsTo,
+ assertRaise: assertRaise,
+ assertNothingRaised: assertNothingRaised,
+ assertVisible: assertVisible,
+ assertNotVisible: assertNotVisible,
+ assertElementsMatch: assertElementsMatch,
+ assertElementMatches: assertElementMatches
+ }
+})();
+
+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 + ", error=(" + 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
1  assets/unittest_transport.js
@@ -0,0 +1 @@
+function postUnittestResults(results) { alert(results.toString()) };
View
BIN  lib/.DS_Store
Binary file not shown
View
19 lib/unittest_js.rb
@@ -0,0 +1,19 @@
+require 'fileutils'
+include FileUtils
+
+$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'unittest_js'))
+
+require 'dir_pathname'
+require 'builder'
+require 'webrick_runner'
+require 'browsers'
+
+module UnittestJS
+ TEMPLATES_DIR_NAME = 'templates'
+ FIXTURES_DIR_NAME = 'fixtures'
+ ASSETS_DIR_NAME = 'assets'
+
+ def self.browsers
+ @browsers ||= Browser.supported.freeze
+ end
+end
View
BIN  lib/unittest_js/.DS_Store
Binary file not shown
View
11 lib/unittest_js/browsers.rb
@@ -0,0 +1,11 @@
+$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'browsers'))
+
+require 'abstract'
+
+module UnittestJS
+ module Browser
+ SUPPORTED = %w[chrome firefox ie konqueror opera safari].freeze
+ end
+end
+
+UnittestJS::Browser::SUPPORTED.each{ |browser| require browser }
View
40 lib/unittest_js/browsers/abstract.rb
@@ -0,0 +1,40 @@
+module UnittestJS
+ module Browser
+ class Abstract
+ def supported?
+ true
+ end
+
+ def setup
+ end
+
+ def teardown
+ end
+
+ def open(url)
+ 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
+ end
+end
View
29 lib/unittest_js/browsers/chrome.rb
@@ -0,0 +1,29 @@
+module UnittestJS
+ module Browser
+ class Chrome < Abstract
+ 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
+ end
+end
View
19 lib/unittest_js/browsers/firefox.rb
@@ -0,0 +1,19 @@
+module UnittestJS
+ module Browser
+ class Firefox < Abstract
+ 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
+ end
+end
View
26 lib/unittest_js/browsers/ie.rb
@@ -0,0 +1,26 @@
+module UnittestJS
+ module Browser
+ class IE < Abstract
+ 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
+ end
+end
View
41 lib/unittest_js/browsers/konqueror.rb
@@ -0,0 +1,41 @@
+module UnittestJS
+ module Browser
+ class Konqueror < Abstract
+ @@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
+ end
+end
View
32 lib/unittest_js/browsers/opera.rb
@@ -0,0 +1,32 @@
+module UnittestJS
+ module Browser
+ class Opera < Abstract
+ 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
+ end
+end
+
View
25 lib/unittest_js/browsers/safari.rb
@@ -0,0 +1,25 @@
+module UnittestJS
+ module Browser
+ class Safari < Abstract
+ 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
+ end
+end
View
19 lib/unittest_js/builder.rb
@@ -0,0 +1,19 @@
+$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'builder'))
+
+require 'assets_handler'
+require 'html_helper'
+require 'test_builder'
+require 'template'
+require 'fixtures'
+require 'builder_options'
+require 'suite_builder'
+require 'missing_template_error'
+
+module UnittestJS
+ module Builder
+ def self.empty_dir!(dir)
+ dir = DirPathname.new(dir)
+ dir.exists? ? dir.empty! : dir.mk_and_stamp!
+ end
+ end
+end
View
36 lib/unittest_js/builder/assets_handler.rb
@@ -0,0 +1,36 @@
+module UnittestJS
+ module Builder
+ class AssetsHandler
+ ASSETS = 'assets'
+
+ def initialize(options = {})
+ @options = options
+ end
+
+ def copy_assets
+ if @options.assets_dir.exists?
+ assets = Dir[@options.assets_dir.join('*')]
+ FileUtils.cp_r(assets, @options.output_assets_dir)
+ end
+ end
+
+ def copy_unittest_assets
+ unittest_assets_dir.copy_to(@options.output_assets_dir)
+ end
+
+ def unittest_assets_dir
+ @unittest_assets_dir ||= DirPathname.new(File.dirname(__FILE__), '..', '..', '..', ASSETS)
+ end
+
+ def copy_fixtures
+ @options.fixtures_dir.copy_to(@options.output_fixtures_dir) if @options.fixtures_dir.exists?
+ end
+
+ def copy
+ copy_unittest_assets
+ copy_assets
+ copy_fixtures
+ end
+ end
+ end
+end
View
69 lib/unittest_js/builder/builder_options.rb
@@ -0,0 +1,69 @@
+module UnittestJS
+ module Builder
+ class BuilderOptions
+ OUTPUT = 'tmp'
+ ASSETS = 'assets'
+ FIXTURES = 'fixtures'
+ TEMPLATES = 'templates'
+ TESTS = 'tests'
+
+ def initialize(options = {})
+ @options = options
+ end
+
+ def input_dir
+ @input_dir ||= DirPathname.new(@options[:input_dir] || File.join(File.dirname($0), INPUT))
+ end
+
+ def assets_dir
+ @assets_dir ||= normalize_input_dir(@options[:assets_dir], ASSETS)
+ end
+
+ def fixtures_dir
+ @fixtures_dir ||= normalize_input_dir(@options[:fixtures_dir], FIXTURES)
+ end
+
+ def templates_dir
+ @templates_dir ||= normalize_input_dir(@options[:templates_dir], TEMPLATES)
+ end
+
+ def output_dir
+ @output_dir ||= DirPathname.new(@options[:output_dir] ? @options[:output_dir] : input_dir.join(OUTPUT))
+ end
+
+ def output_assets_dir
+ @output_assets_dir ||= DirPathname.new(output_dir, @options[:output_assets_dir_name] || ASSETS)
+ end
+
+ def output_fixtures_dir
+ @output_fixtures_dir ||= DirPathname.new(output_dir, @options[:output_fixtures_dir_name] || FIXTURES)
+ end
+
+ def output_tests_dir
+ @output_tests_dir ||= DirPathname.new(output_dir, TESTS)
+ end
+
+ def template
+ @template ||= Template.from(@options[:template] || :default, templates_dir)
+ end
+
+ def test_file_suffix
+ @test_file_suffix ||= @options[:test_file_suffix] || '_test.js'
+ end
+
+ def test_file_pattern
+ "*#{test_file_suffix}"
+ end
+
+ def self_contained?
+ @self_contained ||= @options[:self_contained] || true
+ end
+
+ private
+ def normalize_input_dir(dir, default)
+ dir = File.join(input_dir, default) unless dir
+ DirPathname.new(dir)
+ end
+ end
+ end
+end
View
59 lib/unittest_js/builder/fixtures.rb
@@ -0,0 +1,59 @@
+module UnittestJS
+ module Builder
+ class Fixture
+ include HtmlHelper
+
+ attr_reader :name
+ def initialize(name, options)
+ @name = name
+ @options = options
+ end
+
+ def exists?
+ File.exists?(filepath)
+ end
+
+ def filepath
+ @options.fixtures_dir.join(name)
+ end
+
+ def rel_filepath
+ @options.output_fixtures_dir.join(name).relative_path_from(@options.output_dir)
+ end
+
+ def filename
+ name
+ end
+ end
+
+ class HTMLFixture < Fixture
+ def initialize(name, options)
+ super("#{name}.html", options)
+ end
+
+ def to_s
+ exists? ? File.read(filepath) : ''
+ end
+ end
+
+ class CSSFixture < Fixture
+ def initialize(name, options)
+ super("#{name}.css", options)
+ end
+
+ def to_s
+ exists? ? to_link_tag(rel_filepath) : ''
+ end
+ end
+
+ class JSFixture < Fixture
+ def initialize(name, options)
+ super("#{name}.js", options)
+ end
+
+ def to_s
+ exists? ? to_script_tag(rel_filepath) : ''
+ end
+ end
+ end
+end
View
13 lib/unittest_js/builder/html_helper.rb
@@ -0,0 +1,13 @@
+module UnittestJS
+ module Builder
+ module HtmlHelper
+ def to_script_tag(filename)
+ "<script src=\"#{filename}\" type=\"text/javascript\" charset=\"utf-8\"></script>"
+ end
+
+ def to_link_tag(filename)
+ "<link rel=\"stylesheet\" href=\"#{filename}\" type=\"text/css\" />"
+ end
+ end
+ end
+end
View
9 lib/unittest_js/builder/missing_template_error.rb
@@ -0,0 +1,9 @@
+module UnittestJS
+ module Builder
+ class MissingTemplateError < StandardError
+ def initialize(template)
+ super("Can't find template '#{template}'")
+ end
+ end
+ end
+end
View
38 lib/unittest_js/builder/suite_builder.rb
@@ -0,0 +1,38 @@
+module UnittestJS
+ module Builder
+ class SuiteBuilder
+ def initialize(options = {})
+ @options = BuilderOptions.new(options)
+ end
+
+ def collect(*args)
+ if args.empty?
+ @tests = all_tests
+ else
+ @tests = all_tests.select { |test| args.include?(test.name) }
+ end
+ end
+
+ def render
+ assets_handler.copy
+ @options.output_tests_dir.mk_and_stamp!
+ render_tests
+ end
+
+ def assets_handler
+ @assets_handler ||= AssetsHandler.new(@options)
+ end
+
+ def render_tests
+ @tests.each { |test| test.render }
+ end
+
+ private
+ def all_tests
+ @all_tests ||= Dir[@options.input_dir.join(@options.test_file_pattern)].map do |file|
+ TestBuilder.new(file, @options)
+ end
+ end
+ end
+ end
+end
View
56 lib/unittest_js/builder/template.rb
@@ -0,0 +1,56 @@
+require 'erb'
+module UnittestJS
+ module Builder
+ class Template
+ TEMPLATES = 'templates'
+ TEMPLATES_DIR = DirPathname.new(File.dirname(__FILE__), '..', '..', '..', TEMPLATES)
+
+ def self.default_dir
+ TEMPLATES_DIR
+ end
+
+ def self.from(name, directory = nil)
+ if name.is_a?(Symbol)
+ self.from_sym(name, directory)
+ elsif File.exists?(name)
+ self.from_file(name)
+ else
+ self.from_filename(name, directory)
+ end
+ end
+
+ def self.from_sym(sym, directory = nil)
+ self.from_filename("#{sym}.erb", directory)
+ end
+
+ def self.from_filename(filename, directory = nil)
+ file = directory.join(filename) if directory
+ file = self.default_dir.join(filename) unless file.exists?
+ raise MissingTemplateError.new(filename) unless file.exists?
+ self.new(file)
+ end
+
+ def self.from_file(file)
+ raise MissingTemplateError.new(file) unless File.exist?(file)
+ self.new(file)
+ end
+
+ def initialize(file)
+ @file = file
+ @template = ERB.new(IO.read(file), nil, '%')
+ end
+
+ def result(bind)
+ @template.result(bind)
+ end
+
+ def name
+ @name ||= File.basename(@file, File.extname(@file))
+ end
+
+ def to_s
+ name
+ end
+ end
+ end
+end
View
90 lib/unittest_js/builder/test_builder.rb
@@ -0,0 +1,90 @@
+module UnittestJS
+ module Builder
+ class TestBuilder
+ include HtmlHelper
+
+ attr_reader :name, :file, :filename
+ alias :path :file
+ alias :pathname :file
+
+ def initialize(file, options)
+ @file = file
+ @options = options
+ @filename = File.basename(file)
+ @name = @filename.sub(@options.test_file_suffix, '')
+ end
+
+ def html_fixtures
+ @html_fixtures ||= HTMLFixture.new(name, @options)
+ end
+
+ def js_fixtures
+ @js_fixtures ||= JSFixture.new(name, @options)
+ end
+
+ def css_fixtures
+ @css_fixtures ||= CSSFixture.new(name, @options)
+ end
+
+ def test_file
+ to_script_tag("#{@options.output_tests_dir.name}/#{filename}")
+ end
+
+ def timestamp
+ @timestamp ||= Time.now.strftime("%Y-%m-%d %H:%M")
+ end
+
+ def lib_files
+ assets = @options.output_assets_dir.name
+ [
+ to_script_tag("#{assets}/prototype.js"),
+ to_script_tag("#{assets}/unittest.js"),
+ to_link_tag("#{assets}/unittest.css")
+ ].join("\n")
+ end
+
+ def title
+ @title ||= name.split('_').map{ |w| w.capitalize }.join(' ').strip
+ end
+
+ def render
+ File.open(rendered_file, 'w+') do |file|
+ file << template.result(binding)
+ end
+ copy_file
+ end
+
+ def rendered_filename
+ to_filename(name, short_template_name, 'test', :html)
+ end
+
+ def rendered_file
+ @options.output_dir.join(rendered_filename)
+ end
+
+ def template
+ @options.template
+ end
+
+ def template_name
+ template.name
+ end
+
+ private
+ def short_template_name
+ template_name == 'default' ? '' : template_name
+ end
+
+ def to_filename(*args)
+ args.reject!{ |a| a.nil? || a === '' }
+ ext = args.pop
+ "#{args.join('_')}.#{ext}"
+ end
+
+ def copy_file
+ FileUtils.cp(file, @options.output_tests_dir)
+ end
+ end
+ end
+end
+
View
78 lib/unittest_js/dir_pathname.rb
@@ -0,0 +1,78 @@
+require 'pathname'
+module UnittestJS
+ class DirPathname < Pathname
+ STAMP = '.unittest'
+
+ alias :path :to_s
+ def initialize(*args)
+ args.map! { |a| a.to_s }
+ super(File.expand_path(File.join(args)))
+ end
+
+ def name
+ File.basename(path)
+ end
+
+ def exists?
+ File.exists?(path)
+ end
+
+ def empty?
+ Dir.entries(path).size <= 2
+ end
+
+ def writeable?
+ exists? && !frozen? && (empty? || stamped?)
+ end
+
+ def stamp!
+ File.open(stamp, 'w+') unless frozen?
+ end
+
+ def stamped?
+ File.exists?(stamp)
+ end
+
+ def mk!
+ Dir.mkdir(path)
+ end
+
+ def mk_and_stamp!
+ unless exists?
+ mk!
+ stamp!
+ end
+ end
+
+ def empty!
+ if writeable?
+ FileUtils.rm_rf(path)
+ mk_and_stamp!
+ else
+ raise "Cannot empty directory '#{name}'."
+ end
+ end
+
+ def copy_to(dest)
+ FileUtils.cp_r(path, dest.to_s)
+ end
+
+ def join(*args)
+ self.class.new(path, *args)
+ end
+
+ def freeze
+ @frozen = true
+ self
+ end
+
+ def frozen?
+ @frozen ||= false
+ end
+
+ private
+ def stamp
+ join(STAMP)
+ end
+ end
+end
View
18 lib/unittest_js/webrick_runner.rb
@@ -0,0 +1,18 @@
+require 'webrick'
+require 'thread'
+
+$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'webrick_runner'))
+
+require 'webrick_helper'
+require 'servlets'
+require 'runner'
+require 'runner_options'
+require 'test'
+require 'test_results'
+require 'suite'
+require 'suite_results'
+
+module UnittestJS
+ module WEBrickRunner
+ end
+end
View
BIN  lib/unittest_js/webrick_runner/.DS_Store
Binary file not shown
View
92 lib/unittest_js/webrick_runner/runner.rb
@@ -0,0 +1,92 @@
+module UnittestJS
+ module WEBrickRunner
+ class Runner
+ def initialize(options = {})
+ @options = RunnerOptions.new(options)
+ @tests = []
+ @browsers = []
+ @queue = Queue.new
+ @server = WEBrick::HTTPServer.new(:Port => @options.port)
+ mount_default_servlets
+ mount_helper_servlets
+ end
+
+ def run
+ t = Thread.new { setup }
+ @browsers.each do |browser|
+ if browser.supported?
+ Suite.new(browser, @tests).run(@queue)
+ else
+ puts "\nSkipping #{browser}, not supported on this OS."
+ end
+ end
+ teardown
+ t.join
+ end
+
+ def setup
+ @server.start
+ end
+
+ def teardown
+ @server.shutdown
+ end
+
+ def mount(path, dir=nil)
+ dir = Dir.pwd + path unless dir
+ @server.mount(path, Servlet::NonCachingFileHandler, dir)
+ end
+
+ # def collect_tests(tests = nil, testcases = nil)
+ # Dir[@options.test_dir.join('*_test.html')].each do |file|
+ # file = File.basename(file)
+ # add_test(file, testcases)
+ # end
+ # end
+
+ def add_test(filepath, testcases = nil)
+ @tests << Test.new(filepath, testcases, @options)
+ end
+
+ def add_browser(browser)
+ browser =
+ case(browser)
+ when :firefox
+ Browser::Firefox.new
+ when :safari
+ Browser::Safari.new
+ when :ie
+ Browser::IE.new
+ when :konqueror
+ Browser::Konqueror.new
+ when :opera
+ Browser::Opera.new
+ when :chrome
+ Browser::Chrome.new
+ else
+ browser
+ end
+
+ @browsers << browser
+ end
+
+ private
+ def mount_default_servlets
+ @server.mount('/results', Servlet::Result, @queue)
+ Dir.chdir(@options.test_dir.join('..')) do
+ mount('/')
+ mount("/#{@options.test_dir.name}")
+ mount("/#{@options.fixtures_dir.name}")
+ mount("/#{@options.assets_dir.name}")
+ end
+ end
+
+ def mount_helper_servlets
+ @server.mount('/response', Servlet::Basic)
+ @server.mount('/slow', Servlet::Slow)
+ @server.mount('/down', Servlet::Down)
+ @server.mount('/inspect', Servlet::Inspection)
+ end
+ end
+ end
+end
View
43 lib/unittest_js/webrick_runner/runner_options.rb
@@ -0,0 +1,43 @@
+module UnittestJS
+ module WEBrickRunner
+ class RunnerOptions
+ INPUT = 'tmp'
+ ASSETS = 'assets'
+ FIXTURES = 'fixtures'
+
+ def initialize(options = {})
+ @options = options
+ end
+
+ def test_dir
+ @test_dir ||= DirPathname.new(@options[:test_dir] || File.join(File.dirname($0), INPUT))
+ end
+
+ def assets_dir
+ @assets_dir ||= normalize_dir(@options[:assets_dir], ASSETS)
+ end
+
+ def fixtures_dir
+ @fixtures_dir ||= normalize_dir(@options[:fixtures_dir], FIXTURES)
+ end
+
+ def port
+ @port ||= @options[:port] || 4711
+ end
+
+ def test_file_suffix
+ @test_file_suffix ||= @options[:test_file_suffix] || '_test.html'
+ end
+
+ def test_file_pattern
+ "*#{test_file_suffix}"
+ end
+
+ private
+ def normalize_dir(dir, default)
+ dir = File.join(test_dir, default) unless dir
+ DirPathname.new(dir)
+ end
+ end
+ end
+end
View
16 lib/unittest_js/webrick_runner/servlets.rb
@@ -0,0 +1,16 @@
+$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'servlets'))
+
+require 'basic'
+require 'slow'
+require 'down'
+require 'inspection'
+require 'file_handler'
+require 'result'
+
+module UnittestJS
+ module WEBrickRunner
+ module Servlet
+ end
+ end
+end
+
View
24 lib/unittest_js/webrick_runner/servlets/basic.rb
@@ -0,0 +1,24 @@
+module UnittestJS
+ module WEBrickRunner
+ module Servlet
+ class Basic < 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
+ end
+ end
+end
+
View
12 lib/unittest_js/webrick_runner/servlets/down.rb
@@ -0,0 +1,12 @@
+module UnittestJS
+ module WEBrickRunner
+ module Servlet
+ class Down < Basic
+ def do_GET(req, res)
+ res.fail_silently!
+ end
+ end
+ end
+ end
+end
+
View
23 lib/unittest_js/webrick_runner/servlets/file_handler.rb
@@ -0,0 +1,23 @@
+module UnittestJS
+ module WEBrickRunner
+ module Servlet
+ class NonCachingFileHandler < WEBrick::HTTPServlet::FileHandler
+ def do_GET(req, res)
+ super
+ res['Content-Type'] = default_content_type(req.path)
+ prevent_caching(res)
+ end
+
+ def default_content_type(path)
+ case path
+ when /\.js$/ then 'text/javascript'
+ when /\.html$/ then 'text/html'
+ when /\.css$/ then 'text/css'
+ else 'text/plain'
+ end
+ end
+ end
+ end
+ end
+end
+
View
15 lib/unittest_js/webrick_runner/servlets/inspection.rb
@@ -0,0 +1,15 @@
+module UnittestJS
+ module WEBrickRunner
+ module Servlet
+ class Inspection < Basic
+ def do_GET(req, res)
+ prevent_caching(res)
+ res['Content-Type'] = "application/json"
+ res.body = req.to_json
+ raise WEBrick::HTTPStatus::OK
+ end
+ end
+ end
+ end
+end
+
View
22 lib/unittest_js/webrick_runner/servlets/result.rb
@@ -0,0 +1,22 @@
+module UnittestJS
+ module WEBrickRunner
+ module Servlet
+ class Result < WEBrick::HTTPServlet::AbstractServlet
+ def do_GET(req, res)
+ prevent_caching(res)
+ queue.push(WEBrickRunner::TestResults.new(req))
+ raise WEBrick::HTTPStatus::OK
+ end
+
+ def queue
+ @options.first
+ end
+
+ def do_POST(req, res)
+ do_GET(req, res)
+ end
+ end
+ end
+ end
+end
+
View
13 lib/unittest_js/webrick_runner/servlets/slow.rb
@@ -0,0 +1,13 @@
+module UnittestJS
+ module WEBrickRunner
+ module Servlet
+ class Slow < Basic
+ def do_GET(req, res)
+ sleep(2)
+ super
+ end
+ end
+ end
+ end
+end
+
View
31 lib/unittest_js/webrick_runner/suite.rb
@@ -0,0 +1,31 @@
+module UnittestJS
+ module WEBrickRunner
+ class Suite
+ def initialize(browser, tests)
+ @browser = browser
+ @tests = tests
+ @results = SuiteResults.new(@browser)
+ end
+
+ def run(queue)
+ setup
+ @tests.each do |test|
+ test.run(@browser, @results, queue)
+ end
+ teardown
+ end
+
+ def setup
+ @browser.setup
+ puts "\nStarted tests in #{@browser}."
+ @t0 = Time.now
+ end
+
+ def teardown
+ print "\nFinished in #{Time.now - @t0} seconds."
+ print @results
+ @browser.teardown
+ end
+ end
+ end
+end
View
44 lib/unittest_js/webrick_runner/suite_results.rb
@@ -0,0 +1,44 @@
+module UnittestJS
+ module WEBrickRunner
+ class SuiteResults
+ attr_reader :browser, :tests, :assertions, :failures, :errors
+ def initialize(browser)
+ @browser = browser.to_s
+ @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
+ end
+end
View
35 lib/unittest_js/webrick_runner/test.rb
@@ -0,0 +1,35 @@
+module UnittestJS
+ module WEBrickRunner
+ class Test
+ def initialize(filename, testcases, options)
+ @filename = filename
+ @testcases = testcases
+ @options = options
+ end
+
+ def run(browser, results, queue)
+ browser.visit(url)
+ result = queue.pop
+ results << result
+ print result
+ end
+
+ def url
+ "http://localhost:#{@options.port}/#{@options.test_dir.name}/#{@filename}?#{params}"
+ end
+
+ def to_s
+ @filename
+ end
+
+ private
+ def params
+ {
+ :resultsURL => "http://localhost:#{@options.port}/results",
+ :t => "%.6f" % Time.now.to_f,
+ :tests => @testcases
+ }.map { |k, v| "#{k}=#{v}" if v }.compact.join('&')
+ end
+ end
+ end
+end
View
42 lib/unittest_js/webrick_runner/test_results.rb
@@ -0,0 +1,42 @@
+require 'uri'
+module UnittestJS
+ module WEBrickRunner
+ class TestResults
+ FAILURE = 'F'
+ ERROR = 'E'
+ SUCCESS = '.'
+
+ attr_reader :tests, :assertions, :failures, :errors
+
+ def initialize(req)
+ @req = req
+ @tests = req.query['tests'].to_i
+ @assertions = req.query['assertions'].to_i
+ @failures = req.query['failures'].to_i
+ @errors = req.query['errors'].to_i
+ end
+
+ def filename
+ @filename ||= URI::parse(@req.header['referer'].first).path
+ end
+
+ def error?
+ @errors > 0
+ end
+
+ def failure?
+ @failures > 0
+ end
+
+ def to_s
+ if error?
+ ERROR
+ elsif failure?
+ FAILURE
+ else
+ SUCCESS
+ end
+ end
+ end
+ end
+end
View
45 lib/unittest_js/webrick_runner/webrick_helper.rb
@@ -0,0 +1,45 @@
+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
View
20 templates/default.erb
@@ -0,0 +1,20 @@
+<!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" />
+ <%= lib_files %>
+ <%= css_fixtures %>
+ <%= js_fixtures %>
+ <%= test_file %>
+</head>
+<body>
+
+<!-- This file is programmatically generated. Do not modify it. -->
+
+<div id="testlog"></div>
+
+<%= html_fixtures %>
+</body>
+</html>
View
25 templates/doctypes.txt
@@ -0,0 +1,25 @@
+Version DTD list DOCTYPE Declaration in documents
+HTML 2.0 DTD
+<!DOCTYPE html PUBLIC "-//IETF//DTD HTML 2.0//EN">
+HTML 3.2 DTD
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+HTML 4.01 Strict, Transitional, Frameset
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
+ "http://www.w3.org/TR/html4/frameset.dtd">
+
+XHTML 1.0 Strict, Transitional, Frameset
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+
+XHTML 1.1 DTD
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
Please sign in to comment.
Something went wrong with that request. Please try again.