Compare commits

..

3 Commits

Author SHA1 Message Date
John Bintz
f832228325 qmake is being found, now get the versions 2011-09-09 15:56:24 -04:00
John Bintz
ade490ba14 what is going on? 2011-09-09 15:45:24 -04:00
John Bintz
64046bc308 some output for testing 2011-09-09 15:38:11 -04:00
187 changed files with 2062 additions and 3783 deletions

4
.gitignore vendored
View File

@ -13,7 +13,3 @@ hydra-runner.log
jhw-test jhw-test
.jhw-cache/ .jhw-cache/
_site/ _site/
jhw.*.html
coverage/
tmp/
cache dir/

View File

@ -1,30 +1,3 @@
## 0.7.2
* Improved finding of CoffeeScript spec line locations
* Improved Runner reporting of which expectations failed
* Initial vendored helper support, paving the way for Sprockets integratioon (maybe!)
* Add 1.9.3-rc1 test support and fixes
* Add console.peek()
## 0.7.1
* Bugfix for missing digest/sha1 import
## 0.7.0
* Major C++ cleanup, now much more modular
* Greatly improved object inspection and printing provided by jsDump and beautify-js
## 0.6.3
* Ensure Rubygems is available before doing version comparison
* Fix other build problems
* Better output for jQuery nodes
## 0.6.2
* Clean up C++ and test running
## 0.6.1 ## 0.6.1
* Ensure YAML is loaded before use * Ensure YAML is loaded before use

21
Gemfile
View File

@ -6,25 +6,12 @@ gemspec
gem 'rspec' gem 'rspec'
gem 'fakefs', :require => nil gem 'fakefs', :require => nil
gem 'guard' gem 'guard'
gem 'guard-rspec' gem 'guard-rspec'
gem 'guard-shell' gem 'guard-shell'
gem 'guard-coffeescript' gem 'guard-coffeescript'
gem 'guard-cucumber' gem 'growl'
gem 'rake', '0.8.7'
require 'rbconfig' gem 'mocha', '0.9.12'
case RbConfig::CONFIG['host_os']
when /darwin/
when /linux/
gem 'libnotify'
end
gem 'mocha'
gem 'cucumber'
gem 'jquery-rails', '~> 1.0.0'
gem 'ejs'
gem 'guard-jasmine-headless-webkit', :git => 'git://github.com/johnbintz/guard-jasmine-headless-webkit.git' gem 'guard-jasmine-headless-webkit', :git => 'git://github.com/johnbintz/guard-jasmine-headless-webkit.git'
gem 'facter'

View File

@ -3,8 +3,6 @@
# watch('file/path') { `command(s)` } # watch('file/path') { `command(s)` }
# #
guard 'coffeescript', :input => 'vendor/assets/coffeescripts', :output => 'vendor/assets/javascripts'
guard 'shell' do guard 'shell' do
watch(%r{ext/jasmine-webkit-specrunner/.*\.(cpp|h|pro|pri)}) { |m| watch(%r{ext/jasmine-webkit-specrunner/.*\.(cpp|h|pro|pri)}) { |m|
if !m[0]['moc_'] if !m[0]['moc_']
@ -22,20 +20,16 @@ guard 'rspec', :version => 2, :all_on_start => false do
watch('spec/spec_helper.rb') { "spec" } watch('spec/spec_helper.rb') { "spec" }
end end
guard 'cucumber', :cli => '-r features --format pretty' do
watch(%r{^features/.+\.feature$})
watch(%r{^features/support/.+$}) { 'features' }
watch(%r{^features/steps/(.+)_steps\.rb$}) { 'features' }
end
guard 'jasmine-headless-webkit', :all_on_start => false do guard 'jasmine-headless-webkit', :all_on_start => false do
watch(%r{^spec/javascripts/.+_spec\.coffee}) watch(%r{^spec/javascripts/.+_spec\.coffee})
watch(%r{^jasmine/(.+)\.coffee$}) { |m| "spec/javascripts/#{m[1]}_spec.coffee" } watch(%r{^jasmine/(.+)\.coffee$}) { |m| "spec/javascripts/#{m[1]}_spec.coffee" }
end end
def compile def compile
system %{cd ext/jasmine-webkit-specrunner && ruby extconf.rb} #system %{cd ext/jasmine-webkit-specrunner && ruby test.rb && ruby extconf.rb}
system %{cd ext/jasmine-webkit-specrunner && ruby test.rb && ruby extconf.rb}
end end
compile compile
guard 'coffeescript', :input => 'jasmine'

View File

@ -1,5 +1,3 @@
_This project is dead. You should use [Karma](http://karma-runner.github.io/) instead. I do._
# Jasmine Headless WebKit runner # Jasmine Headless WebKit runner
Run your specs at sonic boom speed! No pesky reload button or page rendering slowdowns! Run your specs at sonic boom speed! No pesky reload button or page rendering slowdowns!
@ -7,19 +5,6 @@ Run your specs at sonic boom speed! No pesky reload button or page rendering slo
http://johnbintz.github.com/jasmine-headless-webkit/ has the most up-to-date information on using http://johnbintz.github.com/jasmine-headless-webkit/ has the most up-to-date information on using
this project. You can see the source of that site on the gh-pages branch. this project. You can see the source of that site on the gh-pages branch.
## For those who want to hack on the project...
The best way to get everything running that you need for development and testing is
to use Guard:
``` bash
bundle install
bundle exec guard
... build Qt runner ...
... compile CoffeeScript to JS ...
... run RSpec and JHW ...
```
## License ## License
* Copyright (c) 2011 John Bintz * Copyright (c) 2011 John Bintz

View File

@ -14,24 +14,12 @@ require 'jasmine/headless/task'
Jasmine::Headless::Task.new Jasmine::Headless::Task.new
PLATFORMS = %w{1.9.2 1.9.3}
def rvm_bundle(command = '')
Bundler.with_clean_env do
system %{bash -c 'unset BUNDLE_BIN_PATH && unset BUNDLE_GEMFILE && rvm #{PLATFORMS.join(',')} do bundle #{command}'}
end
end
class SpecFailure < StandardError; end
class BundleFailure < StandardError; end
namespace :spec do namespace :spec do
desc "Run on three Rubies" desc "Run on three Rubies"
task :platforms do task :platforms do
rvm_bundle system %{rvm 1.8.7,1.9.2,ree ruby bundle}
rvm_bundle "exec rspec spec" system %{rvm 1.8.7,1.9.2,ree ruby bundle exec rake spec}
rvm_bundle "exec cucumber" raise StandardError.new if $?.exitstatus != 0
raise SpecError.new if $?.exitstatus != 0
end end
end end
@ -43,23 +31,3 @@ task :build_runner do
system %{ruby extconf.rb} system %{ruby extconf.rb}
end end
end end
desc "Generate vendored JS"
task :generate_js do
require 'sprockets'
source = 'vendor/assets/coffeescripts'
target = 'vendor/assets/javascripts'
env = Sprockets::Environment.new { |s| s.append_path 'vendor/assets/coffeescripts' }
Dir[File.join(File.expand_path(source), '*.coffee')].each do |file|
file_target = file.gsub(source, target).gsub('.coffee', '.js')
puts "#{file} => #{file_target}"
File.open(file_target, 'wb') do |fh|
fh.print env.find_asset(File.expand_path(file)).to_s
end
end
end

View File

@ -1,11 +1,38 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require 'rubygems' require 'rubygems'
require 'rainbow'
$: << File.expand_path('../../lib', __FILE__) def gem_dir
File.expand_path('../..', __FILE__)
end
$:.unshift(File.join(gem_dir, 'lib'))
require 'jasmine-headless-webkit' require 'jasmine-headless-webkit'
require 'coffee-script'
Jasmine::Headless::CommandLine.run! require 'jasmine/headless/errors'
require 'jasmine/headless/runner'
require 'jasmine/headless/options'
begin
options = Jasmine::Headless::Options.from_command_line
runner = Jasmine::Headless::Runner.new(options)
if options[:do_list]
files_list = Jasmine::FilesList.new(
:config => runner.jasmine_config
)
files_list.files.each { |file| puts file }
else
puts "Running Jasmine specs...".color(:white)
exit runner.run
end
rescue CoffeeScript::CompilationError
exit 1
rescue StandardError => e
$stderr.puts "[%s] %s (%s)" % [ "jasmine-headless-webkit".color(:red), e.message.color(:white), e.class.name.color(:yellow) ]
$stderr.puts e.backtrace.collect { |line| " #{line}" }.join("\n")
exit 1
end

View File

@ -1,2 +0,0 @@
default: -r features

5
dev-bin/hooks/pre-commit Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
bundle exec rake
if [ $? -ne 0 ]; then exit 1; fi

6
dev-bin/install-hooks Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
for i in $PWD/dev-bin/hooks/*; do
ln -sf $i .git/hooks/${i##*/}
done

View File

@ -0,0 +1,140 @@
#include "ConsoleOutput.h"
ConsoleOutput::ConsoleOutput() : QObject(),
showColors(false),
consoleLogUsed(false) {
outputIO = &std::cout;
}
void ConsoleOutput::passed(const QString &specDetail) {
green();
*outputIO << '.';
clear();
outputIO->flush();
consoleLogUsed = false;
successes.push(specDetail);
}
void ConsoleOutput::failed(const QString &specDetail) {
red();
*outputIO << 'F';
clear();
outputIO->flush();
consoleLogUsed = false;
failures.push(specDetail);
}
void ConsoleOutput::green() {
if (showColors) std::cout << "\033[0;32m";
}
void ConsoleOutput::clear() {
if (showColors) std::cout << "\033[m";
}
void ConsoleOutput::red() {
if (showColors) std::cout << "\033[0;31m";
}
void ConsoleOutput::yellow()
{
if (showColors) std::cout << "\033[0;33m";
}
void ConsoleOutput::errorLog(const QString &msg, int lineNumber, const QString &sourceID) {
red();
*outputIO << "[error] ";
clear();
*outputIO << qPrintable(sourceID) << ":" << lineNumber << " : " << qPrintable(msg);
*outputIO << std::endl;
}
void ConsoleOutput::internalLog(const QString &note, const QString &msg) {
red();
*outputIO << "[" << qPrintable(note) << "] ";
clear();
*outputIO << qPrintable(msg);
*outputIO << std::endl;
}
void ConsoleOutput::consoleLog(const QString &msg) {
if (!consoleLogUsed) {
*outputIO << std::endl;
consoleLogUsed = true;
}
green();
*outputIO << "[console] ";
if (msg.contains("\n"))
*outputIO << std::endl;
clear();
*outputIO << qPrintable(msg);
*outputIO << std::endl;
}
void ConsoleOutput::logSpecFilename(const QString &name) {
*outputIO << std::endl << std::endl;
red();
*outputIO << qPrintable(name) << std::endl;
clear();
}
void ConsoleOutput::logSpecResult(const QString &result) {
red();
*outputIO << " " << qPrintable(result) << std::endl;
clear();
}
void ConsoleOutput::reportFailure(const QString &totalTests, const QString &failedTests, const QString &duration) {
red();
*outputIO << std::endl << "FAIL: ";
formatTestResults(totalTests, failedTests, duration);
*outputIO << std::endl;
clear();
}
void ConsoleOutput::reportSuccess(const QString &totalTests, const QString &failedTests, const QString &duration) {
green();
*outputIO << std::endl << "PASS: ";
formatTestResults(totalTests, failedTests, duration);
*outputIO << std::endl;
clear();
}
void ConsoleOutput::reportSuccessWithJSErrors(const QString &totalTests, const QString &failedTests, const QString &duration) {
yellow();
*outputIO << std::endl << "PASS with JS errors: ";
formatTestResults(totalTests, failedTests, duration);
*outputIO << std::endl;
clear();
}
void ConsoleOutput::formatTestResults(const QString &totalTests, const QString &failedTests, const QString &duration) {
*outputIO << qPrintable(totalTests) << " ";
if (totalTests == "1") {
*outputIO << "test";
} else {
*outputIO << "tests";
}
*outputIO << ", ";
*outputIO << qPrintable(failedTests) << " ";
if (failedTests == "1") {
*outputIO << "failure";
} else {
*outputIO << "failures";
}
*outputIO << ", ";
*outputIO << qPrintable(duration) << " ";
if (duration == "1") {
*outputIO << "sec.";
} else {
*outputIO << "secs.";
}
}

View File

@ -0,0 +1,38 @@
#ifndef JHW_CONSOLE_OUTPUT
#define JHW_CONSOLE_OUTPUT
#include <QObject>
#include <iostream>
#include <QStack>
class ConsoleOutput : public QObject {
public:
ConsoleOutput();
void passed(const QString &specDetail);
void failed(const QString &specDetail);
void errorLog(const QString &msg, int lineNumber, const QString &sourceID);
void internalLog(const QString &note, const QString &msg);
void consoleLog(const QString &msg);
void logSpecFilename(const QString &name);
void logSpecResult(const QString &result);
void reportFailure(const QString &totalTests, const QString &failedTests, const QString &duration);
void reportSuccess(const QString &totalTests, const QString &failedTests, const QString &duration);
void reportSuccessWithJSErrors(const QString &totalTests, const QString &failedTests, const QString &duration);
std::ostream *outputIO;
QStack<QString> successes;
QStack<QString> failures;
bool showColors;
bool consoleLogUsed;
private:
void green();
void clear();
void red();
void yellow();
void formatTestResults(const QString &totalTests, const QString &failedTests, const QString &duration);
};
#endif

View File

@ -0,0 +1,129 @@
#include <QtTest/QtTest>
#include "ConsoleOutput.h"
#include "ConsoleOutput_test.h"
using namespace std;
ConsoleOutputTest::ConsoleOutputTest() : QObject() {}
void ConsoleOutputTest::testPassed() {
stringstream buffer;
ConsoleOutput output;
output.consoleLogUsed = true;
output.outputIO = &buffer;
output.passed("test");
QVERIFY(buffer.str() == ".");
QVERIFY(output.successes.size() == 1);
QVERIFY(output.failures.size() == 0);
QVERIFY(output.consoleLogUsed == false);
}
void ConsoleOutputTest::testFailed() {
stringstream buffer;
ConsoleOutput output;
output.consoleLogUsed = true;
output.outputIO = &buffer;
output.failed("test");
QVERIFY(buffer.str() == "F");
QVERIFY(output.successes.size() == 0);
QVERIFY(output.failures.size() == 1);
QVERIFY(output.consoleLogUsed == false);
}
void ConsoleOutputTest::testErrorLog() {
stringstream buffer;
ConsoleOutput output;
output.outputIO = &buffer;
output.errorLog("message", 1, "source");
QVERIFY(buffer.str() == "[error] source:1 : message\n");
}
void ConsoleOutputTest::testInternalLog() {
stringstream buffer;
ConsoleOutput output;
output.outputIO = &buffer;
output.internalLog("note", "message");
QVERIFY(buffer.str() == "[note] message\n");
}
void ConsoleOutputTest::testConsoleLog() {
stringstream buffer;
ConsoleOutput output;
output.consoleLogUsed = false;
output.outputIO = &buffer;
output.consoleLog("log");
QVERIFY(buffer.str() == "\n[console] log\n");
}
void ConsoleOutputTest::testConsoleLogUsed() {
stringstream buffer;
ConsoleOutput output;
output.consoleLogUsed = true;
output.outputIO = &buffer;
output.consoleLog("log");
QVERIFY(buffer.str() == "[console] log\n");
}
void ConsoleOutputTest::testLogSpecFilename() {
stringstream buffer;
ConsoleOutput output;
output.outputIO = &buffer;
output.logSpecFilename("whatever");
QVERIFY(buffer.str() == "\n\nwhatever\n");
}
void ConsoleOutputTest::testLogSpecResult() {
stringstream buffer;
ConsoleOutput output;
output.outputIO = &buffer;
output.logSpecResult("whatever");
QVERIFY(buffer.str() == " whatever\n");
}
void ConsoleOutputTest::testReportResultsFailedSingular() {
stringstream buffer;
ConsoleOutput output;
output.outputIO = &buffer;
output.reportFailure("1", "1", "1");
QVERIFY(buffer.str() == "\nFAIL: 1 test, 1 failure, 1 sec.\n");
}
void ConsoleOutputTest::testReportResultsFailedPlural() {
stringstream buffer;
ConsoleOutput output;
output.outputIO = &buffer;
output.reportFailure("2", "2", "2");
QVERIFY(buffer.str() == "\nFAIL: 2 tests, 2 failures, 2 secs.\n");
}
void ConsoleOutputTest::testReportResultsSucceeded() {
stringstream buffer;
ConsoleOutput output;
output.outputIO = &buffer;
output.reportSuccess("2", "2", "2");
QVERIFY(buffer.str() == "\nPASS: 2 tests, 2 failures, 2 secs.\n");
}
void ConsoleOutputTest::testReportResultsSucceededWithJSErrors() {
stringstream buffer;
ConsoleOutput output;
output.outputIO = &buffer;
output.reportSuccessWithJSErrors("2", "2", "2");
QVERIFY(buffer.str() == "\nPASS with JS errors: 2 tests, 2 failures, 2 secs.\n");
}
QTEST_MAIN(ConsoleOutputTest);

View File

@ -0,0 +1,32 @@
#ifndef JHW_TEST_CONSOLE_OUTPUT
#define JHW_TEST_CONSOLE_OUTPUT
#include <QtTest/QtTest>
#include <iostream>
#include <sstream>
#include <string>
#include "ConsoleOutput.h"
class ConsoleOutputTest : public QObject {
Q_OBJECT
public:
ConsoleOutputTest();
private slots:
void testPassed();
void testFailed();
void testErrorLog();
void testInternalLog();
void testConsoleLog();
void testConsoleLogUsed();
void testLogSpecFilename();
void testLogSpecResult();
void testReportResultsFailedSingular();
void testReportResultsFailedPlural();
void testReportResultsSucceeded();
void testReportResultsSucceededWithJSErrors();
};
#endif

View File

@ -0,0 +1,7 @@
include(common.pri)
include(test.pri)
SOURCES += ConsoleOutput_test.cpp
HEADERS += ConsoleOutput_test.h

View File

@ -4,12 +4,26 @@
#include "Page.h" #include "Page.h"
Page::Page() : QWebPage() {} Page::Page() : QWebPage(), confirmResult(true) {}
void Page::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID) { void Page::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID) {
emit handleError(message, lineNumber, sourceID); emit consoleLog(message, lineNumber, sourceID);
} }
void Page::javaScriptAlert(QWebFrame *, const QString &) {} bool Page::javaScriptConfirm(QWebFrame*, const QString&) {
bool Page::javaScriptConfirm(QWebFrame *, const QString &) { return false; } if (confirmResult) {
bool Page::javaScriptPrompt(QWebFrame *, const QString &, const QString &, QString *) { return false; } emit internalLog("TODO", "jasmine-headless-webkit can't handle confirm() yet! You should mock window.confirm for now. Returning true.");
return true;
} else {
confirmResult = true;
return false;
}
}
void Page::javaScriptAlert(QWebFrame*, const QString &msg) {
emit internalLog("alert", msg);
}
void Page::oneFalseConfirm() {
confirmResult = false;
}

