jasmine-coverage 0.2.2 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -0
- data/lib/jasmine/coverage/version.rb +1 -1
- data/lib/tasks/base64.min.js +2 -0
- data/lib/tasks/coverage_output_generator.js +17 -10
- data/lib/tasks/jasmine_coverage.rake +60 -27
- data/lib/tasks/jasmine_headless_coverage_patches.rb +29 -9
- metadata +12 -11
data/README.md
CHANGED
@@ -59,6 +59,10 @@ it generates regardless, you can specify that in an environment variable.
|
|
59
59
|
|
60
60
|
bundle exec rake jasmine:coverage JASMINE_COVERAGE_KEEP_TEST_RIG=true
|
61
61
|
|
62
|
+
You can also specify if you want missing coverage warnings
|
63
|
+
|
64
|
+
bundle exec rake jasmine:coverage JASMINE_COVERAGE_WARNINGS=true
|
65
|
+
|
62
66
|
# How it works
|
63
67
|
|
64
68
|
First Sprockets is interrogated to get a list of JS files concerned. This way, the right JS files
|
@@ -0,0 +1,2 @@
|
|
1
|
+
// credit to https://github.com/dankogai/js-base64
|
2
|
+
(function(global){"use strict";if(global.Base64)return;var version="2.1.1";var buffer;if(typeof module!=="undefined"&&module.exports){buffer=require("buffer").Buffer}var b64chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var b64tab=function(bin){var t={};for(var i=0,l=bin.length;i<l;i++)t[bin.charAt(i)]=i;return t}(b64chars);var fromCharCode=String.fromCharCode;var cb_utob=function(c){if(c.length<2){var cc=c.charCodeAt(0);return cc<128?c:cc<2048?fromCharCode(192|cc>>>6)+fromCharCode(128|cc&63):fromCharCode(224|cc>>>12&15)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}else{var cc=65536+(c.charCodeAt(0)-55296)*1024+(c.charCodeAt(1)-56320);return fromCharCode(240|cc>>>18&7)+fromCharCode(128|cc>>>12&63)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}};var re_utob=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;var utob=function(u){return u.replace(re_utob,cb_utob)};var cb_encode=function(ccc){var padlen=[0,2,1][ccc.length%3],ord=ccc.charCodeAt(0)<<16|(ccc.length>1?ccc.charCodeAt(1):0)<<8|(ccc.length>2?ccc.charCodeAt(2):0),chars=[b64chars.charAt(ord>>>18),b64chars.charAt(ord>>>12&63),padlen>=2?"=":b64chars.charAt(ord>>>6&63),padlen>=1?"=":b64chars.charAt(ord&63)];return chars.join("")};var btoa=global.btoa||function(b){return b.replace(/[\s\S]{1,3}/g,cb_encode)};var _encode=buffer?function(u){return new buffer(u).toString("base64")}:function(u){return btoa(utob(u))};var encode=function(u,urisafe){return!urisafe?_encode(u):_encode(u).replace(/[+\/]/g,function(m0){return m0=="+"?"-":"_"}).replace(/=/g,"")};var encodeURI=function(u){return encode(u,true)};var re_btou=new RegExp(["[À-ß][-¿]","[à-ï][-¿]{2}","[ð-÷][-¿]{3}"].join("|"),"g");var cb_btou=function(cccc){switch(cccc.length){case 4:var cp=(7&cccc.charCodeAt(0))<<18|(63&cccc.charCodeAt(1))<<12|(63&cccc.charCodeAt(2))<<6|63&cccc.charCodeAt(3),offset=cp-65536;return fromCharCode((offset>>>10)+55296)+fromCharCode((offset&1023)+56320);case 3:return fromCharCode((15&cccc.charCodeAt(0))<<12|(63&cccc.charCodeAt(1))<<6|63&cccc.charCodeAt(2));default:return fromCharCode((31&cccc.charCodeAt(0))<<6|63&cccc.charCodeAt(1))}};var btou=function(b){return b.replace(re_btou,cb_btou)};var cb_decode=function(cccc){var len=cccc.length,padlen=len%4,n=(len>0?b64tab[cccc.charAt(0)]<<18:0)|(len>1?b64tab[cccc.charAt(1)]<<12:0)|(len>2?b64tab[cccc.charAt(2)]<<6:0)|(len>3?b64tab[cccc.charAt(3)]:0),chars=[fromCharCode(n>>>16),fromCharCode(n>>>8&255),fromCharCode(n&255)];chars.length-=[0,0,2,1][padlen];return chars.join("")};var atob=global.atob||function(a){return a.replace(/[\s\S]{1,4}/g,cb_decode)};var _decode=buffer?function(a){return new buffer(a,"base64").toString()}:function(a){return btou(atob(a))};var decode=function(a){return _decode(a.replace(/[-_]/g,function(m0){return m0=="-"?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,""))};global.Base64={VERSION:version,atob:atob,btoa:btoa,fromBase64:decode,toBase64:encode,utob:utob,encode:encode,encodeURI:encodeURI,btou:btou,decode:decode};if(typeof Object.defineProperty==="function"){var noEnum=function(v){return{value:v,enumerable:false,writable:true,configurable:true}};global.Base64.extendString=function(){Object.defineProperty(String.prototype,"fromBase64",noEnum(function(){return decode(this)}));Object.defineProperty(String.prototype,"toBase64",noEnum(function(urisafe){return encode(this,urisafe)}));Object.defineProperty(String.prototype,"toBase64URI",noEnum(function(){return encode(this,true)}))}}})(this);
|
@@ -31,8 +31,8 @@ function generateEncodedCoverage() {
|
|
31
31
|
for (var file_name in window._$jscoverage) {
|
32
32
|
var jscov = window._$jscoverage[ file_name ];
|
33
33
|
var file_report = rv[ file_name ] = {
|
34
|
-
coverage:new Array(jscov.length),
|
35
|
-
source:new Array(jscov.length)
|
34
|
+
coverage: new Array(jscov.length),
|
35
|
+
source: new Array(jscov.length)
|
36
36
|
};
|
37
37
|
for (var i = 0; i < jscov.length; ++i) {
|
38
38
|
var hit_count = jscov[ i ] !== undefined ? jscov[ i ] : null;
|
@@ -47,7 +47,7 @@ function generateEncodedCoverage() {
|
|
47
47
|
|
48
48
|
function coverageForAllFiles() {
|
49
49
|
|
50
|
-
var totals = { files:0, statements:0, executed:0 };
|
50
|
+
var totals = { files: 0, statements: 0, executed: 0 };
|
51
51
|
|
52
52
|
var output = "Coverage was:\n";
|
53
53
|
|
@@ -58,8 +58,14 @@ function coverageForAllFiles() {
|
|
58
58
|
totals['files']++;
|
59
59
|
totals['statements'] += simple_file_coverage['statements'];
|
60
60
|
totals['executed'] += simple_file_coverage['executed'];
|
61
|
+
if (simple_file_coverage['missing'].length > 0 && JasmineCoverage.warnings) {
|
62
|
+
var i, ar = simple_file_coverage['missing'], l = ar.length;
|
63
|
+
for (i = 0; i < l; i++) {
|
64
|
+
console.log("\033[31m" + "WARNING -- File '" + file_name + "' has no coverage on line: " + ar[i] + "\033[0m");
|
65
|
+
}
|
66
|
+
}
|
61
67
|
|
62
|
-
var fraction = (simple_file_coverage['executed']+"/"+simple_file_coverage['statements']).lpad(' ', 10);
|
68
|
+
var fraction = (simple_file_coverage['executed'] + "/" + simple_file_coverage['statements']).lpad(' ', 10);
|
63
69
|
output += fraction + (" = " + simple_file_coverage['percentage'] + "").lpad(' ', 3) + "% for " + file_name + "\n";
|
64
70
|
}
|
65
71
|
|
@@ -69,10 +75,10 @@ function coverageForAllFiles() {
|
|
69
75
|
}
|
70
76
|
|
71
77
|
if (totals['statements'] === 0) {
|
72
|
-
log("No Javascript was found to test coverage for.");
|
78
|
+
console.log("No Javascript was found to test coverage for.");
|
73
79
|
} else {
|
74
|
-
output += ( totals['executed'] +"/"+totals['statements']+ " = "+ coverage + "").lpad(' ', 15) + "% Total\n";
|
75
|
-
log(output);
|
80
|
+
output += ( totals['executed'] + "/" + totals['statements'] + " = " + coverage + "").lpad(' ', 15) + "% Total\n";
|
81
|
+
console.log(output);
|
76
82
|
}
|
77
83
|
|
78
84
|
return coverage;
|
@@ -120,8 +126,9 @@ function coverageForFile(fileCC) {
|
|
120
126
|
var percentage = ( num_statements === 0 ? 0 : parseInt(100 * num_executed / num_statements) );
|
121
127
|
|
122
128
|
return {
|
123
|
-
statements:num_statements,
|
124
|
-
executed:num_executed,
|
125
|
-
percentage:percentage
|
129
|
+
statements: num_statements,
|
130
|
+
executed: num_executed,
|
131
|
+
percentage: percentage,
|
132
|
+
missing: missing
|
126
133
|
};
|
127
134
|
}
|
@@ -24,11 +24,11 @@ if env =~ /^(development|test)$/
|
|
24
24
|
FileUtils.rm_rf output_dir
|
25
25
|
FileUtils.mkdir_p instrumented_dir
|
26
26
|
|
27
|
-
# The reprocessing folder map
|
28
|
-
files_map = {
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
# The reprocessing folder map. Use an environment variable if available.
|
28
|
+
files_map = ENV['JS_SRC_PATH'] ? {ENV['JS_SRC_PATH'] => instrumented_dir+'public'} : {
|
29
|
+
File.expand_path('app/assets/javascripts') => instrumented_dir+'app',
|
30
|
+
File.expand_path('lib/assets/javascripts') => instrumented_dir+'lib',
|
31
|
+
File.expand_path('public/javascripts') => instrumented_dir+'public',
|
32
32
|
}
|
33
33
|
|
34
34
|
# Instrument the source files into the instrumented folders
|
@@ -38,6 +38,7 @@ if env =~ /^(development|test)$/
|
|
38
38
|
FileUtils.mv(Dir.glob(files_map[folder]+'/jscoverage*'), output_dir)
|
39
39
|
end
|
40
40
|
|
41
|
+
Jasmine::Coverage.warnings = ENV['JASMINE_COVERAGE_WARNINGS'] || 'false'
|
41
42
|
Jasmine::Coverage.resources = files_map
|
42
43
|
Jasmine::Coverage.output_dir = output_dir
|
43
44
|
test_rig_folder = "#{Jasmine::Coverage.output_dir}/test-rig"
|
@@ -46,26 +47,40 @@ if env =~ /^(development|test)$/
|
|
46
47
|
|
47
48
|
# Run Jasmine using the original config.
|
48
49
|
status_code = Jasmine::Headless::Runner.run(
|
49
|
-
|
50
|
+
# Any options from the options.rb file in jasmine-headless-webkit can be used here.
|
50
51
|
|
51
|
-
|
52
|
+
:reporters => [['File', "#{output_dir}/rawreport.txt"]]
|
52
53
|
)
|
53
|
-
errStr =
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
54
|
+
errStr = <<-EOS
|
55
|
+
JSCoverage exited with error code: #{status_code}
|
56
|
+
|
57
|
+
This implies one of five things:
|
58
|
+
0) Your JS files had exactly zero instructions. Are they all blank or just comments?
|
59
|
+
1) A test failed (run bundle exec rake jasmine:headless to see a better error)
|
60
|
+
2) The sourcecode has a syntax error (which JSLint should find)
|
61
|
+
3) An error occurred in a deferred block, e.g. a setTimeout or underscore _.defer. This caused a window error which Jasmine will never see.
|
62
|
+
4) The source files are being loaded out of sequence (so global variables are not being declared in order)
|
63
|
+
To check this, run bundle exec jasmine-headless-webkit -l to see the ordering
|
64
|
+
|
65
|
+
In any case, try running the standard jasmine command to get better errors:
|
66
|
+
|
67
|
+
bundle exec rake jasmine:headless
|
68
|
+
|
69
|
+
Finally, try opening the test-rig in firefox to see the tests run in a browser and get a stacktrace. Chrome has strict security settings
|
70
|
+
that make this difficult since it accesses the local filesystem from Javascript (but you can switch the settings off at the command line).
|
71
|
+
|
72
|
+
|
73
|
+
**********************************************************************************************
|
74
|
+
|
75
|
+
The test rig file needs to load JS directly off disk, which Chrome prevents by default. Your best bet is to open the rig in Firefox.
|
76
|
+
|
77
|
+
The file can be found here: #{test_rig_folder}/jscoverage-test-rig.html
|
78
|
+
|
79
|
+
**********************************************************************************************
|
68
80
|
|
81
|
+
EOS
|
82
|
+
|
83
|
+
fail errStr if status_code == 1
|
69
84
|
# Delete the test_rig folder if not required
|
70
85
|
if ENV['JASMINE_COVERAGE_KEEP_TEST_RIG']
|
71
86
|
p "A copy of the page and files that were used as the jasmine test environment can be found here: #{test_rig_folder}"
|
@@ -77,8 +92,18 @@ if env =~ /^(development|test)$/
|
|
77
92
|
contents = File.open("#{output_dir}/rawreport.txt") { |f| f.read }
|
78
93
|
# Get our Base64.
|
79
94
|
json_report_enc = contents.split(/ENCODED-COVERAGE-EXPORT-STARTS:/m)[1]
|
80
|
-
#
|
81
|
-
|
95
|
+
# Provide warnings to use
|
96
|
+
warning_regex = /^CONSOLE\|\|.{1,6}WARNING.{4}(.*).{5}$/
|
97
|
+
warnings = contents.scan warning_regex
|
98
|
+
if (warnings.length != 0)
|
99
|
+
puts "Detected #{warnings.length} warnings:"
|
100
|
+
puts warnings
|
101
|
+
fail "Aborting. All lines must be covered by a test." if ENV['MUST_COVER_ALL']
|
102
|
+
else
|
103
|
+
puts "No warnings detected."
|
104
|
+
end if
|
105
|
+
# Remove the junk at the end
|
106
|
+
json_report_enc_stripped = json_report_enc[0, json_report_enc.index("\"")]
|
82
107
|
|
83
108
|
# Unpack it from Base64
|
84
109
|
json_report = Base64.decode64(json_report_enc_stripped)
|
@@ -119,6 +144,8 @@ if env =~ /^(development|test)$/
|
|
119
144
|
module Jasmine
|
120
145
|
module Coverage
|
121
146
|
@resources
|
147
|
+
@output_dir
|
148
|
+
@warnings
|
122
149
|
|
123
150
|
def self.resources= resources
|
124
151
|
@resources = resources
|
@@ -128,8 +155,6 @@ if env =~ /^(development|test)$/
|
|
128
155
|
@resources
|
129
156
|
end
|
130
157
|
|
131
|
-
@output_dir
|
132
|
-
|
133
158
|
def self.output_dir= output_dir
|
134
159
|
@output_dir = output_dir
|
135
160
|
end
|
@@ -137,7 +162,15 @@ if env =~ /^(development|test)$/
|
|
137
162
|
def self.output_dir
|
138
163
|
@output_dir
|
139
164
|
end
|
165
|
+
|
166
|
+
def self.warnings= warnings
|
167
|
+
@warnings = warnings
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.warnings
|
171
|
+
@warnings
|
172
|
+
end
|
140
173
|
end
|
141
174
|
end
|
142
175
|
|
143
|
-
end
|
176
|
+
end
|
@@ -16,14 +16,11 @@ module Jasmine::Headless
|
|
16
16
|
FileUtils.mkdir_p test_rigfolder
|
17
17
|
|
18
18
|
p "Copying all view files and potential javascript fixture folders so the jasmine-coverage run has access to the html fixtures."
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# Here we must also copy the spec and app folders to that we have access to all the files if we need them for the test rig
|
25
|
-
FileUtils.copy_entry("#{Jasmine::Coverage.output_dir}/../../spec", "#{test_rigfolder}/spec")
|
26
|
-
FileUtils.copy_entry("#{Jasmine::Coverage.output_dir}/../../app", "#{test_rigfolder}/app")
|
19
|
+
copy_assets_to_test_dir(test_rigfolder, '../fixtures', 'target/fixtures')
|
20
|
+
copy_assets_to_test_dir(test_rigfolder, '../views', 'target/views')
|
21
|
+
# Here we must also copy the spec and app folders so that we have access to all the files if we need them for the test rig
|
22
|
+
copy_assets_to_test_dir(test_rigfolder, '../../spec', 'spec')
|
23
|
+
copy_assets_to_test_dir(test_rigfolder, '../../app', 'app')
|
27
24
|
|
28
25
|
jss = str.scan(/<script type="text\/javascript" src="(.*)"><\/script>/)
|
29
26
|
jss << str.scan(/<link rel="stylesheet" href="(.*)" type="text\/css" \/>/)
|
@@ -52,6 +49,15 @@ module Jasmine::Headless
|
|
52
49
|
|
53
50
|
ret
|
54
51
|
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def copy_assets_to_test_dir(test_rigfolder, from_dir, to_dir)
|
56
|
+
if File.exists? "#{Jasmine::Coverage.output_dir}/#{from_dir}"
|
57
|
+
FileUtils.mkdir_p "#{test_rigfolder}/#{to_dir}"
|
58
|
+
FileUtils.copy_entry("#{Jasmine::Coverage.output_dir}/#{from_dir}", "#{test_rigfolder}/#{to_dir}")
|
59
|
+
end
|
60
|
+
end
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
@@ -63,7 +69,7 @@ module Jasmine::Headless
|
|
63
69
|
|
64
70
|
def to_html(files)
|
65
71
|
# Declare our test runner files
|
66
|
-
cov_files = ['/jscoverage.js', '/coverage_output_generator.js']
|
72
|
+
cov_files = ['/jscoverage.js', '/base64.min.js', '/coverage_output_generator.js']
|
67
73
|
|
68
74
|
# Add the original files, remapping to instrumented where necessary
|
69
75
|
tags = []
|
@@ -85,6 +91,8 @@ module Jasmine::Headless
|
|
85
91
|
# Attach the "in context" test runners
|
86
92
|
tags = tags + old_to_html(cov_files.map { |f| File.dirname(__FILE__)+f })
|
87
93
|
|
94
|
+
add_coverage_js_config(tags)
|
95
|
+
|
88
96
|
tags
|
89
97
|
end
|
90
98
|
|
@@ -97,5 +105,17 @@ module Jasmine::Headless
|
|
97
105
|
@sprockets_environment.append_path(File.dirname(__FILE__))
|
98
106
|
@sprockets_environment
|
99
107
|
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# This method injects the config we have defined in Ruby that needs to be present in the JS context
|
112
|
+
def add_coverage_js_config(tags)
|
113
|
+
cov_conf = "#{Jasmine::Coverage.output_dir}/coverage_config.js"
|
114
|
+
tags << "<script type=\"text/javascript\" src=\"#{cov_conf}\"></script>"
|
115
|
+
|
116
|
+
aFile = File.new(cov_conf, "w")
|
117
|
+
aFile.write("var JasmineCoverage = { warnings: #{Jasmine::Coverage.warnings}}")
|
118
|
+
aFile.close
|
119
|
+
end
|
100
120
|
end
|
101
121
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jasmine-coverage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,56 +9,56 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-05-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
type: :runtime
|
16
16
|
name: jasmine-headless-webkit
|
17
17
|
prerelease: false
|
18
18
|
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
19
20
|
requirements:
|
20
21
|
- - ! '>='
|
21
22
|
- !ruby/object:Gem::Version
|
22
23
|
version: 0.9.0.rc.2
|
23
|
-
none: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
25
26
|
requirements:
|
26
27
|
- - ! '>='
|
27
28
|
- !ruby/object:Gem::Version
|
28
29
|
version: 0.9.0.rc.2
|
29
|
-
none: false
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
type: :runtime
|
32
32
|
name: coffee-script-source
|
33
33
|
prerelease: false
|
34
34
|
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
35
36
|
requirements:
|
36
37
|
- - ! '>='
|
37
38
|
- !ruby/object:Gem::Version
|
38
39
|
version: '0'
|
39
|
-
none: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
41
42
|
requirements:
|
42
43
|
- - ! '>='
|
43
44
|
- !ruby/object:Gem::Version
|
44
45
|
version: '0'
|
45
|
-
none: false
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
type: :runtime
|
48
48
|
name: headless
|
49
49
|
prerelease: false
|
50
50
|
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
51
52
|
requirements:
|
52
53
|
- - ! '>='
|
53
54
|
- !ruby/object:Gem::Version
|
54
55
|
version: '0'
|
55
|
-
none: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
57
58
|
requirements:
|
58
59
|
- - ! '>='
|
59
60
|
- !ruby/object:Gem::Version
|
60
61
|
version: '0'
|
61
|
-
none: false
|
62
62
|
description:
|
63
63
|
email:
|
64
64
|
- harry@harrylascelles.com
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- lib/jasmine/coverage.rb
|
70
70
|
- lib/jasmine/coverage/version.rb
|
71
71
|
- lib/jasmine/coverage/jasmine_coverage_railtie.rb
|
72
|
+
- lib/tasks/base64.min.js
|
72
73
|
- lib/tasks/jasmine_headless_coverage_patches.rb
|
73
74
|
- lib/tasks/jasmine_coverage.rake
|
74
75
|
- lib/tasks/jscoverage.js
|
@@ -82,20 +83,20 @@ rdoc_options: []
|
|
82
83
|
require_paths:
|
83
84
|
- lib
|
84
85
|
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
85
87
|
requirements:
|
86
88
|
- - ! '>='
|
87
89
|
- !ruby/object:Gem::Version
|
88
90
|
version: '0'
|
89
|
-
none: false
|
90
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
91
93
|
requirements:
|
92
94
|
- - ! '>='
|
93
95
|
- !ruby/object:Gem::Version
|
94
96
|
version: '0'
|
95
|
-
none: false
|
96
97
|
requirements: []
|
97
98
|
rubyforge_project:
|
98
|
-
rubygems_version: 1.8.
|
99
|
+
rubygems_version: 1.8.25
|
99
100
|
signing_key:
|
100
101
|
specification_version: 3
|
101
102
|
summary: A blend of JS unit testing and coverage
|