dripdrop 0.10.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.document +5 -0
  2. data/.gitignore +31 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +20 -0
  5. data/README.md +164 -0
  6. data/Rakefile +16 -0
  7. data/dripdrop.gemspec +37 -0
  8. data/example/agent_test.rb +14 -0
  9. data/example/combined.rb +33 -0
  10. data/example/complex/README +22 -0
  11. data/example/complex/client.rb +20 -0
  12. data/example/complex/server.rb +102 -0
  13. data/example/complex/service.rb +8 -0
  14. data/example/complex/websocket.rb +442 -0
  15. data/example/http.rb +23 -0
  16. data/example/pubsub.rb +29 -0
  17. data/example/pushpull.rb +21 -0
  18. data/example/subclass.rb +54 -0
  19. data/example/xreq_xrep.rb +24 -0
  20. data/js/dripdrop.html +186 -0
  21. data/js/dripdrop.js +107 -0
  22. data/js/qunit.css +155 -0
  23. data/js/qunit.js +1261 -0
  24. data/lib/dripdrop.rb +2 -0
  25. data/lib/dripdrop/agent.rb +40 -0
  26. data/lib/dripdrop/handlers/base.rb +42 -0
  27. data/lib/dripdrop/handlers/http_client.rb +38 -0
  28. data/lib/dripdrop/handlers/http_server.rb +59 -0
  29. data/lib/dripdrop/handlers/mongrel2.rb +163 -0
  30. data/lib/dripdrop/handlers/websocket_server.rb +86 -0
  31. data/lib/dripdrop/handlers/zeromq.rb +300 -0
  32. data/lib/dripdrop/message.rb +190 -0
  33. data/lib/dripdrop/node.rb +351 -0
  34. data/lib/dripdrop/node/nodelet.rb +35 -0
  35. data/lib/dripdrop/version.rb +3 -0
  36. data/spec/gimite-websocket.rb +442 -0
  37. data/spec/message_spec.rb +94 -0
  38. data/spec/node/http_spec.rb +77 -0
  39. data/spec/node/nodelet_spec.rb +67 -0
  40. data/spec/node/routing_spec.rb +67 -0
  41. data/spec/node/websocket_spec.rb +98 -0
  42. data/spec/node/zmq_m2_spec.rb +77 -0
  43. data/spec/node/zmq_pushpull_spec.rb +54 -0
  44. data/spec/node/zmq_xrepxreq_spec.rb +108 -0
  45. data/spec/node_spec.rb +85 -0
  46. data/spec/spec_helper.rb +20 -0
  47. metadata +167 -0