View File

@ -8,13 +8,16 @@ class Page: public QWebPage {
Q_OBJECT Q_OBJECT
public: public:
Page(); Page();
void oneFalseConfirm();
signals:
void consoleLog(const QString &msg, int lineNumber, const QString &sourceID);
void internalLog(const QString &note, const QString &msg);
protected: protected:
void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID); void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID);
void javaScriptAlert(QWebFrame *, const QString &); bool javaScriptConfirm(QWebFrame *frame, const QString &msg);
bool javaScriptConfirm(QWebFrame *, const QString &); void javaScriptAlert(QWebFrame *frame, const QString &msg);
bool javaScriptPrompt(QWebFrame *, const QString &, const QString &, QString *); private:
signals: bool confirmResult;
void handleError(const QString & message, int lineNumber, const QString & sourceID);
}; };
#endif #endif

View File

@ -0,0 +1,43 @@
#include <QtTest/QtTest>
#include "Page.h"
#include "Page_test.h"
PageTest::PageTest() : QObject(), internalLogCalled(false) {
}
void PageTest::internalLog(const QString &, const QString &) {
internalLogCalled = true;
}
void PageTest::consoleLog(const QString &, int, const QString &) {
consoleLogCalled = true;
}
void PageTest::testJavaScriptConfirmWithLog() {
connect(&page, SIGNAL(internalLog(QString, QString)), this, SLOT(internalLog(QString, QString)));
internalLogCalled = false;
page.mainFrame()->setHtml("<script>confirm('test')</script>");
QVERIFY(internalLogCalled);
}
void PageTest::testJavaScriptConfirmWithoutLog() {
connect(&page, SIGNAL(internalLog(QString, QString)), this, SLOT(internalLog(QString, QString)));
internalLogCalled = false;
page.oneFalseConfirm();
page.mainFrame()->setHtml("<script>confirm('test')</script>");
QVERIFY(!internalLogCalled);
}
void PageTest::testJavaScriptConsoleMessage() {
connect(&page, SIGNAL(consoleLog(QString, int, QString)), this, SLOT(consoleLog(QString, int, QString)));
consoleLogCalled = false;
page.mainFrame()->setHtml("<script>cats();</script>");
QVERIFY(consoleLogCalled);
}
QTEST_MAIN(PageTest);

View File

@ -0,0 +1,27 @@
#ifndef JHW_TEST_PAGE
#define JHW_TEST_PAGE
#include <QtTest/QtTest>
#include "Page.h"
class PageTest : public QObject {
Q_OBJECT
public:
PageTest();
private:
bool internalLogCalled;
bool consoleLogCalled;
Page page;
private slots:
void internalLog(const QString &note, const QString &msg);
void consoleLog(const QString &message, int lineNumber, const QString &source);
void testJavaScriptConfirmWithLog();
void testJavaScriptConfirmWithoutLog();
void testJavaScriptConsoleMessage();
};
#endif

View File

@ -0,0 +1,6 @@
include(common.pri)
include(test.pri)
SOURCES += Page_test.cpp
HEADERS += Page_test.h

View File

