js-test-server 0.2.1 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/CHANGES +3 -2
  2. data/Rakefile +12 -5
  3. data/lib/js_test_server/configuration.rb +2 -2
  4. data/lib/js_test_server/server.rb +1 -1
  5. data/lib/js_test_server/server/resources/file.rb +1 -1
  6. data/lib/js_test_server/server/resources/not_found.rb +1 -1
  7. data/lib/js_test_server/server/resources/remote_control.rb +1 -1
  8. data/lib/js_test_server/server/resources/spec_file.rb +1 -1
  9. data/lib/js_test_server/server/runner.rb +15 -0
  10. data/lib/js_test_server/server/standalone.ru +1 -0
  11. data/lib/js_test_server/server/views.rb +12 -0
  12. data/lib/js_test_server/server/{representations → views}/dir.html.rb +1 -1
  13. data/lib/js_test_server/server/{representations → views}/frameworks.rb +0 -0
  14. data/lib/js_test_server/server/{representations → views}/not_found.html.rb +1 -1
  15. data/lib/js_test_server/server/{representations → views}/page.html.rb +1 -1
  16. data/lib/js_test_server/server/{representations → views}/remote_control_subscriber.rb +2 -2
  17. data/lib/js_test_server/server/{representations → views}/suite.html.rb +1 -1
  18. data/lib/js_test_server/server/{representations → views}/suites.rb +1 -1
  19. data/lib/js_test_server/server/{representations → views}/suites/jasmine.html.rb +2 -2
  20. data/lib/js_test_server/server/{representations → views}/suites/screw_unit.html.rb +6 -7
  21. data/public/js_test_server.js +568 -0
  22. data/public/js_test_server/jasmine_driver.js +62 -0
  23. data/public/js_test_server/remote_control.js +28 -0
  24. data/public/js_test_server/screw_unit_driver.js +31 -0
  25. data/spec/unit/js_test_core/resources/spec_file_spec.rb +4 -4
  26. data/spec/unit/js_test_core/server/server_spec.rb +38 -2
  27. data/vendor/lucky-luciano/lib/lucky_luciano.rb +5 -0
  28. data/vendor/lucky-luciano/lib/lucky_luciano/resource.rb +142 -0
  29. data/vendor/lucky-luciano/lib/lucky_luciano/resource/path.rb +24 -0
  30. data/vendor/lucky-luciano/lib/lucky_luciano/rspec.rb +4 -0
  31. data/vendor/lucky-luciano/lib/lucky_luciano/rspec/be_http.rb +32 -0
  32. data/vendor/lucky-luciano/spec/lucky_luciano/resource_spec.rb +276 -0
  33. data/vendor/lucky-luciano/spec/spec_helper.rb +48 -0
  34. data/vendor/lucky-luciano/spec/spec_suite.rb +4 -0
  35. metadata +60 -28
  36. data/lib/js_test_server/server/representations.rb +0 -12
data/CHANGES CHANGED
@@ -1,5 +1,6 @@
1
- - Added JsTestCore::Representations::Spec.project_js_files and .project_css_files
2
- - Updated to erector 0.6.7, which fixes a bug related to including modules into Representations.
1
+ - Renamed JsTestServer::Representations to JsTestServer::Views
2
+ - Added JsTestServer::Views::Spec.project_js_files and .project_css_files
3
+ - Updated to erector 0.6.7, which fixes a bug related to including modules into Views.
3
4
  - Allow test suite to clear all cookies
4
5
  - No longer using the /implementanions directory. Replace all cases of /implementations with /javascripts.
5
6
  - Added support for custom suite files
data/Rakefile CHANGED
@@ -16,20 +16,27 @@ task(:spec) do
16
16
  run_suite
17
17
  end
18
18
 
19
+ desc "Tag the release and push"
20
+ task :release do
21
+ tag_name = "v#{PKG_VERSION}"
22
+ system("git tag #{tag_name} && git push origin #{tag_name}")
23
+ end
24
+
19
25
  def run_suite
20
26
  dir = File.dirname(__FILE__)
21
27
  system("ruby #{dir}/spec/spec_suite.rb") || raise("Example Suite failed")
