jasmine 0.10.2.4 → 0.10.3
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/jasmine/lib/TrivialReporter.js +64 -9
- data/jasmine/lib/{jasmine-0.10.2.js → jasmine-0.10.3.js} +16 -3
- data/jasmine/lib/jasmine.css +92 -12
- data/lib/jasmine/base.rb +2 -2
- data/lib/jasmine/server.rb +1 -1
- data/spec/server_spec.rb +2 -4
- data/spec/spec_helper.rb +4 -1
- metadata +60 -32
- data/jasmine/contrib/ruby/jasmine_runner.rb +0 -334
- data/jasmine/contrib/ruby/jasmine_spec_builder.rb +0 -153
- data/jasmine/contrib/ruby/run.html +0 -47
@@ -17,7 +17,11 @@ jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarA
|
|
17
17
|
}
|
18
18
|
|
19
19
|
for (var attr in attrs) {
|
20
|
+
if (attr == "className") {
|
20
21
|
el[attr] = attrs[attr];
|
22
|
+
} else {
|
23
|
+
el.setAttribute(attr, attrs[attr]);
|
24
|
+
}
|
21
25
|
}
|
22
26
|
|
23
27
|
return el;
|
@@ -26,10 +30,29 @@ jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarA
|
|
26
30
|
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
|
27
31
|
var suites = runner.suites();
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
+
var showPassed, showSkipped;
|
34
|
+
|
35
|
+
this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
|
36
|
+
this.createDom('div', { className: 'banner' },
|
37
|
+
this.createDom('div', { className: 'logo' },
|
38
|
+
"Jasmine",
|
39
|
+
this.createDom('span', { className: 'version' }, runner.env.versionString())),
|
40
|
+
this.createDom('div', { className: 'options' },
|
41
|
+
"Show ",
|
42
|
+
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
|
43
|
+
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
|
44
|
+
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
|
45
|
+
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
|
46
|
+
)
|
47
|
+
),
|
48
|
+
|
49
|
+
this.runnerDiv = this.createDom('div', { className: 'runner running' },
|
50
|
+
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
|
51
|
+
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
|
52
|
+
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
|
53
|
+
);
|
54
|
+
|
55
|
+
this.document.body.appendChild(this.outerDiv);
|
33
56
|
|
34
57
|
for (var i = 0; i < suites.length; i++) {
|
35
58
|
var suite = suites[i];
|
@@ -37,7 +60,7 @@ jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
|
|
37
60
|
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
|
38
61
|
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
|
39
62
|
this.suiteDivs[suite.getFullName()] = suiteDiv;
|
40
|
-
var parentDiv = this.
|
63
|
+
var parentDiv = this.outerDiv;
|
41
64
|
if (suite.parentSuite) {
|
42
65
|
parentDiv = this.suiteDivs[suite.parentSuite.getFullName()];
|
43
66
|
}
|
@@ -45,6 +68,23 @@ jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
|
|
45
68
|
}
|
46
69
|
|
47
70
|
this.startedAt = new Date();
|
71
|
+
|
72
|
+
var self = this;
|
73
|
+
showPassed.onchange = function(evt) {
|
74
|
+
if (evt.target.checked) {
|
75
|
+
self.outerDiv.className += ' show-passed';
|
76
|
+
} else {
|
77
|
+
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
|
78
|
+
}
|
79
|
+
};
|
80
|
+
|
81
|
+
showSkipped.onchange = function(evt) {
|
82
|
+
if (evt.target.checked) {
|
83
|
+
self.outerDiv.className += ' show-skipped';
|
84
|
+
} else {
|
85
|
+
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
|
86
|
+
}
|
87
|
+
};
|
48
88
|
};
|
49
89
|
|
50
90
|
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
|
@@ -63,6 +103,8 @@ jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
|
|
63
103
|
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
|
64
104
|
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
|
65
105
|
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
|
106
|
+
|
107
|
+
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
|
66
108
|
};
|
67
109
|
|
68
110
|
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
|
@@ -82,17 +124,30 @@ jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
|
|
82
124
|
}
|
83
125
|
var specDiv = this.createDom('div', { className: 'spec ' + status },
|
84
126
|
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
|
85
|
-
this.createDom('a', {
|
127
|
+
this.createDom('a', {
|
128
|
+
className: 'description',
|
129
|
+
href: '?spec=' + encodeURIComponent(spec.getFullName()),
|
130
|
+
title: spec.getFullName()
|
131
|
+
}, spec.description));
|
86
132
|
|
87
133
|
|
88
134
|
var resultItems = results.getItems();
|
135
|
+
var messagesDiv = this.createDom('div', { className: 'messages' });
|
89
136
|
for (var i = 0; i < resultItems.length; i++) {
|
90
137
|
var result = resultItems[i];
|
91
138
|
if (result.passed && !result.passed()) {
|
92
|
-
|
93
|
-
|
139
|
+
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
140
|
+
|
141
|
+
if (result.trace.stack) {
|
142
|
+
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
143
|
+
}
|
94
144
|
}
|
95
145
|
}
|
146
|
+
|
147
|
+
if (messagesDiv.childNodes.length > 0) {
|
148
|
+
specDiv.appendChild(messagesDiv);
|
149
|
+
}
|
150
|
+
|
96
151
|
this.suiteDivs[spec.suite.getFullName()].appendChild(specDiv);
|
97
152
|
};
|
98
153
|
|
@@ -114,4 +169,4 @@ jasmine.TrivialReporter.prototype.specFilter = function(spec) {
|
|
114
169
|
|
115
170
|
if (!paramMap["spec"]) return true;
|
116
171
|
return spec.getFullName().indexOf(paramMap["spec"]) == 0;
|
117
|
-
};
|
172
|
+
};
|
@@ -13,7 +13,7 @@ jasmine.unimplementedMethod_ = function() {
|
|
13
13
|
};
|
14
14
|
|
15
15
|
/**
|
16
|
-
* Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code is just
|
16
|
+
* Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
|
17
17
|
* a plain old variable and may be redefined by somebody else.
|
18
18
|
*
|
19
19
|
* @private
|
@@ -558,6 +558,7 @@ jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
|
|
558
558
|
*
|
559
559
|
* @param {String} url path to the file to include
|
560
560
|
* @param {Boolean} opt_global
|
561
|
+
* @deprecated We suggest you use a different method of including JS source files. <code>jasmine.include</code> will be removed soon.
|
561
562
|
*/
|
562
563
|
jasmine.include = function(url, opt_global) {
|
563
564
|
if (opt_global) {
|
@@ -690,6 +691,18 @@ jasmine.Env.prototype.version = function () {
|
|
690
691
|
}
|
691
692
|
};
|
692
693
|
|
694
|
+
/**
|
695
|
+
* @returns string containing jasmine version build info, if set.
|
696
|
+
*/
|
697
|
+
jasmine.Env.prototype.versionString = function() {
|
698
|
+
if (jasmine.version_) {
|
699
|
+
var version = this.version();
|
700
|
+
return version.major + "." + version.minor + "." + version.build + " revision " + version.revision;
|
701
|
+
} else {
|
702
|
+
return "version unknown";
|
703
|
+
}
|
704
|
+
};
|
705
|
+
|
693
706
|
/**
|
694
707
|
* @returns a sequential integer starting at 0
|
695
708
|
*/
|
@@ -2313,6 +2326,6 @@ window.clearInterval = function(timeoutKey) {
|
|
2313
2326
|
jasmine.version_= {
|
2314
2327
|
"major": 0,
|
2315
2328
|
"minor": 10,
|
2316
|
-
"build":
|
2317
|
-
"revision":
|
2329
|
+
"build": 3,
|
2330
|
+
"revision": 1270162784
|
2318
2331
|
};
|
data/jasmine/lib/jasmine.css
CHANGED
@@ -3,33 +3,67 @@ body {
|
|
3
3
|
}
|
4
4
|
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
.jasmine_reporter a:visited, .jasmine_reporter a {
|
7
|
+
color: #303;
|
8
8
|
}
|
9
9
|
|
10
|
-
.
|
11
|
-
|
10
|
+
.jasmine_reporter a:hover, .jasmine_reporter a:active {
|
11
|
+
color: blue;
|
12
12
|
}
|
13
13
|
|
14
|
+
.run_spec {
|
15
|
+
float:right;
|
16
|
+
padding-right: 5px;
|
17
|
+
font-size: .8em;
|
18
|
+
text-decoration: none;
|
19
|
+
}
|
14
20
|
|
21
|
+
.jasmine_reporter {
|
22
|
+
margin: 0 5px;
|
23
|
+
}
|
15
24
|
|
16
|
-
.
|
17
|
-
|
18
|
-
|
25
|
+
.banner {
|
26
|
+
color: #303;
|
27
|
+
background-color: #fef;
|
28
|
+
padding: 5px;
|
29
|
+
}
|
30
|
+
|
31
|
+
.logo {
|
32
|
+
float: left;
|
33
|
+
font-size: 1.1em;
|
34
|
+
padding-left: 5px;
|
35
|
+
}
|
36
|
+
|
37
|
+
.logo .version {
|
38
|
+
font-size: .6em;
|
19
39
|
padding-left: 1em;
|
20
|
-
|
40
|
+
}
|
41
|
+
|
42
|
+
.runner.running {
|
43
|
+
background-color: yellow;
|
44
|
+
}
|
45
|
+
|
46
|
+
|
47
|
+
.options {
|
48
|
+
text-align: right;
|
49
|
+
font-size: .8em;
|
21
50
|
}
|
22
51
|
|
23
52
|
|
24
53
|
|
54
|
+
|
25
55
|
.suite {
|
26
56
|
border: 1px outset gray;
|
27
|
-
margin: 5px;
|
57
|
+
margin: 5px 0;
|
28
58
|
padding-left: 1em;
|
29
59
|
}
|
30
60
|
|
61
|
+
.suite .suite {
|
62
|
+
margin: 5px;
|
63
|
+
}
|
64
|
+
|
31
65
|
.suite.passed {
|
32
|
-
background-color: #
|
66
|
+
background-color: #dfd;
|
33
67
|
}
|
34
68
|
|
35
69
|
.suite.failed {
|
@@ -38,22 +72,51 @@ body .run_spec {
|
|
38
72
|
|
39
73
|
.spec {
|
40
74
|
margin: 5px;
|
75
|
+
padding-left: 1em;
|
41
76
|
clear: both;
|
42
77
|
}
|
43
78
|
|
79
|
+
.spec.failed, .spec.passed, .spec.skipped {
|
80
|
+
padding-bottom: 5px;
|
81
|
+
border: 1px solid gray;
|
82
|
+
}
|
83
|
+
|
84
|
+
.spec.failed {
|
85
|
+
background-color: #fbb;
|
86
|
+
border-color: red;
|
87
|
+
}
|
88
|
+
|
89
|
+
.spec.passed {
|
90
|
+
background-color: #bfb;
|
91
|
+
border-color: green;
|
92
|
+
}
|
93
|
+
|
94
|
+
.spec.skipped {
|
95
|
+
background-color: #bbb;
|
96
|
+
}
|
97
|
+
|
98
|
+
.messages {
|
99
|
+
border-left: 1px dashed gray;
|
100
|
+
padding-left: 1em;
|
101
|
+
padding-right: 1em;
|
102
|
+
}
|
103
|
+
|
44
104
|
.passed {
|
45
105
|
background-color: #cfc;
|
106
|
+
display: none;
|
46
107
|
}
|
47
108
|
|
48
109
|
.failed {
|
49
|
-
background-color: #
|
110
|
+
background-color: #fbb;
|
50
111
|
}
|
51
112
|
|
52
113
|
.skipped {
|
53
114
|
color: #777;
|
54
115
|
background-color: #eee;
|
116
|
+
display: none;
|
55
117
|
}
|
56
118
|
|
119
|
+
|
57
120
|
/*.resultMessage {*/
|
58
121
|
/*white-space: pre;*/
|
59
122
|
/*}*/
|
@@ -72,15 +135,32 @@ body .run_spec {
|
|
72
135
|
white-space: pre;
|
73
136
|
font-size: .8em;
|
74
137
|
margin-left: 10px;
|
75
|
-
height: 5em;
|
138
|
+
max-height: 5em;
|
76
139
|
overflow: auto;
|
77
140
|
border: 1px inset red;
|
78
141
|
padding: 1em;
|
79
142
|
background: #eef;
|
80
143
|
}
|
81
144
|
|
145
|
+
.finished-at {
|
146
|
+
padding-left: 1em;
|
147
|
+
font-size: .6em;
|
148
|
+
}
|
149
|
+
|
150
|
+
.show-passed .passed,
|
151
|
+
.show-skipped .skipped {
|
152
|
+
display: block;
|
153
|
+
}
|
154
|
+
|
82
155
|
|
83
156
|
#jasmine_content {
|
84
157
|
position:fixed;
|
85
158
|
right: 100%;
|
86
159
|
}
|
160
|
+
|
161
|
+
.runner {
|
162
|
+
border: 1px solid gray;
|
163
|
+
display: block;
|
164
|
+
margin: 5px 0;
|
165
|
+
padding: 2px 0 2px 10px;
|
166
|
+
}
|
data/lib/jasmine/base.rb
CHANGED
@@ -4,7 +4,7 @@ require 'json'
|
|
4
4
|
|
5
5
|
module Jasmine
|
6
6
|
def self.root
|
7
|
-
File.expand_path(File.join(File.dirname(__FILE__), '../../jasmine'))
|
7
|
+
ENV["JASMINE_ROOT"] || File.expand_path(File.join(File.dirname(__FILE__), '../../jasmine'))
|
8
8
|
end
|
9
9
|
|
10
10
|
# this seemingly-over-complex method is necessary to get an open port on at least some of our Macs
|
@@ -60,4 +60,4 @@ module Jasmine
|
|
60
60
|
"#{file_name}?cachebust=#{digest}"
|
61
61
|
end
|
62
62
|
end
|
63
|
-
end
|
63
|
+
end
|
data/lib/jasmine/server.rb
CHANGED
data/spec/server_spec.rb
CHANGED
@@ -46,7 +46,7 @@ describe Jasmine::Server do
|
|
46
46
|
|
47
47
|
it "should serve focused suites when prefixing spec files with /__suite__/" do
|
48
48
|
Dir.stub!(:glob).and_return do |glob_string|
|
49
|
-
glob_string
|
49
|
+
[glob_string]
|
50
50
|
end
|
51
51
|
code, headers, body = @thin_app.call("PATH_INFO" => "/__suite__/file2.js", "SCRIPT_NAME" => "xxx")
|
52
52
|
code.should == 200
|
@@ -82,7 +82,5 @@ describe Jasmine::Server do
|
|
82
82
|
headers.should == { 'Content-Type' => 'text/html' }
|
83
83
|
body.should == ''
|
84
84
|
end
|
85
|
-
|
86
85
|
end
|
87
|
-
|
88
|
-
end
|
86
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require File.expand_path('../../.bundle/environment', __FILE__)
|
2
|
+
Bundler.require(:default, :test)
|
3
|
+
|
1
4
|
require 'spec'
|
2
5
|
|
3
|
-
require File.expand_path(File.join(File.dirname(__FILE__), "../lib/jasmine"))
|
6
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "../lib/jasmine"))
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jasmine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 10
|
8
|
+
- 3
|
9
|
+
version: 0.10.3
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Rajan Agaskar
|
@@ -15,64 +20,88 @@ default_executable: jasmine
|
|
15
20
|
dependencies:
|
16
21
|
- !ruby/object:Gem::Dependency
|
17
22
|
name: rspec
|
18
|
-
|
19
|
-
|
20
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
21
25
|
requirements:
|
22
26
|
- - ">="
|
23
27
|
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 1
|
31
|
+
- 5
|
24
32
|
version: 1.1.5
|
25
|
-
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
26
35
|
- !ruby/object:Gem::Dependency
|
27
36
|
name: json
|
28
|
-
|
29
|
-
|
30
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
39
|
requirements:
|
32
40
|
- - ">="
|
33
41
|
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 1
|
44
|
+
- 1
|
45
|
+
- 9
|
34
46
|
version: 1.1.9
|
35
|
-
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
36
49
|
- !ruby/object:Gem::Dependency
|
37
50
|
name: rack
|
38
|
-
|
39
|
-
|
40
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
53
|
requirements:
|
42
54
|
- - ">="
|
43
55
|
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 1
|
58
|
+
- 0
|
59
|
+
- 0
|
44
60
|
version: 1.0.0
|
45
|
-
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
46
63
|
- !ruby/object:Gem::Dependency
|
47
64
|
name: thin
|
48
|
-
|
49
|
-
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
51
67
|
requirements:
|
52
68
|
- - ">="
|
53
69
|
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 1
|
72
|
+
- 2
|
73
|
+
- 4
|
54
74
|
version: 1.2.4
|
55
|
-
|
75
|
+
type: :runtime
|
76
|
+
version_requirements: *id004
|
56
77
|
- !ruby/object:Gem::Dependency
|
57
78
|
name: selenium-rc
|
58
|
-
|
59
|
-
|
60
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
61
81
|
requirements:
|
62
82
|
- - ">="
|
63
83
|
- !ruby/object:Gem::Version
|
84
|
+
segments:
|
85
|
+
- 2
|
86
|
+
- 1
|
87
|
+
- 0
|
64
88
|
version: 2.1.0
|
65
|
-
|
89
|
+
type: :runtime
|
90
|
+
version_requirements: *id005
|
66
91
|
- !ruby/object:Gem::Dependency
|
67
92
|
name: selenium-client
|
68
|
-
|
69
|
-
|
70
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
prerelease: false
|
94
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
71
95
|
requirements:
|
72
96
|
- - ">="
|
73
97
|
- !ruby/object:Gem::Version
|
98
|
+
segments:
|
99
|
+
- 1
|
100
|
+
- 2
|
101
|
+
- 17
|
74
102
|
version: 1.2.17
|
75
|
-
|
103
|
+
type: :runtime
|
104
|
+
version_requirements: *id006
|
76
105
|
description: Javascript BDD test framework
|
77
106
|
email: ragaskar@gmail.com
|
78
107
|
executables:
|
@@ -90,12 +119,9 @@ files:
|
|
90
119
|
- generators/jasmine/templates/spec/javascripts/support/jasmine-rails.yml
|
91
120
|
- generators/jasmine/templates/spec/javascripts/support/jasmine.yml
|
92
121
|
- generators/jasmine/templates/spec/javascripts/support/jasmine_runner.rb
|
93
|
-
- jasmine/contrib/ruby/jasmine_runner.rb
|
94
|
-
- jasmine/contrib/ruby/jasmine_spec_builder.rb
|
95
|
-
- jasmine/contrib/ruby/run.html
|
96
122
|
- jasmine/lib/TrivialReporter.js
|
97
123
|
- jasmine/lib/consolex.js
|
98
|
-
- jasmine/lib/jasmine-0.10.
|
124
|
+
- jasmine/lib/jasmine-0.10.3.js
|
99
125
|
- jasmine/lib/jasmine.css
|
100
126
|
- jasmine/lib/json2.js
|
101
127
|
- lib/jasmine.rb
|
@@ -119,18 +145,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
119
145
|
requirements:
|
120
146
|
- - ">="
|
121
147
|
- !ruby/object:Gem::Version
|
148
|
+
segments:
|
149
|
+
- 0
|
122
150
|
version: "0"
|
123
|
-
version:
|
124
151
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
152
|
requirements:
|
126
153
|
- - ">="
|
127
154
|
- !ruby/object:Gem::Version
|
155
|
+
segments:
|
156
|
+
- 0
|
128
157
|
version: "0"
|
129
|
-
version:
|
130
158
|
requirements: []
|
131
159
|
|
132
160
|
rubyforge_project:
|
133
|
-
rubygems_version: 1.3.
|
161
|
+
rubygems_version: 1.3.6
|
134
162
|
signing_key:
|
135
163
|
specification_version: 3
|
136
164
|
summary: Jasmine Ruby Runner
|
@@ -1,334 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
require 'erb'
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module Jasmine
|
6
|
-
def self.root
|
7
|
-
File.expand_path(File.join(File.dirname(__FILE__), '../..'))
|
8
|
-
end
|
9
|
-
|
10
|
-
# this seemingly-over-complex method is necessary to get an open port on at least some of our Macs
|
11
|
-
def self.open_socket_on_unused_port
|
12
|
-
infos = Socket::getaddrinfo("localhost", nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE)
|
13
|
-
families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten]
|
14
|
-
|
15
|
-
return TCPServer.open('0.0.0.0', 0) if families.has_key?('AF_INET')
|
16
|
-
return TCPServer.open('::', 0) if families.has_key?('AF_INET6')
|
17
|
-
return TCPServer.open(0)
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.find_unused_port
|
21
|
-
socket = open_socket_on_unused_port
|
22
|
-
port = socket.addr[1]
|
23
|
-
socket.close
|
24
|
-
port
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.server_is_listening_on(hostname, port)
|
28
|
-
require 'socket'
|
29
|
-
begin
|
30
|
-
socket = TCPSocket.open(hostname, port)
|
31
|
-
rescue Errno::ECONNREFUSED
|
32
|
-
return false
|
33
|
-
end
|
34
|
-
socket.close
|
35
|
-
true
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.wait_for_listener(port, name = "required process", seconds_to_wait = 10)
|
39
|
-
time_out_at = Time.now + seconds_to_wait
|
40
|
-
until server_is_listening_on "localhost", port
|
41
|
-
sleep 0.1
|
42
|
-
puts "Waiting for #{name} on #{port}..."
|
43
|
-
raise "#{name} didn't show up on port #{port} after #{seconds_to_wait} seconds." if Time.now > time_out_at
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.kill_process_group(process_group_id, signal="TERM")
|
48
|
-
Process.kill signal, -process_group_id # negative pid means kill process group. (see man 2 kill)
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.cachebust(files, root_dir="", replace=nil, replace_with=nil)
|
52
|
-
require 'digest/md5'
|
53
|
-
files.collect do |file_name|
|
54
|
-
real_file_name = replace && replace_with ? file_name.sub(replace, replace_with) : file_name
|
55
|
-
begin
|
56
|
-
digest = Digest::MD5.hexdigest(File.read("#{root_dir}#{real_file_name}"))
|
57
|
-
rescue
|
58
|
-
digest = "MISSING-FILE"
|
59
|
-
end
|
60
|
-
"#{file_name}?cachebust=#{digest}"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
class RunAdapter
|
65
|
-
def initialize(spec_files_or_proc, options = {})
|
66
|
-
@spec_files_or_proc = Jasmine.files(spec_files_or_proc) || []
|
67
|
-
@jasmine_files = Jasmine.files(options[:jasmine_files]) || [
|
68
|
-
"/__JASMINE_ROOT__/lib/" + File.basename(Dir.glob("#{Jasmine.root}/lib/jasmine*.js").first),
|
69
|
-
"/__JASMINE_ROOT__/lib/TrivialReporter.js",
|
70
|
-
"/__JASMINE_ROOT__/lib/json2.js",
|
71
|
-
"/__JASMINE_ROOT__/lib/consolex.js",
|
72
|
-
]
|
73
|
-
@stylesheets = ["/__JASMINE_ROOT__/lib/jasmine.css"] + (Jasmine.files(options[:stylesheets]) || [])
|
74
|
-
@spec_helpers = Jasmine.files(options[:spec_helpers]) || []
|
75
|
-
end
|
76
|
-
|
77
|
-
def call(env)
|
78
|
-
run
|
79
|
-
end
|
80
|
-
|
81
|
-
def run
|
82
|
-
stylesheets = @stylesheets
|
83
|
-
spec_helpers = @spec_helpers
|
84
|
-
spec_files = @spec_files_or_proc
|
85
|
-
|
86
|
-
jasmine_files = @jasmine_files
|
87
|
-
jasmine_files = jasmine_files.call if jasmine_files.respond_to?(:call)
|
88
|
-
|
89
|
-
css_files = @stylesheets
|
90
|
-
|
91
|
-
|
92
|
-
body = ERB.new(File.read(File.join(File.dirname(__FILE__), "run.html"))).result(binding)
|
93
|
-
[
|
94
|
-
200,
|
95
|
-
{ 'Content-Type' => 'text/html' },
|
96
|
-
body
|
97
|
-
]
|
98
|
-
end
|
99
|
-
|
100
|
-
|
101
|
-
end
|
102
|
-
|
103
|
-
class Redirect
|
104
|
-
def initialize(url)
|
105
|
-
@url = url
|
106
|
-
end
|
107
|
-
|
108
|
-
def call(env)
|
109
|
-
[
|
110
|
-
302,
|
111
|
-
{ 'Location' => @url },
|
112
|
-
[]
|
113
|
-
]
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
class JsAlert
|
118
|
-
def call(env)
|
119
|
-
[
|
120
|
-
200,
|
121
|
-
{ 'Content-Type' => 'application/javascript' },
|
122
|
-
"document.write('<p>Couldn\\'t load #{env["PATH_INFO"]}!</p>');"
|
123
|
-
]
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
class FocusedSuite
|
128
|
-
def initialize(spec_files_or_proc, options)
|
129
|
-
@spec_files_or_proc = Jasmine.files(spec_files_or_proc) || []
|
130
|
-
@options = options
|
131
|
-
end
|
132
|
-
|
133
|
-
def call(env)
|
134
|
-
spec_files = @spec_files_or_proc
|
135
|
-
matching_specs = spec_files.select {|spec_file| spec_file =~ /#{Regexp.escape(env["PATH_INFO"])}/ }.compact
|
136
|
-
if !matching_specs.empty?
|
137
|
-
run_adapter = Jasmine::RunAdapter.new(matching_specs, @options)
|
138
|
-
run_adapter.run
|
139
|
-
else
|
140
|
-
[
|
141
|
-
200,
|
142
|
-
{ 'Content-Type' => 'application/javascript' },
|
143
|
-
"document.write('<p>Couldn\\'t find any specs matching #{env["PATH_INFO"]}!</p>');"
|
144
|
-
]
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
end
|
149
|
-
|
150
|
-
class SimpleServer
|
151
|
-
def self.start(port, spec_files_or_proc, mappings, options = {})
|
152
|
-
require 'thin'
|
153
|
-
config = {
|
154
|
-
'/__suite__' => Jasmine::FocusedSuite.new(spec_files_or_proc, options),
|
155
|
-
'/run.html' => Jasmine::Redirect.new('/'),
|
156
|
-
'/' => Jasmine::RunAdapter.new(spec_files_or_proc, options)
|
157
|
-
}
|
158
|
-
mappings.each do |from, to|
|
159
|
-
config[from] = Rack::File.new(to)
|
160
|
-
end
|
161
|
-
|
162
|
-
config["/__JASMINE_ROOT__"] = Rack::File.new(Jasmine.root)
|
163
|
-
|
164
|
-
app = Rack::Cascade.new([
|
165
|
-
Rack::URLMap.new(config),
|
166
|
-
JsAlert.new
|
167
|
-
])
|
168
|
-
|
169
|
-
begin
|
170
|
-
Thin::Server.start('0.0.0.0', port, app)
|
171
|
-
rescue RuntimeError => e
|
172
|
-
raise e unless e.message == 'no acceptor'
|
173
|
-
raise RuntimeError.new("A server is already running on port #{port}")
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
class SimpleClient
|
179
|
-
def initialize(selenium_host, selenium_port, selenium_browser_start_command, http_address)
|
180
|
-
require 'selenium/client'
|
181
|
-
@driver = Selenium::Client::Driver.new(
|
182
|
-
selenium_host,
|
183
|
-
selenium_port,
|
184
|
-
selenium_browser_start_command,
|
185
|
-
http_address
|
186
|
-
)
|
187
|
-
@http_address = http_address
|
188
|
-
end
|
189
|
-
|
190
|
-
def tests_have_finished?
|
191
|
-
@driver.get_eval("window.jasmine.getEnv().currentRunner.finished") == "true"
|
192
|
-
end
|
193
|
-
|
194
|
-
def connect
|
195
|
-
@driver.start
|
196
|
-
@driver.open("/")
|
197
|
-
end
|
198
|
-
|
199
|
-
def disconnect
|
200
|
-
@driver.stop
|
201
|
-
end
|
202
|
-
|
203
|
-
def run
|
204
|
-
until tests_have_finished? do
|
205
|
-
sleep 0.1
|
206
|
-
end
|
207
|
-
|
208
|
-
puts @driver.get_eval("window.results()")
|
209
|
-
failed_count = @driver.get_eval("window.jasmine.getEnv().currentRunner.results().failedCount").to_i
|
210
|
-
failed_count == 0
|
211
|
-
end
|
212
|
-
|
213
|
-
def eval_js(script)
|
214
|
-
escaped_script = "'" + script.gsub(/(['\\])/) { '\\' + $1 } + "'"
|
215
|
-
|
216
|
-
result = @driver.get_eval(" try { eval(#{escaped_script}, window); } catch(err) { window.eval(#{escaped_script}); }")
|
217
|
-
JSON.parse("[#{result}]")[0]
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
class Runner
|
222
|
-
def initialize(selenium_jar_path, spec_files, dir_mappings, options={})
|
223
|
-
@selenium_jar_path = selenium_jar_path
|
224
|
-
@spec_files = spec_files
|
225
|
-
@dir_mappings = dir_mappings
|
226
|
-
@options = options
|
227
|
-
|
228
|
-
@browser = options[:browser] ? options[:browser].delete(:browser) : 'firefox'
|
229
|
-
@selenium_pid = nil
|
230
|
-
@jasmine_server_pid = nil
|
231
|
-
@selenium_host = 'localhost'
|
232
|
-
@jasmine_server_port = Jasmine::find_unused_port
|
233
|
-
@selenium_server_port = Jasmine::find_unused_port
|
234
|
-
end
|
235
|
-
|
236
|
-
def start
|
237
|
-
start_jasmine_server
|
238
|
-
start_selenium_server
|
239
|
-
@client = Jasmine::SimpleClient.new(@selenium_host, @selenium_server_port, "*#{@browser}", "http://localhost:#{@jasmine_server_port}/")
|
240
|
-
@client.connect
|
241
|
-
end
|
242
|
-
|
243
|
-
def stop
|
244
|
-
@client.disconnect
|
245
|
-
stop_selenium_server
|
246
|
-
stop_jasmine_server
|
247
|
-
end
|
248
|
-
|
249
|
-
def start_jasmine_server
|
250
|
-
@jasmine_server_pid = fork do
|
251
|
-
Process.setpgrp
|
252
|
-
Jasmine::SimpleServer.start(@jasmine_server_port, @spec_files, @dir_mappings, @options)
|
253
|
-
exit! 0
|
254
|
-
end
|
255
|
-
puts "jasmine server started. pid is #{@jasmine_server_pid}"
|
256
|
-
Jasmine::wait_for_listener(@jasmine_server_port, "jasmine server")
|
257
|
-
end
|
258
|
-
|
259
|
-
def start_selenium_server
|
260
|
-
@selenium_pid = fork do
|
261
|
-
Process.setpgrp
|
262
|
-
exec "java -jar #{@selenium_jar_path} -port #{@selenium_server_port} > /dev/null 2>&1"
|
263
|
-
end
|
264
|
-
puts "selenium started. pid is #{@selenium_pid}"
|
265
|
-
Jasmine::wait_for_listener(@selenium_server_port, "selenium server")
|
266
|
-
end
|
267
|
-
|
268
|
-
def stop_jasmine_server
|
269
|
-
puts "shutting down Jasmine server..."
|
270
|
-
Jasmine::kill_process_group(@jasmine_server_pid) if @jasmine_server_pid
|
271
|
-
end
|
272
|
-
|
273
|
-
def stop_selenium_server
|
274
|
-
puts "shutting down Selenium server..."
|
275
|
-
Jasmine::kill_process_group(@selenium_pid) if @selenium_pid
|
276
|
-
end
|
277
|
-
|
278
|
-
def run
|
279
|
-
begin
|
280
|
-
start
|
281
|
-
puts "servers are listening on their ports -- running the test script..."
|
282
|
-
tests_passed = @client.run
|
283
|
-
ensure
|
284
|
-
stop
|
285
|
-
end
|
286
|
-
return tests_passed
|
287
|
-
end
|
288
|
-
|
289
|
-
def eval_js(script)
|
290
|
-
@client.eval_js(script)
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
class SauceLabsRunner < Runner
|
295
|
-
def initialize(spec_files, dir_mappings, options={})
|
296
|
-
@spec_files = spec_files
|
297
|
-
@dir_mappings = dir_mappings
|
298
|
-
@options = options
|
299
|
-
|
300
|
-
@browser = options[:browser] ? options[:browser].delete(:browser) : 'firefox'
|
301
|
-
@jasmine_server_pid = nil
|
302
|
-
@jasmine_server_port = Jasmine::find_unused_port
|
303
|
-
@saucelabs_config = SeleniumConfig.new(options[:saucelabs_config], options[:saucelabs_config_file], @jasmine_server_port)
|
304
|
-
end
|
305
|
-
|
306
|
-
def start_selenium_server
|
307
|
-
@sauce_tunnel = SauceTunnel.new(@saucelabs_config)
|
308
|
-
end
|
309
|
-
|
310
|
-
def start
|
311
|
-
start_jasmine_server
|
312
|
-
start_selenium_server
|
313
|
-
@client = Jasmine::SimpleClient.new(@saucelabs_config['selenium_server_address'],
|
314
|
-
4444,
|
315
|
-
@saucelabs_config['selenium_browser_key'],
|
316
|
-
"http://#{@saucelabs_config['application_address']}")
|
317
|
-
@client.connect
|
318
|
-
end
|
319
|
-
|
320
|
-
def stop
|
321
|
-
@client.disconnect
|
322
|
-
@sauce_tunnel.shutdown
|
323
|
-
stop_jasmine_server
|
324
|
-
end
|
325
|
-
|
326
|
-
end
|
327
|
-
|
328
|
-
def self.files(f)
|
329
|
-
result = f
|
330
|
-
result = result.call if result.respond_to?(:call)
|
331
|
-
result
|
332
|
-
end
|
333
|
-
|
334
|
-
end
|
@@ -1,153 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), "jasmine_runner.rb"))
|
2
|
-
require 'enumerator'
|
3
|
-
module Jasmine
|
4
|
-
|
5
|
-
class SpecBuilder
|
6
|
-
attr_accessor :suites
|
7
|
-
|
8
|
-
def initialize(spec_files, runner)
|
9
|
-
@spec_files = spec_files
|
10
|
-
@runner = runner
|
11
|
-
@spec_ids = []
|
12
|
-
end
|
13
|
-
|
14
|
-
def start
|
15
|
-
guess_example_locations
|
16
|
-
|
17
|
-
@runner.start
|
18
|
-
load_suite_info
|
19
|
-
wait_for_suites_to_finish_running
|
20
|
-
end
|
21
|
-
|
22
|
-
def stop
|
23
|
-
@runner.stop
|
24
|
-
end
|
25
|
-
|
26
|
-
def script_path
|
27
|
-
File.expand_path(__FILE__)
|
28
|
-
end
|
29
|
-
|
30
|
-
def guess_example_locations
|
31
|
-
@example_locations = {}
|
32
|
-
|
33
|
-
example_name_parts = []
|
34
|
-
previous_indent_level = 0
|
35
|
-
@spec_files.each do |filename|
|
36
|
-
line_number = 1
|
37
|
-
File.open(filename, "r") do |file|
|
38
|
-
file.readlines.each do |line|
|
39
|
-
match = /^(\s*)(describe|it)\s*\(\s*["'](.*)["']\s*,\s*function/.match(line)
|
40
|
-
if (match)
|
41
|
-
indent_level = match[1].length / 2
|
42
|
-
example_name = match[3]
|
43
|
-
example_name_parts[indent_level] = example_name
|
44
|
-
|
45
|
-
full_example_name = example_name_parts.slice(0, indent_level + 1).join(" ")
|
46
|
-
@example_locations[full_example_name] = "#{filename}:#{line_number}: in `it'"
|
47
|
-
end
|
48
|
-
line_number += 1
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def load_suite_info
|
55
|
-
started = Time.now
|
56
|
-
while !eval_js('jsApiReporter && jsApiReporter.started') do
|
57
|
-
raise "couldn't connect to Jasmine after 60 seconds" if (started + 60 < Time.now)
|
58
|
-
sleep 0.1
|
59
|
-
end
|
60
|
-
|
61
|
-
@suites = eval_js("var result = jsApiReporter.suites(); if (window.Prototype && result && result.toJSON) { result.toJSON()} else { JSON.stringify(result) }")
|
62
|
-
end
|
63
|
-
|
64
|
-
def results_for(spec_id)
|
65
|
-
@spec_results ||= load_results
|
66
|
-
@spec_results[spec_id.to_s]
|
67
|
-
end
|
68
|
-
|
69
|
-
def load_results
|
70
|
-
@spec_results = {}
|
71
|
-
@spec_ids.each_slice(50) do |slice|
|
72
|
-
@spec_results.merge!(eval_js("var result = jsApiReporter.resultsForSpecs(#{JSON.generate(slice)}); if (window.Prototype && result && result.toJSON) { result.toJSON()} else { JSON.stringify(result) }"))
|
73
|
-
end
|
74
|
-
@spec_results
|
75
|
-
end
|
76
|
-
|
77
|
-
def wait_for_suites_to_finish_running
|
78
|
-
puts "Waiting for suite to finish in browser ..."
|
79
|
-
while !eval_js('jsApiReporter.finished') do
|
80
|
-
sleep 0.1
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def declare_suites
|
85
|
-
me = self
|
86
|
-
suites.each do |suite|
|
87
|
-
declare_suite(self, suite)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def declare_suite(parent, suite)
|
92
|
-
me = self
|
93
|
-
parent.describe suite["name"] do
|
94
|
-
suite["children"].each do |suite_or_spec|
|
95
|
-
type = suite_or_spec["type"]
|
96
|
-
if type == "suite"
|
97
|
-
me.declare_suite(self, suite_or_spec)
|
98
|
-
elsif type == "spec"
|
99
|
-
me.declare_spec(self, suite_or_spec)
|
100
|
-
else
|
101
|
-
raise "unknown type #{type} for #{suite_or_spec.inspect}"
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def declare_spec(parent, spec)
|
108
|
-
me = self
|
109
|
-
example_name = spec["name"]
|
110
|
-
@spec_ids << spec["id"]
|
111
|
-
backtrace = @example_locations[parent.description + " " + example_name]
|
112
|
-
parent.it example_name, {}, backtrace do
|
113
|
-
me.report_spec(spec["id"])
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def report_spec(spec_id)
|
118
|
-
spec_results = results_for(spec_id)
|
119
|
-
|
120
|
-
out = ""
|
121
|
-
messages = spec_results['messages'].each do |message|
|
122
|
-
case
|
123
|
-
when message["type"] == "MessageResult"
|
124
|
-
puts message["text"]
|
125
|
-
puts "\n"
|
126
|
-
else
|
127
|
-
unless message["message"] =~ /^Passed.$/
|
128
|
-
STDERR << message["message"]
|
129
|
-
STDERR << "\n"
|
130
|
-
|
131
|
-
out << message["message"]
|
132
|
-
out << "\n"
|
133
|
-
end
|
134
|
-
|
135
|
-
if !message["passed"] && message["trace"]["stack"]
|
136
|
-
stack_trace = message["trace"]["stack"].gsub(/<br \/>/, "\n").gsub(/<\/?b>/, " ")
|
137
|
-
STDERR << stack_trace.gsub(/\(.*\)@http:\/\/localhost:[0-9]+\/specs\//, "/spec/")
|
138
|
-
STDERR << "\n"
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
end
|
143
|
-
fail out unless spec_results['result'] == 'passed'
|
144
|
-
puts out unless out.empty?
|
145
|
-
end
|
146
|
-
|
147
|
-
private
|
148
|
-
|
149
|
-
def eval_js(js)
|
150
|
-
@runner.eval_js(js)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
2
|
-
<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
3
|
-
<head>
|
4
|
-
<meta content="text/html;charset=UTF-8" http-equiv="Content-Type"/>
|
5
|
-
<title>Jasmine suite</title>
|
6
|
-
<% css_files.each do |css_file| %>
|
7
|
-
<link rel="stylesheet" href="<%= css_file %>" type="text/css" media="screen"/>
|
8
|
-
<% end %>
|
9
|
-
|
10
|
-
<% jasmine_files.each do |jasmine_file| %>
|
11
|
-
<script src="<%= jasmine_file %>" type="text/javascript"></script>
|
12
|
-
<% end %>
|
13
|
-
|
14
|
-
<% spec_helpers.each do |spec_helper| %>
|
15
|
-
<script src="<%= spec_helper %>" type="text/javascript"></script>
|
16
|
-
<% end %>
|
17
|
-
|
18
|
-
<script type="text/javascript">
|
19
|
-
var jsApiReporter;
|
20
|
-
(function() {
|
21
|
-
var jasmineEnv = jasmine.getEnv();
|
22
|
-
|
23
|
-
jsApiReporter = new jasmine.JsApiReporter();
|
24
|
-
var trivialReporter = new jasmine.TrivialReporter();
|
25
|
-
|
26
|
-
jasmineEnv.addReporter(jsApiReporter);
|
27
|
-
jasmineEnv.addReporter(trivialReporter);
|
28
|
-
|
29
|
-
jasmineEnv.specFilter = function(spec) {
|
30
|
-
return trivialReporter.specFilter(spec);
|
31
|
-
};
|
32
|
-
|
33
|
-
window.onload = function() {
|
34
|
-
jasmineEnv.execute();
|
35
|
-
};
|
36
|
-
})();
|
37
|
-
</script>
|
38
|
-
|
39
|
-
<% spec_files.each do |spec_file| %>
|
40
|
-
<script src="<%= spec_file %>" type="text/javascript"></script>
|
41
|
-
<% end %>
|
42
|
-
|
43
|
-
</head>
|
44
|
-
<body>
|
45
|
-
<div id="jasmine_content"></div>
|
46
|
-
</body>
|
47
|
-
</html>
|