@ -0,0 +1,54 @@
#include "ReportFileOutput.h"
using namespace std;
ReportFileOutput::ReportFileOutput() : QObject() {
reset();
}
void ReportFileOutput::reset() {
buffer = new stringstream();
outputIO = buffer;
}
void ReportFileOutput::passed(const QString &specDetail) {
*outputIO << "PASS||" << qPrintable(specDetail) << std::endl;
successes.push(specDetail);
}
void ReportFileOutput::failed(const QString &specDetail) {
*outputIO << "FAIL||" << qPrintable(specDetail) << std::endl;
failures.push(specDetail);
}
void ReportFileOutput::errorLog(const QString &msg, int lineNumber, const QString &sourceID) {
*outputIO << "ERROR||" << qPrintable(msg) << "||" << qPrintable(sourceID) << ":" << lineNumber << std::endl;
}
void ReportFileOutput::consoleLog(const QString &msg) {
*outputIO << "CONSOLE||" << qPrintable(msg) << std::endl;
}
void ReportFileOutput::internalLog(const QString &, const QString &) {}
void ReportFileOutput::logSpecFilename(const QString &) {}
void ReportFileOutput::logSpecResult(const QString &) {}
void ReportFileOutput::reportFailure(const QString &totalTests, const QString &failedTests, const QString &duration) {
reportTotals(totalTests, failedTests, duration, false);
}
void ReportFileOutput::reportSuccess(const QString &totalTests, const QString &failedTests, const QString &duration) {
reportTotals(totalTests, failedTests, duration, false);
}
void ReportFileOutput::reportSuccessWithJSErrors(const QString &totalTests, const QString &failedTests, const QString &duration) {
reportTotals(totalTests, failedTests, duration, true);
}
void ReportFileOutput::reportTotals(const QString &totalTests, const QString &failedTests, const QString &duration, bool hasJavaScriptError) {
*outputIO << "TOTAL||" << qPrintable(totalTests) << "||" << qPrintable(failedTests) << "||" << qPrintable(duration) << "||";
*outputIO << (hasJavaScriptError ? "T" : "F");
*outputIO << std::endl;
}

View File

@ -0,0 +1,37 @@
#ifndef JHW_REPORT_FILE_OUTPUT
#define JHW_REPORT_FILE_OUTPUT
#include <QObject>
#include <iostream>
#include <QStack>
#include <sstream>
using namespace std;
class ReportFileOutput : public QObject {
public:
ReportFileOutput();
void passed(const QString &specDetail);
void failed(const QString &specDetail);
void errorLog(const QString &msg, int lineNumber, const QString &sourceID);
void internalLog(const QString &note, const QString &msg);
void consoleLog(const QString &msg);
void logSpecFilename(const QString &name);
void logSpecResult(const QString &result);
void reportFailure(const QString &totalTests, const QString &failedTests, const QString &duration);
void reportSuccess(const QString &totalTests, const QString &failedTests, const QString &duration);
void reportSuccessWithJSErrors(const QString &totalTests, const QString &failedTests, const QString &duration);
void reset();
stringstream *buffer;
stringstream *outputIO;
QStack<QString> successes;
QStack<QString> failures;
private:
void reportTotals(const QString &totalTests, const QString &failedTests, const QString &duration, bool hasJavaScriptError);
};
#endif

View File

@ -0,0 +1,88 @@
#include <QtTest/QtTest>
#include "ReportFileOutput.h"
#include "ReportFileOutput_test.h"
using namespace std;
ReportFileOutputTest::ReportFileOutputTest() : QObject() {}
void ReportFileOutputTest::testPassed() {
stringstream buffer;
ReportFileOutput output;
output.outputIO = &buffer;
output.passed("test||done||file.js:23");
QVERIFY(buffer.str() == "PASS||test||done||file.js:23\n");
QVERIFY(output.successes.size() == 1);
QVERIFY(output.failures.size() == 0);
}
void ReportFileOutputTest::testFailed() {
stringstream buffer;
ReportFileOutput output;
output.outputIO = &buffer;
output.failed("test||done||file.js:23");
QVERIFY(buffer.str() == "FAIL||test||done||file.js:23\n");
QVERIFY(output.successes.size() == 0);
QVERIFY(output.failures.size() == 1);
}
void ReportFileOutputTest::testErrorLog() {
stringstream buffer;
ReportFileOutput output;
output.outputIO = &buffer;
output.errorLog("JS Error", 23, "file.js");
QVERIFY(buffer.str() == "ERROR||JS Error||file.js:23\n");
}
void ReportFileOutputTest::testConsoleLog() {
stringstream buffer;
ReportFileOutput output;
output.outputIO = &buffer;
output.consoleLog("Console");
QVERIFY(buffer.str() == "CONSOLE||Console\n");
}
void ReportFileOutputTest::testStubMethods() {
stringstream buffer;
ReportFileOutput output;
output.outputIO = &buffer;
output.internalLog("Internal", "Log");
output.logSpecFilename("Filename");
output.logSpecResult("REsult");
}
void ReportFileOutputTest::testReportFailure() {
stringstream buffer;
ReportFileOutput output;
output.outputIO = &buffer;
output.reportFailure("5", "2", "1.5");
QVERIFY(buffer.str() == "TOTAL||5||2||1.5||F\n");
}
void ReportFileOutputTest::testReportSuccess() {
stringstream buffer;
ReportFileOutput output;
output.outputIO = &buffer;
output.reportSuccess("5", "0", "1.5");
QVERIFY(buffer.str() == "TOTAL||5||0||1.5||F\n");
}
void ReportFileOutputTest::testReportSuccessWithJSErrors() {
stringstream buffer;
ReportFileOutput output;
output.outputIO = &buffer;
output.reportSuccessWithJSErrors("5", "0", "1.5");
QVERIFY(buffer.str() == "TOTAL||5||0||1.5||T\n");
}
QTEST_MAIN(ReportFileOutputTest);

View File

@ -0,0 +1,26 @@
#ifndef JHW_TEST_REPORT_FILE_OUTPUT
#define JHW_TEST_REPORT_FILE_OUTPUT
#include <QtTest/QtTest>
#include <iostream>
#include <sstream>
#include <string>
#include "ReportFileOutput.h"
class ReportFileOutputTest : public QObject {
Q_OBJECT
public:
ReportFileOutputTest();
private slots:
void testPassed();
void testFailed();
void testErrorLog();
void testConsoleLog();
void testStubMethods();
void testReportFailure();
void testReportSuccess();
void testReportSuccessWithJSErrors();
};
#endif

View File

@ -0,0 +1,7 @@
include(common.pri)
include(test.pri)
SOURCES += ReportFileOutput_test.cpp
HEADERS += ReportFileOutput_test.h

View File

