js_spec 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +5 -1
- data/Rakefile +3 -3
- data/bin/js_spec +10 -0
- data/bin/js_spec_server +2 -1
- data/core/JSSpec.js +11 -5
- data/core/JSSpecExtensions.js +127 -8
- data/core/demo.html +8 -1
- data/lib/js_spec/rails_server.rb +22 -0
- data/lib/js_spec/{dir.rb → resources/dir.rb} +7 -5
- data/lib/js_spec/{file.rb → resources/file.rb} +9 -3
- data/lib/js_spec/resources/runners/firefox_runner.rb +90 -0
- data/lib/js_spec/resources/runners.rb +16 -0
- data/lib/js_spec/{spec_dir_runner.rb → resources/spec_dir_runner.rb} +5 -3
- data/lib/js_spec/{spec_file_runner.rb → resources/spec_file_runner.rb} +4 -2
- data/lib/js_spec/{spec_runner.rb → resources/spec_runner.rb} +3 -1
- data/lib/js_spec/resources/suite.rb +24 -0
- data/lib/js_spec/resources/suite_finish.rb +20 -0
- data/lib/js_spec/resources/web_root.rb +34 -0
- data/lib/js_spec/server.rb +16 -6
- data/lib/js_spec.rb +16 -7
- data/spec/{functional_spec_suite.rb → functional_suite.rb} +2 -2
- data/spec/spec_suite.rb +2 -2
- data/spec/unit/rails_server_spec.rb +44 -0
- data/spec/unit/{dir_spec.rb → resources/dir_spec.rb} +10 -8
- data/spec/unit/{file_spec.rb → resources/file_spec.rb} +12 -6
- data/spec/unit/resources/runner_spec.rb +24 -0
- data/spec/unit/resources/runners/firefox_runner_spec.rb +87 -0
- data/spec/unit/{spec_dir_runner_spec.rb → resources/spec_dir_runner_spec.rb} +5 -3
- data/spec/unit/resources/spec_file_runner_spec.rb +20 -0
- data/spec/unit/resources/suite_finish_spec.rb +42 -0
- data/spec/unit/resources/suite_spec.rb +44 -0
- data/spec/unit/resources/web_root_spec.rb +55 -0
- data/spec/unit/server_spec.rb +75 -14
- data/spec/unit/unit_spec_helper.rb +9 -8
- data/spec/{unit_spec_suite.rb → unit_suite.rb} +2 -2
- metadata +37 -28
- data/lib/js_spec/suite_result.rb +0 -11
- data/lib/js_spec/web_root.rb +0 -18
- data/spec/unit/spec_file_runner_spec.rb +0 -18
- data/spec/unit/suite_result_spec.rb +0 -31
- data/spec/unit/web_root_spec.rb +0 -39
data/CHANGES
CHANGED
data/Rakefile
CHANGED
@@ -26,7 +26,7 @@ def run_suite
|
|
26
26
|
end
|
27
27
|
|
28
28
|
PKG_NAME = "js_spec"
|
29
|
-
PKG_VERSION = "0.0
|
29
|
+
PKG_VERSION = "0.1.0"
|
30
30
|
PKG_FILES = FileList[
|
31
31
|
'[A-Z]*',
|
32
32
|
'*.rb',
|
@@ -39,7 +39,7 @@ PKG_FILES = FileList[
|
|
39
39
|
spec = Gem::Specification.new do |s|
|
40
40
|
s.name = PKG_NAME
|
41
41
|
s.version = PKG_VERSION
|
42
|
-
s.summary = "
|
42
|
+
s.summary = "The JSSpec client library (http://code.google.com/p/jsspec/) plus a convenient ruby server."
|
43
43
|
s.test_files = "spec/spec_suite.rb"
|
44
44
|
s.description = s.summary
|
45
45
|
|
@@ -69,4 +69,4 @@ def tag_release
|
|
69
69
|
dashed_version = PKG_VERSION.gsub('.', '-')
|
70
70
|
svn_user = "#{ENV["SVN_USER"]}@" || ""
|
71
71
|
`svn cp svn+ssh://#{svn_user}rubyforge.org/var/svn/pivotalrb/js_spec/trunk svn+ssh://#{svn_user}rubyforge.org/var/svn/pivotalrb/js_spec/tags/REL-#{dashed_version} -m 'Version #{PKG_VERSION}'`
|
72
|
-
end
|
72
|
+
end
|
data/bin/js_spec
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
dir = File.dirname(__FILE__)
|
4
|
+
$LOAD_PATH.unshift("#{dir}/../lib")
|
5
|
+
require "js_spec"
|
6
|
+
require "net/http"
|
7
|
+
|
8
|
+
response = Net::HTTP.start(JsSpec::Server::DEFAULT_HOST, JsSpec::Server::DEFAULT_PORT) do |http|
|
9
|
+
http.post('/runners/firefox', {})
|
10
|
+
end
|
data/bin/js_spec_server
CHANGED
@@ -6,4 +6,5 @@ require "js_spec"
|
|
6
6
|
|
7
7
|
spec_root_path = ARGV[0] || raise("You need to pass in a spec root")
|
8
8
|
implementation_root_path = ARGV[1] || raise("You need to pass in an implementation root")
|
9
|
-
|
9
|
+
public_path = ARGV[2] || raise("You need to pass in a public path")
|
10
|
+
JsSpec::Server.run(spec_root_path, implementation_root_path, public_path)
|
data/core/JSSpec.js
CHANGED
@@ -455,8 +455,9 @@ JSSpec.Logger.prototype.onRunnerStart = function() {
|
|
455
455
|
var title = document.createElement("DIV");
|
456
456
|
title.id = "title";
|
457
457
|
title.innerHTML = [
|
458
|
-
'<h1>JSSpec
|
458
|
+
'<h1>JSSpec</h1>',
|
459
459
|
'<ul>',
|
460
|
+
JSSpec.options.rerun ? '<li>[<a href="?" title="rerun all specs">X</a>] ' + JSSpec.util.escapeTags(decodeURIComponent(JSSpec.options.rerun)) + '</li>' : '',
|
460
461
|
' <li><span id="total_examples">' + JSSpec.runner.totalExamples + '</span> examples</li>',
|
461
462
|
' <li><span id="total_failures">0</span> failures</li>',
|
462
463
|
' <li><span id="total_errors">0</span> errors</li>',
|
@@ -645,15 +646,20 @@ JSSpec.IncludeMatcher.prototype.makeExplain = function() {
|
|
645
646
|
};
|
646
647
|
|
647
648
|
JSSpec.IncludeMatcher.prototype.makeExplainForNotArray = function() {
|
649
|
+
if(this.condition) {
|
650
|
+
this.match = !!this.actual[this.expected];
|
651
|
+
} else {
|
652
|
+
this.match = !this.actual[this.expected];
|
653
|
+
}
|
654
|
+
|
648
655
|
var sb = [];
|
649
656
|
sb.push('<p>actual value:</p>');
|
650
|
-
sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
|
657
|
+
sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, this.expected) + '</p>');
|
651
658
|
sb.push('<p>should ' + (this.condition ? '' : 'not') + ' include:</p>');
|
652
659
|
sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
|
653
|
-
sb.push('<p>but since it\s not an array, include or not doesn\'t make any sense.</p>');
|
654
660
|
return sb.join("");
|
655
|
-
}
|
656
|
-
|
661
|
+
};
|
662
|
+
|
657
663
|
JSSpec.IncludeMatcher.prototype.makeExplainForArray = function() {
|
658
664
|
var matches;
|
659
665
|
if(this.condition) {
|
data/core/JSSpecExtensions.js
CHANGED
@@ -1,27 +1,146 @@
|
|
1
1
|
var Require = {
|
2
2
|
require: function(path_from_javascripts) {
|
3
3
|
if(!Require.required_paths[path_from_javascripts]) {
|
4
|
-
var full_path = path_from_javascripts + ".js
|
4
|
+
var full_path = path_from_javascripts + ".js";
|
5
|
+
if (Require.use_cache_buster) {
|
6
|
+
full_path += '?' + Require.cache_buster;
|
7
|
+
}
|
5
8
|
document.write("<script src='" + full_path + "' type='text/javascript'></script>");
|
6
9
|
Require.required_paths[path_from_javascripts] = true;
|
7
10
|
}
|
8
11
|
},
|
9
12
|
|
10
13
|
required_paths: {},
|
14
|
+
use_cache_buster: true, // TODO: NS/CTI - make this configurable from the UI.
|
11
15
|
cache_buster: parseInt(new Date().getTime()/(1*1000))
|
12
16
|
}
|
13
|
-
|
14
17
|
var require = Require.require;
|
15
18
|
|
19
|
+
var URL_REGEX = /^((\w+):\/\/)(([^:]+):?([^@]+)?@)?([^\/\?:]*):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(.+)?/;
|
20
|
+
function parse_url(url) {
|
21
|
+
var fields = ['url', null, 'protocol', null, 'username', 'password', 'host', 'port', 'pathname', 'search', 'hash'];
|
22
|
+
var result = URL_REGEX.exec(url);
|
23
|
+
if (!result) {
|
24
|
+
throw new SeleniumError("Invalid URL: " + url);
|
25
|
+
}
|
26
|
+
var loc = new Object();
|
27
|
+
for (var i = 0; i < fields.length; i++) {
|
28
|
+
var field = fields[i];
|
29
|
+
if (field == null) {
|
30
|
+
continue;
|
31
|
+
}
|
32
|
+
loc[field] = result[i];
|
33
|
+
}
|
34
|
+
return loc;
|
35
|
+
}
|
36
|
+
|
16
37
|
JSSpec.Logger.prototype.onRunnerEndWithoutServerNotification = JSSpec.Logger.prototype.onRunnerEnd;
|
17
38
|
JSSpec.Logger.prototype.onRunnerEndWithServerNotification = function() {
|
18
39
|
this.onRunnerEndWithoutServerNotification();
|
19
|
-
|
40
|
+
var data = {
|
41
|
+
'text': this.get_error_message_text()
|
42
|
+
};
|
43
|
+
|
44
|
+
current_location_params = parse_url(window.location.href);
|
45
|
+
if(current_location_params.guid) {
|
46
|
+
data.guid = current_location_params.guid;
|
47
|
+
}
|
48
|
+
jQuery.realAjax({
|
20
49
|
type: 'POST',
|
21
|
-
url: '/
|
22
|
-
data:
|
23
|
-
|
50
|
+
url: '/suites/1/finish',
|
51
|
+
data: data
|
52
|
+
});
|
53
|
+
}
|
54
|
+
JSSpec.Logger.prototype.onRunnerEnd = JSSpec.Logger.prototype.onRunnerEndWithServerNotification;
|
55
|
+
|
56
|
+
JSSpec.Logger.prototype.get_error_message_text = function() {
|
57
|
+
var error_messages = [];
|
58
|
+
for(var i=0; i < JSSpec.specs.length; i++) {
|
59
|
+
var spec = JSSpec.specs[i];
|
60
|
+
if(spec.hasException()) {
|
61
|
+
for(var j=0; j < spec.getExamples().length; j++) {
|
62
|
+
var example = spec.getExamples()[j];
|
63
|
+
var error_message = spec.context + " " + example.name;
|
64
|
+
if (example.exception) {
|
65
|
+
error_message += "\n" + example.exception.message + "\n" + example.exception.fileName + ":" + example.exception.lineNumber;
|
66
|
+
}
|
67
|
+
error_messages.push(error_message);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
var full_error_text = error_messages.join("\n");
|
73
|
+
full_error_text = full_error_text.replace(/<\/p>/g, "\n");
|
74
|
+
full_error_text = full_error_text.replace(/<(.|\n)*?>/g, "");
|
75
|
+
return full_error_text;
|
76
|
+
}
|
77
|
+
|
78
|
+
// Custom Matchers
|
79
|
+
|
80
|
+
JSSpec.DSL.Subject.prototype.should_be_disabled = function() {
|
81
|
+
if (!this.target.disabled) {
|
82
|
+
JSSpec._assertionFailure = {message: "Element " + JSSpec.util.inspect(this.target) + " should have been disabled"};
|
83
|
+
throw JSSpec._assertionFailure;
|
84
|
+
}
|
85
|
+
};
|
86
|
+
|
87
|
+
JSSpec.DSL.Subject.prototype.should_be_enabled = function() {
|
88
|
+
if (this.target.disabled) {
|
89
|
+
JSSpec._assertionFailure = {message: "Element " + JSSpec.util.inspect(this.target) + " should have been enabled"};
|
90
|
+
throw JSSpec._assertionFailure;
|
91
|
+
}
|
92
|
+
};
|
93
|
+
|
94
|
+
JSSpec.DSL.Subject.prototype.should_raise = function(expected) {
|
95
|
+
if("function" != typeof(this.target)) {
|
96
|
+
JSSpec._assertionFailure = {message: 'should_raise expects value_of(target) to have a target that is a function'};
|
97
|
+
throw JSSpec._assertionFailure;
|
98
|
+
}
|
99
|
+
|
100
|
+
var raised = false;
|
101
|
+
try {
|
102
|
+
this.target();
|
103
|
+
}
|
104
|
+
catch(e) {
|
105
|
+
if (expected == null || expected == e) {
|
106
|
+
raised = true;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
if(!raised) {
|
110
|
+
var message = "should have raised an error but didn't";
|
111
|
+
if(expected) {
|
112
|
+
message = "should have raised '" + expected + "' but didn't";
|
24
113
|
}
|
25
|
-
|
114
|
+
JSSpec._assertionFailure = {message: message};
|
115
|
+
throw JSSpec._assertionFailure;
|
116
|
+
}
|
26
117
|
}
|
27
|
-
|
118
|
+
|
119
|
+
JSSpec.DSL.Subject.prototype.should_not_raise = function(expected) {
|
120
|
+
if("function" != typeof(this.target)) {
|
121
|
+
JSSpec._assertionFailure = {message: 'should_not_raise expects value_of(target) to have a target that is a function'};
|
122
|
+
throw JSSpec._assertionFailure;
|
123
|
+
}
|
124
|
+
|
125
|
+
var raised = false;
|
126
|
+
try {
|
127
|
+
this.target();
|
128
|
+
}
|
129
|
+
catch(e) {
|
130
|
+
if (expected == null || expected == e) {
|
131
|
+
raised = true;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
if(raised) {
|
135
|
+
var message = "should not have raised an error but did";
|
136
|
+
if(expected) {
|
137
|
+
message = "should not have raised '" + expected + "' but did";
|
138
|
+
}
|
139
|
+
JSSpec._assertionFailure = {message: message};
|
140
|
+
throw JSSpec._assertionFailure;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
|
data/core/demo.html
CHANGED
@@ -44,7 +44,10 @@ describe('"Should include"s', {
|
|
44
44
|
value_of(new Date()).should_include(4);
|
45
45
|
},
|
46
46
|
'Should not include / Non-array object': function() {
|
47
|
-
value_of(new Date()).should_not_include(
|
47
|
+
value_of(new Date()).should_not_include('getMonth');
|
48
|
+
},
|
49
|
+
'Should include 2': function() {
|
50
|
+
value_of({a:1, b:2}).should_not_include('a');
|
48
51
|
}
|
49
52
|
})
|
50
53
|
|
@@ -69,6 +72,10 @@ describe('"Should have"s', {
|
|
69
72
|
},
|
70
73
|
'At most': function() {
|
71
74
|
value_of([1,2,3]).should_have_at_most(2, "items");
|
75
|
+
},
|
76
|
+
'Member': function() {
|
77
|
+
value_of({x: 0}).should_have_member('x'); // true
|
78
|
+
value_of({x: 0}).should_have_member('y'); // false
|
72
79
|
}
|
73
80
|
})
|
74
81
|
describe('"Should be empty"s', {
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module JsSpec
|
2
|
+
class RailsServer < Server
|
3
|
+
class << self
|
4
|
+
def run(rails_root, server_options = {})
|
5
|
+
server_options[:Host] ||= Server::DEFAULT_HOST
|
6
|
+
server_options[:Port] ||= Server::DEFAULT_PORT
|
7
|
+
Server.instance = new(rails_root, server_options[:Host], server_options[:Port])
|
8
|
+
Rack::Handler::Mongrel.run(Server.instance, server_options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(rails_root, host=DEFAULT_HOST, port=DEFAULT_PORT)
|
13
|
+
super(
|
14
|
+
"#{rails_root}/spec/javascripts",
|
15
|
+
"#{rails_root}/public/javascripts",
|
16
|
+
"#{rails_root}/public",
|
17
|
+
host,
|
18
|
+
port
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module JsSpec
|
2
|
-
|
2
|
+
module Resources
|
3
|
+
class Dir < File
|
3
4
|
def locate(name)
|
4
5
|
if file = file(name)
|
5
6
|
file
|
@@ -22,8 +23,8 @@ module JsSpec
|
|
22
23
|
|
23
24
|
protected
|
24
25
|
def determine_child_paths(name)
|
25
|
-
absolute_child_path =
|
26
|
-
relative_child_path =
|
26
|
+
absolute_child_path = "#{absolute_path}/#{name}"
|
27
|
+
relative_child_path = "#{relative_path}/#{name}"
|
27
28
|
[absolute_child_path, relative_child_path]
|
28
29
|
end
|
29
30
|
|
@@ -40,7 +41,7 @@ module JsSpec
|
|
40
41
|
def file(name)
|
41
42
|
absolute_file_path, relative_file_path = determine_child_paths(name)
|
42
43
|
if ::File.exists?(absolute_file_path) && !::File.directory?(absolute_file_path)
|
43
|
-
|
44
|
+
Resources::File.new(absolute_file_path, relative_file_path)
|
44
45
|
else
|
45
46
|
nil
|
46
47
|
end
|
@@ -49,10 +50,11 @@ module JsSpec
|
|
49
50
|
def subdir(name)
|
50
51
|
absolute_dir_path, relative_dir_path = determine_child_paths(name)
|
51
52
|
if ::File.directory?(absolute_dir_path)
|
52
|
-
|
53
|
+
Resources::Dir.new(absolute_dir_path, relative_dir_path)
|
53
54
|
else
|
54
55
|
nil
|
55
56
|
end
|
56
57
|
end
|
57
58
|
end
|
59
|
+
end
|
58
60
|
end
|
@@ -1,12 +1,17 @@
|
|
1
1
|
module JsSpec
|
2
|
-
|
2
|
+
module Resources
|
3
|
+
class File
|
3
4
|
MIME_TYPES = {
|
4
5
|
'.js' => 'text/javascript',
|
5
6
|
'.css' => 'text/css',
|
7
|
+
'.png' => 'image/png',
|
8
|
+
'.jpg' => 'image/jpeg',
|
9
|
+
'.jpeg' => 'image/jpeg',
|
10
|
+
'.gif' => 'image/gif',
|
6
11
|
}
|
7
12
|
|
8
13
|
attr_reader :absolute_path, :relative_path
|
9
|
-
|
14
|
+
|
10
15
|
def initialize(absolute_path, relative_path)
|
11
16
|
@absolute_path = absolute_path
|
12
17
|
@relative_path = relative_path
|
@@ -19,8 +24,9 @@ module JsSpec
|
|
19
24
|
end
|
20
25
|
|
21
26
|
def ==(other)
|
22
|
-
return false unless other.
|
27
|
+
return false unless other.class == self.class
|
23
28
|
absolute_path == other.absolute_path && relative_path == other.relative_path
|
24
29
|
end
|
25
30
|
end
|
31
|
+
end
|
26
32
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module JsSpec
|
2
|
+
module Resources
|
3
|
+
class Runners
|
4
|
+
class FirefoxRunner
|
5
|
+
class << self
|
6
|
+
def resume(guid, text)
|
7
|
+
responses[guid] = text
|
8
|
+
threads[guid].kill
|
9
|
+
end
|
10
|
+
|
11
|
+
def threads
|
12
|
+
@threads ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def response_value(guid)
|
16
|
+
responses[guid]
|
17
|
+
ensure
|
18
|
+
responses.delete guid
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def responses
|
23
|
+
@responses ||= {}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
include FileUtils
|
28
|
+
attr_reader :profile_dir
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
profile_base = "#{::Dir.tmpdir}/js_spec/firefox"
|
32
|
+
mkdir_p profile_base
|
33
|
+
@profile_dir = "#{profile_base}/#{Time.now.to_i}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def post
|
37
|
+
copy_profile_files
|
38
|
+
create_profile
|
39
|
+
wait_for_full_profile_to_be_created
|
40
|
+
|
41
|
+
guid = UUID.new
|
42
|
+
start_browser guid
|
43
|
+
thread = Thread.start {sleep}
|
44
|
+
self.class.threads[guid] = thread
|
45
|
+
thread.join
|
46
|
+
|
47
|
+
self.class.response_value guid
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
def copy_profile_files
|
52
|
+
dir = ::File.dirname(__FILE__)
|
53
|
+
firefox_profile_path = ::File.expand_path("#{dir}/../../../../resources/firefox")
|
54
|
+
system("cp -R #{firefox_profile_path} #{profile_dir}") || raise("Copying Firefox profile failed")
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_profile
|
58
|
+
system("firefox -profile #{profile_dir} -chrome chrome://killff/content/kill.html") || raise("Initializing profile failed")
|
59
|
+
end
|
60
|
+
|
61
|
+
def wait_for_full_profile_to_be_created
|
62
|
+
Timeout.timeout(5) do
|
63
|
+
wait_for_file_lock_to_go_away
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def start_browser(guid)
|
68
|
+
Thread.start do
|
69
|
+
system("firefox -profile #{profile_dir} http://localhost:8080/specs?guid=#{guid}") || raise("Starting Firefox failed")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def wait_for_file_lock_to_go_away
|
74
|
+
loop do
|
75
|
+
return if lock_is_gone? && lock_remains_gone?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def lock_is_gone?
|
80
|
+
!::File.exists?("#{profile_dir}/parent.lock")
|
81
|
+
end
|
82
|
+
|
83
|
+
def lock_remains_gone?
|
84
|
+
sleep 0.5
|
85
|
+
lock_is_gone?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
require File.expand_path("#{dir}/runners/firefox_runner")
|
3
|
+
|
4
|
+
module JsSpec
|
5
|
+
module Resources
|
6
|
+
class Runners
|
7
|
+
def locate(name)
|
8
|
+
if name == 'firefox'
|
9
|
+
FirefoxRunner.new
|
10
|
+
else
|
11
|
+
raise "Invalid path #{name}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module JsSpec
|
2
|
-
|
2
|
+
module Resources
|
3
|
+
class SpecDirRunner < SpecRunner
|
3
4
|
attr_reader :dir
|
4
|
-
|
5
|
+
|
5
6
|
def initialize(dir)
|
6
7
|
@dir = dir
|
7
8
|
end
|
8
|
-
|
9
|
+
|
9
10
|
def spec_files
|
10
11
|
dir.glob("/**/*_spec.js")
|
11
12
|
end
|
12
13
|
end
|
14
|
+
end
|
13
15
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module JsSpec
|
2
|
-
|
2
|
+
module Resources
|
3
|
+
class SpecRunner
|
3
4
|
def get
|
4
5
|
html = <<-HTML
|
5
6
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
@@ -24,4 +25,5 @@ module JsSpec
|
|
24
25
|
html.gsub(/^ /, "")
|
25
26
|
end
|
26
27
|
end
|
28
|
+
end
|
27
29
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module JsSpec
|
2
|
+
module Resources
|
3
|
+
class Suite
|
4
|
+
class << self
|
5
|
+
def locate(id)
|
6
|
+
new id
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :id
|
11
|
+
def initialize(id)
|
12
|
+
@id = id
|
13
|
+
end
|
14
|
+
|
15
|
+
def locate(name)
|
16
|
+
if name == 'finish'
|
17
|
+
SuiteFinish.new self
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Invalid path: #{name}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module JsSpec
|
2
|
+
module Resources
|
3
|
+
class SuiteFinish
|
4
|
+
attr_reader :suite
|
5
|
+
def initialize(suite)
|
6
|
+
@suite = suite
|
7
|
+
end
|
8
|
+
|
9
|
+
def post
|
10
|
+
guid = Server.request['guid']
|
11
|
+
if guid
|
12
|
+
Runners::FirefoxRunner.resume(guid, Server.request['text'])
|
13
|
+
else
|
14
|
+
STDOUT.puts Server.request['text']
|
15
|
+
end
|
16
|
+
""
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module JsSpec
|
2
|
+
module Resources
|
3
|
+
class WebRoot
|
4
|
+
attr_reader :public_path
|
5
|
+
def initialize(public_path)
|
6
|
+
@public_path = ::File.expand_path(public_path)
|
7
|
+
end
|
8
|
+
|
9
|
+
def locate(name)
|
10
|
+
case name
|
11
|
+
when 'specs'
|
12
|
+
Resources::Dir.new(JsSpec::Server.spec_root_path, "/specs")
|
13
|
+
when 'core'
|
14
|
+
Resources::Dir.new(JsSpec::Server.core_path, "/core")
|
15
|
+
when 'implementations'
|
16
|
+
Resources::Dir.new(JsSpec::Server.implementation_root_path, "/implementations")
|
17
|
+
when 'suites'
|
18
|
+
Resources::Suite
|
19
|
+
when 'runners'
|
20
|
+
Resources::Runners.new
|
21
|
+
else
|
22
|
+
potential_file_in_public_path = "#{public_path}/#{name}"
|
23
|
+
if ::File.directory?(potential_file_in_public_path)
|
24
|
+
Resources::Dir.new(potential_file_in_public_path, "/#{name}")
|
25
|
+
elsif ::File.exists?(potential_file_in_public_path)
|
26
|
+
Resources::File.new(potential_file_in_public_path, "/#{name}")
|
27
|
+
else
|
28
|
+
raise "Invalid path: #{name}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/js_spec/server.rb
CHANGED
@@ -6,27 +6,33 @@ module JsSpec
|
|
6
6
|
class << self
|
7
7
|
attr_accessor :instance
|
8
8
|
|
9
|
-
def run(spec_root_path, implementation_root_path, server_options = {})
|
9
|
+
def run(spec_root_path, implementation_root_path, public_path, server_options = {})
|
10
10
|
server_options[:Host] ||= DEFAULT_HOST
|
11
11
|
server_options[:Port] ||= DEFAULT_PORT
|
12
|
-
@instance = new(spec_root_path, implementation_root_path, server_options[:Host], server_options[:Port])
|
12
|
+
@instance = new(spec_root_path, implementation_root_path, public_path, server_options[:Host], server_options[:Port])
|
13
13
|
Rack::Handler::Mongrel.run(instance, server_options)
|
14
14
|
end
|
15
15
|
|
16
|
+
def default_url
|
17
|
+
"http://#{DEFAULT_HOST}:#{DEFAULT_PORT}"
|
18
|
+
end
|
19
|
+
|
16
20
|
def spec_root_path; instance.spec_root_path; end
|
17
21
|
def implementation_root_path; instance.implementation_root_path; end
|
22
|
+
def public_path; instance.public_path; end
|
18
23
|
def core_path; instance.core_path; end
|
19
24
|
def request; instance.request; end
|
20
25
|
def response; instance.response; end
|
21
26
|
end
|
22
27
|
|
23
|
-
attr_reader :host, :port, :spec_root_path, :implementation_root_path, :core_path
|
28
|
+
attr_reader :host, :port, :spec_root_path, :implementation_root_path, :core_path, :public_path
|
24
29
|
|
25
|
-
def initialize(spec_root_path, implementation_root_path, host=DEFAULT_HOST, port=DEFAULT_PORT)
|
30
|
+
def initialize(spec_root_path, implementation_root_path, public_path, host=DEFAULT_HOST, port=DEFAULT_PORT)
|
26
31
|
dir = ::File.dirname(__FILE__)
|
27
32
|
@core_path = ::File.expand_path("#{dir}/../../core")
|
28
33
|
@spec_root_path = ::File.expand_path(spec_root_path)
|
29
34
|
@implementation_root_path = ::File.expand_path(implementation_root_path)
|
35
|
+
@public_path = ::File.expand_path(public_path)
|
30
36
|
@host = host
|
31
37
|
@port = port
|
32
38
|
end
|
@@ -44,7 +50,7 @@ module JsSpec
|
|
44
50
|
|
45
51
|
def run(path)
|
46
52
|
system(%{firefox "http://#{host}:#{port}/#{path}"})
|
47
|
-
|
53
|
+
Suite.new
|
48
54
|
end
|
49
55
|
|
50
56
|
def request
|
@@ -69,9 +75,13 @@ module JsSpec
|
|
69
75
|
end
|
70
76
|
|
71
77
|
def get_resource
|
72
|
-
path_parts.inject(WebRoot.new) do |resource, child_resource_name|
|
78
|
+
path_parts.inject(Resources::WebRoot.new(public_path)) do |resource, child_resource_name|
|
73
79
|
resource.locate(child_resource_name)
|
74
80
|
end
|
81
|
+
rescue Exception => e
|
82
|
+
detailed_exception = Exception.new("Error handling path #{request.path_info}\n#{e.message}")
|
83
|
+
detailed_exception.set_backtrace(e.backtrace)
|
84
|
+
raise detailed_exception
|
75
85
|
end
|
76
86
|
end
|
77
87
|
end
|