22
28
  end
23
29
 
24
30
  PKG_NAME = "js-test-server"
25
- PKG_VERSION = "0.2.1"
31
+ PKG_VERSION = "0.2.6"
26
32
  PKG_FILES = FileList[
27
33
  '[A-Z]*',
28
34
  '*.rb',
29
- 'lib/**/*.rb',
30
- 'core/**',
31
- 'bin/**',
32
- 'spec/**/*.rb'
35
+ 'lib/**/**',
36
+ 'public/**/**',
37
+ 'bin/**/**',
38
+ 'spec/**/*.rb',
39
+ 'vendor/**/*.rb'
33
40
  ]
34
41
 
35
42
  spec = Gem::Specification.new do |s|
@@ -25,9 +25,9 @@ module JsTestServer
25
25
  @framework_name = params[:framework_name]
26
26
  end
27
27
 
28
- def suite_representation_class
28
+ def suite_view_class
29
29
  if framework_name
30
- JsTestServer::Server::Representations::Suites.const_get(framework_name.gsub("-", "_").camelcase)
30
+ JsTestServer::Server::Views::Suites.const_get(framework_name.gsub("-", "_").camelcase)
31
31
  end
32
32
  end
33
33
 
@@ -10,5 +10,5 @@ end
10
10
  dir = File.dirname(__FILE__)
11
11
  require "#{dir}/server/runner"
12
12
  require "#{dir}/server/resources"
13
- require "#{dir}/server/representations"
13
+ require "#{dir}/server/views"
14
14
  require "#{dir}/server/app"
@@ -49,7 +49,7 @@ class JsTestServer::Server::Resources::File < JsTestServer::Server::Resources::R
49
49
  end
50
50
 
51
51
  def render_dir
52
- JsTestServer::Server::Representations::Dir.new(:relative_path => relative_path, :absolute_path => absolute_path).to_s
52
+ JsTestServer::Server::Views::Dir.new(:relative_path => relative_path, :absolute_path => absolute_path).to_s
53
53
  end
54
54
 
55
55
  def render_file
@@ -19,7 +19,7 @@ class JsTestServer::Server::Resources::NotFound < JsTestServer::Server::Resource
19
19
  end
20
20
 
21
21
  def call
22
- body = Representations::NotFound.new(:message => "File #{request.path_info} not found").to_s
22
+ body = Views::NotFound.new(:message => "File #{request.path_info} not found").to_s
23
23
  [ 404, { "Content-Type" => "text/html" }, body ]
24
24
  end
25
25
  end
@@ -45,7 +45,7 @@ module JsTestServer
45
45
  map "/remote_control"
46
46
 
47
47
  get "subscriber" do
48
- [200, {}, Representations::RemoteControlSubscriber.new.to_s]
48
+ [200, {}, Views::RemoteControlSubscriber.new.to_s]
49
49
  end
50
50
 
51
51
  post "commands" do
@@ -38,7 +38,7 @@ class JsTestServer::Server::Resources::SpecFile < JsTestServer::Server::Resource
38
38
  end
39
39
 
40
40
  def render_spec(spec_files)
41
- JsTestServer.suite_representation_class.new(:spec_files => spec_files, :framework_path => framework_path).to_s
41
+ JsTestServer.suite_view_class.new(:spec_files => spec_files, :framework_path => framework_path).to_s
42
42
  end
43
43
 
44
44
  def absolute_path
@@ -32,6 +32,18 @@ class JsTestServer::Server::Runner
32
32
  :type => Integer,
33
33
  :default => DEFAULTS[:port]
34
34
  )
35
+ opt(
36
+ :javascript_files,
37
+ "The javascript files under test",
38
+ :type => String,
39
+ :default => ""
40
+ )
41
+ opt(
42
+ :css_files,
43
+ "The css files under test",
44
+ :type => String,
45
+ :default => ""
46
+ )
35
47
  end
36
48
 
37
49
  JsTestServer.port = opts[:port]
@@ -39,6 +51,9 @@ class JsTestServer::Server::Runner
39
51
  JsTestServer.framework_path = opts[:framework_path]