@ -3,178 +3,181 @@
#include <QFile> #include <QFile>
#include <QTextStream> #include <QTextStream>
#include <iostream> #include <iostream>
#include <sstream>
#include <QQueue> #include <QQueue>
#include "Runner.h" #include "Runner.h"
#include "Page.h"
using namespace std; using namespace std;
Runner::Runner() : QObject() Runner::Runner() : QObject()
, runs(0) , m_runs(0)
, hasErrors(false) , hasErrors(false)
, _hasSpecFailure(false)
, usedConsole(false) , usedConsole(false)
, isFinished(false) , isFinished(false)
, useColors(false) , didFail(false)
, quiet(false)
{ {
page.settings()->enablePersistentStorage(); m_page.settings()->enablePersistentStorage();
ticker.setInterval(TIMER_TICK); m_ticker.setInterval(TIMER_TICK);
connect(&ticker, SIGNAL(timeout()), this, SLOT(timerEvent())); connect(&m_ticker, SIGNAL(timeout()), this, SLOT(timerEvent()));
connect(&page, SIGNAL(loadFinished(bool)), this, SLOT(watch(bool))); connect(&m_page, SIGNAL(loadFinished(bool)), this, SLOT(watch(bool)));
connect(&page, SIGNAL(handleError(const QString &, int, const QString &)), this, SLOT(handleError(const QString &, int, const QString &))); connect(&m_page, SIGNAL(consoleLog(QString, int, QString)), this, SLOT(errorLog(QString, int, QString)));
connect(page.mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(addJHW())); connect(&m_page, SIGNAL(internalLog(QString, QString)), this, SLOT(internalLog(QString, QString)));
connect(m_page.mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(addJHW()));
} }
void Runner::addFile(const QString &spec) { void Runner::addFile(const QString &spec) {
runnerFiles.enqueue(spec); runnerFiles.enqueue(spec);
} }
void Runner::go() { void Runner::go()
ticker.stop(); {
page.setPreferredContentsSize(QSize(1024, 600)); m_ticker.stop();
m_page.setPreferredContentsSize(QSize(1024, 600));
addJHW(); addJHW();
loadSpec(); loadSpec();
m_ticker.start();
} }
void Runner::addJHW() { void Runner::addJHW()
page.mainFrame()->addToJavaScriptWindowObject("JHW", this); {
} m_page.mainFrame()->addToJavaScriptWindowObject("JHW", this);
void Runner::handleError(const QString &message, int lineNumber, const QString &sourceID) {
QString messageEscaped = QString(message);
QString sourceIDEscaped = QString(sourceID);
messageEscaped.replace(QString("\""), QString("\\\""));
sourceIDEscaped.replace(QString("\""), QString("\\\""));
std::stringstream ss;
ss << lineNumber;
QString command("JHW._handleError(\"" + messageEscaped + "\", " + QString(ss.str().c_str()) + ", \"" + sourceIDEscaped + "\"); false;");
page.mainFrame()->evaluateJavaScript(command);
hasErrors = true;
} }
void Runner::loadSpec() void Runner::loadSpec()
{ {
QVectorIterator<QString> iterator(reportFiles); m_page.mainFrame()->load(runnerFiles.dequeue());
m_ticker.start();
while (iterator.hasNext()) {
QFile *outputFile = new QFile(iterator.next());
outputFile->open(QIODevice::WriteOnly);
outputFiles.enqueue(outputFile);
} }
QString runnerFile = runnerFiles.dequeue(); void Runner::watch(bool ok)
{
page.mainFrame()->load(runnerFile);
ticker.start();
}
void Runner::watch(bool ok) {
if (!ok) { if (!ok) {
std::cerr << "Can't load " << qPrintable(page.mainFrame()->url().toString()) << ", the file may be broken." << std::endl; std::cerr << "Can't load " << qPrintable(m_page.mainFrame()->url().toString()) << ", the file may be broken." << std::endl;
std::cerr << "Out of curiosity, did your tests try to submit a form and you haven't prevented that?" << std::endl; std::cerr << "Out of curiosity, did your tests try to submit a form and you haven't prevented that?" << std::endl;
std::cerr << "Try running your tests in your browser with the Jasmine server and see what happens." << std::endl; std::cerr << "Try running your tests in your browser with the Jasmine server and see what happens." << std::endl;
QApplication::instance()->exit(1); QApplication::instance()->exit(1);
return; return;
} }
page.mainFrame()->evaluateJavaScript(QString("JHW._setColors(") + (useColors ? QString("true") : QString("false")) + QString("); false;")); }
bool Runner::hasElement(const char *select)
{
return !m_page.mainFrame()->findFirstElement(select).isNull();
} }
void Runner::setColors(bool colors) { void Runner::setColors(bool colors) {
useColors = colors; consoleOutput.showColors = colors;
} }
void Runner::hasUsedConsole() { void Runner::reportFile(const QString &file) {
usedConsole = true; reportFileName = file;
} }
void Runner::hasError() { bool Runner::hasError() {
hasErrors = true; return hasErrors;
}
void Runner::hasSpecFailure() {
_hasSpecFailure = true;
}
void Runner::setReportFiles(QStack<QString> &files) {
reportFiles = files;
} }
void Runner::timerPause() { void Runner::timerPause() {
ticker.stop(); m_ticker.stop();
} }
void Runner::timerDone() { void Runner::timerDone() {
ticker.start(); m_ticker.start();
} }
void Runner::ping() { void Runner::specPassed(const QString &specDetail) {
runs = 0; consoleOutput.passed(specDetail);
reportFileOutput.passed(specDetail);
} }
void Runner::setSeed(QString s) { void Runner::specFailed(const QString &specDetail) {
seed = s; consoleOutput.failed(specDetail);
reportFileOutput.failed(specDetail);
didFail = true;
failedSpecs.push(specDetail);
} }
void Runner::setQuiet(bool q) { void Runner::errorLog(const QString &msg, int lineNumber, const QString &sourceID)
quiet = q; {
consoleOutput.errorLog(msg, lineNumber, sourceID);
reportFileOutput.errorLog(msg, lineNumber, sourceID);
hasErrors = true;
m_runs = 0;
m_ticker.start();
} }
QString Runner::getSeed() { void Runner::internalLog(const QString &note, const QString &msg) {
return seed; consoleOutput.internalLog(note, msg);
reportFileOutput.internalLog(note, msg);
} }
bool Runner::isQuiet() { void Runner::log(const QString &msg)
return quiet; {
usedConsole = true;
consoleOutput.consoleLog(msg);
reportFileOutput.consoleLog(msg);
} }
void Runner::print(const QString &fh, const QString &content) { void Runner::leavePageAttempt(const QString &msg)
if (fh == "stdout") { {
std::cout << qPrintable(content); consoleOutput.internalLog("error", msg);
std::cout.flush(); m_page.oneFalseConfirm();
hasErrors = true;
} }
if (fh == "stderr") { void Runner::printName(const QString &name)
std::cerr << qPrintable(content); {
std::cerr.flush(); consoleOutput.logSpecFilename(name);
} }
if (fh.contains("report")) { void Runner::printResult(const QString &result)
int index = (int)fh.split(":").last().toUInt(); {
consoleOutput.logSpecResult(result);
}
QTextStream ts(outputFiles.at(index)); void Runner::finishSuite(const QString &duration, const QString &total, const QString& failed)
ts << qPrintable(content); {
ts.flush(); if (didFail) {
consoleOutput.reportFailure(total, failed, duration);
reportFileOutput.reportFailure(total, failed, duration);
} else {
if (hasErrors) {
consoleOutput.reportSuccessWithJSErrors(total, failed, duration);
reportFileOutput.reportSuccessWithJSErrors(total, failed, duration);
} else {
consoleOutput.reportSuccess(total, failed, duration);
reportFileOutput.reportSuccess(total, failed, duration);
} }
} }
void Runner::finishSuite() { if (!reportFileName.isEmpty()) {
QFile outputFile(reportFileName);
outputFile.open(QIODevice::WriteOnly);
QTextStream ts(&outputFile);
ts << reportFileOutput.outputIO->str().c_str();
outputFile.close();
}
isFinished = true; isFinished = true;
runs = 0;
} }
void Runner::timerEvent() { void Runner::timerEvent()
++runs; {
++m_runs;
if (hasErrors && runs > 2) if (hasErrors && m_runs > 2)
QApplication::instance()->exit(1); QApplication::instance()->exit(1);
if (isFinished && runs > 2) { if (isFinished) {
while (!outputFiles.isEmpty()) {
outputFiles.dequeue()->close();
}
int exitCode = 0; int exitCode = 0;
if (_hasSpecFailure || hasErrors) { if (didFail || hasErrors) {
exitCode = 1; exitCode = 1;
} else { } else {
if (usedConsole) { if (usedConsole) {
@ -200,8 +203,8 @@ void Runner::timerEvent() {
} }
} }
if (runs > MAX_LOOPS) { if (m_runs > MAX_LOOPS) {
std::cerr << "WARNING: too many runs and the test is still not finished!" << std::endl; std::cout << "WARNING: too many runs and the test is still not finished!" << std::endl;
QApplication::instance()->exit(1); QApplication::instance()->exit(1);
} }
} }

View File

@ -7,67 +7,60 @@
#include <QTextStream> #include <QTextStream>
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <sstream>
#include <QQueue> #include <QQueue>
#include <QApplication>
#include "Page.h" #include "Page.h"
#include "ConsoleOutput.h"
#include "ReportFileOutput.h"
using namespace std; using namespace std;
class Runner: public QObject { class Runner: public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum { TIMER_TICK = 200, MAX_LOOPS = 50 }; enum { TIMER_TICK = 200, MAX_LOOPS = 25 };
Runner(); Runner();
void setColors(bool colors); void setColors(bool colors);
void setReportFiles(QStack<QString> &files); void reportFile(const QString &file);
void setSeed(QString s);
void setQuiet(bool q);
void addFile(const QString &spec); void addFile(const QString &spec);
void go(); void go();
public slots: public slots:
void log(const QString &msg);
bool hasError();
void leavePageAttempt(const QString &msg);
void timerPause(); void timerPause();
void timerDone(); void timerDone();
void hasUsedConsole(); void specPassed(const QString &specDetail);
void hasError(); void specFailed(const QString &specDetail);
void hasSpecFailure(); void printName(const QString &name);
void printResult(const QString &result);
bool isQuiet(); void finishSuite(const QString &duration, const QString &total, const QString& failed);
QString getSeed();
void print(const QString &fh, const QString &content);
void finishSuite();
void ping();
private slots: private slots:
void watch(bool ok); void watch(bool ok);
void errorLog(const QString &msg, int lineNumber, const QString &sourceID);
void internalLog(const QString &note, const QString &msg);
void addJHW(); void addJHW();
void timerEvent(); void timerEvent();
void handleError(const QString & message, int lineNumber, const QString & sourceID); protected:
bool hasElement(const char *select);
private: private:
Page page; Page m_page;
QTimer ticker; QTimer m_ticker;
int runs; int m_runs;
bool hasErrors; bool hasErrors;
bool _hasSpecFailure;
bool usedConsole; bool usedConsole;
bool isFinished; bool isFinished;
bool useColors; bool didFail;
bool quiet;
QString seed;
QQueue<QString> runnerFiles; QQueue<QString> runnerFiles;
QStack<QString> reportFiles; QStack<QString> failedSpecs;
ConsoleOutput consoleOutput;
ReportFileOutput reportFileOutput;
QString reportFileName;
void loadSpec(); void loadSpec();
QQueue<QFile *> outputFiles;
}; };
#endif #endif

View File

@ -1,8 +1,9 @@
TEMPLATE = app TEMPLATE = app
CONFIG -= app_bundle CONFIG -= app_bundle
QMAKE_INFO_PLIST = Info.plist QMAKE_INFO_PLIST = Info.plist
QMAKESPEC = macx-g++
QT += network webkit QT += network webkit
SOURCES = Page.cpp Runner.cpp SOURCES = Page.cpp Runner.cpp ConsoleOutput.cpp ReportFileOutput.cpp
HEADERS = Page.h Runner.h HEADERS = Page.h Runner.h ConsoleOutput.h ReportFileOutput.h

View File

@ -0,0 +1,19 @@
######################################################################
# Automatically generated by qmake (2.01a) Tue Aug 2 10:37:48 2011
######################################################################
TEMPLATE = app
TARGET =
DEPENDPATH += . HeadlessSpecRunner Test
INCLUDEPATH += . HeadlessSpecRunner Test
# Input
HEADERS += HeadlessSpecRunner/ConsoleOutput.h \
HeadlessSpecRunner/Page.h \
HeadlessSpecRunner/Runner.h \
Test/Page_test.h
SOURCES += specrunner.cpp \
HeadlessSpecRunner/ConsoleOutput.cpp \
HeadlessSpecRunner/Page.cpp \
HeadlessSpecRunner/Runner.cpp \
Test/Page_test.cpp

View File

@ -21,56 +21,43 @@
THE SOFTWARE. THE SOFTWARE.
*/ */
#include "Page.h"
#include "Runner.h" #include "Runner.h"
#if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0)
#include <getopt.h>
#endif
#if QT_VERSION < QT_VERSION_CHECK(4, 7, 0) #if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
#error Use Qt 4.7 or later version #error Use Qt 4.7 or later version
#endif #endif
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
bool showColors = false; char *reporter = NULL;
bool isQuiet = false; char showColors = false;
QString seed;
QStack<QString> reporterFiles;
int c, index; int c, index;
while ((c = getopt(argc, argv, "cr:s:q")) != -1) { while ((c = getopt(argc, argv, "cr:")) != -1) {
switch(c) { switch(c) {
case 'c': case 'c':
showColors = true; showColors = true;
break; break;
case 'q':
isQuiet = true;
break;
case 'r': case 'r':
reporterFiles.push(QString(optarg)); reporter = optarg;
break;
case 's':
seed = QString(optarg);
break; break;
} }
} }
if (optind == argc) { if (optind == argc) {
std::cerr << "Run Jasmine's SpecRunner headlessly" << std::endl << std::endl; std::cerr << "Run Jasmine's SpecRunner headlessly" << std::endl << std::endl;
std::cerr << " specrunner [-c] [-s seed] [-r report file ...] specrunner.html ..." << std::endl; std::cerr << " specrunner [-c] [-r <report file>] specrunner.html ..." << std::endl;
return 1; return 1;
} }
QApplication app(argc, argv); QApplication app(argc, argv);
app.setApplicationName("jasmine-headless-webkit"); app.setApplicationName("jasmine-headless-webkit");
Runner runner; Runner runner;
runner.setColors(showColors); runner.setColors(showColors);
runner.setQuiet(isQuiet);
runner.setReportFiles(reporterFiles); runner.reportFile(reporter);
runner.setSeed(seed);
for (index = optind; index < argc; index++) { for (index = optind; index < argc; index++) {
runner.addFile(QString::fromLocal8Bit(argv[index])); runner.addFile(QString::fromLocal8Bit(argv[index]));

View File

@ -2,4 +2,3 @@ include(common.pri)
SOURCES += specrunner.cpp SOURCES += specrunner.cpp
TARGET = jasmine-webkit-specrunner TARGET = jasmine-webkit-specrunner

View File

@ -0,0 +1,3 @@
TARGET = jhw-test
QT += testlib

View File

@ -0,0 +1,24 @@
#!/usr/bin/env ruby
require 'fileutils'
system %{make clean}
$: << File.expand_path("../../../lib", __FILE__)
require 'qt/qmake'
Dir['*_test.pro'].each do |test|
FileUtils.rm_f('jhw-test')
Qt::Qmake.make!('jasmine-headless-webkit', test)
if File.file?('jhw-test')
system %{./jhw-test}
if $?.exitstatus != 0
exit 1
end
else
exit 1
end
end

View File

@ -1,7 +0,0 @@
Feature: Bin - Failure
Scenario: Run a failing test
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/failure/failure.yml -f File:spec/report.txt`
Then the exit status should be 1
And the report file "spec/report.txt" should have 1 total, 1 failure, no console usage

View File

@ -1,7 +0,0 @@
Feature: Bin - Files
Scenario: List the files a test suite will use
Given I have a test suite
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success/success.yml -l`
Then the exit status should be 0
And the output should include "spec/jasmine/success/success.js"
And the output should include "spec/jasmine/success/success_spec.js"

View File

@ -1,18 +0,0 @@
Feature: Bin - Filtered Run - Both Runs
Background:
Given there is no existing "spec/report.txt" file
Scenario: Run one and fail
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_failure/filtered_failure.yml -f File:spec/report.txt ./spec/jasmine/filtered_failure/failure_spec.js`
Then the exit status should be 1
And the report file "spec/report.txt" should have 1 total, 1 failure, no console usage
Scenario: Run both and succeed
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_success/filtered_success.yml -f File:spec/report.txt ./spec/jasmine/filtered_success/success_one_spec.js`
Then the exit status should be 0
And the report file "spec/report.txt" should have 2 total, 0 failures, no console usage
Scenario: Run both with console.log
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_success_with_console/filtered_success.yml -f File:spec/report.txt ./spec/jasmine/filtered_success_with_console/success_one_spec.js`
Then the exit status should be 2
And the report file "spec/report.txt" should have 2 total, 0 failures, yes console usage

View File

@ -1,14 +0,0 @@
Feature: Bin - No Full Run
Background:
Given there is no existing "spec/report.txt" file
Scenario: Only run the filtered run
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_success/filtered_success.yml -f File:spec/report.txt --no-full-run ./spec/jasmine/filtered_success/success_one_spec.js`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failure, no console usage
Scenario: Use a file outside of the normal test run
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_success/filtered_success.yml -f File:spec/report.txt ./spec/jasmine/filtered_success/success_other_file.js`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failure, no console usage

View File

@ -1,7 +0,0 @@
Feature: Bin - Help
Scenario: Display the Help
Given I have a test suite
When I run `bin/jasmine-headless-webkit -h`
Then I should get help output
And the exit status should be 0

View File

@ -1,8 +0,0 @@
Feature: Bin - Quiet Messages
Scenario: Run a test that would cause a lot of messages to be displayed and silence them all
Given I have a test suite
When I run `bin/jasmine-headless-webkit -q -j spec/jasmine/noisy/noisy.yml`
Then the exit status should be 0
And the output should not include "[Skipping File]"
And the output should not include "You should mock"

View File

@ -1,8 +0,0 @@
Feature: Bin - Runner Out
Scenario: Write out the runner to a specified file
Given I have a test suite
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success/success.yml --runner-out spec/runner.html`
Then the exit status should be 0
And the file "spec/runner.html" should contain a JHW runner
When I delete the file "spec/runner.html"

View File

@ -1,7 +0,0 @@
Feature: Bin - Two spec files with same basename
Scenario: Run both files
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/two_spec_files_same_basename/jasmine.yml -f File:spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 2 total, 0 failures, no console usage

View File

@ -1,20 +0,0 @@
Feature: Bin - Success
Scenario: Run a successful test with long format definition
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit --seed 1234 -j spec/jasmine/success/success.yml --format File --out spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage
And the report file "spec/report.txt" should have seed 1234
Scenario: Run a successful test with legacy file reporting
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success/success.yml --report spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage
Scenario: Run a successful test with shortened format definition
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success/success.yml -f File:spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage

View File

@ -1,5 +0,0 @@
Feature: Bin - Success with JS Error
Scenario: Succeed
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success_with_error/success_with_error.yml -f File:spec/report.txt`
Then the exit status should be 1

View File

@ -1,7 +0,0 @@
Feature: Bin - Try to Leave Page
Scenario: Fail on trying to leave the page
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/leave_page/leave_page.yml -f File:spec/report.txt`
Then the exit status should be 1
And the report file "spec/report.txt" should exist

View File

@ -1,7 +0,0 @@
Feature: Bin - Try to Click A Button
Scenario: Don't leave page when clicking a button
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/click_button/click_button.yml -f File:spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 0 total, 0 failures, no console usage

View File

@ -1,11 +0,0 @@
Feature: Two files from source files
Scenario: Files are ordered directly
Given I have a test suite
When I run `bin/jasmine-headless-webkit -j spec/jasmine/two_files_from_src_files/jasmine.yml -l`
Then the exit status should be 0
And the following files should be loaded in order:
| vendor/vendor-file.js |
| vendor/vendor.js |
| app/app-file.js |
| app/app.js |

View File

@ -1,7 +0,0 @@
Feature: Bin - With CoffeeScript error
Scenario: Fail on CoffeeScript error
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/coffeescript_error/coffeescript_error.yml -f File:spec/report.txt`
Then the exit status should be 1
And the report file "spec/report.txt" should not exist

View File

@ -1,7 +0,0 @@
Feature: Use console.log
Scenario: Run a successful test that uses console.log
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/console_log/console_log.yml -f File:spec/report.txt`
Then the exit status should be 2
And the report file "spec/report.txt" should have 1 total, 0 failures, yes console usage

View File

@ -1,7 +0,0 @@
Feature: Bin - With Server
Scenario: Run using an HTTP server
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit --use-server -j spec/jasmine/success/success.yml -f File:spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage

View File

@ -1,7 +0,0 @@
Feature: Bin - With window.prompt()
Scenario: Alert the user that window.prompt() needs to be stubbed
Given I have a test suite
When I run `bin/jasmine-headless-webkit -j spec/jasmine/window_prompt/window_prompt.yml`
Then the exit status should be 0
And the output should include "You should mock window.prompt"

View File

@ -1,24 +0,0 @@
Feature: Reporters
In order to allow for multiple types of output
I should be able to
Manage reporters and decide which ones to use
Scenario: Use default reporters
Given I have the default runner options
When I get a runner
And I get a template writer
Then the template should use the "Console" reporter to "stdout"
And the command to run the runner should not include a report file
Scenario: Use a file reporter
Given I have the default runner options
And I have the following reporters:
| Name | File |
| Console | |
| File | file |
When I get a runner
And I get a template writer
Then the template should use the "Console" reporter to "stdout"
And the template should use the "File" reporter to "report:0"
And the command to run the runner should include the report file "file"

View File

@ -1,34 +0,0 @@
Feature: Using the Runner directly
Scenario: Succeed
Given I have the following runner options:
"""
:jasmine_config: spec/jasmine/success/success.yml
:reporters:
- [ 'File', 'spec/report.txt' ]
"""
When I get a runner
And I run the runner
Then the runner should have an exit status of 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage
Scenario: JavaScript Error
Given I have the following runner options:
"""
:jasmine_config: spec/jasmine/success_with_error/success_with_error.yml
"""
When I get a runner
And I run the runner
Then the runner should have an exit status of 1
Scenario: Failure
Given I have the following runner options:
"""
:jasmine_config: spec/jasmine/failure/failure.yml
:reporters:
- [ 'File', 'spec/report.txt' ]
"""
When I get a runner
And I run the runner
Then the runner should have an exit status of 1
And the report file "spec/report.txt" should have 1 total, 1 failure, no console usage

View File

@ -1,2 +0,0 @@
Given /^I have a test suite$/ do
end

View File

@ -1,4 +0,0 @@
Given /^there is no existing "([^"]*)" file$/ do |file|
FileUtils.rm_rf file
end

View File

@ -1,4 +0,0 @@
Given /^I have the default runner options$/ do
@options = Jasmine::Headless::Options.new
end

View File

@ -1,10 +0,0 @@
Given /^I have the following reporters:$/ do |table|
@options[:reporters] = []
table.hashes.each do |hash|
reporter = [ hash['Name'] ]
reporter << hash['File'] if !hash['File'].empty?
@options[:reporters] << reporter
end
end

View File

@ -1,3 +0,0 @@
Given /^I have the following runner options:$/ do |string|
@options = YAML.load(string)
end

View File

@ -1,3 +0,0 @@
Then /^the exit status should be (\d+)$/ do |exitstatus|
$?.exitstatus.should == exitstatus.to_i
end

View File

@ -1,4 +0,0 @@
Then /^the file "([^"]*)" should contain a JHW runner$/ do |file|
File.read(file).should include('jasmine.HeadlessReporter')
end

