jqueryplugingen 0.1.4 → 0.2.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.
@@ -6,7 +6,7 @@ rescue LoadError
6
6
  end
7
7
 
8
8
  desc "Package"
9
- Rake::PackageTask.new("#{PLUGIN}", "#{VERSION}") do |p|
9
+ Rake::PackageTask.new(JQUERY_PLUGIN, PLUGIN_VERSION) do |p|
10
10
  p.need_tar = true
11
11
  p.need_zip = true
12
12
  p.package_files.include("build/**/*.*")
@@ -0,0 +1,20 @@
1
+ desc "Run acceptance test in browser"
2
+ task :acceptance => :compile do
3
+ `open test/acceptance.html`
4
+ end
5
+
6
+ desc "Run spec tests in browser"
7
+ task :specs do
8
+ `open test/specs.html`
9
+ end
10
+
11
+ desc "Show example"
12
+ task :example => :merge do
13
+ `open example.html`
14
+ end
15
+
16
+ desc "Show all browser examples and tests"
17
+ task :show => [:specs, :acceptance, :example]
18
+
19
+ desc "First time run to demonstrate that pages are working"
20
+ task :first_time => ['jquery:add', :show]
@@ -5,5 +5,5 @@ require 'jquery_plugin_gen/quick_template'
5
5
  require 'jquery_plugin_gen/generator'
6
6
 
7
7
  module JqueryPluginGen
8
- VERSION = '0.1.4'
8
+ VERSION = '0.2.0'
9
9
  end