40
52
  JsTestServer.spec_path = opts[:spec_path]
41
53
  JsTestServer.root_path = opts[:root_path]
54
+ suite_view_class = JsTestServer::Configuration.instance.suite_view_class
55
+ suite_view_class.project_js_files.push(*opts[:javascript_files].split(","))
56
+ suite_view_class.project_css_files.push(*opts[:css_files].split(","))
42
57
  STDOUT.puts "root-path is #{JsTestServer.root_path}"
43
58
  STDOUT.puts "spec-path is #{JsTestServer.spec_path}"
44
59
  start
@@ -0,0 +1 @@
1
+ JsTestServer::Server::Runner.new.standalone_rackup(self)
@@ -0,0 +1,12 @@
1
+ dir = File.dirname(__FILE__)
2
+
3
+ module JsTestServer::Server::Views
4
+ end
5
+
6
+ require "#{dir}/views/page.html"
7
+ require "#{dir}/views/not_found.html"
8
+ require "#{dir}/views/suite.html"
9
+ require "#{dir}/views/dir.html"
10
+ require "#{dir}/views/frameworks"
11
+ require "#{dir}/views/suites"
12
+ require "#{dir}/views/remote_control_subscriber"
@@ -1,4 +1,4 @@
1
- class JsTestServer::Server::Representations::Dir < JsTestServer::Server::Representations::Page
1
+ class JsTestServer::Server::Views::Dir < JsTestServer::Server::Views::Page
2
2
  needs :relative_path, :absolute_path
3
3
  attr_reader :relative_path, :absolute_path
4
4
  protected
@@ -1,4 +1,4 @@
1
- class JsTestServer::Server::Representations::NotFound < JsTestServer::Server::Representations::Page
1
+ class JsTestServer::Server::Views::NotFound < JsTestServer::Server::Views::Page
2
2
  needs :message
3
3
  attr_reader :message
4
4
  protected
@@ -1,4 +1,4 @@
1
- class JsTestServer::Server::Representations::Page < Erector::Widget
1
+ class JsTestServer::Server::Views::Page < Erector::Widget
2
2
  def content(&block)