View File

@ -1,10 +0,0 @@
Then /^the following files should be loaded in order:$/ do |table|
files = table.raw.flatten
@output.lines.collect(&:strip).each do |line|
files.shift if line[files.first]
end
files.should be_empty
end

View File

@ -1,4 +0,0 @@
Then /^I should get help output$/ do
@output.should include("Usage:")
end

View File

@ -1,3 +0,0 @@
Then /^the output should include "([^"]*)"$/ do |string|
@output.should include(string)
end

View File

@ -1,4 +0,0 @@
Then /^the output should not include "([^"]*)"$/ do |string|
@output.should_not include(string)
end

View File

@ -1,4 +0,0 @@
Then /^the report file "([^"]*)" should not exist$/ do |file|
File.file?(file).should be_false
end

View File

@ -1,3 +0,0 @@
Then /^the report file "([^"]*)" should exist$/ do |file|
File.file?(file).should be_true
end

View File

@ -1,7 +0,0 @@
Then /^the report file "(.*)" should have (\d+) total, (\d+) failures?, (no|yes) console usage$/ do |file, total, failures, console_usage|
report = Jasmine::Headless::Report.load(file)
report.total.should == total.to_i
report.failed.should == failures.to_i
report.has_used_console?.should == (console_usage == 'yes')
end

View File

@ -1,4 +0,0 @@
Then /^the report file "([^"]*)" should have seed (\d+)$/ do |file, seed|
report = Jasmine::Headless::Report.load(file)
report.seed.should == seed.to_i
end

View File

@ -1,3 +0,0 @@
Then /^the runner should have an exit status of (\d+)$/ do |exit_status|
@result.should == exit_status.to_i
end

View File

@ -1,4 +0,0 @@
Then /^the command to run the runner should include the report file "([^"]*)"$/ do |file|
@runner.jasmine_command.should include("-r #{file}")
end

View File

@ -1,3 +0,0 @@
Then /^the command to run the runner should not include a report file$/ do
@runner.jasmine_command.should_not include('-r')
end

View File