@@ -0,0 +1,224 @@
1
+ @CHARSET "UTF-8";
2
+
3
+ /* --------------------
4
+ * @Layout
5
+ */
6
+
7
+ html {
8
+ overflow: hidden;
9
+ }
10
+
11
+ body, #jsspec_container {
12
+ overflow: hidden;
13
+ padding: 0;
14
+ margin: 0;
15
+ width: 100%;
16
+ height: 100%;
17
+ background-color: white;
18
+ }
19
+
20
+ #title {
21
+ padding: 0;
22
+ margin: 0;
23
+ position: absolute;
24
+ top: 0px;
25
+ left: 0px;
26
+ width: 100%;
27
+ height: 40px;
28
+ overflow: hidden;
29
+ }
30
+
31
+ #list {
32
+ padding: 0;
33
+ margin: 0;
34
+ position: absolute;
35
+ top: 40px;
36
+ left: 0px;
37
+ bottom: 0px;
38
+ overflow: auto;
39
+ width: 250px;
40
+ _height:expression(document.body.clientHeight-40);
41
+ }
42
+
43
+ #log {
44
+ padding: 0;
45
+ margin: 0;
46
+ position: absolute;
47
+ top: 40px;
48
+ left: 250px;
49
+ right: 0px;
50
+ bottom: 0px;
51
+ overflow: auto;
52
+ _height:expression(document.body.clientHeight-40);
53
+ _width:expression(document.body.clientWidth-250);
54
+ }
55
+
56
+
57
+
58
+ /* --------------------
59
+ * @Decorations and colors
60
+ */
61
+ * {
62
+ padding: 0;
63
+ margin: 0;
64
+ font-family: "Lucida Grande", Helvetica, sans-serif;
65
+ }
66
+
67
+ li {
68
+ list-style: none;
69
+ }
70
+
71
+ /* hiding subtitles */
72
+ h2 {
73
+ display: none;
74
+ }
75
+
76
+ /* title section */
77
+ div#title {
78
+ padding: 0em 0.5em;
79
+ }
80
+
81
+ div#title h1 {
82
+ font-size: 1.5em;
83
+ float: left;
84
+ }
85
+
86
+ div#title ul li {
87
+ float: left;
88
+ padding: 0.5em 0em 0.5em 0.75em;
89
+ }
90
+
91
+ div#title p {
92
+ float:right;
93
+ margin-right:1em;
94
+ font-size: 0.75em;
95
+ }
96
+
97
+ /* spec container */
98
+ ul.specs {
99
+ margin: 0.5em;
100
+ }
101
+ ul.specs li {
102
+ margin-bottom: 0.1em;
103
+ }
104
+
105
+ /* spec title */
106
+ ul.specs li h3 {
107
+ font-weight: bold;
108
+ font-size: 0.75em;
109
+ padding: 0.2em 1em;
110
+ cursor: pointer;
111
+ _cursor: hand;
112
+ }
113
+
114
+ /* example container */
115
+ ul.examples li {
116
+ border-style: solid;
117
+ border-width: 0px 0px 1px 5px;
118
+ margin: 0.2em 0em 0.2em 1em;
119
+ }
120
+
121
+ /* example title */
122
+ ul.examples li h4 {
123
+ font-weight: normal;
124
+ font-size: 0.75em;
125
+ margin-left: 1em;
126
+ }
127
+
128
+ pre.examples-code {
129
+ margin: 0.5em 2em;
130
+ padding: 0.5em;
131
+ background: white;
132
+ border: solid 1px #CCC;
133
+ }
134
+
135
+ /* example explaination */
136
+ ul.examples li div {
137
+ padding: 1em 2em;
138
+ font-size: 0.75em;
139
+ }
140
+
141
+ /* styles for ongoing, success, failure, error */
142
+ div.success, div.success a {
143
+ color: #FFFFFF;
144
+ background-color: #65C400;
145
+ }
146
+
147
+ ul.specs li.success h3, ul.specs li.success h3 a {
148
+ color: #FFFFFF;
149
+ background-color: #65C400;
150
+ }
151
+
152
+ ul.examples li.success, ul.examples li.success a {
153
+ color: #3D7700;
154
+ background-color: #DBFFB4;
155
+ border-color: #65C400;
156
+ }
157
+
158
+ div.exception, div.exception a {
159
+ color: #FFFFFF;
160
+ background-color: #C20000;
161
+ }
162
+
163
+ ul.specs li.exception h3, ul.specs li.exception h3 a {
164
+ color: #FFFFFF;
165
+ background-color: #C20000;
166
+ }
167
+
168
+ ul.examples li.exception, ul.examples li.exception a {
169
+ color: #C20000;
170
+ background-color: #FFFBD3;
171
+ border-color: #C20000;
172
+ }
173
+
174
+ div.ongoing, div.ongoing a {
175
+ color: #000000;
176
+ background-color: #FFFF80;
177
+ }
178
+
179
+ ul.specs li.ongoing h3, ul.specs li.ongoing h3 a {
180
+ color: #000000;
181
+ background-color: #FFFF80;
182
+ }
183
+
184
+ ul.examples li.ongoing, ul.examples li.ongoing a {
185
+ color: #000000;
186
+ background-color: #FFFF80;
187
+ border-color: #DDDD00;
188
+ }
189
+
190
+
191
+
192
+ /* --------------------
193
+ * values
194
+ */
195
+ .number_value, .string_value, .regexp_value, .boolean_value, .dom_value {
196
+ font-family: monospace;
197
+ color: blue;
198
+ }
199
+ .object_value, .array_value {
200
+ line-height: 2em;
201
+ padding: 0.1em 0.2em;
202
+ margin: 0.1em 0;
203
+ }
204
+ .date_value {
205
+ font-family: monospace;
206
+ color: olive;
207
+ }
208
+ .undefined_value, .null_value {
209
+ font-style: italic;
210
+ color: blue;
211
+ }
212
+ .dom_attr_name {
213
+ }
214
+ .dom_attr_value {
215
+ color: red;
216
+ }
217
+ .dom_path {
218
+ font-size: 0.75em;
219
+ color: gray;
220
+ }
221
+ strong {
222
+ font-weight: normal;
223
+ background-color: #FFC6C6;
224
+ }
@@ -0,0 +1,1548 @@
1
+ /**
2
+ * JSSpec
3
+ *
4
+ * Copyright 2007 Alan Kang
5
+ * - mailto:jania902@gmail.com
6
+ * - http://jania.pe.kr
7
+ *
8
+ * http://jania.pe.kr/aw/moin.cgi/JSSpec
9
+ *
10
+ * Dependencies:
11
+ * - diff_match_patch.js ( http://code.google.com/p/google-diff-match-patch )
12
+ *
13
+ * This library is free software; you can redistribute it and/or
14
+ * modify it under the terms of the GNU Lesser General Public
15
+ * License as published by the Free Software Foundation; either
16
+ * version 2.1 of the License, or (at your option) any later version.
17
+ *
18
+ * This library is distributed in the hope that it will be useful,
19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21
+ * Lesser General Public License for more details.
22
+ *
23
+ * You should have received a copy of the GNU Lesser General Public
24
+ * License along with this library; if not, write to the Free Software
25
+ * Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26
+ */
27
+
28
+ /**
29
+ * Namespace
30
+ */
31
+
32
+ var JSSpec = {
33
+ specs: [],
34
+
35
+ EMPTY_FUNCTION: function() {},
36
+
37
+ Browser: {
38
+ // By Rendering Engines
39
+ Trident: navigator.appName === "Microsoft Internet Explorer",
40
+ Webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
41
+ Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') === -1,
42
+ KHTML: navigator.userAgent.indexOf('KHTML') !== -1,
43
+ Presto: navigator.appName === "Opera",
44
+
45
+ // By Platforms
46
+ Mac: navigator.userAgent.indexOf("Macintosh") !== -1,
47
+ Ubuntu: navigator.userAgent.indexOf('Ubuntu') !== -1,
48
+ Win: navigator.userAgent.indexOf('Windows') !== -1,
49
+
50
+ // By Browsers
51
+ IE: navigator.appName === "Microsoft Internet Explorer",
52
+ IE6: navigator.userAgent.indexOf('MSIE 6') !== -1,
53
+ IE7: navigator.userAgent.indexOf('MSIE 7') !== -1,
54
+ IE8: navigator.userAgent.indexOf('MSIE 8') !== -1,
55
+
56
+ FF: navigator.userAgent.indexOf('Firefox') !== -1,
57
+ FF2: navigator.userAgent.indexOf('Firefox/2') !== -1,
58
+ FF3: navigator.userAgent.indexOf('Firefox/3') !== -1,
59
+ Safari: navigator.userAgent.indexOf('Safari') !== -1
60
+ }
61
+ };
62
+
63
+
64
+
65
+ /**
66
+ * Executor
67
+ */
68
+ JSSpec.Executor = function(target, onSuccess, onException) {
69
+ this.target = target;
70
+ this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
71
+ this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
72
+
73
+ if(JSSpec.Browser.Trident) {
74
+ // Exception handler for Trident. It helps to collect exact line number where exception occured.
75
+ window.onerror = function(message, fileName, lineNumber) {
76
+ var self = window._curExecutor;
77
+ var ex = {message:message, fileName:fileName, lineNumber:lineNumber};
78
+
79
+ if(JSSpec._secondPass) {
80
+ ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
81
+ delete JSSpec._secondPass;
82
+ delete JSSpec._assertionFailure;
83
+
84
+ ex.type = "failure";
85
+ self.onException(self, ex);
86
+ } else if(JSSpec._assertionFailure) {
87
+ JSSpec._secondPass = true;
88
+ self.run();
89
+ } else {
90
+ self.onException(self, ex);
91
+ }
92
+
93
+ return true;
94
+ };
95
+ }
96
+ };
97
+ JSSpec.Executor.prototype.mergeExceptions = function(assertionFailure, normalException) {
98
+ var merged = {
99
+ message:assertionFailure.message,
100
+ fileName:normalException.fileName,
101
+ lineNumber:normalException.lineNumber
102
+ };
103
+
104
+ return merged;
105
+ };
106
+
107
+ JSSpec.Executor.prototype.run = function() {
108
+ var self = this;
109
+ var target = this.target;
110
+ var onSuccess = this.onSuccess;
111
+ var onException = this.onException;
112
+
113
+ window.setTimeout(
114
+ function() {
115
+ var result;
116
+ if(JSSpec.Browser.Trident) {
117
+ window._curExecutor = self;
118
+
119
+ result = self.target();
120
+ self.onSuccess(self, result);
121
+ } else {
122
+ try {
123
+ result = self.target();
124
+ self.onSuccess(self, result);
125
+ } catch(ex) {
126
+ if(JSSpec.Browser.Webkit) ex = {message:ex.message, fileName:ex.sourceURL, lineNumber:ex.line};
127
+
128
+ if(JSSpec._secondPass) {
129
+ ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
130
+ delete JSSpec._secondPass;
131
+ delete JSSpec._assertionFailure;
132
+
133
+ ex.type = "failure";
134
+ self.onException(self, ex);
135
+ } else if(JSSpec._assertionFailure) {
136
+ JSSpec._secondPass = true;
137
+ self.run();
138
+ } else {
139
+ self.onException(self, ex);
140
+ }
141
+ }
142
+ }
143
+ },
144
+ 0
145
+ );
146
+ };
147
+
148
+
149
+
150
+ /**
151
+ * CompositeExecutor composites one or more executors and execute them sequencially.
152
+ */
153
+ JSSpec.CompositeExecutor = function(onSuccess, onException, continueOnException) {
154
+ this.queue = [];
155
+ this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
156
+ this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
157
+ this.continueOnException = !!continueOnException;
158
+ };
159
+
160
+ JSSpec.CompositeExecutor.prototype.addFunction = function(func) {
161
+ this.addExecutor(new JSSpec.Executor(func));
162
+ };
163
+
164
+ JSSpec.CompositeExecutor.prototype.addExecutor = function(executor) {
165
+ var last = this.queue.length == 0 ? null : this.queue[this.queue.length - 1];
166
+ if(last) {
167
+ last.next = executor;
168
+ }
169
+
170
+ executor.parent = this;
171
+ executor.onSuccessBackup = executor.onSuccess;
172
+ executor.onSuccess = function(result) {
173
+ this.onSuccessBackup(result);
174
+ if(this.next) {
175
+ this.next.run();
176
+ } else {
177
+ this.parent.onSuccess();
178
+ }
179
+ };
180
+ executor.onExceptionBackup = executor.onException;
181
+ executor.onException = function(executor, ex) {
182
+ this.onExceptionBackup(executor, ex);
183
+
184
+ if(this.parent.continueOnException) {
185
+ if(this.next) {
186
+ this.next.run();
187
+ } else {
188
+ this.parent.onSuccess();
189
+ }
190
+ } else {
191
+ this.parent.onException(executor, ex);
192
+ }
193
+ };
194
+
195
+ this.queue.push(executor);
196
+ };
197
+
198
+ JSSpec.CompositeExecutor.prototype.run = function() {
199
+ if(this.queue.length > 0) {
200
+ this.queue[0].run();
201
+ }
202
+ };
203
+
204
+ /**
205
+ * Spec is a set of Examples in a specific context
206
+ */
207
+ JSSpec.Spec = function(context, entries) {
208
+ this.id = JSSpec.Spec.id++;
209
+ this.context = context;
210
+ this.url = location.href;
211
+
212
+ this.filterEntriesByEmbeddedExpressions(entries);
213
+ this.extractOutSpecialEntries(entries);
214
+ this.examples = this.makeExamplesFromEntries(entries);
215
+ this.examplesMap = this.makeMapFromExamples(this.examples);
216
+ };
217
+
218
+ JSSpec.Spec.id = 0;
219
+ JSSpec.Spec.prototype.getExamples = function() {
220
+ return this.examples;
221
+ };
222
+
223
+ JSSpec.Spec.prototype.hasException = function() {
224
+ return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
225
+ };
226
+
227
+ JSSpec.Spec.prototype.getTotalFailures = function() {
228
+ var examples = this.examples;
229
+ var failures = 0;
230
+ for(var i = 0; i < examples.length; i++) {
231
+ if(examples[i].isFailure()) failures++;
232
+ }
233
+ return failures;
234
+ };
235
+
236
+ JSSpec.Spec.prototype.getTotalErrors = function() {
237
+ var examples = this.examples;
238
+ var errors = 0;
239
+ for(var i = 0; i < examples.length; i++) {
240
+ if(examples[i].isError()) errors++;
241
+ }
242
+ return errors;
243
+ };
244
+
245
+ JSSpec.Spec.prototype.filterEntriesByEmbeddedExpressions = function(entries) {
246
+ var isTrue;
247
+ for(name in entries) if(entries.hasOwnProperty(name)) {
248
+ var m = name.match(/\[\[(.+)\]\]/);
249
+ if(m && m[1]) {
250
+ eval("isTrue = (" + m[1] + ")");
251
+ if(!isTrue) delete entries[name];
252
+ }
253
+ }
254
+ };
255
+
256
+ JSSpec.Spec.prototype.extractOutSpecialEntries = function(entries) {
257
+ this.beforeEach = JSSpec.EMPTY_FUNCTION;
258
+ this.beforeAll = JSSpec.EMPTY_FUNCTION;
259
+ this.afterEach = JSSpec.EMPTY_FUNCTION;
260
+ this.afterAll = JSSpec.EMPTY_FUNCTION;
261
+
262
+ for(name in entries) if(entries.hasOwnProperty(name)) {
263
+ if(name == 'before' || name == 'before each' || name == 'before_each') {
264
+ this.beforeEach = entries[name];
265
+ } else if(name == 'before all' || name == 'before_all') {
266
+ this.beforeAll = entries[name];
267
+ } else if(name == 'after' || name == 'after each' || name == 'after_each') {
268
+ this.afterEach = entries[name];
269
+ } else if(name == 'after all' || name == 'after_all') {
270
+ this.afterAll = entries[name];
271
+ }
272
+ }
273
+
274
+ delete entries['before'];
275
+ delete entries['before each'];
276
+ delete entries['before_each'];
277
+ delete entries['before all'];
278
+ delete entries['before_all'];
279
+ delete entries['after'];
280
+ delete entries['after each'];
281
+ delete entries['after_each'];
282
+ delete entries['after all'];
283
+ delete entries['after_all'];
284
+ };
285
+
286
+ JSSpec.Spec.prototype.makeExamplesFromEntries = function(entries) {
287
+ var examples = [];
288
+ for(name in entries) if(entries.hasOwnProperty(name)) {
289
+ examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach));
290
+ }
291
+ return examples;
292
+ };
293
+
294
+ JSSpec.Spec.prototype.makeMapFromExamples = function(examples) {
295
+ var map = {};
296
+ for(var i = 0; i < examples.length; i++) {
297
+ var example = examples[i];
298
+ map[example.id] = examples[i];
299
+ }
300
+ return map;
301
+ };
302
+
303
+ JSSpec.Spec.prototype.getExampleById = function(id) {
304
+ return this.examplesMap[id];
305
+ };
306
+
307
+ JSSpec.Spec.prototype.getExecutor = function() {
308
+ var self = this;
309
+ var onException = function(executor, ex) {
310
+ self.exception = ex;
311
+ };
312
+
313
+ var composite = new JSSpec.CompositeExecutor();
314
+ composite.addFunction(function() {JSSpec.log.onSpecStart(self);});
315
+ composite.addExecutor(new JSSpec.Executor(this.beforeAll, null, function(exec, ex) {
316
+ self.exception = ex;
317
+ JSSpec.log.onSpecEnd(self);
318
+ }));
319
+
320
+ var exampleAndAfter = new JSSpec.CompositeExecutor(null,null,true);
321
+ for(var i = 0; i < this.examples.length; i++) {
322
+ exampleAndAfter.addExecutor(this.examples[i].getExecutor());
323
+ }
324
+ exampleAndAfter.addExecutor(new JSSpec.Executor(this.afterAll, null, onException));
325
+ exampleAndAfter.addFunction(function() {JSSpec.log.onSpecEnd(self);});
326
+ composite.addExecutor(exampleAndAfter);
327
+
328
+ return composite;
329
+ };
330
+
331
+ /**
332
+ * Example
333
+ */
334
+ JSSpec.Example = function(name, target, before, after) {
335
+ this.id = JSSpec.Example.id++;
336
+ this.name = name;
337
+ this.target = target;
338
+ this.before = before;
339
+ this.after = after;
340
+ };
341
+
342
+ JSSpec.Example.id = 0;
343
+ JSSpec.Example.prototype.isFailure = function() {
344
+ return this.exception && this.exception.type == "failure";
345
+ };
346
+
347
+ JSSpec.Example.prototype.isError = function() {
348
+ return this.exception && !this.exception.type;
349
+ };
350
+
351
+ JSSpec.Example.prototype.getExecutor = function() {
352
+ var self = this;
353
+ var onException = function(executor, ex) {
354
+ self.exception = ex;
355
+ };
356
+
357
+ var composite = new JSSpec.CompositeExecutor();
358
+ composite.addFunction(function() {JSSpec.log.onExampleStart(self);});
359
+ composite.addExecutor(new JSSpec.Executor(this.before, null, function(exec, ex) {
360
+ self.exception = ex;
361
+ JSSpec.log.onExampleEnd(self);
362
+ }));
363
+
364
+ var targetAndAfter = new JSSpec.CompositeExecutor(null,null,true);
365
+
366
+ targetAndAfter.addExecutor(new JSSpec.Executor(this.target, null, onException));
367
+ targetAndAfter.addExecutor(new JSSpec.Executor(this.after, null, onException));
368
+ targetAndAfter.addFunction(function() {JSSpec.log.onExampleEnd(self);});
369
+
370
+ composite.addExecutor(targetAndAfter);
371
+
372
+ return composite;
373
+ };
374
+
375
+ /**
376
+ * Runner
377
+ */
378
+ JSSpec.Runner = function(specs, logger) {
379
+ JSSpec.log = logger;
380
+
381
+ this.totalExamples = 0;
382
+ this.specs = [];
383
+ this.specsMap = {};
384
+ this.addAllSpecs(specs);
385
+ };
386
+
387
+ JSSpec.Runner.prototype.addAllSpecs = function(specs) {
388
+ for(var i = 0; i < specs.length; i++) {
389
+ this.addSpec(specs[i]);
390
+ }
391
+ };
392
+
393
+ JSSpec.Runner.prototype.addSpec = function(spec) {
394
+ this.specs.push(spec);
395
+ this.specsMap[spec.id] = spec;
396
+ this.totalExamples += spec.getExamples().length;
397
+ };
398
+
399
+ JSSpec.Runner.prototype.getSpecById = function(id) {
400
+ return this.specsMap[id];
401
+ };
402
+
403
+ JSSpec.Runner.prototype.getSpecByContext = function(context) {
404
+ for(var i = 0; i < this.specs.length; i++) {
405
+ if(this.specs[i].context == context) return this.specs[i];
406
+ }
407
+ return null;
408
+ };
409
+
410
+ JSSpec.Runner.prototype.getSpecs = function() {
411
+ return this.specs;
412
+ };
413
+
414
+ JSSpec.Runner.prototype.hasException = function() {
415
+ return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
416
+ };
417
+
418
+ JSSpec.Runner.prototype.getTotalFailures = function() {
419
+ var specs = this.specs;
420
+ var failures = 0;
421
+ for(var i = 0; i < specs.length; i++) {
422
+ failures += specs[i].getTotalFailures();
423
+ }
424
+ return failures;
425
+ };
426
+
427
+ JSSpec.Runner.prototype.getTotalErrors = function() {
428
+ var specs = this.specs;
429
+ var errors = 0;
430
+ for(var i = 0; i < specs.length; i++) {
431
+ errors += specs[i].getTotalErrors();
432
+ }
433
+ return errors;
434
+ };
435
+
436
+
437
+ JSSpec.Runner.prototype.run = function() {
438
+ JSSpec.log.onRunnerStart();
439
+ var executor = new JSSpec.CompositeExecutor(function() {JSSpec.log.onRunnerEnd()},null,true);
440
+ for(var i = 0; i < this.specs.length; i++) {
441
+ executor.addExecutor(this.specs[i].getExecutor());
442
+ }
443
+ executor.run();
444
+ };
445
+
446
+
447
+ JSSpec.Runner.prototype.rerun = function(context) {
448
+ JSSpec.runner = new JSSpec.Runner([this.getSpecByContext(context)], JSSpec.log);
449
+ JSSpec.runner.run();
450
+ };
451
+
452
+ /**
453
+ * Logger
454
+ */
455
+ JSSpec.Logger = function() {
456
+ this.finishedExamples = 0;
457
+ this.startedAt = null;
458
+ };
459
+
460
+ JSSpec.Logger.prototype.onRunnerStart = function() {
461
+ this._title = document.title;
462
+
463
+ this.startedAt = new Date();
464
+ var container = document.getElementById('jsspec_container');
465
+ if(container) {
466
+ container.innerHTML = "";
467
+ } else {
468
+ container = document.createElement("DIV");
469
+ container.id = "jsspec_container";
470
+ document.body.appendChild(container);
471
+ }
472
+
473
+ var title = document.createElement("DIV");
474
+ title.id = "title";
475
+ title.innerHTML = [
476
+ '<h1>JSSpec</h1>',
477
+ '<ul>',
478
+ JSSpec.options.rerun ? '<li>[<a href="?" title="rerun all specs">X</a>] ' + JSSpec.util.escapeTags(decodeURIComponent(JSSpec.options.rerun)) + '</li>' : '',
479
+ ' <li><span id="total_examples">' + JSSpec.runner.totalExamples + '</span> examples</li>',
480
+ ' <li><span id="total_failures">0</span> failures</li>',
481
+ ' <li><span id="total_errors">0</span> errors</li>',
482
+ ' <li><span id="progress">0</span>% done</li>',
483
+ ' <li><span id="total_elapsed">0</span> secs</li>',
484
+ '</ul>',
485
+ '<p><a href="http://jania.pe.kr/aw/moin.cgi/JSSpec">JSSpec homepage</a></p>',
486
+ ].join("");
487
+ container.appendChild(title);
488
+
489
+ var list = document.createElement("DIV");
490
+ list.id = "list";
491
+ list.innerHTML = [
492
+ '<h2>List</h2>',
493
+ '<ul class="specs">',
494
+ function() {
495
+ var specs = JSSpec.runner.getSpecs();
496
+ var sb = [];
497
+ for(var i = 0; i < specs.length; i++) {
498
+ var spec = specs[i];
499
+ sb.push('<li id="spec_' + specs[i].id + '_list"><h3><a href="#spec_' + specs[i].id + '">' + JSSpec.util.escapeTags(specs[i].context) + '</a> [<a href="?rerun=' + encodeURIComponent(specs[i].context) + '">rerun</a>]</h3> </li>');
500
+ }
501
+ return sb.join("");
502
+ }(),
503
+ '</ul>'
504
+ ].join("");
505
+ container.appendChild(list);
506
+
507
+ var log = document.createElement("DIV");
508
+ log.id = "log";
509
+ log.innerHTML = [
510
+ '<h2>Log</h2>',
511
+ '<ul class="specs">',
512
+ function() {
513
+ var specs = JSSpec.runner.getSpecs();
514
+ var sb = [];
515
+ for(var i = 0; i < specs.length; i++) {
516
+ var spec = specs[i];
517
+ sb.push(' <li id="spec_' + specs[i].id + '">');
518
+ sb.push(' <h3>' + JSSpec.util.escapeTags(specs[i].context) + ' [<a href="?rerun=' + encodeURIComponent(specs[i].context) + '">rerun</a>]</h3>');
519
+ sb.push(' <ul id="spec_' + specs[i].id + '_examples" class="examples">');
520
+ for(var j = 0; j < spec.examples.length; j++) {
521
+ var example = spec.examples[j];
522
+ sb.push(' <li id="example_' + example.id + '">');
523
+ sb.push(' <h4>' + JSSpec.util.escapeTags(example.name) + '</h4>');
524
+ sb.push(' <pre class="examples-code"><code>'+JSSpec.util.escapeTags(example.target.toString())+'</code></pre>');
525
+ sb.push(' </li>');
526
+ }
527
+ sb.push(' </ul>');
528
+ sb.push(' </li>');
529
+ }
530
+ return sb.join("");
531
+ }(),
532
+ '</ul>'
533
+ ].join("");
534
+
535
+ container.appendChild(log);
536
+
537
+ // add event handler for toggling
538
+ var specs = JSSpec.runner.getSpecs();
539
+ var sb = [];
540
+ for(var i = 0; i < specs.length; i++) {
541
+ var spec = document.getElementById("spec_" + specs[i].id);
542
+ var title = spec.getElementsByTagName("H3")[0];
543
+ title.onclick = function(e) {
544
+ var target = document.getElementById(this.parentNode.id + "_examples");
545
+ target.style.display = target.style.display == "none" ? "block" : "none";
546
+ return true;
547
+ }
548
+ }
549
+ };
550
+
551
+ JSSpec.Logger.prototype.onRunnerEnd = function() {
552
+ if(JSSpec.runner.hasException()) {
553
+ var times = 4;
554
+ var title1 = "*" + this._title;
555
+ var title2 = "*F" + JSSpec.runner.getTotalFailures() + " E" + JSSpec.runner.getTotalErrors() + "* " + this._title;
556
+ } else {
557
+ var times = 2;
558
+ var title1 = this._title;
559
+ var title2 = "Success";
560
+ }
561
+ this.blinkTitle(times,title1,title2);
562
+ };
563
+
564
+ JSSpec.Logger.prototype.blinkTitle = function(times, title1, title2) {
565
+ var times = times * 2;
566
+ var mode = true;
567
+
568
+ var f = function() {
569
+ if(times > 0) {
570
+ document.title = mode ? title1 : title2;
571
+ mode = !mode;
572
+ times--;
573
+ window.setTimeout(f, 500);
574
+ } else {
575
+ document.title = title1;
576
+ }
577
+ };
578
+
579
+ f();
580
+ };
581
+
582
+ JSSpec.Logger.prototype.onSpecStart = function(spec) {
583
+ var spec_list = document.getElementById("spec_" + spec.id + "_list");
584
+ var spec_log = document.getElementById("spec_" + spec.id);
585
+
586
+ spec_list.className = "ongoing";
587
+ spec_log.className = "ongoing";
588
+ };
589
+
590
+ JSSpec.Logger.prototype.onSpecEnd = function(spec) {
591
+ var spec_list = document.getElementById("spec_" + spec.id + "_list");
592
+ var spec_log = document.getElementById("spec_" + spec.id);
593
+ var examples = document.getElementById("spec_" + spec.id + "_examples");
594
+ var className = spec.hasException() ? "exception" : "success";
595
+
596
+ spec_list.className = className;
597
+ spec_log.className = className;
598
+
599
+ if(JSSpec.options.autocollapse && !spec.hasException()) examples.style.display = "none";
600
+
601
+ if(spec.exception) {
602
+ spec_log.appendChild(document.createTextNode(" - " + spec.exception.message));
603
+ }
604
+ };
605
+
606
+ JSSpec.Logger.prototype.onExampleStart = function(example) {
607
+ var li = document.getElementById("example_" + example.id);
608
+ li.className = "ongoing";
609
+ };
610
+
611
+ JSSpec.Logger.prototype.onExampleEnd = function(example) {
612
+ var li = document.getElementById("example_" + example.id);
613
+ li.className = example.exception ? "exception" : "success";
614
+
615
+ if(example.exception) {
616
+ var div = document.createElement("DIV");
617
+ div.innerHTML = example.exception.message + "<p><br />" + " at " + example.exception.fileName + ", line " + example.exception.lineNumber + "</p>";
618
+ li.appendChild(div);
619
+ }
620
+
621
+ var title = document.getElementById("title");
622
+ var runner = JSSpec.runner;
623
+
624
+ title.className = runner.hasException() ? "exception" : "success";
625
+
626
+ this.finishedExamples++;
627
+ document.getElementById("total_failures").innerHTML = runner.getTotalFailures();
628
+ document.getElementById("total_errors").innerHTML = runner.getTotalErrors();
629
+ var progress = parseInt(this.finishedExamples / runner.totalExamples * 100);
630
+ document.getElementById("progress").innerHTML = progress;
631
+ document.getElementById("total_elapsed").innerHTML = (new Date().getTime() - this.startedAt.getTime()) / 1000;
632
+
633
+ document.title = progress + "%: " + this._title;
634
+ };
635
+
636
+ /**
637
+ * IncludeMatcher
638
+ */
639
+ JSSpec.IncludeMatcher = function(actual, expected, condition) {
640
+ this.actual = actual;
641
+ this.expected = expected;
642
+ this.condition = condition;
643
+ this.match = false;
644
+ this.explaination = this.makeExplain();
645
+ };
646
+
647
+ JSSpec.IncludeMatcher.createInstance = function(actual, expected, condition) {
648
+ return new JSSpec.IncludeMatcher(actual, expected, condition);
649
+ };
650
+
651
+ JSSpec.IncludeMatcher.prototype.matches = function() {
652
+ return this.match;
653
+ };
654
+
655
+ JSSpec.IncludeMatcher.prototype.explain = function() {
656
+ return this.explaination;
657
+ };
658
+
659
+ JSSpec.IncludeMatcher.prototype.makeExplain = function() {
660
+ if(typeof this.actual.length == 'undefined') {
661
+ return this.makeExplainForNotArray();
662
+ } else {
663
+ return this.makeExplainForArray();
664
+ }
665
+ };
666
+
667
+ JSSpec.IncludeMatcher.prototype.makeExplainForNotArray = function() {
668
+ if(this.condition) {
669
+ this.match = !!this.actual[this.expected];
670
+ } else {
671
+ this.match = !this.actual[this.expected];
672
+ }
673
+
674
+ var sb = [];
675
+ sb.push('<p>actual value:</p>');
676
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, this.expected) + '</p>');
677
+ sb.push('<p>should ' + (this.condition ? '' : 'not') + ' include:</p>');
678
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
679
+ return sb.join("");
680
+ };
681
+
682
+ JSSpec.IncludeMatcher.prototype.makeExplainForArray = function() {
683
+ var matches;
684
+ if(this.condition) {
685
+ for(var i = 0; i < this.actual.length; i++) {
686
+ matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
687
+ if(matches) {
688
+ this.match = true;
689
+ break;
690
+ }
691
+ }
692
+ } else {
693
+ for(var i = 0; i < this.actual.length; i++) {
694
+ matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
695
+ if(matches) {
696
+ this.match = false;
697
+ break;
698
+ }
699
+ }
700
+ }
701
+
702
+ if(this.match) return "";
703
+
704
+ var sb = [];
705
+ sb.push('<p>actual value:</p>');
706
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, this.condition ? null : i) + '</p>');
707
+ sb.push('<p>should ' + (this.condition ? '' : 'not') + ' include:</p>');
708
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
709
+ return sb.join("");
710
+ };
711
+
712
+ /**
713
+ * PropertyLengthMatcher
714
+ */
715
+ JSSpec.PropertyLengthMatcher = function(num, property, o, condition) {
716
+ this.num = num;
717
+ this.o = o;
718
+ this.property = property;
719
+ if((property == 'characters' || property == 'items') && typeof o.length != 'undefined') {
720
+ this.property = 'length';
721
+ }
722
+
723
+ this.condition = condition;
724
+ this.conditionMet = function(x) {
725
+ if(condition == 'exactly') return x.length == num;
726
+ if(condition == 'at least') return x.length >= num;
727
+ if(condition == 'at most') return x.length <= num;
728
+
729
+ throw "Unknown condition '" + condition + "'";
730
+ };
731
+ this.match = false;
732
+ this.explaination = this.makeExplain();
733
+ };
734
+
735
+ JSSpec.PropertyLengthMatcher.prototype.makeExplain = function() {
736
+ if(this.o._type == 'String' && this.property == 'length') {
737
+ this.match = this.conditionMet(this.o);
738
+ return this.match ? '' : this.makeExplainForString();
739
+ } else if(typeof this.o.length != 'undefined' && this.property == "length") {
740
+ this.match = this.conditionMet(this.o);
741
+ return this.match ? '' : this.makeExplainForArray();
742
+ } else if(typeof this.o[this.property] != 'undefined' && this.o[this.property] != null) {
743
+ this.match = this.conditionMet(this.o[this.property]);
744
+ return this.match ? '' : this.makeExplainForObject();
745
+ } else if(typeof this.o[this.property] == 'undefined' || this.o[this.property] == null) {
746
+ this.match = false;
747
+ return this.makeExplainForNoProperty();
748
+ }
749
+
750
+ this.match = true;
751
+ };
752
+
753
+ JSSpec.PropertyLengthMatcher.prototype.makeExplainForString = function() {
754
+ var sb = [];
755
+
756
+ var exp = this.num == 0 ?
757
+ 'be an <strong>empty string</strong>' :
758
+ 'have <strong>' + this.condition + ' ' + this.num + ' characters</strong>';
759
+
760
+ sb.push('<p>actual value has <strong>' + this.o.length + ' characters</strong>:</p>');
761
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o) + '</p>');
762
+ sb.push('<p>but it should ' + exp + '.</p>');
763
+
764
+ return sb.join("");
765
+ };
766
+
767
+ JSSpec.PropertyLengthMatcher.prototype.makeExplainForArray = function() {
768
+ var sb = [];
769
+
770
+ var exp = this.num == 0 ?
771
+ 'be an <strong>empty array</strong>' :
772
+ 'have <strong>' + this.condition + ' ' + this.num + ' items</strong>';
773
+
774
+ sb.push('<p>actual value has <strong>' + this.o.length + ' items</strong>:</p>');
775
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o) + '</p>');
776
+ sb.push('<p>but it should ' + exp + '.</p>');
777
+
778
+ return sb.join("");
779
+ };
780
+
781
+ JSSpec.PropertyLengthMatcher.prototype.makeExplainForObject = function() {
782
+ var sb = [];
783
+
784
+ var exp = this.num == 0 ?
785
+ 'be <strong>empty</strong>' :
786
+ 'have <strong>' + this.condition + ' ' + this.num + ' ' + this.property + '.</strong>';
787
+
788
+ sb.push('<p>actual value has <strong>' + this.o[this.property].length + ' ' + this.property + '</strong>:</p>');
789
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o, false, this.property) + '</p>');
790
+ sb.push('<p>but it should ' + exp + '.</p>');
791
+
792
+ return sb.join("");
793
+ };
794
+
795
+ JSSpec.PropertyLengthMatcher.prototype.makeExplainForNoProperty = function() {
796
+ var sb = [];
797
+
798
+ sb.push('<p>actual value:</p>');
799
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o) + '</p>');
800
+ sb.push('<p>should have <strong>' + this.condition + ' ' + this.num + ' ' + this.property + '</strong> but there\'s no such property.</p>');
801
+
802
+ return sb.join("");
803
+ };
804
+
805
+ JSSpec.PropertyLengthMatcher.prototype.matches = function() {
806
+ return this.match;
807
+ };
808
+
809
+ JSSpec.PropertyLengthMatcher.prototype.explain = function() {
810
+ return this.explaination;
811
+ };
812
+
813
+ JSSpec.PropertyLengthMatcher.createInstance = function(num, property, o, condition) {
814
+ return new JSSpec.PropertyLengthMatcher(num, property, o, condition);
815
+ };
816
+
817
+ /**
818
+ * EqualityMatcher
819
+ */
820
+ JSSpec.EqualityMatcher = {};
821
+
822
+ JSSpec.EqualityMatcher.createInstance = function(expected, actual) {
823
+ if(expected == null || actual == null) {
824
+ return new JSSpec.NullEqualityMatcher(expected, actual);
825
+ } else if(expected._type && expected._type == actual._type) {
826
+ if(expected._type == "String") {
827
+ return new JSSpec.StringEqualityMatcher(expected, actual);
828
+ } else if(expected._type == "Date") {
829
+ return new JSSpec.DateEqualityMatcher(expected, actual);
830
+ } else if(expected._type == "Number") {
831
+ return new JSSpec.NumberEqualityMatcher(expected, actual);
832
+ } else if(expected._type == "Array") {
833
+ return new JSSpec.ArrayEqualityMatcher(expected, actual);
834
+ } else if(expected._type == "Boolean") {
835
+ return new JSSpec.BooleanEqualityMatcher(expected, actual);
836
+ }
837
+ }
838
+
839
+ return new JSSpec.ObjectEqualityMatcher(expected, actual);
840
+ };
841
+
842
+ JSSpec.EqualityMatcher.basicExplain = function(expected, actual, expectedDesc, actualDesc) {
843
+ var sb = [];
844
+
845
+ sb.push(actualDesc || '<p>actual value:</p>');
846
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(actual) + '</p>');
847
+ sb.push(expectedDesc || '<p>should be:</p>');
848
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(expected) + '</p>');
849
+
850
+ return sb.join("");
851
+ };
852
+
853
+ JSSpec.EqualityMatcher.diffExplain = function(expected, actual) {
854
+ var sb = [];
855
+
856
+ sb.push('<p>diff:</p>');
857
+ sb.push('<p style="margin-left:2em;">');
858
+
859
+ var dmp = new diff_match_patch();
860
+ var diff = dmp.diff_main(expected, actual);
861
+ dmp.diff_cleanupEfficiency(diff);
862
+
863
+ sb.push(JSSpec.util.inspect(dmp.diff_prettyHtml(diff), true));
864
+
865
+ sb.push('</p>');
866
+
867
+ return sb.join("");
868
+ };
869
+
870
+ /**
871
+ * BooleanEqualityMatcher
872
+ */
873
+ JSSpec.BooleanEqualityMatcher = function(expected, actual) {
874
+ this.expected = expected;
875
+ this.actual = actual;
876
+ };
877
+
878
+ JSSpec.BooleanEqualityMatcher.prototype.explain = function() {
879
+ var sb = [];
880
+
881
+ sb.push('<p>actual value:</p>');
882
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
883
+ sb.push('<p>should be:</p>');
884
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
885
+
886
+ return sb.join("");
887
+ };
888
+
889
+ JSSpec.BooleanEqualityMatcher.prototype.matches = function() {
890
+ return this.expected == this.actual;
891
+ };
892
+
893
+ /**
894
+ * NullEqualityMatcher
895
+ */
896
+ JSSpec.NullEqualityMatcher = function(expected, actual) {
897
+ this.expected = expected;
898
+ this.actual = actual;
899
+ };
900
+
901
+ JSSpec.NullEqualityMatcher.prototype.matches = function() {
902
+ return this.expected == this.actual && typeof this.expected == typeof this.actual;
903
+ };
904
+
905
+ JSSpec.NullEqualityMatcher.prototype.explain = function() {
906
+ return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
907
+ };
908
+
909
+ JSSpec.DateEqualityMatcher = function(expected, actual) {
910
+ this.expected = expected;
911
+ this.actual = actual;
912
+ };
913
+
914
+ JSSpec.DateEqualityMatcher.prototype.matches = function() {
915
+ return this.expected.getTime() == this.actual.getTime();
916
+ };
917
+
918
+ JSSpec.DateEqualityMatcher.prototype.explain = function() {
919
+ var sb = [];
920
+
921
+ sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
922
+ sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected.toString(), this.actual.toString()));
923
+
924
+ return sb.join("");
925
+ };
926
+
927
+ /**
928
+ * ObjectEqualityMatcher
929
+ */
930
+ JSSpec.ObjectEqualityMatcher = function(expected, actual) {
931
+ this.expected = expected;
932
+ this.actual = actual;
933
+ this.match = this.expected == this.actual;
934
+ this.explaination = this.makeExplain();
935
+ };
936
+
937
+ JSSpec.ObjectEqualityMatcher.prototype.matches = function() {return this.match};
938
+
939
+ JSSpec.ObjectEqualityMatcher.prototype.explain = function() {return this.explaination};
940
+
941
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplain = function() {
942
+ if(this.expected == this.actual) {
943
+ this.match = true;
944
+ return "";
945
+ }
946
+
947
+ if(JSSpec.util.isDomNode(this.expected)) {
948
+ return this.makeExplainForDomNode();
949
+ }
950
+
951
+ var key, expectedHasItem, actualHasItem;
952
+
953
+ for(key in this.expected) {
954
+ expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined';
955
+ actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined';
956
+ if(expectedHasItem && !actualHasItem) return this.makeExplainForMissingItem(key);
957
+ }
958
+ for(key in this.actual) {
959
+ expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined';
960
+ actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined';
961
+ if(actualHasItem && !expectedHasItem) return this.makeExplainForUnknownItem(key);
962
+ }
963
+
964
+ for(key in this.expected) {
965
+ var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[key], this.actual[key]);
966
+ if(!matcher.matches()) return this.makeExplainForItemMismatch(key);
967
+ }
968
+
969
+ this.match = true;
970
+ };
971
+
972
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplainForDomNode = function(key) {
973
+ var sb = [];
974
+
975
+ sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
976
+
977
+ return sb.join("");
978
+ };
979
+
980
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplainForMissingItem = function(key) {
981
+ var sb = [];
982
+
983
+ sb.push('<p>actual value has no item named <strong>' + JSSpec.util.inspect(key) + '</strong></p>');
984
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, key) + '</p>');
985
+ sb.push('<p>but it should have the item whose value is <strong>' + JSSpec.util.inspect(this.expected[key]) + '</strong></p>');
986
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, key) + '</p>');
987
+
988
+ return sb.join("");
989
+ };
990
+
991
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplainForUnknownItem = function(key) {
992
+ var sb = [];
993
+
994
+ sb.push('<p>actual value has item named <strong>' + JSSpec.util.inspect(key) + '</strong></p>');
995
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, key) + '</p>');
996
+ sb.push('<p>but there should be no such item</p>');
997
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, key) + '</p>');
998
+
999
+ return sb.join("");
1000
+ };
1001
+
1002
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplainForItemMismatch = function(key) {
1003
+ var sb = [];
1004
+
1005
+ sb.push('<p>actual value has an item named <strong>' + JSSpec.util.inspect(key) + '</strong> whose value is <strong>' + JSSpec.util.inspect(this.actual[key]) + '</strong></p>');
1006
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, key) + '</p>');
1007
+ sb.push('<p>but it\'s value should be <strong>' + JSSpec.util.inspect(this.expected[key]) + '</strong></p>');
1008
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, key) + '</p>');
1009
+
1010
+ return sb.join("");
1011
+ };
1012
+
1013
+
1014
+
1015
+
1016
+ /**
1017
+ * ArrayEqualityMatcher
1018
+ */
1019
+ JSSpec.ArrayEqualityMatcher = function(expected, actual) {
1020
+ this.expected = expected;
1021
+ this.actual = actual;
1022
+ this.match = this.expected == this.actual;
1023
+ this.explaination = this.makeExplain();
1024
+ };
1025
+
1026
+ JSSpec.ArrayEqualityMatcher.prototype.matches = function() {return this.match};
1027
+
1028
+ JSSpec.ArrayEqualityMatcher.prototype.explain = function() {return this.explaination};
1029
+
1030
+ JSSpec.ArrayEqualityMatcher.prototype.makeExplain = function() {
1031
+ if(this.expected.length != this.actual.length) return this.makeExplainForLengthMismatch();
1032
+
1033
+ for(var i = 0; i < this.expected.length; i++) {
1034
+ var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[i], this.actual[i]);
1035
+ if(!matcher.matches()) return this.makeExplainForItemMismatch(i);
1036
+ }
1037
+
1038
+ this.match = true;
1039
+ };
1040
+
1041
+ JSSpec.ArrayEqualityMatcher.prototype.makeExplainForLengthMismatch = function() {
1042
+ return JSSpec.EqualityMatcher.basicExplain(
1043
+ this.expected,
1044
+ this.actual,
1045
+ '<p>but it should be <strong>' + this.expected.length + '</strong></p>',
1046
+ '<p>actual value has <strong>' + this.actual.length + '</strong> items</p>'
1047
+ );
1048
+ };
1049
+
1050
+ JSSpec.ArrayEqualityMatcher.prototype.makeExplainForItemMismatch = function(index) {
1051
+ var postfix = ["th", "st", "nd", "rd", "th"][Math.min((index + 1) % 10,4)];
1052
+
1053
+ var sb = [];
1054
+
1055
+ sb.push('<p>' + (index + 1) + postfix + ' item (index ' + index + ') of actual value is <strong>' + JSSpec.util.inspect(this.actual[index]) + '</strong>:</p>');
1056
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, index) + '</p>');
1057
+ sb.push('<p>but it should be <strong>' + JSSpec.util.inspect(this.expected[index]) + '</strong>:</p>');
1058
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, index) + '</p>');
1059
+
1060
+ return sb.join("");
1061
+ };
1062
+
1063
+ /**
1064
+ * NumberEqualityMatcher
1065
+ */
1066
+ JSSpec.NumberEqualityMatcher = function(expected, actual) {
1067
+ this.expected = expected;
1068
+ this.actual = actual;
1069
+ };
1070
+
1071
+ JSSpec.NumberEqualityMatcher.prototype.matches = function() {
1072
+ if(this.expected == this.actual) return true;
1073
+ };
1074
+
1075
+ JSSpec.NumberEqualityMatcher.prototype.explain = function() {
1076
+ return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
1077
+ };
1078
+
1079
+ /**
1080
+ * StringEqualityMatcher
1081
+ */
1082
+ JSSpec.StringEqualityMatcher = function(expected, actual) {
1083
+ this.expected = expected;
1084
+ this.actual = actual;
1085
+ };
1086
+
1087
+ JSSpec.StringEqualityMatcher.prototype.matches = function() {
1088
+ return this.expected == this.actual;
1089
+ };
1090
+
1091
+ JSSpec.StringEqualityMatcher.prototype.explain = function() {
1092
+ var sb = [];
1093
+
1094
+ sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
1095
+ sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected, this.actual));
1096
+ return sb.join("");
1097
+ };
1098
+
1099
+ /**
1100
+ * PatternMatcher
1101
+ */
1102
+ JSSpec.PatternMatcher = function(actual, pattern, condition) {
1103
+ this.actual = actual;
1104
+ this.pattern = pattern;
1105
+ this.condition = condition;
1106
+ this.match = false;
1107
+ this.explaination = this.makeExplain();
1108
+ };
1109
+
1110
+ JSSpec.PatternMatcher.createInstance = function(actual, pattern, condition) {
1111
+ return new JSSpec.PatternMatcher(actual, pattern, condition);
1112
+ };
1113
+
1114
+ JSSpec.PatternMatcher.prototype.makeExplain = function() {
1115
+ var sb;
1116
+ if(this.actual == null || this.actual._type != 'String') {
1117
+ sb = [];
1118
+ sb.push('<p>actual value:</p>');
1119
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
1120
+ sb.push('<p>should ' + (this.condition ? '' : 'not') + ' match with pattern:</p>');
1121
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.pattern) + '</p>');
1122
+ sb.push('<p>but pattern matching cannot be performed.</p>');
1123
+ return sb.join("");
1124
+ } else {
1125
+ this.match = this.condition == !!this.actual.match(this.pattern);
1126
+ if(this.match) return "";
1127
+
1128
+ sb = [];
1129
+ sb.push('<p>actual value:</p>');
1130
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
1131
+ sb.push('<p>should ' + (this.condition ? '' : 'not') + ' match with pattern:</p>');
1132
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.pattern) + '</p>');
1133
+ return sb.join("");
1134
+ }
1135
+ };
1136
+
1137
+ JSSpec.PatternMatcher.prototype.matches = function() {
1138
+ return this.match;
1139
+ };
1140
+
1141
+ JSSpec.PatternMatcher.prototype.explain = function() {
1142
+ return this.explaination;
1143
+ };
1144
+
1145
+ /**
1146
+ * Domain Specific Languages
1147
+ */
1148
+ JSSpec.DSL = {};
1149
+
1150
+ JSSpec.DSL.forString = {
1151
+ normalizeHtml: function() {
1152
+ var html = this;
1153
+
1154
+ // Uniformize quotation, turn tag names and attribute names into lower case
1155
+ html = html.replace(/<(\/?)(\w+)([^>]*?)>/img, function(str, closingMark, tagName, attrs) {
1156
+ var sortedAttrs = JSSpec.util.sortHtmlAttrs(JSSpec.util.correctHtmlAttrQuotation(attrs).toLowerCase())
1157
+ return "<" + closingMark + tagName.toLowerCase() + sortedAttrs + ">"
1158
+ });
1159
+
1160
+ // validation self-closing tags
1161
+ html = html.replace(/<(br|hr|img)([^>]*?)>/mg, function(str, tag, attrs) {
1162
+ return "<" + tag + attrs + " />";
1163
+ });
1164
+
1165
+ // append semi-colon at the end of style value
1166
+ html = html.replace(/style="(.*?)"/mg, function(str, styleStr) {
1167
+ styleStr = JSSpec.util.sortStyleEntries(styleStr.strip()); // for Safari
1168
+ if(styleStr.charAt(styleStr.length - 1) != ';') styleStr += ";"
1169
+
1170
+ return 'style="' + styleStr + '"'
1171
+ });
1172
+
1173
+ // sort style entries
1174
+
1175
+ // remove empty style attributes
1176
+ html = html.replace(/ style=";"/mg, "");
1177
+
1178
+ // remove new-lines
1179
+ html = html.replace(/\r/mg, '');
1180
+ html = html.replace(/\n/mg, '');
1181
+
1182
+ return html;
1183
+ }
1184
+ };
1185
+
1186
+
1187
+ JSSpec.DSL.describe = function(context, entries, base) {
1188
+ if(base) {
1189
+ for(var i = 0; i < JSSpec.specs.length; i++) {
1190
+ if(JSSpec.specs[i].context === base) {
1191
+ base = JSSpec.specs[i];
1192
+ break;
1193
+ }
1194
+ }
1195
+
1196
+ for(var i = 0; i < base.examples.length; i++) {
1197
+ var example = base.examples[i];
1198
+
1199
+ if(!entries[example.name]) entries[example.name] = example.target;
1200
+ }
1201
+ }
1202
+
1203
+ JSSpec.specs.push(new JSSpec.Spec(context, entries));
1204
+ };
1205
+
1206
+ JSSpec.DSL.value_of = function(target) {
1207
+ if(JSSpec._secondPass) return {};
1208
+
1209
+ var subject = new JSSpec.DSL.Subject(target);
1210
+ return subject;
1211
+ };
1212
+
1213
+ JSSpec.DSL.Subject = function(target) {
1214
+ this.target = target;
1215
+ };
1216
+
1217
+ JSSpec.DSL.Subject.prototype._type = 'Subject';
1218
+
1219
+ JSSpec.DSL.Subject.prototype.should_fail = function(message) {
1220
+ JSSpec._assertionFailure = {message:message};
1221
+ throw JSSpec._assertionFailure;
1222
+ };
1223
+
1224
+ JSSpec.DSL.Subject.prototype.should_be = function(expected) {
1225
+ var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
1226
+ if(!matcher.matches()) {
1227
+ JSSpec._assertionFailure = {message:matcher.explain()};
1228
+ throw JSSpec._assertionFailure;
1229
+ }
1230
+ };
1231
+
1232
+ JSSpec.DSL.Subject.prototype.should_not_be = function(expected) {
1233
+ // TODO JSSpec.EqualityMatcher should support 'condition'
1234
+ var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
1235
+ if(matcher.matches()) {
1236
+ JSSpec._assertionFailure = {message:"'" + this.target + "' should not be '" + expected + "'"};
1237
+ throw JSSpec._assertionFailure;
1238
+ }
1239
+ };
1240
+
1241
+ JSSpec.DSL.Subject.prototype.should_be_empty = function() {
1242
+ this.should_have(0, this.getType() == 'String' ? 'characters' : 'items');
1243
+ };
1244
+
1245
+ JSSpec.DSL.Subject.prototype.should_not_be_empty = function() {
1246
+ this.should_have_at_least(1, this.getType() == 'String' ? 'characters' : 'items');
1247
+ };
1248
+
1249
+ JSSpec.DSL.Subject.prototype.should_be_true = function() {
1250
+ this.should_be(true);
1251
+ };
1252
+
1253
+ JSSpec.DSL.Subject.prototype.should_be_false = function() {
1254
+ this.should_be(false);
1255
+ };
1256
+
1257
+ JSSpec.DSL.Subject.prototype.should_be_null = function() {
1258
+ this.should_be(null);
1259
+ };
1260
+
1261
+ JSSpec.DSL.Subject.prototype.should_be_undefined = function() {
1262
+ this.should_be(undefined);
1263
+ };
1264
+
1265
+ JSSpec.DSL.Subject.prototype.should_not_be_null = function() {
1266
+ this.should_not_be(null);
1267
+ };
1268
+
1269
+ JSSpec.DSL.Subject.prototype.should_not_be_undefined = function() {
1270
+ this.should_not_be(undefined);
1271
+ };
1272
+
1273
+ JSSpec.DSL.Subject.prototype._should_have = function(num, property, condition) {
1274
+ var matcher = JSSpec.PropertyLengthMatcher.createInstance(num, property, this.target, condition);
1275
+ if(!matcher.matches()) {
1276
+ JSSpec._assertionFailure = {message:matcher.explain()};
1277
+ throw JSSpec._assertionFailure;
1278
+ }
1279
+ };
1280
+
1281
+ JSSpec.DSL.Subject.prototype.should_have = function(num, property) {
1282
+ this._should_have(num, property, "exactly");
1283
+ };
1284
+
1285
+ JSSpec.DSL.Subject.prototype.should_have_exactly = function(num, property) {
1286
+ this._should_have(num, property, "exactly");
1287
+ };
1288
+
1289
+ JSSpec.DSL.Subject.prototype.should_have_at_least = function(num, property) {
1290
+ this._should_have(num, property, "at least");
1291
+ };
1292
+
1293
+ JSSpec.DSL.Subject.prototype.should_have_at_most = function(num, property) {
1294
+ this._should_have(num, property, "at most");
1295
+ };
1296
+
1297
+ JSSpec.DSL.Subject.prototype.should_include = function(expected) {
1298
+ var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, true);
1299
+ if(!matcher.matches()) {
1300
+ JSSpec._assertionFailure = {message:matcher.explain()};
1301
+ throw JSSpec._assertionFailure;
1302
+ }
1303
+ };
1304
+
1305
+ JSSpec.DSL.Subject.prototype.should_not_include = function(expected) {
1306
+ var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, false);
1307
+ if(!matcher.matches()) {
1308
+ JSSpec._assertionFailure = {message:matcher.explain()};
1309
+ throw JSSpec._assertionFailure;
1310
+ }
1311
+ };
1312
+
1313
+ JSSpec.DSL.Subject.prototype.should_match = function(pattern) {
1314
+ var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, true);
1315
+ if(!matcher.matches()) {
1316
+ JSSpec._assertionFailure = {message:matcher.explain()};
1317
+ throw JSSpec._assertionFailure;
1318
+ }
1319
+ }
1320
+ JSSpec.DSL.Subject.prototype.should_not_match = function(pattern) {
1321
+ var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, false);
1322
+ if(!matcher.matches()) {
1323
+ JSSpec._assertionFailure = {message:matcher.explain()};
1324
+ throw JSSpec._assertionFailure;
1325
+ }
1326
+ };
1327
+
1328
+ JSSpec.DSL.Subject.prototype.getType = function() {
1329
+ if(typeof this.target == 'undefined') {
1330
+ return 'undefined';
1331
+ } else if(this.target == null) {
1332
+ return 'null';
1333
+ } else if(this.target._type) {
1334
+ return this.target._type;
1335
+ } else if(JSSpec.util.isDomNode(this.target)) {
1336
+ return 'DomNode';
1337
+ } else {
1338
+ return 'object';
1339
+ }
1340
+ };
1341
+
1342
+ /**
1343
+ * Utilities
1344
+ */
1345
+ JSSpec.util = {
1346
+ escapeTags: function(string) {
1347
+ return string.replace(/</img, '&lt;').replace(/>/img, '&gt;');
1348
+ },
1349
+ escapeMetastring: function(string) {
1350
+ return string.replace(/\r/img, '\\r').replace(/\n/img, '\\n').replace(/\&para\;\<BR\>/img, '\\n').replace(/\t/img, '\\t');
1351
+ },
1352
+ parseOptions: function(defaults) {
1353
+ var options = defaults;
1354
+
1355
+ var url = location.href;
1356
+ var queryIndex = url.indexOf('?');
1357
+ if(queryIndex == -1) return options;
1358
+
1359
+ var query = url.substring(queryIndex + 1).split('#')[0];
1360
+ var pairs = query.split('&');
1361
+ for(var i = 0; i < pairs.length; i++) {
1362
+ var tokens = pairs[i].split('=');
1363
+ options[tokens[0]] = tokens[1];
1364
+ }
1365
+
1366
+ return options;
1367
+ },
1368
+ correctHtmlAttrQuotation: function(html) {
1369
+ html = html.replace(/(\w+)=['"]([^'"]+)['"]/mg,function (str, name, value) {return name + '=' + '"' + value + '"';});
1370
+ html = html.replace(/(\w+)=([^ '"]+)/mg,function (str, name, value) {return name + '=' + '"' + value + '"';});
1371
+ html = html.replace(/'/mg, '"');
1372
+
1373
+ return html;
1374
+ },
1375
+ sortHtmlAttrs: function(html) {
1376
+ var attrs = [];
1377
+ html.replace(/((\w+)="[^"]+")/mg, function(str, matched) {
1378
+ attrs.push(matched);
1379
+ });
1380
+ return attrs.length == 0 ? "" : " " + attrs.sort().join(" ");
1381
+ },
1382
+ sortStyleEntries: function(styleText) {
1383
+ var entries = styleText.split(/; /);
1384
+ return entries.sort().join("; ");
1385
+ },
1386
+ escapeHtml: function(str) {
1387
+ if(!this._div) {
1388
+ this._div = document.createElement("DIV");
1389
+ this._text = document.createTextNode('');
1390
+ this._div.appendChild(this._text);
1391
+ }
1392
+ this._text.data = str;
1393
+ return this._div.innerHTML;
1394
+ },
1395
+ isDomNode: function(o) {
1396
+ // TODO: make it more stricter
1397
+ return (typeof o.nodeName == 'string') && (typeof o.nodeType == 'number');
1398
+ },
1399
+ inspectDomPath: function(o) {
1400
+ var sb = [];
1401
+ while(o && o.nodeName != '#document' && o.parent) {
1402
+ var siblings = o.parentNode.childNodes;
1403
+ for(var i = 0; i < siblings.length; i++) {
1404
+ if(siblings[i] == o) {
1405
+ sb.push(o.nodeName + (i == 0 ? '' : '[' + i + ']'));
1406
+ break;
1407
+ }
1408
+ }
1409
+ o = o.parentNode;
1410
+ }
1411
+ return sb.join(" &gt; ");
1412
+ },
1413
+ inspectDomNode: function(o) {
1414
+ if(o.nodeType == 1) {
1415
+ var nodeName = o.nodeName.toLowerCase();
1416
+ var sb = [];
1417
+ sb.push('<span class="dom_value">');
1418
+ sb.push("&lt;");
1419
+ sb.push(nodeName);
1420
+
1421
+ var attrs = o.attributes;
1422
+ for(var i = 0; i < attrs.length; i++) {
1423
+ if(
1424
+ attrs[i].nodeValue &&
1425
+ attrs[i].nodeName != 'contentEditable' &&
1426
+ attrs[i].nodeName != 'style' &&
1427
+ typeof attrs[i].nodeValue != 'function'
1428
+ ) sb.push(' <span class="dom_attr_name">' + attrs[i].nodeName.toLowerCase() + '</span>=<span class="dom_attr_value">"' + attrs[i].nodeValue + '"</span>');
1429
+ }
1430
+ if(o.style && o.style.cssText) {
1431
+ sb.push(' <span class="dom_attr_name">style</span>=<span class="dom_attr_value">"' + o.style.cssText + '"</span>');
1432
+ }
1433
+ sb.push('&gt;');
1434
+ sb.push(JSSpec.util.escapeHtml(o.innerHTML));
1435
+ sb.push('&lt;/' + nodeName + '&gt;');
1436
+ sb.push(' <span class="dom_path">(' + JSSpec.util.inspectDomPath(o) + ')</span>' );
1437
+ sb.push('</span>');
1438
+ return sb.join("");
1439
+ } else if(o.nodeType == 3) {
1440
+ return '<span class="dom_value">#text ' + o.nodeValue + '</span>';
1441
+ } else {
1442
+ return '<span class="dom_value">UnknownDomNode</span>';
1443
+ }
1444
+ },
1445
+ inspect: function(o, dontEscape, emphasisKey) {
1446
+ var sb, inspected;
1447
+
1448
+ if(typeof o == 'undefined') return '<span class="undefined_value">undefined</span>';
1449
+ if(o == null) return '<span class="null_value">null</span>';
1450
+ if(o._type == 'String') return '<span class="string_value">"' + (dontEscape ? JSSpec.util.escapeMetastring(o) : JSSpec.util.escapeHtml(JSSpec.util.escapeMetastring(o))) + '"</span>';
1451
+
1452
+ if(o._type == 'Date') {
1453
+ return '<span class="date_value">"' + o.toString() + '"</span>';
1454
+ }
1455
+
1456
+ if(o._type == 'Number') return '<span class="number_value">' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + '</span>';
1457
+
1458
+ if(o._type == 'Boolean') return '<span class="boolean_value">' + o + '</span>';
1459
+
1460
+ if(o._type == 'RegExp') return '<span class="regexp_value">' + JSSpec.util.escapeHtml(o.toString()) + '</span>';
1461
+
1462
+ if(JSSpec.util.isDomNode(o)) return JSSpec.util.inspectDomNode(o);
1463
+
1464
+ if(o._type == 'Array' || typeof o.length != 'undefined') {
1465
+ sb = [];
1466
+ for(var i = 0; i < o.length; i++) {
1467
+ inspected = JSSpec.util.inspect(o[i]);
1468
+ sb.push(i == emphasisKey ? ('<strong>' + inspected + '</strong>') : inspected);
1469
+ }
1470
+ return '<span class="array_value">[' + sb.join(', ') + ']</span>';
1471
+ }
1472
+
1473
+ // object
1474
+ sb = [];
1475
+ for(var key in o) {
1476
+ if(key == 'should') continue;
1477
+
1478
+ inspected = JSSpec.util.inspect(key) + ":" + JSSpec.util.inspect(o[key]);
1479
+ sb.push(key == emphasisKey ? ('<strong>' + inspected + '</strong>') : inspected);
1480
+ }
1481
+ return '<span class="object_value">{' + sb.join(', ') + '}</span>';
1482
+ }
1483
+ };
1484
+
1485
+ describe = JSSpec.DSL.describe;
1486
+ behavior_of = JSSpec.DSL.describe;
1487
+ value_of = JSSpec.DSL.value_of;
1488
+ expect = JSSpec.DSL.value_of; // @deprecated
1489
+
1490
+ String.prototype._type = "String";
1491
+ Number.prototype._type = "Number";
1492
+ Date.prototype._type = "Date";
1493
+ Array.prototype._type = "Array";
1494
+ Boolean.prototype._type = "Boolean";
1495
+ RegExp.prototype._type = "RegExp";
1496
+
1497
+ var targets = [Array.prototype, Date.prototype, Number.prototype, String.prototype, Boolean.prototype, RegExp.prototype];
1498
+
1499
+ String.prototype.normalizeHtml = JSSpec.DSL.forString.normalizeHtml;
1500
+ String.prototype.asHtml = String.prototype.normalizeHtml; //@deprecated
1501
+ String.prototype.strip = function() {return this.replace(/^\s+/, '').replace(/\s+$/, '');}
1502
+
1503
+
1504
+ /**
1505
+ * Main
1506
+ */
1507
+ JSSpec.defaultOptions = {
1508
+ autorun: 1,
1509
+ specIdBeginsWith: 0,
1510
+ exampleIdBeginsWith: 0,
1511
+ autocollapse: 1
1512
+ };
1513
+ JSSpec.options = JSSpec.util.parseOptions(JSSpec.defaultOptions);
1514
+
1515
+ JSSpec.Spec.id = JSSpec.options.specIdBeginsWith;
1516
+ JSSpec.Example.id = JSSpec.options.exampleIdBeginsWith;
1517
+
1518
+
1519
+
1520
+ window.onload = function() {
1521
+ if(JSSpec.specs.length > 0) {
1522
+ if(!JSSpec.options.inSuite) {
1523
+ JSSpec.runner = new JSSpec.Runner(JSSpec.specs, new JSSpec.Logger());
1524
+ if(JSSpec.options.rerun) {
1525
+ JSSpec.runner.rerun(decodeURIComponent(JSSpec.options.rerun));
1526
+ } else {
1527
+ JSSpec.runner.run();
1528
+ }
1529
+ } else {
1530
+ // in suite, send all specs to parent
1531
+ var parentWindow = window.frames.parent.window;
1532
+ for(var i = 0; i < JSSpec.specs.length; i++) {
1533
+ parentWindow.JSSpec.specs.push(JSSpec.specs[i]);
1534
+ }
1535
+ }
1536
+ } else {
1537
+ var links = document.getElementById('list').getElementsByTagName('A');
1538
+ var frameContainer = document.createElement('DIV');
1539
+ frameContainer.style.display = 'none';
1540
+ document.body.appendChild(frameContainer);
1541
+
1542
+ for(var i = 0; i < links.length; i++) {
1543
+ var frame = document.createElement('IFRAME');
1544
+ frame.src = links[i].href + '?inSuite=0&specIdBeginsWith=' + (i * 10000) + '&exampleIdBeginsWith=' + (i * 10000);
1545
+ frameContainer.appendChild(frame);
1546
+ }
1547
+ }
1548
+ }