3
3
  rawtext %Q{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">}
4
4
  html :xmlns => "http://www.w3.org/1999/xhtml", :"xml:lang" => "en" do
@@ -1,7 +1,7 @@
1
1
  module JsTestServer
2
2
  module Server
3
- module Representations
4
- class RemoteControlSubscriber < JsTestServer::Server::Representations::Page
3
+ module Views
4
+ class RemoteControlSubscriber < JsTestServer::Server::Views::Page
5
5
  def head_content
6
6
  javascript :src => "/js_test_server.js"
7
7
  javascript :src => "/js_test_server/remote_control.js"
@@ -1,4 +1,4 @@
1
- class JsTestServer::Server::Representations::Suite < JsTestServer::Server::Representations::Page
1
+ class JsTestServer::Server::Views::Suite < JsTestServer::Server::Views::Page
2
2
  class << self
3
3
  def project_js_files
4
4
  @@project_js_files ||= []
@@ -1,4 +1,4 @@
1
- module JsTestServer::Server::Representations::Suites
1
+ module JsTestServer::Server::Views::Suites
2
2
  end
3
3
 
4
4
  Dir["#{File.dirname(__FILE__)}/suites/*.html.rb"].each do |file|
@@ -1,4 +1,4 @@
1
- class JsTestServer::Server::Representations::Suites::Jasmine < JsTestServer::Server::Representations::Suite
1
+ class JsTestServer::Server::Views::Suites::Jasmine < JsTestServer::Server::Views::Suite
2
2
  needs :spec_files, :framework_path
3
3
  attr_reader :spec_files, :framework_path
4
4
 
@@ -19,7 +19,7 @@ class JsTestServer::Server::Representations::Suites::Jasmine < JsTestServer::Ser
19
19
  end
20
20
 
21
21
  def core_js_files
22
- jasmine_file = File.basename(Dir["#{framework_path}/jasmine*.js"].last)
22
+ jasmine_file = File.basename(Dir["#{framework_path}/jasmine*.js"].sort.last)
23
23
  javascript :src => "/framework/#{jasmine_file}"
24
24
  javascript :src => "/framework/TrivialReporter.js"
25
25
  javascript :src => "/js_test_server.js"
@@ -1,10 +1,6 @@
1
- class JsTestServer::Server::Representations::Suites::ScrewUnit < JsTestServer::Server::Representations::Suite
1
+ class JsTestServer::Server::Views::Suites::ScrewUnit < JsTestServer::Server::Views::Suite
2
2
  class << self
3
- def jquery_js_file
4
- @jquery_js_file ||= "/framework/jquery-1.3.2.js"
5
- end
6
-
7
- attr_writer :jquery_js_file
3
+ attr_accessor :jquery_js_file
8
4
  end
9
5
 
10
6
  needs :spec_files, :framework_path
@@ -36,7 +32,10 @@ class JsTestServer::Server::Representations::Suites::ScrewUnit < JsTestServer::S
36
32
  end
37
33
 
38
34
  def jquery_js_file
39
- self.class.jquery_js_file
35
+ self.class.jquery_js_file || (
36
+ (jquery_path = Dir["#{framework_path}/jquery-*.js"].sort.last) &&
37
+ "/framework/#{File.basename(jquery_path)}"
38
+ )
40
39
  end
41
40
 
42
41
  def body_content
@@ -0,0 +1,568 @@
1
+ (function() {
2
+ function JsTestServer() {
3
+ }
4
+
5
+ ;
6
+ window.JsTestServer = JsTestServer;
7
+
8
+ JsTestServer.status = function() {
9
+ throw "You must implement the JsTestServer.status method";
10
+ };
11
+
12
+ JsTestServer.Assets = {};
13
+ JsTestServer.Assets.use_cache_buster = false; // TODO: NS/CTI - make this configurable from the UI.
14
+ var required_paths = [];
15
+ var included_stylesheets = {};
16
+ var cache_buster = parseInt(new Date().getTime() / (1 * 1000));
17
+
18
+ function tag(name, attributes) {
19
+ var html = "<" + name;
20
+ for (var attribute in attributes) {
21
+ html += (" " + attribute + "='" + attributes[attribute]) + "'";
22
+ }
23
+ ;
24
+ html += "></";
25
+ html += name;
26
+ html += ">";
27
+ return html;
28
+ }
29
+
30
+ JsTestServer.Assets.require = function(javascript_path, onload) {
31
+ if (!required_paths[javascript_path]) {
32
+ var full_path = javascript_path + ".js";
33
+ if (JsTestServer.Assets.use_cache_buster) {
34
+ full_path += '?' + cache_buster;
35
+ }
36
+ document.write(tag("script", {src: full_path, type: 'text/javascript'}));
37
+ if (onload) {
38
+ var scripts = document.getElementsByTagName('script');
39
+ scripts[scripts.length - 1].onload = onload;
40
+ }
41
+ required_paths[javascript_path] = true;
42
+ }
43
+ };
44
+
45
+ JsTestServer.Assets.stylesheet = function(stylesheet_path) {
46
+ if (!included_stylesheets[stylesheet_path]) {
47
+ var full_path = stylesheet_path + ".css";
48
+ if (JsTestServer.Assets.use_cache_buster) {
49
+ full_path += '?' + cache_buster;
50
+ }
51
+ document.write(tag("link", {rel: 'stylesheet', type: 'text/css', href: full_path}));
52
+ included_stylesheets[stylesheet_path] = true;
53
+ }
54
+ };
55
+
56
+ JsTestServer.xhrGet = function(url, onComplete) {
57
+ var request;
58
+ try {
59
+ request = new XMLHttpRequest();
60
+ } catch(e) {
61
+ try {
62
+ request = new ActiveXObject("Msxml2.XMLHTTP");
63
+ } catch(e) {
64
+ request = new ActiveXObject("Microsoft.XMLHTTP");
65
+ }
66
+ };
67
+
68
+ request.onreadystatechange = function() {
69
+ if (request.readyState == 4) {
70
+ if (request.status == 200) {
71
+ onComplete(request);
72
+ }
73
+ }
74
+ };
75
+
76
+ request.open("get", url, true);
77
+ request.send(null);
78
+ };
79
+
80
+ window.require = JsTestServer.Assets.require;
81
+ window.stylesheet = JsTestServer.Assets.stylesheet;
82
+ })();
83
+
84
+
85
+ //////////////////////////////////////////////////////////////////
86
+ // Inlining json2.js library because there should only be one js script tag
87
+ // and its simpler to inline rather than cat the js together on the server.
88
+ //////////////////////////////////////////////////////////////////
89
+
90
+ /*
91
+ http://www.JSON.org/json2.js
92
+ 2009-06-29
93
+
94
+ Public Domain.
95
+
96
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
97
+
98
+ See http://www.JSON.org/js.html
99
+
100
+ This file creates a global JSON object containing two methods: stringify
101
+ and parse.
102
+
103
+ JSON.stringify(value, replacer, space)
104
+ value any JavaScript value, usually an object or array.
105
+
106
+ replacer an optional parameter that determines how object
107
+ values are stringified for objects. It can be a
108
+ function or an array of strings.
109
+
110
+ space an optional parameter that specifies the indentation
111
+ of nested structures. If it is omitted, the text will
112
+ be packed without extra whitespace. If it is a number,
113
+ it will specify the number of spaces to indent at each
114
+ level. If it is a string (such as '\t' or '&nbsp;'),
115
+ it contains the characters used to indent at each level.
116
+
117
+ This method produces a JSON text from a JavaScript value.
118
+
119
+ When an object value is found, if the object contains a toJSON
120
+ method, its toJSON method will be called and the result will be
121
+ stringified. A toJSON method does not serialize: it returns the
122
+ value represented by the name/value pair that should be serialized,
123
+ or undefined if nothing should be serialized. The toJSON method
124
+ will be passed the key associated with the value, and this will be
125
+ bound to the object holding the key.
126
+
127
+ For example, this would serialize Dates as ISO strings.
128
+
129
+ Date.prototype.toJSON = function (key) {
130
+ function f(n) {
131
+ // Format integers to have at least two digits.
132
+ return n < 10 ? '0' + n : n;
133
+ }
134
+
135
+ return this.getUTCFullYear() + '-' +
136
+ f(this.getUTCMonth() + 1) + '-' +
137
+ f(this.getUTCDate()) + 'T' +
138
+ f(this.getUTCHours()) + ':' +
139
+ f(this.getUTCMinutes()) + ':' +
140
+ f(this.getUTCSeconds()) + 'Z';
141
+ };
142
+
143
+ You can provide an optional replacer method. It will be passed the
144
+ key and value of each member, with this bound to the containing
145
+ object. The value that is returned from your method will be
146
+ serialized. If your method returns undefined, then the member will
147
+ be excluded from the serialization.
148
+
149
+ If the replacer parameter is an array of strings, then it will be
150
+ used to select the members to be serialized. It filters the results
151
+ such that only members with keys listed in the replacer array are
152
+ stringified.
153
+
154
+ Values that do not have JSON views, such as undefined or
155
+ functions, will not be serialized. Such values in objects will be
156
+ dropped; in arrays they will be replaced with null. You can use
157
+ a replacer function to replace those with JSON values.
158
+ JSON.stringify(undefined) returns undefined.
159
+
160
+ The optional space parameter produces a stringification of the
161
+ value that is filled with line breaks and indentation to make it
162
+ easier to read.
163
+
164
+ If the space parameter is a non-empty string, then that string will
165
+ be used for indentation. If the space parameter is a number, then
166
+ the indentation will be that many spaces.
167
+
168
+ Example:
169
+
170
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
171
+ // text is '["e",{"pluribus":"unum"}]'
172
+
173
+
174
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
175
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
176
+
177
+ text = JSON.stringify([new Date()], function (key, value) {
178
+ return this[key] instanceof Date ?
179
+ 'Date(' + this[key] + ')' : value;
180
+ });
181
+ // text is '["Date(---current time---)"]'
182
+
183
+
184
+ JSON.parse(text, reviver)
185
+ This method parses a JSON text to produce an object or array.
186
+ It can throw a SyntaxError exception.
187
+
188
+ The optional reviver parameter is a function that can filter and
189
+ transform the results. It receives each of the keys and values,
190
+ and its return value is used instead of the original value.
191
+ If it returns what it received, then the structure is not modified.
192
+ If it returns undefined then the member is deleted.
193
+
194
+ Example:
195
+
196
+ // Parse the text. Values that look like ISO date strings will
197
+ // be converted to Date objects.
198
+
199
+ myData = JSON.parse(text, function (key, value) {
200
+ var a;
201
+ if (typeof value === 'string') {
202
+ a =
203
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
204
+ if (a) {
205
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
206
+ +a[5], +a[6]));
207
+ }
208
+ }
209
+ return value;
210
+ });
211
+
212
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
213
+ var d;
214
+ if (typeof value === 'string' &&
215
+ value.slice(0, 5) === 'Date(' &&
216
+ value.slice(-1) === ')') {
217
+ d = new Date(value.slice(5, -1));
218
+ if (d) {
219
+ return d;
220
+ }
221
+ }
222
+ return value;
223
+ });
224
+
225
+
226
+ This is a reference implementation. You are free to copy, modify, or
227
+ redistribute.
228
+
229
+ This code should be minified before deployment.
230
+ See http://javascript.crockford.com/jsmin.html
231
+
232
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
233
+ NOT CONTROL.
234
+ */
235
+
236
+ /*jslint evil: true */
237
+
238
+ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
239
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
240
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
241
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
242
+ test, toJSON, toString, valueOf
243
+ */
244
+
245
+ // Create a JSON object only if one does not already exist. We create the
246
+ // methods in a closure to avoid creating global variables.
247
+
248
+ (function(JsTestServer) {
249
+ var JSON = JSON || {};
250
+ JsTestServer.JSON = JSON;
251
+
252
+ (function () {
253
+
254
+ function f(n) {
255
+ // Format integers to have at least two digits.
256
+ return n < 10 ? '0' + n : n;
257
+ }
258
+
259
+ if (typeof Date.prototype.toJSON !== 'function') {
260
+
261
+ Date.prototype.toJSON = function (key) {
262
+
263
+ return isFinite(this.valueOf()) ?
264
+ this.getUTCFullYear() + '-' +
265
+ f(this.getUTCMonth() + 1) + '-' +
266
+ f(this.getUTCDate()) + 'T' +
267
+ f(this.getUTCHours()) + ':' +
268
+ f(this.getUTCMinutes()) + ':' +
269
+ f(this.getUTCSeconds()) + 'Z' : null;
270
+ };
271
+
272
+ String.prototype.toJSON =
273
+ Number.prototype.toJSON =
274
+ Boolean.prototype.toJSON = function (key) {
275
+ return this.valueOf();
276
+ };
277
+ }
278
+
279
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
280
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
281
+ gap,
282
+ indent,
283
+ meta = { // table of character substitutions
284
+ '\b': '\\b',
285
+ '\t': '\\t',
286
+ '\n': '\\n',
287
+ '\f': '\\f',
288
+ '\r': '\\r',
289
+ '"' : '\\"',
290
+ '\\': '\\\\'
291
+ },
292
+ rep;
293
+
294
+
295
+ function quote(string) {
296
+
297
+ // If the string contains no control characters, no quote characters, and no
298
+ // backslash characters, then we can safely slap some quotes around it.
299
+ // Otherwise we must also replace the offending characters with safe escape
300
+ // sequences.
301
+
302
+ escapable.lastIndex = 0;
303
+ return escapable.test(string) ?
304
+ '"' + string.replace(escapable, function (a) {
305
+ var c = meta[a];
306
+ return typeof c === 'string' ? c :
307
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
308
+ }) + '"' :
309
+ '"' + string + '"';
310
+ }
311
+
312
+
313
+ function str(key, holder) {
314
+
315
+ // Produce a string from holder[key].
316
+
317
+ var i, // The loop counter.
318
+ k, // The member key.
319
+ v, // The member value.
320
+ length,
321
+ mind = gap,
322
+ partial,
323
+ value = holder[key];
324
+
325
+ // If the value has a toJSON method, call it to obtain a replacement value.
326
+
327
+ if (value && typeof value === 'object' &&
328
+ typeof value.toJSON === 'function') {
329
+ value = value.toJSON(key);
330
+ }
331
+
332
+ // If we were called with a replacer function, then call the replacer to
333
+ // obtain a replacement value.
334
+
335
+ if (typeof rep === 'function') {
336
+ value = rep.call(holder, key, value);
337
+ }
338
+
339
+ // What happens next depends on the value's type.
340
+
341
+ switch (typeof value) {
342
+ case 'string':
343
+ return quote(value);
344
+
345
+ case 'number':
346
+
347
+ // JSON numbers must be finite. Encode non-finite numbers as null.
348
+
349
+ return isFinite(value) ? String(value) : 'null';
350
+
351
+ case 'boolean':
352
+ case 'null':
353
+
354
+ // If the value is a boolean or null, convert it to a string. Note:
355
+ // typeof null does not produce 'null'. The case is included here in
356
+ // the remote chance that this gets fixed someday.
357
+
358
+ return String(value);
359
+
360
+ // If the type is 'object', we might be dealing with an object or an array or
361
+ // null.
362
+
363
+ case 'object':
364
+
365
+ // Due to a specification blunder in ECMAScript, typeof null is 'object',
366
+ // so watch out for that case.
367
+
368
+ if (!value) {
369
+ return 'null';
370
+ }
371
+
372
+ // Make an array to hold the partial results of stringifying this object value.
373
+
374
+ gap += indent;
375
+ partial = [];
376
+
377
+ // Is the value an array?
378
+
379
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
380
+
381
+ // The value is an array. Stringify every element. Use null as a placeholder
382
+ // for non-JSON values.
383
+
384
+ length = value.length;
385
+ for (i = 0; i < length; i += 1) {
386
+ partial[i] = str(i, value) || 'null';
387
+ }
388
+
389
+ // Join all of the elements together, separated with commas, and wrap them in
390
+ // brackets.
391
+
392
+ v = partial.length === 0 ? '[]' :
393
+ gap ? '[\n' + gap +
394
+ partial.join(',\n' + gap) + '\n' +
395
+ mind + ']' :
396
+ '[' + partial.join(',') + ']';
397
+ gap = mind;
398
+ return v;
399
+ }
400
+
401
+ // If the replacer is an array, use it to select the members to be stringified.
402
+
403
+ if (rep && typeof rep === 'object') {
404
+ length = rep.length;
405
+ for (i = 0; i < length; i += 1) {
406
+ k = rep[i];
407
+ if (typeof k === 'string') {
408
+ v = str(k, value);
409
+ if (v) {
410
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
411
+ }
412
+ }
413
+ }
414
+ } else {
415
+
416
+ // Otherwise, iterate through all of the keys in the object.
417
+
418
+ for (k in value) {
419
+ if (Object.hasOwnProperty.call(value, k)) {
420
+ v = str(k, value);
421
+ if (v) {
422
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
423
+ }
424
+ }
425
+ }
426
+ }
427
+
428
+ // Join all of the member texts together, separated with commas,
429
+ // and wrap them in braces.
430
+
431
+ v = partial.length === 0 ? '{}' :
432
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
433
+ mind + '}' : '{' + partial.join(',') + '}';
434
+ gap = mind;
435
+ return v;
436
+ }
437
+ }
438
+
439
+ // If the JSON object does not yet have a stringify method, give it one.
440
+
441
+ if (typeof JSON.stringify !== 'function') {
442
+ JSON.stringify = function (value, replacer, space) {
443
+
444
+ // The stringify method takes a value and an optional replacer, and an optional
445
+ // space parameter, and returns a JSON text. The replacer can be a function
446
+ // that can replace values, or an array of strings that will select the keys.
447
+ // A default replacer method can be provided. Use of the space parameter can
448
+ // produce text that is more easily readable.
449
+
450
+ var i;
451
+ gap = '';
452
+ indent = '';
453
+
454
+ // If the space parameter is a number, make an indent string containing that
455
+ // many spaces.
456
+
457
+ if (typeof space === 'number') {
458
+ for (i = 0; i < space; i += 1) {
459
+ indent += ' ';
460
+ }
461
+
462
+ // If the space parameter is a string, it will be used as the indent string.
463
+
464
+ } else if (typeof space === 'string') {
465
+ indent = space;
466
+ }
467
+
468
+ // If there is a replacer, it must be a function or an array.
469
+ // Otherwise, throw an error.
470
+
471
+ rep = replacer;
472
+ if (replacer && typeof replacer !== 'function' &&
473
+ (typeof replacer !== 'object' ||
474
+ typeof replacer.length !== 'number')) {
475
+ throw new Error('JSON.stringify');
476
+ }
477
+
478
+ // Make a fake root object containing our value under the key of ''.
479
+ // Return the result of stringifying the value.
480
+
481
+ return str('', {'': value});
482
+ };
483
+ }
484
+
485
+
486
+ // If the JSON object does not yet have a parse method, give it one.
487
+
488
+ if (typeof JSON.parse !== 'function') {
489
+ JSON.parse = function (text, reviver) {
490
+
491
+ // The parse method takes a text and an optional reviver function, and returns
492
+ // a JavaScript value if the text is a valid JSON text.
493
+
494
+ var j;
495
+
496
+ function walk(holder, key) {
497
+
498
+ // The walk method is used to recursively walk the resulting structure so
499
+ // that modifications can be made.
500
+
501
+ var k, v, value = holder[key];
502
+ if (value && typeof value === 'object') {
503
+ for (k in value) {
504
+ if (Object.hasOwnProperty.call(value, k)) {
505
+ v = walk(value, k);
506
+ if (v !== undefined) {
507
+ value[k] = v;
508
+ } else {
509
+ delete value[k];
510
+ }
511
+ }
512
+ }
513
+ }
514
+ return reviver.call(holder, key, value);
515
+ }
516
+
517
+
518
+ // Parsing happens in four stages. In the first stage, we replace certain
519
+ // Unicode characters with escape sequences. JavaScript handles many characters
520
+ // incorrectly, either silently deleting them, or treating them as line endings.
521
+
522
+ cx.lastIndex = 0;
523
+ if (cx.test(text)) {
524
+ text = text.replace(cx, function (a) {
525
+ return '\\u' +
526
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
527
+ });
528
+ }
529
+
530
+ // In the second stage, we run the text against regular expressions that look
531
+ // for non-JSON patterns. We are especially concerned with '()' and 'new'
532
+ // because they can cause invocation, and '=' because it can cause mutation.
533
+ // But just to be safe, we want to reject all unexpected forms.
534
+
535
+ // We split the second stage into 4 regexp operations in order to work around
536
+ // crippling inefficiencies in IE's and Safari's regexp engines. First we
537
+ // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
538
+ // replace all simple value tokens with ']' characters. Third, we delete all
539
+ // open brackets that follow a colon or comma or that begin the text. Finally,
540
+ // we look to see that the remaining characters are only whitespace or ']' or
541
+ // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
542
+
543
+ if (/^[\],:{}\s]*$/.
544
+ test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
545
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
546
+ replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
547
+
548
+ // In the third stage we use the eval function to compile the text into a
549
+ // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
550
+ // in JavaScript: it can begin a block or an object literal. We wrap the text
551
+ // in parens to eliminate the ambiguity.
552
+
553
+ j = eval('(' + text + ')');
554
+
555
+ // In the optional fourth stage, we recursively walk the new structure, passing
556
+ // each name/value pair to a reviver function for possible transformation.
557
+
558
+ return typeof reviver === 'function' ?
559
+ walk({'': j}, '') : j;
560
+ }
561
+
562
+ // If the text is not JSON parseable, then a SyntaxError is thrown.
563
+
564
+ throw new SyntaxError('JSON.parse');
565
+ };
566
+ }
567
+ }());
568
+ })(JsTestServer);