data/js/qunit.css ADDED
@@ -0,0 +1,155 @@
1
+ /** Font Family and Sizes */
2
+
3
+ #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
4
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
5
+ }
6
+
7
+ #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
8
+ #qunit-tests { font-size: smaller; }
9
+
10
+
11
+ /** Resets */
12
+
13
+ #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
14
+ margin: 0;
15
+ padding: 0;
16
+ }
17
+
18
+
19
+ /** Header */
20
+
21
+ #qunit-header {
22
+ padding: 0.5em 0 0.5em 1em;
23
+
24
+ color: #fff;
25
+ text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
26
+ background-color: #0d3349;
27
+
28
+ border-radius: 15px 15px 0 0;
29
+ -moz-border-radius: 15px 15px 0 0;
30
+ -webkit-border-top-right-radius: 15px;
31
+ -webkit-border-top-left-radius: 15px;
32
+ }
33
+
34
+ #qunit-header a {
35
+ text-decoration: none;
36
+ color: white;
37
+ }
38
+
39
+ #qunit-banner {
40
+ height: 5px;
41
+ }
42
+
43
+ #qunit-testrunner-toolbar {
44
+ padding: 0em 0 0.5em 2em;
45
+ }
46
+
47
+ #qunit-userAgent {
48
+ padding: 0.5em 0 0.5em 2.5em;
49
+ background-color: #2b81af;
50
+ color: #fff;
51
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
52
+ }
53
+
54
+
55
+ /** Tests: Pass/Fail */
56
+
57
+ #qunit-tests {
58
+ list-style-position: inside;
59
+ }
60
+
61
+ #qunit-tests li {
62
+ padding: 0.4em 0.5em 0.4em 2.5em;
63
+ border-bottom: 1px solid #fff;
64
+ list-style-position: inside;
65
+ }
66
+
67
+ #qunit-tests li strong {
68
+ cursor: pointer;
69
+ }
70
+
71
+ #qunit-tests ol {
72
+ margin-top: 0.5em;
73
+ padding: 0.5em;
74
+
75
+ background-color: #fff;
76
+
77
+ border-radius: 15px;
78
+ -moz-border-radius: 15px;
79
+ -webkit-border-radius: 15px;
80
+
81
+ box-shadow: inset 0px 2px 13px #999;
82
+ -moz-box-shadow: inset 0px 2px 13px #999;
83
+ -webkit-box-shadow: inset 0px 2px 13px #999;
84
+ }
85
+
86
+ /*** Test Counts */
87
+
88
+ #qunit-tests b.counts { color: black; }
89
+ #qunit-tests b.passed { color: #5E740B; }
90
+ #qunit-tests b.failed { color: #710909; }
91
+
92
+ #qunit-tests li li {
93
+ margin: 0.5em;
94
+ padding: 0.4em 0.5em 0.4em 0.5em;
95
+ background-color: #fff;
96
+ border-bottom: none;
97
+ list-style-position: inside;
98
+ }
99
+
100
+ /*** Passing Styles */
101
+
102
+ #qunit-tests li li.pass {
103
+ color: #5E740B;
104
+ background-color: #fff;
105
+ border-left: 26px solid #C6E746;
106
+ }
107
+
108
+ #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
109
+ #qunit-tests .pass .test-name { color: #366097; }
110
+
111
+ #qunit-tests .pass .test-actual,
112
+ #qunit-tests .pass .test-expected { color: #999999; }
113
+
114
+ #qunit-banner.qunit-pass { background-color: #C6E746; }
115
+
116
+ /*** Failing Styles */
117
+
118
+ #qunit-tests li li.fail {
119
+ color: #710909;
120
+ background-color: #fff;
121
+ border-left: 26px solid #EE5757;
122
+ }
123
+
124
+ #qunit-tests .fail { color: #000000; background-color: #EE5757; }
125
+ #qunit-tests .fail .test-name,
126
+ #qunit-tests .fail .module-name { color: #000000; }
127
+
128
+ #qunit-tests .fail .test-actual { color: #EE5757; }
129
+ #qunit-tests .fail .test-expected { color: green; }
130
+
131
+ #qunit-banner.qunit-fail,
132
+ #qunit-testrunner-toolbar { background-color: #EE5757; }
133
+
134
+
135
+ /** Footer */
136
+
137
+ #qunit-testresult {
138
+ padding: 0.5em 0.5em 0.5em 2.5em;
139
+
140
+ color: #2b81af;
141
+ background-color: #D2E0E6;
142
+
143
+ border-radius: 0 0 15px 15px;
144
+ -moz-border-radius: 0 0 15px 15px;
145
+ -webkit-border-bottom-right-radius: 15px;
146
+ -webkit-border-bottom-left-radius: 15px;
147
+ }
148
+
149
+ /** Fixture */
150
+
151
+ #qunit-fixture {
152
+ position: absolute;
153
+ top: -10000px;
154
+ left: -10000px;
155
+ }
data/js/qunit.js ADDED
@@ -0,0 +1,1261 @@
1
+ /*
2
+ * QUnit - A JavaScript Unit Testing Framework
3
+ *
4
+ * http://docs.jquery.com/QUnit
5
+ *
6
+ * Copyright (c) 2009 John Resig, Jörn Zaefferer
7
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
8
+ * and GPL (GPL-LICENSE.txt) licenses.
9
+ */
10
+
11
+ (function(window) {
12
+
13
+ var QUnit = {
14
+
15
+ // call on start of module test to prepend name to all tests
16
+ module: function(name, testEnvironment) {
17
+ config.currentModule = name;
18
+
19
+ synchronize(function() {
20
+ if ( config.currentModule ) {
21
+ QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
22
+ }
23
+
24
+ config.currentModule = name;
25
+ config.moduleTestEnvironment = testEnvironment;
26
+ config.moduleStats = { all: 0, bad: 0 };
27
+
28
+ QUnit.moduleStart( name, testEnvironment );
29
+ });
30
+ },
31
+
32
+ asyncTest: function(testName, expected, callback) {
33
+ if ( arguments.length === 2 ) {
34
+ callback = expected;
35
+ expected = 0;
36
+ }
37
+
38
+ QUnit.test(testName, expected, callback, true);
39
+ },
40
+
41
+ test: function(testName, expected, callback, async) {
42
+ var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg;
43
+
44
+ if ( arguments.length === 2 ) {
45
+ callback = expected;
46
+ expected = null;
47
+ }
48
+ // is 2nd argument a testEnvironment?
49
+ if ( expected && typeof expected === 'object') {
50
+ testEnvironmentArg = expected;
51
+ expected = null;
52
+ }
53
+
54
+ if ( config.currentModule ) {
55
+ name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
56
+ }
57
+
58
+ if ( !validTest(config.currentModule + ": " + testName) ) {
59
+ return;
60
+ }
61
+
62
+ synchronize(function() {
63
+
64
+ testEnvironment = extend({
65
+ setup: function() {},
66
+ teardown: function() {}
67
+ }, config.moduleTestEnvironment);
68
+ if (testEnvironmentArg) {
69
+ extend(testEnvironment,testEnvironmentArg);
70
+ }
71
+
72
+ QUnit.testStart( testName, testEnvironment );
73
+
74
+ // allow utility functions to access the current test environment
75
+ QUnit.current_testEnvironment = testEnvironment;
76
+
77
+ config.assertions = [];
78
+ config.expected = expected;
79
+
80
+ var tests = id("qunit-tests");
81
+ if (tests) {
82
+ var b = document.createElement("strong");
83
+ b.innerHTML = "Running " + name;
84
+ var li = document.createElement("li");
85
+ li.appendChild( b );
86
+ li.id = "current-test-output";
87
+ tests.appendChild( li )
88
+ }
89
+
90
+ try {
91
+ if ( !config.pollution ) {
92
+ saveGlobal();
93
+ }
94
+
95
+ testEnvironment.setup.call(testEnvironment);
96
+ } catch(e) {
97
+ QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
98
+ }
99
+ });
100
+
101
+ synchronize(function() {
102
+ if ( async ) {
103
+ QUnit.stop();
104
+ }
105
+
106
+ try {
107
+ callback.call(testEnvironment);
108
+ } catch(e) {
109
+ fail("Test " + name + " died, exception and test follows", e, callback);
110
+ QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
111
+ // else next test will carry the responsibility
112
+ saveGlobal();
113
+
114
+ // Restart the tests if they're blocking
115
+ if ( config.blocking ) {
116
+ start();
117
+ }
118
+ }
119
+ });
120
+
121
+ synchronize(function() {
122
+ try {
123
+ checkPollution();
124
+ testEnvironment.teardown.call(testEnvironment);
125
+ } catch(e) {
126
+ QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
127
+ }
128
+ });
129
+
130
+ synchronize(function() {
131
+ try {
132
+ QUnit.reset();
133
+ } catch(e) {
134
+ fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);
135
+ }
136
+
137
+ if ( config.expected && config.expected != config.assertions.length ) {
138
+ QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
139
+ }
140
+
141
+ var good = 0, bad = 0,
142
+ tests = id("qunit-tests");
143
+
144
+ config.stats.all += config.assertions.length;
145
+ config.moduleStats.all += config.assertions.length;
146
+
147
+ if ( tests ) {
148
+ var ol = document.createElement("ol");
149
+
150
+ for ( var i = 0; i < config.assertions.length; i++ ) {
151
+ var assertion = config.assertions[i];
152
+
153
+ var li = document.createElement("li");
154
+ li.className = assertion.result ? "pass" : "fail";
155
+ li.innerHTML = assertion.message || "(no message)";
156
+ ol.appendChild( li );
157
+
158
+ if ( assertion.result ) {
159
+ good++;
160
+ } else {
161
+ bad++;
162
+ config.stats.bad++;
163
+ config.moduleStats.bad++;
164
+ }
165
+ }
166
+ if (bad == 0) {
167
+ ol.style.display = "none";
168
+ }
169
+
170
+ var b = document.createElement("strong");
171
+ b.innerHTML = name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + config.assertions.length + ")</b>";
172
+
173
+ addEvent(b, "click", function() {
174
+ var next = b.nextSibling, display = next.style.display;
175
+ next.style.display = display === "none" ? "block" : "none";
176
+ });
177
+
178
+ addEvent(b, "dblclick", function(e) {
179
+ var target = e && e.target ? e.target : window.event.srcElement;
180
+ if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
181
+ target = target.parentNode;
182
+ }
183
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
184
+ window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
185
+ }
186
+ });
187
+
188
+ var li = id("current-test-output");
189
+ li.id = "";
190
+ li.className = bad ? "fail" : "pass";
191
+ li.removeChild( li.firstChild );
192
+ li.appendChild( b );
193
+ li.appendChild( ol );
194
+
195
+ if ( bad ) {
196
+ var toolbar = id("qunit-testrunner-toolbar");
197
+ if ( toolbar ) {
198
+ toolbar.style.display = "block";
199
+ id("qunit-filter-pass").disabled = null;
200
+ id("qunit-filter-missing").disabled = null;
201
+ }
202
+ }
203
+
204
+ } else {
205
+ for ( var i = 0; i < config.assertions.length; i++ ) {
206
+ if ( !config.assertions[i].result ) {
207
+ bad++;
208
+ config.stats.bad++;
209
+ config.moduleStats.bad++;
210
+ }
211
+ }
212
+ }
213
+
214
+ QUnit.testDone( testName, bad, config.assertions.length );
215
+
216
+ if ( !window.setTimeout && !config.queue.length ) {
217
+ done();
218
+ }
219
+ });
220
+
221
+ synchronize( done );
222
+ },
223
+
224
+ /**
225
+ * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
226
+ */
227
+ expect: function(asserts) {
228
+ config.expected = asserts;
229
+ },
230
+
231
+ /**
232
+ * Asserts true.
233
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
234
+ */
235
+ ok: function(a, msg) {
236
+ msg = escapeHtml(msg);
237
+ QUnit.log(a, msg);
238
+
239
+ config.assertions.push({
240
+ result: !!a,
241
+ message: msg
242
+ });
243
+ },
244
+
245
+ /**
246
+ * Checks that the first two arguments are equal, with an optional message.
247
+ * Prints out both actual and expected values.
248
+ *
249
+ * Prefered to ok( actual == expected, message )
250
+ *
251
+ * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
252
+ *
253
+ * @param Object actual
254
+ * @param Object expected
255
+ * @param String message (optional)
256
+ */
257
+ equal: function(actual, expected, message) {
258
+ push(expected == actual, actual, expected, message);
259
+ },
260
+
261
+ notEqual: function(actual, expected, message) {
262
+ push(expected != actual, actual, expected, message);
263
+ },
264
+
265
+ deepEqual: function(actual, expected, message) {
266
+ push(QUnit.equiv(actual, expected), actual, expected, message);
267
+ },
268
+
269
+ notDeepEqual: function(actual, expected, message) {
270
+ push(!QUnit.equiv(actual, expected), actual, expected, message);
271
+ },
272
+
273
+ strictEqual: function(actual, expected, message) {
274
+ push(expected === actual, actual, expected, message);
275
+ },
276
+
277
+ notStrictEqual: function(actual, expected, message) {
278
+ push(expected !== actual, actual, expected, message);
279
+ },
280
+
281
+ raises: function(fn, message) {
282
+ try {
283
+ fn();
284
+ ok( false, message );
285
+ }
286
+ catch (e) {
287
+ ok( true, message );
288
+ }
289
+ },
290
+
291
+ start: function() {
292
+ // A slight delay, to avoid any current callbacks
293
+ if ( window.setTimeout ) {
294
+ window.setTimeout(function() {
295
+ if ( config.timeout ) {
296
+ clearTimeout(config.timeout);
297
+ }
298
+
299
+ config.blocking = false;
300
+ process();
301
+ }, 13);
302
+ } else {
303
+ config.blocking = false;
304
+ process();
305
+ }
306
+ },
307
+
308
+ stop: function(timeout) {
309
+ config.blocking = true;
310
+
311
+ if ( timeout && window.setTimeout ) {
312
+ config.timeout = window.setTimeout(function() {
313
+ QUnit.ok( false, "Test timed out" );
314
+ QUnit.start();
315
+ }, timeout);
316
+ }
317
+ }
318
+
319
+ };
320
+
321
+ // Backwards compatibility, deprecated
322
+ QUnit.equals = QUnit.equal;
323
+ QUnit.same = QUnit.deepEqual;
324
+
325
+ // Maintain internal state
326
+ var config = {
327
+ // The queue of tests to run
328
+ queue: [],
329
+
330
+ // block until document ready
331
+ blocking: true
332
+ };
333
+
334
+ // Load paramaters
335
+ (function() {
336
+ var location = window.location || { search: "", protocol: "file:" },
337
+ GETParams = location.search.slice(1).split('&');
338
+
339
+ for ( var i = 0; i < GETParams.length; i++ ) {
340
+ GETParams[i] = decodeURIComponent( GETParams[i] );
341
+ if ( GETParams[i] === "noglobals" ) {
342
+ GETParams.splice( i, 1 );
343
+ i--;
344
+ config.noglobals = true;
345
+ } else if ( GETParams[i].search('=') > -1 ) {
346
+ GETParams.splice( i, 1 );
347
+ i--;
348
+ }
349
+ }
350
+
351
+ // restrict modules/tests by get parameters
352
+ config.filters = GETParams;
353
+
354
+ // Figure out if we're running the tests from a server or not
355
+ QUnit.isLocal = !!(location.protocol === 'file:');
356
+ })();
357
+
358
+ // Expose the API as global variables, unless an 'exports'
359
+ // object exists, in that case we assume we're in CommonJS
360
+ if ( typeof exports === "undefined" || typeof require === "undefined" ) {
361
+ extend(window, QUnit);
362
+ window.QUnit = QUnit;
363
+ } else {
364
+ extend(exports, QUnit);
365
+ exports.QUnit = QUnit;
366
+ }
367
+
368
+ // define these after exposing globals to keep them in these QUnit namespace only
369
+ extend(QUnit, {
370
+ config: config,
371
+
372
+ // Initialize the configuration options
373
+ init: function() {
374
+ extend(config, {
375
+ stats: { all: 0, bad: 0 },
376
+ moduleStats: { all: 0, bad: 0 },
377
+ started: +new Date,
378
+ updateRate: 1000,
379
+ blocking: false,
380
+ autostart: true,
381
+ autorun: false,
382
+ assertions: [],
383
+ filters: [],
384
+ queue: []
385
+ });
386
+
387
+ var tests = id("qunit-tests"),
388
+ banner = id("qunit-banner"),
389
+ result = id("qunit-testresult");
390
+
391
+ if ( tests ) {
392
+ tests.innerHTML = "";
393
+ }
394
+
395
+ if ( banner ) {
396
+ banner.className = "";
397
+ }
398
+
399
+ if ( result ) {
400
+ result.parentNode.removeChild( result );
401
+ }
402
+ },
403
+
404
+ /**
405
+ * Resets the test setup. Useful for tests that modify the DOM.
406
+ */
407
+ reset: function() {
408
+ if ( window.jQuery ) {
409
+ jQuery("#main, #qunit-fixture").html( config.fixture );
410
+ }
411
+ },
412
+
413
+ /**
414
+ * Trigger an event on an element.
415
+ *
416
+ * @example triggerEvent( document.body, "click" );
417
+ *
418
+ * @param DOMElement elem
419
+ * @param String type
420
+ */
421
+ triggerEvent: function( elem, type, event ) {
422
+ if ( document.createEvent ) {
423
+ event = document.createEvent("MouseEvents");
424
+ event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
425
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
426
+ elem.dispatchEvent( event );
427
+
428
+ } else if ( elem.fireEvent ) {
429
+ elem.fireEvent("on"+type);
430
+ }
431
+ },
432
+
433
+ // Safe object type checking
434
+ is: function( type, obj ) {
435
+ return QUnit.objectType( obj ) == type;
436
+ },
437
+
438
+ objectType: function( obj ) {
439
+ if (typeof obj === "undefined") {
440
+ return "undefined";
441
+
442
+ // consider: typeof null === object
443
+ }
444
+ if (obj === null) {
445
+ return "null";
446
+ }
447
+
448
+ var type = Object.prototype.toString.call( obj )
449
+ .match(/^\[object\s(.*)\]$/)[1] || '';
450
+
451
+ switch (type) {
452
+ case 'Number':
453
+ if (isNaN(obj)) {
454
+ return "nan";
455
+ } else {
456
+ return "number";
457
+ }
458
+ case 'String':
459
+ case 'Boolean':
460
+ case 'Array':
461
+ case 'Date':
462
+ case 'RegExp':
463
+ case 'Function':
464
+ return type.toLowerCase();
465
+ }
466
+ if (typeof obj === "object") {
467
+ return "object";
468
+ }
469
+ return undefined;
470
+ },
471
+
472
+ // Logging callbacks
473
+ begin: function() {},
474
+ done: function(failures, total) {},
475
+ log: function(result, message) {},
476
+ testStart: function(name, testEnvironment) {},
477
+ testDone: function(name, failures, total) {},
478
+ moduleStart: function(name, testEnvironment) {},
479
+ moduleDone: function(name, failures, total) {}
480
+ });
481
+
482
+ if ( typeof document === "undefined" || document.readyState === "complete" ) {
483
+ config.autorun = true;
484
+ }
485
+
486
+ addEvent(window, "load", function() {
487
+ QUnit.begin();
488
+
489
+ // Initialize the config, saving the execution queue
490
+ var oldconfig = extend({}, config);
491
+ QUnit.init();
492
+ extend(config, oldconfig);
493
+
494
+ config.blocking = false;
495
+
496
+ var userAgent = id("qunit-userAgent");
497
+ if ( userAgent ) {
498
+ userAgent.innerHTML = navigator.userAgent;
499
+ }
500
+ var banner = id("qunit-header");
501
+ if ( banner ) {
502
+ banner.innerHTML = '<a href="' + location.href + '">' + banner.innerHTML + '</a>';
503
+ }
504
+
505
+ var toolbar = id("qunit-testrunner-toolbar");
506
+ if ( toolbar ) {
507
+ toolbar.style.display = "none";
508
+
509
+ var filter = document.createElement("input");
510
+ filter.type = "checkbox";
511
+ filter.id = "qunit-filter-pass";
512
+ filter.disabled = true;
513
+ addEvent( filter, "click", function() {
514
+ var li = document.getElementsByTagName("li");
515
+ for ( var i = 0; i < li.length; i++ ) {
516
+ if ( li[i].className.indexOf("pass") > -1 ) {
517
+ li[i].style.display = filter.checked ? "none" : "";
518
+ }
519
+ }
520
+ });
521
+ toolbar.appendChild( filter );
522
+
523
+ var label = document.createElement("label");
524
+ label.setAttribute("for", "qunit-filter-pass");
525
+ label.innerHTML = "Hide passed tests";
526
+ toolbar.appendChild( label );
527
+
528
+ var missing = document.createElement("input");
529
+ missing.type = "checkbox";
530
+ missing.id = "qunit-filter-missing";
531
+ missing.disabled = true;
532
+ addEvent( missing, "click", function() {
533
+ var li = document.getElementsByTagName("li");
534
+ for ( var i = 0; i < li.length; i++ ) {
535
+ if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
536
+ li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
537
+ }
538
+ }
539
+ });
540
+ toolbar.appendChild( missing );
541
+
542
+ label = document.createElement("label");
543
+ label.setAttribute("for", "qunit-filter-missing");
544
+ label.innerHTML = "Hide missing tests (untested code is broken code)";
545
+ toolbar.appendChild( label );
546
+ }
547
+
548
+ var main = id('main') || id('qunit-fixture');
549
+ if ( main ) {
550
+ config.fixture = main.innerHTML;
551
+ }
552
+
553
+ if (config.autostart) {
554
+ QUnit.start();
555
+ }
556
+ });
557
+
558
+ function done() {
559
+ if ( config.doneTimer && window.clearTimeout ) {
560
+ window.clearTimeout( config.doneTimer );
561
+ config.doneTimer = null;
562
+ }
563
+
564
+ if ( config.queue.length ) {
565
+ config.doneTimer = window.setTimeout(function(){
566
+ if ( !config.queue.length ) {
567
+ done();
568
+ } else {
569
+ synchronize( done );
570
+ }
571
+ }, 13);
572
+
573
+ return;
574
+ }
575
+
576
+ config.autorun = true;
577
+
578
+ // Log the last module results
579
+ if ( config.currentModule ) {
580
+ QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
581
+ }
582
+
583
+ var banner = id("qunit-banner"),
584
+ tests = id("qunit-tests"),
585
+ html = ['Tests completed in ',
586
+ +new Date - config.started, ' milliseconds.<br/>',
587
+ '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join('');
588
+
589
+ if ( banner ) {
590
+ banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
591
+ }
592
+
593
+ if ( tests ) {
594
+ var result = id("qunit-testresult");
595
+
596
+ if ( !result ) {
597
+ result = document.createElement("p");
598
+ result.id = "qunit-testresult";
599
+ result.className = "result";
600
+ tests.parentNode.insertBefore( result, tests.nextSibling );
601
+ }
602
+
603
+ result.innerHTML = html;
604
+ }
605
+
606
+ QUnit.done( config.stats.bad, config.stats.all );
607
+ }
608
+
609
+ function validTest( name ) {
610
+ var i = config.filters.length,
611
+ run = false;
612
+
613
+ if ( !i ) {
614
+ return true;
615
+ }
616
+
617
+ while ( i-- ) {
618
+ var filter = config.filters[i],
619
+ not = filter.charAt(0) == '!';
620
+
621
+ if ( not ) {
622
+ filter = filter.slice(1);
623
+ }
624
+
625
+ if ( name.indexOf(filter) !== -1 ) {
626
+ return !not;
627
+ }
628
+
629
+ if ( not ) {
630
+ run = true;
631
+ }
632
+ }
633
+
634
+ return run;
635
+ }
636
+
637
+ function escapeHtml(s) {
638
+ s = s === null ? "" : s + "";
639
+ return s.replace(/[\&"<>\\]/g, function(s) {
640
+ switch(s) {
641
+ case "&": return "&amp;";
642
+ case "\\": return "\\\\";
643
+ case '"': return '\"';
644
+ case "<": return "&lt;";
645
+ case ">": return "&gt;";
646
+ default: return s;
647
+ }
648
+ });
649
+ }
650
+
651
+ function push(result, actual, expected, message) {
652
+ message = escapeHtml(message) || (result ? "okay" : "failed");
653
+ message = '<span class="test-message">' + message + "</span>";
654
+ expected = escapeHtml(QUnit.jsDump.parse(expected));
655
+ actual = escapeHtml(QUnit.jsDump.parse(actual));
656
+ var output = message + ', expected: <span class="test-expected">' + expected + '</span>';
657
+ if (actual != expected) {
658
+ output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual);
659
+ }
660
+
661
+ // can't use ok, as that would double-escape messages
662
+ QUnit.log(result, output);
663
+ config.assertions.push({
664
+ result: !!result,
665
+ message: output
666
+ });
667
+ }
668
+
669
+ function synchronize( callback ) {
670
+ config.queue.push( callback );
671
+
672
+ if ( config.autorun && !config.blocking ) {
673
+ process();
674
+ }
675
+ }
676
+
677
+ function process() {
678
+ var start = (new Date()).getTime();
679
+
680
+ while ( config.queue.length && !config.blocking ) {
681
+ if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
682
+ config.queue.shift()();
683
+
684
+ } else {
685
+ setTimeout( process, 13 );
686
+ break;
687
+ }
688
+ }
689
+ }
690
+
691
+ function saveGlobal() {
692
+ config.pollution = [];
693
+
694
+ if ( config.noglobals ) {
695
+ for ( var key in window ) {
696
+ config.pollution.push( key );
697
+ }
698
+ }
699
+ }
700
+
701
+ function checkPollution( name ) {
702
+ var old = config.pollution;
703
+ saveGlobal();
704
+
705
+ var newGlobals = diff( old, config.pollution );
706
+ if ( newGlobals.length > 0 ) {
707
+ ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
708
+ config.expected++;
709
+ }
710
+
711
+ var deletedGlobals = diff( config.pollution, old );
712
+ if ( deletedGlobals.length > 0 ) {
713
+ ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
714
+ config.expected++;
715
+ }
716
+ }
717
+
718
+ // returns a new Array with the elements that are in a but not in b
719
+ function diff( a, b ) {
720
+ var result = a.slice();
721
+ for ( var i = 0; i < result.length; i++ ) {
722
+ for ( var j = 0; j < b.length; j++ ) {
723
+ if ( result[i] === b[j] ) {
724
+ result.splice(i, 1);
725
+ i--;
726
+ break;
727
+ }
728
+ }
729
+ }
730
+ return result;
731
+ }
732
+
733
+ function fail(message, exception, callback) {
734
+ if ( typeof console !== "undefined" && console.error && console.warn ) {
735
+ console.error(message);
736
+ console.error(exception);
737
+ console.warn(callback.toString());
738
+
739
+ } else if ( window.opera && opera.postError ) {
740
+ opera.postError(message, exception, callback.toString);
741
+ }
742
+ }
743
+
744
+ function extend(a, b) {
745
+ for ( var prop in b ) {
746
+ a[prop] = b[prop];
747
+ }
748
+
749
+ return a;
750
+ }
751
+
752
+ function addEvent(elem, type, fn) {
753
+ if ( elem.addEventListener ) {
754
+ elem.addEventListener( type, fn, false );
755
+ } else if ( elem.attachEvent ) {
756
+ elem.attachEvent( "on" + type, fn );
757
+ } else {
758
+ fn();
759
+ }
760
+ }
761
+
762
+ function id(name) {
763
+ return !!(typeof document !== "undefined" && document && document.getElementById) &&
764
+ document.getElementById( name );
765
+ }
766
+
767
+ // Test for equality any JavaScript type.
768
+ // Discussions and reference: http://philrathe.com/articles/equiv
769
+ // Test suites: http://philrathe.com/tests/equiv
770
+ // Author: Philippe Rathé <prathe@gmail.com>
771
+ QUnit.equiv = function () {
772
+
773
+ var innerEquiv; // the real equiv function
774
+ var callers = []; // stack to decide between skip/abort functions
775
+ var parents = []; // stack to avoiding loops from circular referencing
776
+
777
+ // Call the o related callback with the given arguments.
778
+ function bindCallbacks(o, callbacks, args) {
779
+ var prop = QUnit.objectType(o);
780
+ if (prop) {
781
+ if (QUnit.objectType(callbacks[prop]) === "function") {
782
+ return callbacks[prop].apply(callbacks, args);
783
+ } else {
784
+ return callbacks[prop]; // or undefined
785
+ }
786
+ }
787
+ }
788
+
789
+ var callbacks = function () {
790
+
791
+ // for string, boolean, number and null
792
+ function useStrictEquality(b, a) {
793
+ if (b instanceof a.constructor || a instanceof b.constructor) {
794
+ // to catch short annotaion VS 'new' annotation of a declaration
795
+ // e.g. var i = 1;
796
+ // var j = new Number(1);
797
+ return a == b;
798
+ } else {
799
+ return a === b;
800
+ }
801
+ }
802
+
803
+ return {
804
+ "string": useStrictEquality,
805
+ "boolean": useStrictEquality,
806
+ "number": useStrictEquality,
807
+ "null": useStrictEquality,
808
+ "undefined": useStrictEquality,
809
+
810
+ "nan": function (b) {
811
+ return isNaN(b);
812
+ },
813
+
814
+ "date": function (b, a) {
815
+ return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
816
+ },
817
+
818
+ "regexp": function (b, a) {
819
+ return QUnit.objectType(b) === "regexp" &&
820
+ a.source === b.source && // the regex itself
821
+ a.global === b.global && // and its modifers (gmi) ...
822
+ a.ignoreCase === b.ignoreCase &&
823
+ a.multiline === b.multiline;
824
+ },
825
+
826
+ // - skip when the property is a method of an instance (OOP)
827
+ // - abort otherwise,
828
+ // initial === would have catch identical references anyway
829
+ "function": function () {
830
+ var caller = callers[callers.length - 1];
831
+ return caller !== Object &&
832
+ typeof caller !== "undefined";
833
+ },
834
+
835
+ "array": function (b, a) {
836
+ var i, j, loop;
837
+ var len;
838
+
839
+ // b could be an object literal here
840
+ if ( ! (QUnit.objectType(b) === "array")) {
841
+ return false;
842
+ }
843
+
844
+ len = a.length;
845
+ if (len !== b.length) { // safe and faster
846
+ return false;
847
+ }
848
+
849
+ //track reference to avoid circular references
850
+ parents.push(a);
851
+ for (i = 0; i < len; i++) {
852
+ loop = false;
853
+ for(j=0;j<parents.length;j++){
854
+ if(parents[j] === a[i]){
855
+ loop = true;//dont rewalk array
856
+ }
857
+ }
858
+ if (!loop && ! innerEquiv(a[i], b[i])) {
859
+ parents.pop();
860
+ return false;
861
+ }
862
+ }
863
+ parents.pop();
864
+ return true;
865
+ },
866
+
867
+ "object": function (b, a) {
868
+ var i, j, loop;
869
+ var eq = true; // unless we can proove it
870
+ var aProperties = [], bProperties = []; // collection of strings
871
+
872
+ // comparing constructors is more strict than using instanceof
873
+ if ( a.constructor !== b.constructor) {
874
+ return false;
875
+ }
876
+
877
+ // stack constructor before traversing properties
878
+ callers.push(a.constructor);
879
+ //track reference to avoid circular references
880
+ parents.push(a);
881
+
882
+ for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
883
+ loop = false;
884
+ for(j=0;j<parents.length;j++){
885
+ if(parents[j] === a[i])
886
+ loop = true; //don't go down the same path twice
887
+ }
888
+ aProperties.push(i); // collect a's properties
889
+
890
+ if (!loop && ! innerEquiv(a[i], b[i])) {
891
+ eq = false;
892
+ break;
893
+ }
894
+ }
895
+
896
+ callers.pop(); // unstack, we are done
897
+ parents.pop();
898
+
899
+ for (i in b) {
900
+ bProperties.push(i); // collect b's properties
901
+ }
902
+
903
+ // Ensures identical properties name
904
+ return eq && innerEquiv(aProperties.sort(), bProperties.sort());
905
+ }
906
+ };
907
+ }();
908
+
909
+ innerEquiv = function () { // can take multiple arguments
910
+ var args = Array.prototype.slice.apply(arguments);
911
+ if (args.length < 2) {
912
+ return true; // end transition
913
+ }
914
+
915
+ return (function (a, b) {
916
+ if (a === b) {
917
+ return true; // catch the most you can
918
+ } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
919
+ return false; // don't lose time with error prone cases
920
+ } else {
921
+ return bindCallbacks(a, callbacks, [b, a]);
922
+ }
923
+
924
+ // apply transition with (1..n) arguments
925
+ })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
926
+ };
927
+
928
+ return innerEquiv;
929
+
930
+ }();
931
+
932
+ /**
933
+ * jsDump
934
+ * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
935
+ * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
936
+ * Date: 5/15/2008
937
+ * @projectDescription Advanced and extensible data dumping for Javascript.
938
+ * @version 1.0.0
939
+ * @author Ariel Flesler
940
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
941
+ */
942
+ QUnit.jsDump = (function() {
943
+ function quote( str ) {
944
+ return '"' + str.toString().replace(/"/g, '\\"') + '"';
945
+ };
946
+ function literal( o ) {
947
+ return o + '';
948
+ };
949
+ function join( pre, arr, post ) {
950
+ var s = jsDump.separator(),
951
+ base = jsDump.indent(),
952
+ inner = jsDump.indent(1);
953
+ if ( arr.join )
954
+ arr = arr.join( ',' + s + inner );
955
+ if ( !arr )
956
+ return pre + post;
957
+ return [ pre, inner + arr, base + post ].join(s);
958
+ };
959
+ function array( arr ) {
960
+ var i = arr.length, ret = Array(i);
961
+ this.up();
962
+ while ( i-- )
963
+ ret[i] = this.parse( arr[i] );
964
+ this.down();
965
+ return join( '[', ret, ']' );
966
+ };
967
+
968
+ var reName = /^function (\w+)/;
969
+
970
+ var jsDump = {
971
+ parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
972
+ var parser = this.parsers[ type || this.typeOf(obj) ];
973
+ type = typeof parser;
974
+
975
+ return type == 'function' ? parser.call( this, obj ) :
976
+ type == 'string' ? parser :
977
+ this.parsers.error;
978
+ },
979
+ typeOf:function( obj ) {
980
+ var type;
981
+ if ( obj === null ) {
982
+ type = "null";
983
+ } else if (typeof obj === "undefined") {
984
+ type = "undefined";
985
+ } else if (QUnit.is("RegExp", obj)) {
986
+ type = "regexp";
987
+ } else if (QUnit.is("Date", obj)) {
988
+ type = "date";
989
+ } else if (QUnit.is("Function", obj)) {
990
+ type = "function";
991
+ } else if (obj.setInterval && obj.document && !obj.nodeType) {
992
+ type = "window";
993
+ } else if (obj.nodeType === 9) {
994
+ type = "document";
995
+ } else if (obj.nodeType) {
996
+ type = "node";
997
+ } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
998
+ type = "array";
999
+ } else {
1000
+ type = typeof obj;
1001
+ }
1002
+ return type;
1003
+ },
1004
+ separator:function() {
1005
+ return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
1006
+ },
1007
+ indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1008
+ if ( !this.multiline )
1009
+ return '';
1010
+ var chr = this.indentChar;
1011
+ if ( this.HTML )
1012
+ chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
1013
+ return Array( this._depth_ + (extra||0) ).join(chr);
1014
+ },
1015
+ up:function( a ) {
1016
+ this._depth_ += a || 1;
1017
+ },
1018
+ down:function( a ) {
1019
+ this._depth_ -= a || 1;
1020
+ },
1021
+ setParser:function( name, parser ) {
1022
+ this.parsers[name] = parser;
1023
+ },
1024
+ // The next 3 are exposed so you can use them
1025
+ quote:quote,
1026
+ literal:literal,
1027
+ join:join,
1028
+ //
1029
+ _depth_: 1,
1030
+ // This is the list of parsers, to modify them, use jsDump.setParser
1031
+ parsers:{
1032
+ window: '[Window]',
1033
+ document: '[Document]',
1034
+ error:'[ERROR]', //when no parser is found, shouldn't happen
1035
+ unknown: '[Unknown]',
1036
+ 'null':'null',
1037
+ undefined:'undefined',
1038
+ 'function':function( fn ) {
1039
+ var ret = 'function',
1040
+ name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1041
+ if ( name )
1042
+ ret += ' ' + name;
1043
+ ret += '(';
1044
+
1045
+ ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
1046
+ return join( ret, this.parse(fn,'functionCode'), '}' );
1047
+ },
1048
+ array: array,
1049
+ nodelist: array,
1050
+ arguments: array,
1051
+ object:function( map ) {
1052
+ var ret = [ ];
1053
+ this.up();
1054
+ for ( var key in map )
1055
+ ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
1056
+ this.down();
1057
+ return join( '{', ret, '}' );
1058
+ },
1059
+ node:function( node ) {
1060
+ var open = this.HTML ? '&lt;' : '<',
1061
+ close = this.HTML ? '&gt;' : '>';
1062
+
1063
+ var tag = node.nodeName.toLowerCase(),
1064
+ ret = open + tag;
1065
+
1066
+ for ( var a in this.DOMAttrs ) {
1067
+ var val = node[this.DOMAttrs[a]];
1068
+ if ( val )
1069
+ ret += ' ' + a + '=' + this.parse( val, 'attribute' );
1070
+ }
1071
+ return ret + close + open + '/' + tag + close;
1072
+ },
1073
+ functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1074
+ var l = fn.length;
1075
+ if ( !l ) return '';
1076
+
1077
+ var args = Array(l);
1078
+ while ( l-- )
1079
+ args[l] = String.fromCharCode(97+l);//97 is 'a'
1080
+ return ' ' + args.join(', ') + ' ';
1081
+ },
1082
+ key:quote, //object calls it internally, the key part of an item in a map
1083
+ functionCode:'[code]', //function calls it internally, it's the content of the function
1084
+ attribute:quote, //node calls it internally, it's an html attribute value
1085
+ string:quote,
1086
+ date:quote,
1087
+ regexp:literal, //regex
1088
+ number:literal,
1089
+ 'boolean':literal
1090
+ },
1091
+ DOMAttrs:{//attributes to dump from nodes, name=>realName
1092
+ id:'id',
1093
+ name:'name',
1094
+ 'class':'className'
1095
+ },
1096
+ HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1097
+ indentChar:' ',//indentation unit
1098
+ multiline:false //if true, items in a collection, are separated by a \n, else just a space.
1099
+ };
1100
+
1101
+ return jsDump;
1102
+ })();
1103
+
1104
+ // from Sizzle.js
1105
+ function getText( elems ) {
1106
+ var ret = "", elem;
1107
+
1108
+ for ( var i = 0; elems[i]; i++ ) {
1109
+ elem = elems[i];
1110
+
1111
+ // Get the text from text nodes and CDATA nodes
1112
+ if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1113
+ ret += elem.nodeValue;
1114
+
1115
+ // Traverse everything else, except comment nodes
1116
+ } else if ( elem.nodeType !== 8 ) {
1117
+ ret += getText( elem.childNodes );
1118
+ }
1119
+ }
1120
+
1121
+ return ret;
1122
+ };
1123
+
1124
+ /*
1125
+ * Javascript Diff Algorithm
1126
+ * By John Resig (http://ejohn.org/)
1127
+ * Modified by Chu Alan "sprite"
1128
+ *
1129
+ * Released under the MIT license.
1130
+ *
1131
+ * More Info:
1132
+ * http://ejohn.org/projects/javascript-diff-algorithm/
1133
+ *
1134
+ * Usage: QUnit.diff(expected, actual)
1135
+ *
1136
+ * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
1137
+ */
1138
+ QUnit.diff = (function() {
1139
+ function diff(o, n){
1140
+ var ns = new Object();
1141
+ var os = new Object();
1142
+
1143
+ for (var i = 0; i < n.length; i++) {
1144
+ if (ns[n[i]] == null)
1145
+ ns[n[i]] = {
1146
+ rows: new Array(),
1147
+ o: null
1148
+ };
1149
+ ns[n[i]].rows.push(i);
1150
+ }
1151
+
1152
+ for (var i = 0; i < o.length; i++) {
1153
+ if (os[o[i]] == null)
1154
+ os[o[i]] = {
1155
+ rows: new Array(),
1156
+ n: null
1157
+ };
1158
+ os[o[i]].rows.push(i);
1159
+ }
1160
+
1161
+ for (var i in ns) {
1162
+ if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1163
+ n[ns[i].rows[0]] = {
1164
+ text: n[ns[i].rows[0]],
1165
+ row: os[i].rows[0]
1166
+ };
1167
+ o[os[i].rows[0]] = {
1168
+ text: o[os[i].rows[0]],
1169
+ row: ns[i].rows[0]
1170
+ };
1171
+ }
1172
+ }
1173
+
1174
+ for (var i = 0; i < n.length - 1; i++) {
1175
+ if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1176
+ n[i + 1] == o[n[i].row + 1]) {
1177
+ n[i + 1] = {
1178
+ text: n[i + 1],
1179
+ row: n[i].row + 1
1180
+ };
1181
+ o[n[i].row + 1] = {
1182
+ text: o[n[i].row + 1],
1183
+ row: i + 1
1184
+ };
1185
+ }
1186
+ }
1187
+
1188
+ for (var i = n.length - 1; i > 0; i--) {
1189
+ if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1190
+ n[i - 1] == o[n[i].row - 1]) {
1191
+ n[i - 1] = {
1192
+ text: n[i - 1],
1193
+ row: n[i].row - 1
1194
+ };
1195
+ o[n[i].row - 1] = {
1196
+ text: o[n[i].row - 1],
1197
+ row: i - 1
1198
+ };
1199
+ }
1200
+ }
1201
+
1202
+ return {
1203
+ o: o,
1204
+ n: n
1205
+ };
1206
+ }
1207
+
1208
+ return function(o, n){
1209
+ o = o.replace(/\s+$/, '');
1210
+ n = n.replace(/\s+$/, '');
1211
+ var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1212
+
1213
+ var str = "";
1214
+
1215
+ var oSpace = o.match(/\s+/g);
1216
+ if (oSpace == null) {
1217
+ oSpace = [" "];
1218
+ }
1219
+ else {
1220
+ oSpace.push(" ");
1221
+ }
1222
+ var nSpace = n.match(/\s+/g);
1223
+ if (nSpace == null) {
1224
+ nSpace = [" "];
1225
+ }
1226
+ else {
1227
+ nSpace.push(" ");
1228
+ }
1229
+
1230
+ if (out.n.length == 0) {
1231
+ for (var i = 0; i < out.o.length; i++) {
1232
+ str += '<del>' + out.o[i] + oSpace[i] + "</del>";
1233
+ }
1234
+ }
1235
+ else {
1236
+ if (out.n[0].text == null) {
1237
+ for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1238
+ str += '<del>' + out.o[n] + oSpace[n] + "</del>";
1239
+ }
1240
+ }
1241
+
1242
+ for (var i = 0; i < out.n.length; i++) {
1243
+ if (out.n[i].text == null) {
1244
+ str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
1245
+ }
1246
+ else {
1247
+ var pre = "";
1248
+
1249
+ for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1250
+ pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
1251
+ }
1252
+ str += " " + out.n[i].text + nSpace[i] + pre;
1253
+ }
1254
+ }
1255
+ }
1256
+
1257
+ return str;
1258
+ }
1259
+ })();
1260
+
1261
+ })(this);