@ -1,6 +0,0 @@
Then /^the template should use the "([^"]*)" reporter to "([^"]*)"$/ do |reporter, target|
output = @template_writer.render
output.should include(%{jasmine.HeadlessReporter.#{reporter}("#{target}")})
end

View File

@ -1,3 +0,0 @@
When /^I delete the file "([^"]*)"$/ do |file|
FileUtils.rm_f(file)
end

View File

@ -1,4 +0,0 @@
When /^I get a runner$/ do
@runner = Jasmine::Headless::Runner.new(@options)
end

View File

@ -1,4 +0,0 @@
When /^I get a template writer$/ do
@template_writer = Jasmine::Headless::TemplateWriter.new(@runner)
end

View File

@ -1,4 +0,0 @@
When /^I run `(.*)`$/ do |command|
@output = `#{command}`
end

View File

@ -1,4 +0,0 @@
When /^I run the runner$/ do
@result = @runner.run
end

View File

@ -1,7 +0,0 @@
require 'jasmine-headless-webkit'
After do
FileUtils.rm_f 'spec/report.txt'
FileUtils.rm_f 'spec/runner.html'
end

View File

@ -20,11 +20,8 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"] s.require_paths = ["lib"]
s.add_runtime_dependency 'jasmine-core' s.add_dependency 'jasmine-core', '~>1.1.beta'
s.add_runtime_dependency 'coffee-script' s.add_dependency 'coffee-script', '>= 2.2'
s.add_runtime_dependency 'rainbow' s.add_dependency 'rainbow'
s.add_runtime_dependency 'multi_json', '>= 1.2.0' s.add_dependency 'multi_json'
s.add_runtime_dependency 'sprockets'
s.add_runtime_dependency 'sprockets-vendor_gems'
end end

View File

@ -0,0 +1,120 @@
if !jasmine?
throw new Error("jasmine not laoded!")
# Jasmine extensions
getSplitName = (parts) ->
parts.push(String(@description).replace(/[\n\r]/g, ' '))
parts
jasmine.Suite.prototype.getSuiteSplitName = ->
this.getSplitName(if @parentSuite then @parentSuite.getSuiteSplitName() else [])
jasmine.Spec.prototype.getSpecSplitName = ->
this.getSplitName(@suite.getSuiteSplitName())
jasmine.Suite.prototype.getSplitName = getSplitName
jasmine.Spec.prototype.getSplitName = getSplitName
jasmine.Spec.prototype.getJHWSpecInformation = ->
parts = this.getSpecSplitName()
specLineInfo = HeadlessReporterResult.findSpecLine(parts)
if specLineInfo.file
parts.push("#{specLineInfo.file}:#{specLineInfo.lineNumber}")
else
parts.push('')
parts.join("||")
if !jasmine.WaitsBlock.prototype._execute
jasmine.WaitsBlock.prototype._execute = jasmine.WaitsBlock.prototype.execute
jasmine.WaitsForBlock.prototype._execute = jasmine.WaitsForBlock.prototype.execute
pauseAndRun = (onComplete) ->
JHW.timerPause()
this._execute ->
JHW.timerDone()
onComplete()
jasmine.WaitsBlock.prototype.execute = pauseAndRun
jasmine.WaitsForBlock.prototype.execute = pauseAndRun
# Try to get the line number of a failed spec
class window.HeadlessReporterResult
constructor: (@name, @splitName) ->
@results = []
addResult: (message) ->
@results.push(message)
print: ->
output = @name
bestChoice = HeadlessReporterResult.findSpecLine(@splitName)
output += " (#{bestChoice.file}:#{bestChoice.lineNumber})" if bestChoice.file
JHW.printName(output)
for result in @results
JHW.printResult(result)
@findSpecLine: (splitName) ->
bestChoice = { accuracy: 0, file: null, lineNumber: null }
for file, lines of HeadlessReporterResult.specLineNumbers
index = 0
lineNumber = 0
while newLineNumberInfo = lines[splitName[index]]
if newLineNumberInfo.length == 0
lineNumber = newLineNumberInfo[0]
else
lastLine = null
for line in newLineNumberInfo
lastLine = line
break if line > lineNumber
lineNumber = lastLine
index++
if index > bestChoice.accuracy
bestChoice = { accuracy: index, file: file, lineNumber: lineNumber }
bestChoice
# The reporter itself.
class jasmine.HeadlessReporter
constructor: (@callback = null) ->
@results = []
@failedCount = 0
@length = 0
reportRunnerResults: (runner) ->
return if this.hasError()
for result in @results
result.print()
this.callback() if @callback
JHW.finishSuite((new Date() - @startTime) / 1000.0, @length, @failedCount)
reportRunnerStarting: (runner) ->
@startTime = new Date()
reportSpecResults: (spec) ->
return if this.hasError()
results = spec.results()
@length++
if results.passed()
JHW.specPassed(spec.getJHWSpecInformation())
else
JHW.specFailed(spec.getJHWSpecInformation())
@failedCount++
failureResult = new HeadlessReporterResult(spec.getFullName(), spec.getSpecSplitName())
for result in results.getItems()
if result.type == 'expect' and !result.passed_
failureResult.addResult(result.message)
@results.push(failureResult)
reportSpecStarting: (spec) ->
if this.hasError()
spec.finish()
spec.suite.finish()
reportSuiteResults: (suite) ->
hasError: ->
JHW.hasError()

View File

@ -0,0 +1,167 @@
(function() {
var getSplitName, pauseAndRun;
if (!(typeof jasmine !== "undefined" && jasmine !== null)) {
throw new Error("jasmine not laoded!");
}
getSplitName = function(parts) {
parts.push(String(this.description).replace(/[\n\r]/g, ' '));
return parts;
};
jasmine.Suite.prototype.getSuiteSplitName = function() {
return this.getSplitName(this.parentSuite ? this.parentSuite.getSuiteSplitName() : []);
};
jasmine.Spec.prototype.getSpecSplitName = function() {
return this.getSplitName(this.suite.getSuiteSplitName());
};
jasmine.Suite.prototype.getSplitName = getSplitName;
jasmine.Spec.prototype.getSplitName = getSplitName;
jasmine.Spec.prototype.getJHWSpecInformation = function() {
var parts, specLineInfo;
parts = this.getSpecSplitName();
specLineInfo = HeadlessReporterResult.findSpecLine(parts);
if (specLineInfo.file) {
parts.push("" + specLineInfo.file + ":" + specLineInfo.lineNumber);
} else {
parts.push('');
}
return parts.join("||");
};
if (!jasmine.WaitsBlock.prototype._execute) {
jasmine.WaitsBlock.prototype._execute = jasmine.WaitsBlock.prototype.execute;
jasmine.WaitsForBlock.prototype._execute = jasmine.WaitsForBlock.prototype.execute;
pauseAndRun = function(onComplete) {
JHW.timerPause();
return this._execute(function() {
JHW.timerDone();
return onComplete();
});
};
jasmine.WaitsBlock.prototype.execute = pauseAndRun;
jasmine.WaitsForBlock.prototype.execute = pauseAndRun;
}
window.HeadlessReporterResult = (function() {
function HeadlessReporterResult(name, splitName) {
this.name = name;
this.splitName = splitName;
this.results = [];
}
HeadlessReporterResult.prototype.addResult = function(message) {
return this.results.push(message);
};
HeadlessReporterResult.prototype.print = function() {
var bestChoice, output, result, _i, _len, _ref, _results;
output = this.name;
bestChoice = HeadlessReporterResult.findSpecLine(this.splitName);
if (bestChoice.file) {
output += " (" + bestChoice.file + ":" + bestChoice.lineNumber + ")";
}
JHW.printName(output);
_ref = this.results;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
result = _ref[_i];
_results.push(JHW.printResult(result));
}
return _results;
};
HeadlessReporterResult.findSpecLine = function(splitName) {
var bestChoice, file, index, lastLine, line, lineNumber, lines, newLineNumberInfo, _i, _len, _ref;
bestChoice = {
accuracy: 0,
file: null,
lineNumber: null
};
_ref = HeadlessReporterResult.specLineNumbers;
for (file in _ref) {
lines = _ref[file];
index = 0;
lineNumber = 0;
while (newLineNumberInfo = lines[splitName[index]]) {
if (newLineNumberInfo.length === 0) {
lineNumber = newLineNumberInfo[0];
} else {
lastLine = null;
for (_i = 0, _len = newLineNumberInfo.length; _i < _len; _i++) {
line = newLineNumberInfo[_i];
lastLine = line;
if (line > lineNumber) {
break;
}
}
lineNumber = lastLine;
}
index++;
}
if (index > bestChoice.accuracy) {
bestChoice = {
accuracy: index,
file: file,
lineNumber: lineNumber
};
}
}
return bestChoice;
};
return HeadlessReporterResult;
})();
jasmine.HeadlessReporter = (function() {
function HeadlessReporter(callback) {
this.callback = callback != null ? callback : null;
this.results = [];
this.failedCount = 0;
this.length = 0;
}
HeadlessReporter.prototype.reportRunnerResults = function(runner) {
var result, _i, _len, _ref;
if (this.hasError()) {
return;
}
_ref = this.results;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
result = _ref[_i];
result.print();
}
if (this.callback) {
this.callback();
}
return JHW.finishSuite((new Date() - this.startTime) / 1000.0, this.length, this.failedCount);
};
HeadlessReporter.prototype.reportRunnerStarting = function(runner) {
return this.startTime = new Date();
};
HeadlessReporter.prototype.reportSpecResults = function(spec) {
var failureResult, result, results, _i, _len, _ref;
if (this.hasError()) {
return;
}
results = spec.results();
this.length++;
if (results.passed()) {
return JHW.specPassed(spec.getJHWSpecInformation());
} else {
JHW.specFailed(spec.getJHWSpecInformation());
this.failedCount++;
failureResult = new HeadlessReporterResult(spec.getFullName(), spec.getSpecSplitName());
_ref = results.getItems();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
result = _ref[_i];
if (result.type === 'expect' && !result.passed_) {
failureResult.addResult(result.message);
}
}
return this.results.push(failureResult);
}
};
HeadlessReporter.prototype.reportSpecStarting = function(spec) {
if (this.hasError()) {
spec.finish();
return spec.suite.finish();
}
};
HeadlessReporter.prototype.reportSuiteResults = function(suite) {};
HeadlessReporter.prototype.hasError = function() {
return JHW.hasError();
};
return HeadlessReporter;
})();
}).call(this);

View File

@ -1,3 +1,22 @@
require 'jasmine/headless' module Jasmine
autoload :FilesList, 'jasmine/files_list'
autoload :TemplateWriter, 'jasmine/template_writer'
module Headless
autoload :CoffeeScriptCache, 'jasmine/headless/coffee_script_cache'
autoload :SpecFileAnalyzer, 'jasmine/headless/spec_file_analyzer'
autoload :CacheableAction, 'jasmine/headless/cacheable_action'
autoload :VERSION, 'jasmine/headless/version'
autoload :Runner, 'jasmine/headless/runner'
autoload :Options, 'jasmine/headless/options'
autoload :Task, 'jasmine/headless/task'
autoload :Report, 'jasmine/headless/report'
autoload :ReportMessage, 'jasmine/headless/report_message'
end
end
require 'jasmine/headless/errors'
require 'jasmine/headless/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3 require 'jasmine/headless/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3

124
lib/jasmine/files_list.rb Normal file
View File

