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 =~ / - - + + + - + <% 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); - + - +