terminus 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/bin/terminus +5 -5
  2. data/lib/capybara/driver/terminus.rb +24 -13
  3. data/lib/terminus.rb +21 -15
  4. data/lib/terminus/application.rb +6 -6
  5. data/lib/terminus/browser.rb +77 -60
  6. data/lib/terminus/client.rb +33 -16
  7. data/lib/terminus/client/browser.rb +10 -9
  8. data/lib/terminus/client/phantom.js +25 -3
  9. data/lib/terminus/client/phantomjs.rb +44 -7
  10. data/lib/terminus/connector.rb +2 -2
  11. data/lib/terminus/connector/server.rb +15 -15
  12. data/lib/terminus/connector/socket_handler.rb +11 -11
  13. data/lib/terminus/controller.rb +62 -26
  14. data/lib/terminus/headers.rb +25 -0
  15. data/lib/terminus/host.rb +6 -6
  16. data/lib/terminus/node.rb +36 -22
  17. data/lib/terminus/proxy.rb +81 -45
  18. data/lib/terminus/proxy/driver_body.rb +14 -15
  19. data/lib/terminus/proxy/external.rb +12 -6
  20. data/lib/terminus/proxy/rewrite.rb +7 -6
  21. data/lib/terminus/public/compiled/terminus-min.js +3 -3
  22. data/lib/terminus/public/compiled/terminus.js +225 -180
  23. data/lib/terminus/public/pathology.js +87 -87
  24. data/lib/terminus/public/terminus.js +138 -93
  25. data/lib/terminus/server.rb +7 -7
  26. data/lib/terminus/timeouts.rb +8 -8
  27. data/lib/terminus/views/bootstrap.erb +7 -7
  28. data/lib/terminus/views/index.erb +4 -4
  29. data/lib/terminus/views/infinite.html +4 -4
  30. data/spec/1.1/reports/android.txt +874 -0
  31. data/spec/{reports → 1.1/reports}/chrome.txt +72 -69
  32. data/spec/{reports → 1.1/reports}/firefox.txt +72 -69
  33. data/spec/{reports → 1.1/reports}/opera.txt +72 -69
  34. data/spec/{reports → 1.1/reports}/phantomjs.txt +72 -69
  35. data/spec/{reports → 1.1/reports}/safari.txt +72 -69
  36. data/spec/{spec_helper.rb → 1.1/spec_helper.rb} +5 -2
  37. data/spec/{terminus_driver_spec.rb → 1.1/terminus_driver_spec.rb} +2 -2
  38. data/spec/{terminus_session_spec.rb → 1.1/terminus_session_spec.rb} +2 -2
  39. data/spec/2.0/reports/android.txt +815 -0
  40. data/spec/2.0/reports/chrome.txt +806 -0
  41. data/spec/2.0/reports/firefox.txt +812 -0
  42. data/spec/2.0/reports/opera.txt +806 -0
  43. data/spec/2.0/reports/phantomjs.txt +803 -0
  44. data/spec/2.0/reports/safari.txt +806 -0
  45. data/spec/2.0/spec_helper.rb +21 -0
  46. data/spec/2.0/terminus_spec.rb +25 -0
  47. metadata +41 -32
  48. data/spec/reports/android.txt +0 -875