@ -0,0 +1,124 @@
require 'jasmine-core'
require 'iconv'
require 'time'
module Jasmine
class FilesList
attr_reader :files, :spec_files, :filtered_files, :spec_outside_scope
DEFAULT_FILES = [
File.join(Jasmine::Core.path, "jasmine.js"),
File.join(Jasmine::Core.path, "jasmine-html.js"),
File.expand_path('../../../jasmine/jasmine.headless-reporter.js', __FILE__),
File.expand_path('../../../js-lib/jsDump.js', __FILE__),
File.expand_path('../../../js-lib/beautify-html.js', __FILE__)
]
PLEASE_WAIT_IM_WORKING_TIME = 2
def initialize(options = {})
@options = options
@files = DEFAULT_FILES.dup
@filtered_files = @files.dup
@spec_outside_scope = false
@spec_files = []
use_config! if config?
end
def has_spec_outside_scope?
@spec_outside_scope
end
def filtered?
files != filtered_files
end
def files_to_html
to_html(files)
end
def filtered_files_to_html
to_html(filtered_files)
end
def spec_file_line_numbers
@spec_file_line_numbers ||= Hash[@spec_files.collect { |file|
if File.exist?(file)
if !(lines = Jasmine::Headless::SpecFileAnalyzer.for(file)).empty?
[ file, lines ]
end
else
nil
end
}.compact]
end
private
def to_html(files)
alert_time = Time.now + PLEASE_WAIT_IM_WORKING_TIME
files.collect { |file|
if alert_time && alert_time < Time.now
puts "Rebuilding cache, please wait..."
alert_time = nil
end
case File.extname(file)
when '.coffee'
begin
%{<script type="text/javascript">#{Jasmine::Headless::CoffeeScriptCache.for(file)}</script>}
rescue CoffeeScript::CompilationError => ne
puts "[%s] %s: %s" % [ 'coffeescript'.color(:red), file.color(:yellow), ne.message.to_s.color(:white) ]
raise ne
rescue StandardError => e
puts "[%s] Error in compiling one of the followng: %s" % [ 'coffeescript'.color(:red), files.join(' ').color(:yellow) ]
raise e
end
when '.js'
%{<script type="text/javascript" src="#{file}"></script>}
when '.css'
%{<link rel="stylesheet" href="#{file}" type="text/css" />}
end
}.flatten.compact.reject(&:empty?)
end
def spec_filter
@spec_filter ||= (@options[:only] ? @options[:only].collect { |path| Dir[path] }.flatten : [])
end
def use_config!
@filtered_files = @files.dup
data = @options[:config].dup
[ [ 'src_files', 'src_dir' ], [ 'stylesheets', 'src_dir' ], [ 'helpers', 'spec_dir' ], [ 'spec_files', 'spec_dir' ] ].each do |searches, root|
if data[searches]
data[searches].flatten.collect do |search|
path = search
path = File.join(data[root], path) if data[root]
found_files = Dir[path] - @files
@files += found_files
if searches == 'spec_files'
@spec_files += spec_filter.empty? ? found_files : (found_files & spec_filter)
end
@filtered_files += begin
if searches == 'spec_files'
@spec_outside_scope = ((spec_filter | found_files).sort != found_files.sort)
spec_filter.empty? ? found_files : (spec_filter || found_files)
else
found_files
end
end
end
end
end
end
def config?
@options[:config]
end
end
end

View File

@ -1,61 +0,0 @@
require 'pathname'
module Jasmine
module Headless
EXCLUDED_FORMATS = %w{less sass scss erb str}
autoload :CommandLine, 'jasmine/headless/command_line'
autoload :CoffeeScriptCache, 'jasmine/headless/coffee_script_cache'
autoload :SpecFileAnalyzer, 'jasmine/headless/spec_file_analyzer'
autoload :CacheableAction, 'jasmine/headless/cacheable_action'
autoload :VERSION, 'jasmine/headless/version'
autoload :Runner, 'jasmine/headless/runner'
autoload :Options, 'jasmine/headless/options'
autoload :Task, 'jasmine/headless/task'
autoload :FilesList, 'jasmine/headless/files_list'
autoload :UniqueAssetList, 'jasmine/headless/unique_asset_list'
autoload :TemplateWriter, 'jasmine/headless/template_writer'
autoload :FileChecker, 'jasmine/headless/file_checker'
autoload :CoffeeTemplate, 'jasmine/headless/coffee_template'
autoload :JSTemplate, 'jasmine/headless/js_template'
autoload :JSTTemplate, 'jasmine/headless/jst_template'
autoload :CSSTemplate, 'jasmine/headless/css_template'
autoload :NilTemplate, 'jasmine/headless/nil_template'
autoload :Report, 'jasmine/headless/report'
autoload :ReportMessage, 'jasmine/headless/report_message'
class << self
def root
@root ||= Pathname(File.expand_path('../../..', __FILE__))
end
def warn(message)
output.puts message if show_warnings?
end
def show_warnings=(show)
@show_warnings = show
end
def show_warnings?
@show_warnings = true if @show_warnings.nil?
@show_warnings
end
def output
$stdout
end
end
end
end
require 'jasmine/headless/errors'

View File

@ -54,19 +54,11 @@ module Jasmine::Headless
end end
def cache_file def cache_file
@cache_file ||= File.expand_path(relative_cache_file) + '.js' @cache_file ||= File.join(self.class.cache_dir, self.class.cache_type, Digest::SHA1.hexdigest(file))
end
def relative_cache_file
File.join(self.class.cache_dir, self.class.cache_type, file.gsub(Dir.pwd + '/', ''))
end end
def fresh? def fresh?
cached? && (File.mtime(file) < File.mtime(cache_file)) File.exist?(cache_file) && (File.mtime(file) < File.mtime(cache_file))
end
def cached?
File.exist?(cache_file)
end end
def action def action

View File

@ -1,36 +0,0 @@
require 'tilt/template'
require 'rainbow'
module Jasmine::Headless
class CoffeeTemplate < Tilt::Template
include Jasmine::Headless::FileChecker
self.default_mime_type = 'application/javascript'
def prepare ; end
def evaluate(scope, locals, &block)
if bad_format?(file)
alert_bad_format(file)
return ''
end
begin
cache = Jasmine::Headless::CoffeeScriptCache.new(file)
source = cache.handle
if cache.cached?
%{<script type="text/javascript" src="#{cache.cache_file}"></script>
<script type="text/javascript">window.CSTF['#{File.split(cache.cache_file).last}'] = '#{file}';</script>}
else
%{<script type="text/javascript">#{source}</script>}
end
rescue CoffeeScript::CompilationError => ne
puts "[%s] %s: %s" % [ 'coffeescript'.color(:red), file.color(:yellow), "#{ne.message}".color(:white) ]
raise ne
rescue StandardError => e
puts "[%s] Error in compiling file: %s" % [ 'coffeescript'.color(:red), file.color(:yellow) ]
raise e
end
end
end
end

View File

@ -1,31 +0,0 @@
module Jasmine::Headless
class CommandLine
class << self
def run!
require 'coffee-script'
require 'rainbow'
begin
options = Options.from_command_line
runner = Runner.new(options)
if options[:do_list]
FilesList.reset!
files_list = FilesList.new(:config => runner.jasmine_config)
files_list.files.each { |file| puts file }
else
exit runner.run
end
rescue CoffeeScript::CompilationError
exit 1
rescue StandardError => e
$stderr.puts "[%s] %s (%s)" % [ "jasmine-headless-webkit".color(:red), e.message.color(:white), e.class.name.color(:yellow) ]
$stderr.puts e.backtrace.collect { |line| " #{line}" }.join("\n")
exit 1
end
end
end
end
end

View File

@ -1,19 +0,0 @@
require 'tilt/template'
module Jasmine::Headless
class CSSTemplate < Tilt::Template
include Jasmine::Headless::FileChecker
self.default_mime_type = 'text/css'
def prepare ; end
def evaluate(scope, locals, &block)
if bad_format?(file)
alert_bad_format(file)
return ''
end
file ? %{<link rel="stylesheet" href="#{file}" type="text/css" />} : data
end
end
end

View File

@ -1,25 +0,0 @@
module Jasmine::Headless::FileChecker
def excluded_formats
::Jasmine::Headless::EXCLUDED_FORMATS
end
def bad_format?(file)
return if file.nil?
excluded_formats.any? do |format|
file[%r{\.#{format}(\.|$)}]
end
end
def alert_bad_format(file)
Jasmine::Headless.warn("[%s] %s: %s" % [ 'Skipping File'.color(:red), file.color(:yellow), "unsupported format".color(:white) ])
end
def alert_if_bad_format?(file)
if result = bad_format?(file)
alert_bad_format(file)
end
result
end
end

View File

@ -1,332 +0,0 @@
require 'jasmine-core'
require 'time'
require 'multi_json'
require 'set'
require 'sprockets'
require 'sprockets/engines'
require 'sprockets-vendor_gems'
module Jasmine::Headless
class FilesList
include FileChecker
class << self
def asset_paths
@asset_paths ||= Sprockets.find_gem_vendor_paths(:for => 'javascripts')
end
def reset!
@asset_paths = nil
@registered_engines = {}
# register haml-sprockets and handlebars_assets if it's available...
%w{haml-sprockets handlebars_assets}.each do |library|
begin
require library
rescue LoadError
end
end
if ENV['JHW_ENV']
begin
require 'bundler'
Bundler.require(ENV['JHW_ENV'].to_sym)
rescue LoadError
end
end
@sprockets_environment = nil
end
def registered_engines
@registered_engines ||= {}
end
def register_engine(file_extension, template_class)
registered_engines[file_extension] = template_class
end
def register_engines!
registered_engines.each do |file_extension, template_class|
Sprockets.register_engine file_extension, template_class
end
end
def default_files
%w{jasmine.js jasmine-html jasmine.css jasmine-extensions
intense headless_reporter_result jasmine.HeadlessReporter
jasmine.HeadlessReporter.ConsoleBase
jsDump beautify-html}
end
def extension_filter
extensions = (%w{.js .css} + Sprockets.engine_extensions)
%r{(#{extensions.join('|')})$}
end
end
PLEASE_WAIT_IM_WORKING_TIME = 2
attr_reader :options, :required_files, :potential_files_to_filter
def initialize(options = {})
@options = options
Kernel.srand(options[:seed]) if options[:seed]
@required_files = UniqueAssetList.new
@potential_files_to_filter = []
register_engines!
load_initial_assets
use_config if config?
end
def register_engines!
begin
require spec_helper
rescue LoadError
end
self.class.register_engines!
end
def load_initial_assets
self.class.default_files.each do |file|
begin
add_path(file)
rescue InvalidUniqueAsset => e
raise StandardError.new("Not an asset: #{file}")
end
end
(options[:reporters] || []).each do |reporter, identifier, file|
add_path("jasmine.HeadlessReporter.#{reporter}")
end
end
def files
required_files.flatten.collect { |asset| asset.pathname.to_s }.uniq
end
def spec_files
filter_for_requested_specs(
files.find_all { |file| spec_dir.any? { |dir| file[dir] } }
)
end
def filtered_files
filter_for_requested_specs(files)
end
def search_paths
return @search_paths if @search_paths
@search_paths = [ Jasmine::Core.path, Jasmine::Headless.root.join('vendor/assets/javascripts').to_s ]
@search_paths += self.class.asset_paths
@search_paths += src_dir.collect { |dir| File.expand_path(dir) }
@search_paths += asset_paths.collect { |dir| File.expand_path(dir) }
@search_paths += spec_dir.collect { |dir| File.expand_path(dir) }
@search_paths.uniq!
@search_paths
end
def sprockets_environment
return @sprockets_environment if @sprockets_environment
@sprockets_environment = Sprockets::Environment.new
search_paths.each { |path| @sprockets_environment.append_path(path) }
@sprockets_environment.unregister_postprocessor('application/javascript', Sprockets::SafetyColons)
# ...and unregister ones we don't want/need
@sprockets_environment.instance_eval do
EXCLUDED_FORMATS.each do |extension|
register_engine ".#{extension}", Jasmine::Headless::NilTemplate
end
register_engine '.coffee', Jasmine::Headless::CoffeeTemplate
register_engine '.js', Jasmine::Headless::JSTemplate
register_engine '.css', Jasmine::Headless::CSSTemplate
register_engine '.jst', Jasmine::Headless::JSTTemplate
end
@sprockets_environment
end
def has_spec_outside_scope?
if is_outside_scope = !spec_filter.empty?
is_outside_scope = spec_dir.any? do |dir|
spec_file_searches.any? do |search|
!spec_files.any? do |file|
target = File.join(dir, search)
File.fnmatch?(target, file) || File.fnmatch?(target.gsub(%{^**/}, ''), file)
end
end
end
end
is_outside_scope
end
def filtered?
files != filtered_files
end
def files_to_html
to_html(files)
end
def filtered_files_to_html
to_html(filtered_files)
end
def spec_file_line_numbers
@spec_file_line_numbers ||= Hash[spec_files.collect { |file|
if File.exist?(file)
if !(lines = Jasmine::Headless::SpecFileAnalyzer.for(file)).empty?
[ file, lines ]
end
else
nil
end
}.compact]
end
private
def to_html(files)
alert_time = Time.now + PLEASE_WAIT_IM_WORKING_TIME
files.collect do |file|
if alert_time && alert_time < Time.now
puts "Rebuilding cache, please wait..."
alert_time = nil
end
sprockets_environment.find_asset(file, :bundle => false).body
end.compact.reject(&:empty?)
end
def spec_filter
@spec_filter ||= (@options[:only] && @options[:only].collect { |path| expanded_dir(path) }.flatten) || []
end
SEARCH_ROOTS = {
'src_files' => 'src_dir',
'stylesheets' => 'src_dir',
'helpers' => 'spec_dir',
'spec_files' => 'spec_dir'
}
def use_config
@config = @options[:config].dup
@searches = {}
@potential_files_to_filter = []
%w{src_files stylesheets helpers spec_files}.each do |type|
if data = @config[type]
add_files(@searches[type] = data.flatten, type, send(SEARCH_ROOTS[type]))
end
end
end
def add_files(patterns, type, dirs)
patterns.each do |pattern|
dirs.collect { |dir| expanded_dir(File.join(dir, pattern)) }.each do |files|
files.sort! { |a, b| Kernel.rand(3) - 1 } if type == 'spec_files'
files.each do |path|
add_path(path, type)
end
end
end
if type == 'spec_files'
spec_filter.each { |path| add_path(path, type) }
end
end
def config?
@options[:config]
end
def expanded_dir(path)
file_list = Dir.glob(path).sort
file_list.find_all { |file|
file[extension_filter] && !alert_if_bad_format?(file)
}.collect {
|file| File.expand_path(file)
}.find_all {
|path| File.file?(path)
}
end
def extension_filter
self.class.extension_filter
end
def add_path(path, type = nil)
asset = sprockets_environment.find_asset(path)
@required_files << asset
if type == 'spec_files'
@potential_files_to_filter << path
end
end
def src_dir
@src_dir ||= config_dir_or_pwd('src_dir') + asset_paths
end
def spec_dir
@spec_dir ||= config_dir_or_pwd('spec_dir')
end
def asset_paths
@asset_paths ||= config_dir('asset_paths')
end
def spec_file_searches
@searches['spec_files']
end
def config_dir_or_pwd(dir)
if (found = config_dir(dir)).empty?
found = [ Dir.pwd ]
end
found
end
def config_dir(dir)
[ @options[:config] && @options[:config][dir] ].flatten.compact.collect { |dir| File.expand_path(dir) }
end
def filter_for_requested_specs(files)
files.find_all do |file|
if potential_files_to_filter.include?(file)
spec_filter.empty? || spec_filter.any? { |pattern| File.fnmatch?(pattern, file) }
else
true
end
end
end
def spec_helper
File.join(spec_dir, "helpers", "spec_helper")
end
end
end
module Jasmine::Headless
extend self
def register_engine(file_extension, template_class)
Jasmine::Headless::FilesList.register_engine(file_extension, template_class)
end
end

View File

@ -1,23 +0,0 @@
require 'tilt/template'
module Jasmine::Headless
class JSTemplate < Tilt::Template
include Jasmine::Headless::FileChecker
self.default_mime_type = 'application/javascript'
def prepare ; end
def evaluate(scope, locals, &block)
if bad_format?(file)
alert_bad_format(file)
return ''
end
if data[%r{^<script type="text/javascript"}]
data
else
file ? %{<script type="text/javascript" src="#{file}"></script>} : data
end
end
end
end

View File

@ -1,15 +0,0 @@
require 'sprockets/jst_processor'
module Jasmine::Headless
class JSTTemplate < Sprockets::JstProcessor
include Jasmine::Headless::FileChecker
def evaluate(*args)
if bad_format?(file)
alert_bad_format(file)
return ''
end
%{<script type="text/javascript">#{super}</script>}
end
end
end

View File

@ -1,14 +0,0 @@
require 'tilt/template'
require 'rainbow'
module Jasmine::Headless
class NilTemplate < Tilt::Template
def prepare ; end
def evaluate(scope, locals, &block)
return ''
end
end
end

View File

@ -11,22 +11,16 @@ module Jasmine
DEFAULT_OPTIONS = { DEFAULT_OPTIONS = {
:colors => false, :colors => false,
:remove_html_file => true, :remove_html_file => true,
:runner_output_filename => false,
:jasmine_config => 'spec/javascripts/support/jasmine.yml', :jasmine_config => 'spec/javascripts/support/jasmine.yml',
:report => false,
:do_list => false, :do_list => false,
:full_run => true, :full_run => true,
:enable_cache => true, :enable_cache => true,
:files => [], :files => []
:reporters => [ [ 'Console' ] ],
:quiet => false,
:use_server => false,
:server_port => nil
} }
DEFAULTS_FILE = File.join(Dir.pwd, '.jasmine-headless-webkit') DEFAULTS_FILE = File.join(Dir.pwd, '.jasmine-headless-webkit')
GLOBAL_DEFAULTS_FILE = File.expand_path('~/.jasmine-headless-webkit') GLOBAL_DEFAULTS_FILE = File.expand_path("~/#{DEFAULTS_FILE}")
REPORT_DEPRECATED_MESSAGE = "--report is deprecated. Use --format HeadlessFileReporter --out <filename>"
def self.from_command_line def self.from_command_line
options = new options = new
@ -37,10 +31,7 @@ module Jasmine
def initialize(opts = {}) def initialize(opts = {})
@options = DEFAULT_OPTIONS.dup @options = DEFAULT_OPTIONS.dup
srand
@options[:seed] = rand(10000)
read_defaults_files read_defaults_files
opts.each { |k, v| @options[k] = v if v } opts.each { |k, v| @options[k] = v if v }
end end
@ -59,34 +50,13 @@ module Jasmine
when '--keep' when '--keep'
@options[:remove_html_file] = false @options[:remove_html_file] = false
when '--report' when '--report'
warn REPORT_DEPRECATED_MESSAGE @options[:report] = arg
add_reporter('File', arg)
add_reporter('Console')
when '--runner-out'
@options[:runner_output_filename] = arg
when '--jasmine-config', '-j' when '--jasmine-config', '-j'
@options[:jasmine_config] = arg @options[:jasmine_config] = arg
when '--no-full-run' when '--no-full-run'
@options[:full_run] = false @options[:full_run] = false
when '--list', '-l' when '--list', '-l'
@options[:do_list] = true @options[:do_list] = true
when '--quiet', '-q'
@options[:quiet] = true
when '--seed'
@options[:seed] = arg.to_i
when '--format', '-f'
add_reporter(arg)
when '--use-server'
@options[:use_server] = true
when '--server-port'
@options[:server_port] = arg.to_i
when '--out'
add_reporter_file(arg)
when '-h', '--help'
print_help
exit
end end
end end
@ -103,103 +73,16 @@ module Jasmine
[ '--colors', '-c', GetoptLong::NO_ARGUMENT ], [ '--colors', '-c', GetoptLong::NO_ARGUMENT ],
[ '--no-colors', GetoptLong::NO_ARGUMENT ], [ '--no-colors', GetoptLong::NO_ARGUMENT ],
[ '--cache', GetoptLong::NO_ARGUMENT ], [ '--cache', GetoptLong::NO_ARGUMENT ],
[ '--no-cache', GetoptLong::NO_ARGUMENT ], [ '--no-t stcache', GetoptLong::NO_ARGUMENT ],
[ '--keep', GetoptLong::NO_ARGUMENT ], [ '--keep', GetoptLong::NO_ARGUMENT ],
[ '--runner-out', GetoptLong::REQUIRED_ARGUMENT ],
[ '--report', GetoptLong::REQUIRED_ARGUMENT ], [ '--report', GetoptLong::REQUIRED_ARGUMENT ],
[ '--jasmine-config', '-j', GetoptLong::REQUIRED_ARGUMENT ], [ '--jasmine-config', '-j', GetoptLong::REQUIRED_ARGUMENT ],
[ '--no-full-run', GetoptLong::NO_ARGUMENT ], [ '--no-full-run', GetoptLong::NO_ARGUMENT ],
[ '--list', '-l', GetoptLong::NO_ARGUMENT ], [ '--list', '-l', GetoptLong::NO_ARGUMENT ]
[ '--seed', GetoptLong::REQUIRED_ARGUMENT ],
[ '--format', '-f', GetoptLong::REQUIRED_ARGUMENT ],
[ '--out', GetoptLong::REQUIRED_ARGUMENT ],
[ '--use-server', GetoptLong::NO_ARGUMENT ],
[ '--server-port', GetoptLong::REQUIRED_ARGUMENT ],
[ '-h', '--help', GetoptLong::NO_ARGUMENT ],
[ '-q', '--quiet', GetoptLong::NO_ARGUMENT ]
) )
command_line_args.each { |*args| process_option(*args) } command_line_args.each { |*args| process_option(*args) }
end end
def reporters
file_index = 0
@options[:reporters].collect do |reporter, file|
output = [ reporter ]
if file
output << "report:#{file_index}"
output << file
file_index += 1
else
output << "stdout"
end
output
end
end
def file_reporters
reporters.find_all { |reporter| reporter[1]["report:"] }
end
private
def add_reporter(name, file = nil)
if !@added_reporter
@options[:reporters] = []
@added_reporter = true
end
if (parts = name.split(':')).length == 2
name, file = parts
end
@options[:reporters] << [ name ]
add_reporter_file(file) if file
end
def add_reporter_file(file)
@options[:reporters].last << file
end
def print_help
options = [
[ '-c, --colors', 'Enable colors (default: disabled)' ],
[ '-nc, --no-colors', 'Disable colors' ],
[ '--cache', 'Enable cache (default: enabled)' ],
[ '--no-cache', 'Disable cache' ],
[ '--keep', 'Keep runner files on failure' ],
[ '--runner-out <filename>', 'Write runner to specified filename' ],
[ '-j, --jasmine-config <config file>', 'Jasmine Yaml config to use' ],
[ '--no-full-run', 'Do not perform a full spec run after a successful targeted spec run' ],
[ '--use-server', 'Load tests from an HTTP server instead of from filesystem' ],
[ '-l, --list', 'List files in the order they will be required' ],
[ '--seed <seed>', 'Random order seed for spec file ordering' ],
[ '-f, --format <reporter<:filename>>', 'Specify an output reporter and possibly output filename' ],
[ '--out <filename>', 'Specify output filename for last defined reporter' ],
[ '-q, --quiet', "Silence most non-test related warnings" ],
[ '-h, --help', "You're looking at it" ]
]
longest_length = options.collect(&:first).collect(&:length).max
puts <<-HELP
Usage: #{$0} [ options ] [ spec files ]
Options:
#{options.collect { |option, description| " #{option.ljust(longest_length)} #{description}" }.join("\n")}
Available reporters:
Console Write out spec results to the console in a progress format (default)
Verbose Write out spec results to the console in a verbose format
File Write spec results in jasmine-headless-webkit ReportFile format
Tap Write spec results in TAP format
Add reporters to the jasmine.HeadlessReporter object to access them
(ex: jasmine.HeadlessReporter.Teamcity for the Teamcity reporter)
HELP
end
end end
end end
end end

View File

@ -60,13 +60,8 @@ module Jasmine::Headless
}.collect(&:filename).uniq.compact }.collect(&:filename).uniq.compact
end end
def seed
if seed = report.find { |entry| entry.respond_to?(:seed) }
seed.seed
end
end
private private
def last_total def last_total
@report.reverse.find { |entry| entry.respond_to?(:total) } @report.reverse.find { |entry| entry.respond_to?(:total) }
end end

View File

@ -6,7 +6,6 @@ module Jasmine::Headless
autoload :Console, 'jasmine/headless/report_message/console' autoload :Console, 'jasmine/headless/report_message/console'
autoload :Error, 'jasmine/headless/report_message/error' autoload :Error, 'jasmine/headless/report_message/error'
autoload :Total, 'jasmine/headless/report_message/total' autoload :Total, 'jasmine/headless/report_message/total'
autoload :Seed, 'jasmine/headless/report_message/seed'
end end
end end

View File

@ -1,8 +1,10 @@
module Jasmine::Headless::ReportMessage module Jasmine::Headless::ReportMessage
class Console class Console
def self.new_from_parts(parts) class << self
def new_from_parts(parts)
new(parts.first) new(parts.first)
end end
end
attr_reader :message attr_reader :message

View File

@ -1,14 +0,0 @@
module Jasmine::Headless::ReportMessage
class Seed
def self.new_from_parts(parts)
new(parts.first)
end
attr_reader :seed
def initialize(seed)
@seed = seed.to_i
end
end
end

View File

@ -1,10 +1,12 @@
module Jasmine::Headless::ReportMessage module Jasmine::Headless::ReportMessage
class Spec class Spec
def self.new_from_parts(parts) class << self
def new_from_parts(parts)
file_info = parts.pop file_info = parts.pop
new(parts.join(' '), file_info) new(parts.join(' '), file_info)
end end
end
attr_reader :statement, :file_info attr_reader :statement, :file_info

Some files were not shown because too many files have changed in this diff Show More