diff --git a/contrib/ruby/jasmine_runner.rb b/contrib/ruby/jasmine_runner.rb
index 633c89f6..0f0eb4f0 100644
--- a/contrib/ruby/jasmine_runner.rb
+++ b/contrib/ruby/jasmine_runner.rb
@@ -19,15 +19,39 @@ module Jasmine
port
end
- class RunAdapter
- def initialize(spec_files)
- p "spec_files: #{spec_files}"
+ def self.server_is_listening_on(hostname, port)
+ require 'socket'
+ begin
+ socket = TCPSocket.open(hostname, port)
+ rescue Errno::ECONNREFUSED
+ return false
+ end
+ socket.close
+ true
+ end
- @spec_files = spec_files
+ def self.wait_for_listener(port, name = "required process", seconds_to_wait = 10)
+ time_out_at = Time.now + seconds_to_wait
+ until server_is_listening_on "localhost", port
+ sleep 0.1
+ puts "Waiting for #{name} on #{port}..."
+ raise "#{name} didn't show up on port #{port} after #{seconds_to_wait} seconds." if Time.now > time_out_at
+ end
+ end
+
+ def self.kill_process_group(process_group_id, signal="TERM")
+ Process.kill signal, -process_group_id # negative pid means kill process group. (see man 2 kill)
+ end
+
+ class RunAdapter
+ def initialize(spec_files_or_proc)
+ @spec_files_or_proc = spec_files_or_proc
end
def call(env)
- spec_files = @spec_files
+ spec_files = @spec_files_or_proc
+ spec_files = spec_files.call if spec_files.respond_to?(:call)
+
body = ERB.new(File.read(File.join(File.dirname(__FILE__), "run.html"))).result(binding)
[
200,
@@ -38,11 +62,11 @@ module Jasmine
end
class SimpleServer
- def self.start(port, spec_dir, mappings)
+ def self.start(port, spec_files_or_proc, mappings)
require 'thin'
config = {
- '/run.html' => Jasmine::RunAdapter.new(spec_dir)
+ '/run.html' => Jasmine::RunAdapter.new(spec_files_or_proc)
}
mappings.each do |from, to|
config[from] = Rack::File.new(to)
@@ -50,7 +74,6 @@ module Jasmine
app = Rack::URLMap.new(config)
- server_port = Jasmine::find_unused_port
Thin::Server.start('0.0.0.0', port, app)
end
end
@@ -119,26 +142,6 @@ module Jasmine
stop_servers
end
- def server_is_listening_on(hostname, port)
- require 'socket'
- begin
- socket = TCPSocket.open(hostname, port)
- rescue Errno::ECONNREFUSED
- return false
- end
- socket.close
- true
- end
-
- def wait_for_listener(port, name = "required process", seconds_to_wait = 10)
- seconds_waited = 0
- until server_is_listening_on "localhost", port
- sleep 1
- puts "Waiting for #{name} on #{port}..."
- raise "#{name} didn't show up on port #{port} after #{seconds_to_wait} seconds." if (seconds_waited += 1) >= seconds_to_wait
- end
- end
-
def start_servers
@jasmine_server_port = Jasmine::find_unused_port
@selenium_server_port = Jasmine::find_unused_port
@@ -156,18 +159,14 @@ module Jasmine
end
puts "jasmine server started. pid is #{@jasmine_server_pid}"
- wait_for_listener(@selenium_server_port, "selenium server")
- wait_for_listener(@jasmine_server_port, "jasmine server")
- end
-
- def kill_process_group(process_group_id, signal="TERM")
- Process.kill signal, -process_group_id # negative pid means kill process group. (see man 2 kill)
+ Jasmine::wait_for_listener(@selenium_server_port, "selenium server")
+ Jasmine::wait_for_listener(@jasmine_server_port, "jasmine server")
end
def stop_servers
puts "shutting down the servers..."
- kill_process_group(@selenium_pid) if @selenium_pid
- kill_process_group(@jasmine_server_pid) if @jasmine_server_pid
+ Jasmine::kill_process_group(@selenium_pid) if @selenium_pid
+ Jasmine::kill_process_group(@jasmine_server_pid) if @jasmine_server_pid
end
def run
diff --git a/contrib/ruby/jasmine_runner_spec.rb b/contrib/ruby/jasmine_runner_spec.rb
new file mode 100644
index 00000000..83999de4
--- /dev/null
+++ b/contrib/ruby/jasmine_runner_spec.rb
@@ -0,0 +1,51 @@
+require 'spec'
+require 'open-uri'
+require File.dirname(__FILE__) + '/jasmine_runner'
+
+describe Jasmine::SimpleServer do
+ before do
+ @port = Jasmine::find_unused_port
+ end
+
+ after do
+ Jasmine::kill_process_group(@jasmine_server_pid) if @jasmine_server_pid
+ end
+
+ it "should start and print script tags" do
+ @jasmine_server_pid = fork do
+ Process.setpgrp
+ Jasmine::SimpleServer.start(@port, ["file1", "file2"], {})
+ exit! 0
+ end
+
+ Jasmine::wait_for_listener(@port)
+
+ run_html = open("http://localhost:#{@port}/run.html").read
+ run_html.should =~ //, "\n").gsub(/<\/?b>/, " ")
STDERR << stack_trace.gsub(/\(.*\)@http:\/\/localhost:[0-9]+\/specs\//, "/spec/")
STDERR << "\n"
end
diff --git a/contrib/ruby/run.html b/contrib/ruby/run.html
index 7ddbe809..0f3035d3 100644
--- a/contrib/ruby/run.html
+++ b/contrib/ruby/run.html
@@ -3,20 +3,32 @@
Jasmine suite
-
-
-
+
+
+
-
+
<% spec_files.each do |spec_file| %>
diff --git a/lib/TrivialReporter.js b/lib/TrivialReporter.js
index 5d10f5d0..5799d3fb 100644
--- a/lib/TrivialReporter.js
+++ b/lib/TrivialReporter.js
@@ -1,4 +1,6 @@
-jasmine.TrivialReporter = function() {
+jasmine.TrivialReporter = function(doc) {
+ this.document = doc || document;
+ this.suiteDivs = {};
};
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
@@ -25,37 +27,88 @@ jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarA
return el;
};
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+ var suites = runner.getAllSuites();
+
+ this.runnerDiv = this.createDom('div', { className: 'runner running' }, "Running...");
+ this.document.body.appendChild(this.runnerDiv);
+
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ var suiteDiv = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'runSpec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+ suite.description);
+ this.suiteDivs[suite.getFullName()] = suiteDiv;
+ var parentDiv = this.document.body;
+ if (suite.parentSuite) {
+ parentDiv = this.suiteDivs[suite.parentSuite.getFullName()];
+ }
+ parentDiv.appendChild(suiteDiv);
+ }
+};
+
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
- console.log(runner);
+ var results = runner.getResults();
+ var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+ this.runnerDiv.setAttribute("class", className);
+ var message = results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+ this.runnerDiv.replaceChild(this.document.createTextNode(message), this.runnerDiv.firstChild);
};
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
- console.log(suite);
+ var results = suite.getResults();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.totalCount == 0) { // todo: change this to check results.skipped
+ status = 'skipped';
+ }
+ this.suiteDivs[suite.getFullName()].className += " " + status;
};
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
- var specDiv = this.createDom('div', {
- className: spec.getResults().passed() ? 'spec passed' : 'spec failed'
- }, spec.getFullName());
+ var results = spec.getResults();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+ var specDiv = this.createDom('div', { className: 'spec ' + status },
+ this.createDom('a', { className: 'runSpec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+ spec.getFullName());
- var resultItems = spec.getResults().getItems();
+
+ var resultItems = results.getItems();
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
- if (!result.passed) {
- var resultMessageDiv = this.createDom('div', {className: 'resultMessage'});
+ if (!result.passed()) {
+ var resultMessageDiv = this.createDom('div', {className: 'resultMessage fail'});
resultMessageDiv.innerHTML = result.message; // todo: lame; mend
specDiv.appendChild(resultMessageDiv);
specDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
- document.body.appendChild(specDiv);
+ this.suiteDivs[spec.suite.getFullName()].appendChild(specDiv);
};
jasmine.TrivialReporter.prototype.log = function() {
console.log.apply(console, arguments);
};
+jasmine.TrivialReporter.prototype.getLocation = function() {
+ return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+ var paramMap = {};
+ var params = this.getLocation().search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ if (!paramMap["spec"]) return true;
+ return spec.getFullName().indexOf(paramMap["spec"]) == 0;
+};
+
//protect against console.log incidents
if (!("console" in window) || !("firebug" in console)) {
var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
diff --git a/lib/jasmine.css b/lib/jasmine.css
index 0c302a2c..6370d7a1 100644
--- a/lib/jasmine.css
+++ b/lib/jasmine.css
@@ -1,6 +1,5 @@
body {
font: 14px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
- padding-left: 40px;
}
h1 {
@@ -14,11 +13,74 @@ p {
padding-left: 20px;
}
-p.fail {
+.fail {
background: url( ../images/fail-16.png ) no-repeat;
+ padding-left: 20px;
color: red;
}
-p.fail_in_summary {
+.failInSummary {
color: red;
+}
+
+.runner {
+ border: 1px outset gray;
+ margin: 5px;
+ padding-left: 1em;
+}
+
+.runner.running {
+ background-color: yellow;
+}
+
+.suite {
+ border: 1px outset gray;
+ margin: 5px;
+ padding-left: 1em;
+}
+
+.suite.passed {
+ background-color: #cfc;
+}
+
+.suite.failed {
+ background-color: #fdd;
+}
+
+.spec {
+ margin: 5px;
+ clear: both;
+}
+
+.passed {
+ background-color: lightgreen;
+}
+
+.failed {
+ background-color: pink;
+}
+
+.skipped {
+ color: #777;
+ background-color: #eee;
+}
+
+.resultMessage {
+ white-space: pre;
+}
+
+.stackTrace {
+ white-space: pre;
+ font-size: .8em;
+ margin-left: 10px;
+ height: 5em;
+ overflow: auto;
+ border: 1px inset red;
+ padding: 1em;
+ background: #eef;
+}
+
+.runSpec {
+ margin-left: 5px;
+ float: right;
}
\ No newline at end of file
diff --git a/lib/jasmine.js b/lib/jasmine.js
index cf8862c8..0a013778 100644
--- a/lib/jasmine.js
+++ b/lib/jasmine.js
@@ -1084,6 +1084,16 @@ jasmine.Matchers.prototype.toNotContain = function(item) {
'Expected ' + jasmine.Matchers.pp(this.actual) + ' not to contain ' + jasmine.Matchers.pp(item) + ', but it does.');
};
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.report(this.actual < expected,
+ 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be less than ' + jasmine.Matchers.pp(expected) + ', but it was not.');
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.report(this.actual > expected,
+ 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be greater than ' + jasmine.Matchers.pp(expected) + ', but it was not.');
+};
+
/**
* Matcher that checks that the expected exception was thrown by the actual.
*
@@ -1366,7 +1376,8 @@ jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
jasmine.StringPrettyPrinter.prototype.append = function(value) {
this.string += value;
};
-jasmine.Queue = function() {
+jasmine.Queue = function(env) {
+ this.env = env;
this.blocks = [];
this.running = false;
this.index = 0;
@@ -1405,13 +1416,17 @@ jasmine.Queue.prototype.isRunning = function () {
jasmine.Queue.prototype._next = function () {
var self = this;
- self.offset = 0;
- self.index++;
- if (self.index < self.blocks.length) {
- self.blocks[self.index].execute(function () {self._next();});
- } else {
- self.finish();
- }
+ self.env.setTimeout(function () {
+ self.offset = 0;
+ self.index++;
+ if (self.index < self.blocks.length) {
+ self.blocks[self.index].execute(function () {
+ self._next();
+ });
+ } else {
+ self.finish();
+ }
+ }, 0);
};
jasmine.Queue.prototype.finish = function () {
@@ -1472,7 +1487,7 @@ jasmine.Reporters.reporter = function(callbacks) {
jasmine.Runner = function(env) {
var self = this;
self.env = env;
- self.queue = new jasmine.Queue();
+ self.queue = new jasmine.Queue(env);
};
jasmine.Runner.prototype.execute = function() {
@@ -1487,10 +1502,34 @@ jasmine.Runner.prototype.finishCallback = function() {
this.env.reporter.reportRunnerResults(this);
};
+
jasmine.Runner.prototype.add = function(block) {
this.queue.add(block);
};
+
+jasmine.Runner.prototype.getAllSuites = function() {
+ var suitesToReturn = [];
+
+ function addSuite(suite) {
+ suitesToReturn.push(suite);
+
+ for (var j = 0; j < suite.specs.length; j++) {
+ var spec = suite.specs[j];
+ if (spec instanceof jasmine.Suite) {
+ addSuite(spec);
+ }
+ }
+ }
+
+ for (var i = 0; i < this.suites.length; i++) {
+ var suite = this.suites[i];
+ addSuite(suite);
+ }
+
+ return suitesToReturn;
+};
+
jasmine.Runner.prototype.getResults = function() {
var results = new jasmine.NestedResults();
var runnerResults = this.queue.getResults();
@@ -1516,7 +1555,7 @@ jasmine.Spec = function(env, suite, description) {
spec.env = env;
spec.suite = suite;
spec.description = description;
- spec.queue = new jasmine.Queue();
+ spec.queue = new jasmine.Queue(env);
spec.finished = false;
spec.afterCallbacks = [];
@@ -1705,7 +1744,7 @@ jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
var self = this;
self.id = env.nextSuiteId_++;
self.description = description;
- self.queue = new jasmine.Queue();
+ self.queue = new jasmine.Queue(env);
self.parentSuite = parentSuite;
self.env = env;
self.beforeQueue = [];
diff --git a/spec/runner.html b/spec/runner.html
index 99db2a19..5d54a325 100644
--- a/spec/runner.html
+++ b/spec/runner.html
@@ -3,6 +3,7 @@
Jasmine Test Runner
+
@@ -35,33 +36,12 @@
jasmine.include('suites/RunnerTest.js', true);
jasmine.include('suites/SpecRunningTest.js', true);
jasmine.include('suites/SpyTest.js', true);
+ jasmine.include('suites/TrivialReporterTest.js', true);
-
+
-
+