@@ -1,28 +1,28 @@
1
1
  Terminus = {
2
2
  isIE: /\bMSIE\b/.test(navigator.userAgent),
3
-
3
+
4
4
  connect: function(host, port) {
5
5
  if (this._bayeux) return;
6
-
6
+
7
7
  this._host = host;
8
8
  this._pageId = Faye.random();
9
9
  this._id = window.name = window.name || document.name || Faye.random();
10
10
  this._id = this._id.split('|')[0];
11
-
11
+
12
12
  var iframes = document.getElementsByTagName('iframe'), i = iframes.length;
13
13
  while (i--)
14
14
  iframes[i].contentDocument.name = iframes[i].id;
15
-
15
+
16
16
  this.Registry.initialize();
17
17
  this.Worker.initialize();
18
18
  this.AjaxMonitor.initialize();
19
-
19
+
20
20
  Faye.Event.on(window, 'beforeunload', function() { Terminus.disabled = true });
21
-
21
+
22
22
  var endpoint = 'http://' + host + ':' + port + '/messaging',
23
23
  bayeux = this._bayeux = new Faye.Client(endpoint),
24
24
  self = this;
25
-
25
+
26
26
  bayeux.addExtension({
27
27
  outgoing: function(message, callback) {
28
28
  message.href = window.location.href;
@@ -30,16 +30,16 @@ Terminus = {
30
30
  callback(message);
31
31
  }
32
32
  });
33
-
33
+
34
34
  this.getId(function(id) {
35
35
  var url = window.name.split('|')[1];
36
-
36
+
37
37
  if (!url)
38
38
  bayeux.subscribe('/terminus/sockets/' + id, function(message) {
39
39
  window.name += '|' + message.url;
40
40
  this.openSocket(message.url);
41
41
  }, this);
42
-
42
+
43
43
  var sub = bayeux.subscribe('/terminus/clients/' + id, this.handleMessage, this);
44
44
  sub.callback(function() {
45
45
  this.ping();
@@ -47,7 +47,7 @@ Terminus = {
47
47
  }, this);
48
48
  }, this);
49
49
  },
50
-
50
+
51
51
  browserDetails: function(callback, context) {
52
52
  this.getId(function(id) {
53
53
  callback.call(context, {
@@ -61,11 +61,11 @@ Terminus = {
61
61
  });
62
62
  }, this);
63
63
  },
64
-
64
+
65
65
  getId: function(callback, context) {
66
66
  var id = this._id;
67
67
  if (this.isIE) return callback.call(context, id);
68
-
68
+
69
69
  if (opener && opener.Terminus) {
70
70
  opener.Terminus.getId(function(prefix) {
71
71
  callback.call(context, prefix + '/' + id);
@@ -82,14 +82,14 @@ Terminus = {
82
82
  callback.call(context, id);
83
83
  }
84
84
  },
85
-
85
+
86
86
  openSocket: function(endpoint) {
87
87
  if (this.disabled || this._socket) return;
88
-
88
+
89
89
  var self = this,
90
90
  WS = window.MozWebSocket || window.WebSocket,
91
91
  ws = new WS(endpoint);
92
-
92
+
93
93
  ws.onopen = function() {
94
94
  self._socket = ws;
95
95
  up = true;
@@ -106,17 +106,17 @@ Terminus = {
106
106
  self.handleMessage(JSON.parse(event.data));
107
107
  };
108
108
  },
109
-
109
+
110
110
  ping: function() {
111
111
  if (this.disabled) return;
112
-
112
+
113
113
  this.browserDetails(function(details) {
114
114
  this._bayeux.publish('/terminus/ping', details);
115
115
  var self = this;
116
116
  setTimeout(function() { self.ping() }, 3000);
117
117
  }, this);
118
118
  },
119
-
119
+
120
120
  handleMessage: function(message) {
121
121
  var command = message.command,
122
122
  method = command.shift(),
@@ -124,24 +124,24 @@ Terminus = {
124
124
  worker = this.Worker,
125
125
  posted = false,
126
126
  self = this;
127
-
127
+
128
128
  command.push(function(result) {
129
129
  if (posted) return;
130
130
  self.postResult(message.commandId, result);
131
131
  posted = true;
132
132
  });
133
-
133
+
134
134
  worker.monitor = true;
135
135
  driver[method].apply(driver, command);
136
136
  worker.monitor = false;
137
137
  },
138
-
138
+
139
139
  postResult: function(commandId, result) {
140
140
  if (this.disabled || !commandId) return;
141
-
141
+
142
142
  if (this._socket)
143
143
  return this._socket.send(JSON.stringify({value: result}));
144
-
144
+
145
145
  this.getId(function(id) {
146
146
  this._bayeux.publish('/terminus/results', {
147
147
  id: id,
@@ -150,118 +150,154 @@ Terminus = {
150
150
  });
151
151
  }, this);
152
152
  },
153
-
153
+
154
154
  getAttribute: function(node, name) {
155
155
  return Terminus.isIE ? (node.getAttributeNode(name) || {}).nodeValue || false
156
156
  : node.getAttribute(name);
157
157
  },
158
-
158
+
159
+ hideNodes: function(root, list) {
160
+ if (!root) return list;
161
+ list = list || [];
162
+
163
+ var isScript = (root.tagName || '').toLowerCase() === 'script',
164
+ isHidden = (root.style || {}).display === 'none';
165
+
166
+ if (isScript || isHidden) {
167
+ var parent = root.parentNode, next = root.nextSibling;
168
+ if (!isScript) list.push([root, parent, next]);
169
+ if (parent) parent.removeChild(root);
170
+ } else {
171
+ var children = root.childNodes || [];
172
+ for (var i = 0, n = children.length; i < n; i++) {
173
+ this.hideNodes(children[i], list);
174
+ }
175
+ }
176
+ return list;
177
+ },
178
+
179
+ showNodes: function(hidden) {
180
+ var hide, node, parent, next;
181
+ for (var i = 0, n = hidden.length; i < n; i++) {
182
+ hide = hidden[i];
183
+ node = hide[0];
184
+ parent = hide[1];
185
+ next = hide[2];
186
+
187
+ if (!parent) continue;
188
+ if (next) parent.insertBefore(node, next);
189
+ else parent.appendChild(node);
190
+ }
191
+ },
192
+
159
193
  Driver: {
160
194
  _node: function(id) {
161
195
  return Terminus.Registry.get(id);
162
196
  },
163
-
197
+
164
198
  attribute: function(nodeId, name, callback) {
165
199
  var node = this._node(nodeId);
166
200
  if (!node) return callback(null);
167
-
201
+
168
202
  if (!Terminus.isIE && (name === 'checked' || name === 'selected')) {
169
203
  callback(!!node[name]);
204
+ } else if (node.tagName.toLowerCase() === 'textarea' && name === 'type') {
205
+ callback('textarea');
170
206
  } else {
171
207
  callback(Terminus.getAttribute(node, name));
172
208
  }
173
209
  },
174
-
210
+
175
211
  set_attribute: function(nodeId, name, value, callback) {
176
212
  var node = this._node(nodeId);
177
213
  if (!node) return callback(null);
178
214
  node.setAttribute(name, value);
179
215
  callback(true);
180
216
  },
181
-
217
+
182
218
  body: function(callback) {
183
219
  var html = document.getElementsByTagName('html')[0];
184
220
  callback(html.outerHTML ||
185
221
  '<html>\n' + html.innerHTML + '\n</html>\n');
186
222
  },
187
-
223
+
188
224
  clear_cookies: function(callback) {
189
225
  var cookies = document.cookie.split(';'), name;
190
-
226
+
191
227
  var expiry = new Date();
192
228
  expiry.setTime(expiry.getTime() - 24*60*60*1000);
193
-
229
+
194
230
  for (var i = 0, n = cookies.length; i < n; i++) {
195
231
  name = cookies[i].split('=')[0];
196
232
  document.cookie = name + '=; expires=' + expiry.toGMTString() + '; path=/';
197
233
  }
198
234
  callback(true);
199
235
  },
200
-
236
+
201
237
  click: function(nodeId, options, callback) {
202
238
  var element = this._node(nodeId),
203
239
  timeout = options.resynchronization_timeout;
204
-
240
+
205
241
  if (!element) return callback(true);
206
-
242
+
207
243
  Syn.trigger('click', {}, element);
208
-
244
+
209
245
  if (options.resynchronize === false) return callback(true);
210
-
246
+
211
247
  if (timeout)
212
248
  Terminus.Worker._setTimeout.call(window, function() {
213
249
  callback('failed to resynchronize, ajax request timed out');
214
250
  }, 1000 * timeout);
215
-
251
+
216
252
  Terminus.Worker.callback(function() {
217
253
  callback(true);
218
254
  });
219
255
  },
220
-
256
+
221
257
  current_url: function(callback) {
222
258
  Terminus.browserDetails(function(details) {
223
259
  callback(details.url);
224
260
  });
225
261
  },
226
-
262
+
227
263
  drag: function(options, callback) {
228
264
  var draggable = this._node(options.from),
229
265
  droppable = this._node(options.to);
230
-
266
+
231
267
  if (!draggable || !droppable) return callback(null);
232
-
268
+
233
269
  Syn.drag({to: droppable}, draggable, function() {
234
270
  callback(true);
235
271
  });
236
272
  },
237
-
273
+
238
274
  evaluate: function(expression, callback) {
239
275
  callback(eval(expression));
240
276
  },
241
-
277
+
242
278
  execute: function(expression, callback) {
243
279
  eval(expression);
244
280
  callback(true);
245
281
  },
246
-
282
+
247
283
  find: function(xpath, nodeId, callback) {
248
284
  var root = nodeId ? this._node(nodeId) : document;
249
285
  if (!root) return callback([]);
250
-
286
+
251
287
  var result = document.evaluate(xpath, root, null, XPathResult.ANY_TYPE, null),
252
288
  list = [],
253
289
  element;
254
-
290
+
255
291
  while (element = result.iterateNext())
256
292
  list.push(Terminus.Registry.put(element));
257
-
293
+
258
294
  return callback(list);
259
295
  },
260
-
296
+
261
297
  is_visible: function(nodeId, callback) {
262
298
  var node = this._node(nodeId);
263
299
  if (!node) return callback(null);
264
-
300
+
265
301
  while (node.tagName && node.tagName.toLowerCase() !== 'body') {
266
302
  if (node.style.display === 'none' || node.type === 'hidden')
267
303
  return callback(false);
@@ -269,7 +305,7 @@ Terminus = {
269
305
  }
270
306
  callback(true);
271
307
  },
272
-
308
+
273
309
  select: function(nodeId, callback) {
274
310
  var option = this._node(nodeId);
275
311
  if (!option) return callback(null);
@@ -277,17 +313,17 @@ Terminus = {
277
313
  Syn.trigger('change', {}, option.parentNode);
278
314
  callback(true);
279
315
  },
280
-
316
+
281
317
  set: function(nodeId, value, callback) {
282
318
  var field = this._node(nodeId),
283
319
  max = Terminus.getAttribute(field, 'maxlength');
284
-
320
+
285
321
  if (!field) return callback(null);
286
322
  if (field.type === 'file') return callback('not_allowed');
287
-
323
+
288
324
  Syn.trigger('focus', {}, field);
289
325
  Syn.trigger('click', {}, field);
290
-
326
+
291
327
  switch (typeof value) {
292
328
  case 'string':
293
329
  if (max) value = value.substr(0, parseInt(max));
@@ -300,33 +336,38 @@ Terminus = {
300
336
  Syn.trigger('change', {}, field);
301
337
  callback(true);
302
338
  },
303
-
339
+
304
340
  tag_name: function(nodeId, callback) {
305
341
  var node = this._node(nodeId);
306
342
  if (!node) return callback(null);
307
343
  callback(node.tagName.toLowerCase());
308
344
  },
309
-
345
+
310
346
  text: function(nodeId, callback) {
311
347
  var node = this._node(nodeId);
312
348
  if (!node) return callback(null);
313
-
314
- var text = node.textContent || node.innerText || '',
315
- scripts = node.getElementsByTagName('script'),
316
- i = scripts.length;
317
-
318
- while (i--) text = text.replace(scripts[i].textContent || scripts[i].innerText, '');
319
- text = text.replace(/^\s*|\s*$/g, '');
349
+
350
+ var hidden = Terminus.hideNodes(node),
351
+ title = document.title;
352
+
353
+ document.title = '';
354
+
355
+ var text = node.textContent || node.innerText || '';
356
+
357
+ document.title = title;
358
+ Terminus.showNodes(hidden);
359
+
360
+ text = text.replace(/^\s*|\s*$/g, '').replace(/\s+/g, ' ');
320
361
  callback(text);
321
362
  },
322
-
363
+
323
364
  trigger: function(nodeId, eventType, callback) {
324
365
  var node = this._node(nodeId);
325
366
  if (!node) return callback(null);
326
367
  Syn.trigger(eventType, {}, node);
327
368
  callback(true);
328
369
  },
329
-
370
+
330
371
  unselect: function(nodeId, callback) {
331
372
  var option = this._node(nodeId);
332
373
  if (!option) return callback(null);
@@ -335,35 +376,35 @@ Terminus = {
335
376
  Syn.trigger('change', {}, option.parentNode);
336
377
  callback(true);
337
378
  },
338
-
379
+
339
380
  value: function(nodeId, callback) {
340
381
  var node = this._node(nodeId);
341
382
  if (!node) return callback(null);
342
-
383
+
343
384
  if (node.tagName.toLowerCase() !== 'select' || !node.multiple)
344
385
  return callback(node.value);
345
-
386
+
346
387
  var options = node.children,
347
388
  values = [];
348
-
389
+
349
390
  for (var i = 0, n = options.length; i < n; i++) {
350
391
  if (options[i].selected) values.push(options[i].value);
351
392
  }
352
393
  callback(values);
353
394
  },
354
-
395
+
355
396
  visit: function(url, callback) {
356
397
  window.location.href = url;
357
398
  callback(url);
358
399
  }
359
400
  },
360
-
401
+
361
402
  Registry: {
362
403
  initialize: function() {
363
404
  this._namespace = new Faye.Namespace();
364
405
  this._elements = {};
365
406
  },
366
-
407
+
367
408
  get: function(id) {
368
409
  var node = this._elements[id], root = node;
369
410
  while (root && root.tagName !== 'BODY' && root.tagName !== 'HTML')
@@ -371,22 +412,26 @@ Terminus = {
371
412
  if (!root) return null;
372
413
  return node;
373
414
  },
374
-
415
+
375
416
  put: function(element) {
376
- var id = this._namespace.generate();
417
+ var id = element['data-terminus-id'];
418
+ if (!id) {
419
+ id = this._namespace.generate();
420
+ element['data-terminus-id'] = id;
421
+ }
377
422
  this._elements[id] = element;
378
423
  return id;
379
424
  }
380
425
  },
381
-
426
+
382
427
  Worker: {
383
428
  initialize: function() {
384
429
  this._callbacks = [];
385
430
  this._pending = 0;
386
-
431
+
387
432
  if (!Terminus.isIE) this._wrapTimeouts();
388
433
  },
389
-
434
+
390
435
  callback: function(callback, scope) {
391
436
  if (this._pending === 0) {
392
437
  if (this._setTimeout)
@@ -397,16 +442,16 @@ Terminus = {
397
442
  this._callbacks.push([callback, scope]);
398
443
  }
399
444
  },
400
-
445
+
401
446
  suspend: function() {
402
447
  this._pending += 1;
403
448
  },
404
-
449
+
405
450
  resume: function() {
406
451
  if (this._pending === 0) return;
407
452
  this._pending -= 1;
408
453
  if (this._pending !== 0) return;
409
-
454
+
410
455
  var callback;
411
456
  for (var i = 0, n = this._callbacks.length; i < n; i++) {
412
457
  callback = this._callbacks[i];
@@ -414,19 +459,19 @@ Terminus = {
414
459
  }
415
460
  this._callbacks = [];
416
461
  },
417
-
462
+
418
463
  _wrapTimeouts: function() {
419
464
  var timeout = window.setTimeout,
420
465
  clear = window.clearTimeout,
421
466
  timeouts = {},
422
467
  self = this;
423
-
468
+
424
469
  var finish = function(id) {
425
470
  if (!timeouts.hasOwnProperty(id)) return;
426
471
  delete timeouts[id];
427
472
  self.resume();
428
473
  };
429
-
474
+
430
475
  window.setTimeout = function(callback, delay) {
431
476
  var id = timeout.call(window, function() {
432
477
  try {
@@ -438,35 +483,35 @@ Terminus = {
438
483
  finish(id);
439
484
  }
440
485
  }, delay);
441
-
486
+
442
487
  if (self.monitor) {
443
488
  timeouts[id] = true;
444
489
  self.suspend();
445
490
  }
446
491
  return id;
447
492
  };
448
-
493
+
449
494
  window.clearTimeout = function(id) {
450
495
  finish(id);
451
496
  return clear(id);
452
497
  };
453
-
498
+
454
499
  this._setTimeout = timeout;
455
500
  }
456
501
  },
457
-
502
+
458
503
  AjaxMonitor: {
459
504
  initialize: function() {
460
505
  if (window.jQuery) this._patchJquery();
461
506
  },
462
-
507
+
463
508
  _patchJquery: function() {
464
509
  var ajax = jQuery.ajax;
465
510
  jQuery.ajax = function(url, settings) {
466
511
  var options = ((typeof url === 'string') ? settings : url) || {},
467
512
  complete = options.complete,
468
513
  monitor = Terminus.Worker.monitor;
469
-
514
+
470
515
  options.complete = function() {
471
516
  var result;
472
517
  try {
@@ -476,9 +521,9 @@ Terminus = {
476
521
  }
477
522
  return result;
478
523
  };
479
-
524
+
480
525
  if (monitor) Terminus.Worker.suspend();
481
-
526
+
482
527
  if (typeof url === 'string')
483
528
  return ajax.call(jQuery, url, options);
484
529
  else