Merged branch feature/frontend-tests
This commit is contained in:
commit
4c095202bd
|
@ -0,0 +1,22 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
install:
|
||||
- "bin/installDeps.sh"
|
||||
- "export GIT_HASH=$(cat .git/HEAD | head -c 7)"
|
||||
before_script:
|
||||
- "tests/frontend/travis/sauce_tunnel.sh"
|
||||
script:
|
||||
- "tests/frontend/travis/runner.sh"
|
||||
env:
|
||||
global:
|
||||
- secure: "oKA4KbSvyxMOFCiOa3hWswnaIrCmX60MfhBhD8xu8sodOqbdK5RUrxDJew9p\n1nNSewxoVmKhX0G5GxIABfGtdU1nrEzCEoejTDJIFmzEbcLcHpcyarouWLSY\nOpn11FKS1rnb69aflHM7K8l4dhrCkA2i0Dwwl8LN3HayGzDV2Rg="
|
||||
- SAUCE_USER=pita
|
||||
jdk:
|
||||
- oraclejdk6
|
||||
notifications:
|
||||
email:
|
||||
- petermartischka@googlemail.com
|
||||
irc:
|
||||
channels:
|
||||
- "irc.freenode.org#etherpad-lite-dev"
|
|
@ -69,7 +69,7 @@ echo "Ensure that all dependencies are up to date..."
|
|||
cd node_modules
|
||||
[ -e ep_etherpad-lite ] || ln -s ../src ep_etherpad-lite
|
||||
cd ep_etherpad-lite
|
||||
npm install
|
||||
npm install -s
|
||||
) || {
|
||||
rm -rf node_modules
|
||||
exit 1
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
{ "name": "importexport", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/importexport:expressCreateServer" } },
|
||||
{ "name": "errorhandling", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling:expressCreateServer" } },
|
||||
{ "name": "socketio", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio:expressCreateServer" } },
|
||||
{ "name": "tests", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/tests:expressCreateServer" } },
|
||||
{ "name": "admin", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/admin:expressCreateServer" } },
|
||||
{ "name": "adminplugins", "hooks": {
|
||||
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins:expressCreateServer",
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/tests/frontend/specs_list.js', function(req, res){
|
||||
fs.readdir('tests/frontend/specs', function(err, files){
|
||||
if(err){ return res.send(500); }
|
||||
|
||||
res.send("var specs_list = " + JSON.stringify(files.sort()) + ";\n");
|
||||
});
|
||||
});
|
||||
|
||||
var url2FilePath = function(url){
|
||||
var subPath = url.substr("/tests/frontend".length);
|
||||
if (subPath == ""){
|
||||
subPath = "index.html"
|
||||
}
|
||||
subPath = subPath.split("?")[0];
|
||||
|
||||
var filePath = path.normalize(__dirname + "/../../../../tests/frontend/")
|
||||
filePath += subPath.replace("..", "");
|
||||
return filePath;
|
||||
}
|
||||
|
||||
args.app.get('/tests/frontend/specs/*', function (req, res) {
|
||||
var specFilePath = url2FilePath(req.url);
|
||||
var specFileName = path.basename(specFilePath);
|
||||
|
||||
fs.readFile(specFilePath, function(err, content){
|
||||
if(err){ return res.send(500); }
|
||||
|
||||
content = "describe(" + JSON.stringify(specFileName) + ", function(){ " + content + " });";
|
||||
|
||||
res.send(content);
|
||||
});
|
||||
});
|
||||
|
||||
args.app.get('/tests/frontend/*', function (req, res) {
|
||||
var filePath = url2FilePath(req.url);
|
||||
res.sendfile(filePath);
|
||||
});
|
||||
|
||||
args.app.get('/tests/frontend', function (req, res) {
|
||||
res.redirect('/tests/frontend/');
|
||||
});
|
||||
}
|
|
@ -39,7 +39,8 @@
|
|||
},
|
||||
"bin": { "etherpad-lite": "./node/server.js" },
|
||||
"devDependencies": {
|
||||
"jshint" : "*"
|
||||
"jshint" : "*",
|
||||
"wd" : "0.0.26"
|
||||
},
|
||||
"engines" : { "node" : ">=0.6.0",
|
||||
"npm" : ">=1.0"
|
||||
|
|
|
@ -150,7 +150,7 @@ var chat = (function()
|
|||
$("#chatinput").keypress(function(evt)
|
||||
{
|
||||
//if the user typed enter, fire the send
|
||||
if(evt.which == 13)
|
||||
if(evt.which == 13 || evt.which == 10)
|
||||
{
|
||||
evt.preventDefault();
|
||||
self.send();
|
||||
|
|
|
@ -389,6 +389,10 @@ function handshake()
|
|||
});
|
||||
// Bind the colorpicker
|
||||
var fb = $('#colorpicker').farbtastic({ callback: '#mycolorpickerpreview', width: 220});
|
||||
// Bind the read only button
|
||||
$('#readonlyinput').on('click',function(){
|
||||
padeditbar.setEmbedLinks();
|
||||
});
|
||||
}
|
||||
|
||||
var pad = {
|
||||
|
|
|
@ -305,7 +305,7 @@
|
|||
<div id="embed" class="popup">
|
||||
<% e.begin_block("embedPopup"); %>
|
||||
<div id="embedreadonly" class="right acl-write">
|
||||
<input type="checkbox" id="readonlyinput" onClick="padeditbar.setEmbedLinks();">
|
||||
<input type="checkbox" id="readonlyinput">
|
||||
<label for="readonlyinput">Read only</label>
|
||||
</div>
|
||||
<h1>Share this pad</h1>
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
var helper = {};
|
||||
|
||||
(function(){
|
||||
var $iframeContainer, $iframe, jsLibraries = {};
|
||||
|
||||
helper.init = function(cb){
|
||||
$iframeContainer = $("#iframe-container");
|
||||
|
||||
$.get('/static/js/jquery.js').done(function(code){
|
||||
// make sure we don't override existing jquery
|
||||
jsLibraries["jquery"] = "if(typeof $ === 'undefined') {\n" + code + "\n}";
|
||||
|
||||
$.get('/tests/frontend/lib/sendkeys.js').done(function(code){
|
||||
jsLibraries["sendkeys"] = code;
|
||||
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
helper.randomString = function randomString(len)
|
||||
{
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
var randomstring = '';
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var rnum = Math.floor(Math.random() * chars.length);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return randomstring;
|
||||
}
|
||||
|
||||
var getFrameJQuery = function($iframe){
|
||||
/*
|
||||
I tried over 9000 ways to inject javascript into iframes.
|
||||
This is the only way I found that worked in IE 7+8+9, FF and Chrome
|
||||
*/
|
||||
|
||||
var win = $iframe[0].contentWindow;
|
||||
var doc = win.document;
|
||||
|
||||
//IE 8+9 Hack to make eval appear
|
||||
//http://stackoverflow.com/questions/2720444/why-does-this-window-object-not-have-the-eval-function
|
||||
win.execScript && win.execScript("null");
|
||||
|
||||
win.eval(jsLibraries["jquery"]);
|
||||
win.eval(jsLibraries["sendkeys"]);
|
||||
|
||||
win.$.window = win;
|
||||
win.$.document = doc;
|
||||
|
||||
return win.$;
|
||||
}
|
||||
|
||||
helper.clearCookies = function(){
|
||||
window.document.cookie = "";
|
||||
}
|
||||
|
||||
helper.newPad = function(){
|
||||
//build opts object
|
||||
var opts = {clearCookies: true}
|
||||
if(typeof arguments[0] === 'function'){
|
||||
opts.cb = arguments[0]
|
||||
} else {
|
||||
opts = _.defaults(arguments[0], opts);
|
||||
}
|
||||
|
||||
//clear cookies
|
||||
if(opts.clearCookies){
|
||||
helper.clearCookies();
|
||||
}
|
||||
|
||||
var padName = "FRONTEND_TEST_" + helper.randomString(20);
|
||||
$iframe = $("<iframe src='/p/" + padName + "'></iframe>");
|
||||
|
||||
//clean up inner iframe references
|
||||
helper.padChrome$ = helper.padOuter$ = helper.padInner$ = null;
|
||||
|
||||
//clean up iframes properly to prevent IE from memoryleaking
|
||||
$iframeContainer.find("iframe").purgeFrame().done(function(){
|
||||
$iframeContainer.append($iframe);
|
||||
$iframe.one('load', function(){
|
||||
helper.waitFor(function(){
|
||||
return !$iframe.contents().find("#editorloadingbox").is(":visible");
|
||||
}, 50000).done(function(){
|
||||
helper.padChrome$ = getFrameJQuery( $('#iframe-container iframe'));
|
||||
helper.padOuter$ = getFrameJQuery(helper.padChrome$('iframe.[name="ace_outer"]'));
|
||||
helper.padInner$ = getFrameJQuery( helper.padOuter$('iframe.[name="ace_inner"]'));
|
||||
|
||||
//disable all animations, this makes tests faster and easier
|
||||
helper.padChrome$.fx.off = true;
|
||||
helper.padOuter$.fx.off = true;
|
||||
helper.padInner$.fx.off = true;
|
||||
|
||||
opts.cb();
|
||||
}).fail(function(){
|
||||
throw new Error("Pad never loaded");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return padName;
|
||||
}
|
||||
|
||||
helper.waitFor = function(conditionFunc, _timeoutTime, _intervalTime){
|
||||
var timeoutTime = _timeoutTime || 1000;
|
||||
var intervalTime = _intervalTime || 10;
|
||||
|
||||
var deferred = $.Deferred();
|
||||
|
||||
var _fail = deferred.fail;
|
||||
var listenForFail = false;
|
||||
deferred.fail = function(){
|
||||
listenForFail = true;
|
||||
_fail.apply(this, arguments);
|
||||
}
|
||||
|
||||
var intervalCheck = setInterval(function(){
|
||||
var passed = false;
|
||||
|
||||
passed = conditionFunc();
|
||||
|
||||
if(passed){
|
||||
clearInterval(intervalCheck);
|
||||
clearTimeout(timeout);
|
||||
|
||||
deferred.resolve();
|
||||
}
|
||||
}, intervalTime);
|
||||
|
||||
var timeout = setTimeout(function(){
|
||||
clearInterval(intervalCheck);
|
||||
var error = new Error("wait for condition never became true " + conditionFunc.toString());
|
||||
deferred.reject(error);
|
||||
|
||||
if(!listenForFail){
|
||||
throw error;
|
||||
}
|
||||
}, timeoutTime);
|
||||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
/* Ensure console.log doesn't blow up in IE, ugly but ok for a test framework imho*/
|
||||
window.console = window.console || {};
|
||||
window.console.log = window.console.log || function(){}
|
||||
|
||||
//force usage of callbacks in it
|
||||
var _it = it;
|
||||
it = function(name, func){
|
||||
if(func && func.length !== 1){
|
||||
func = function(){
|
||||
throw new Error("Please use always a callback with it() - " + func.toString());
|
||||
}
|
||||
}
|
||||
|
||||
_it(name, func);
|
||||
}
|
||||
})()
|
|
@ -0,0 +1,26 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<title>Frontend tests</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link rel="stylesheet" href="runner.css" />
|
||||
|
||||
<div id="console"></div>
|
||||
<div id="mocha"></div>
|
||||
<div id="iframe-container"></div>
|
||||
|
||||
<script src="/static/js/jquery.js"></script>
|
||||
<script src="lib/underscore.js"></script>
|
||||
|
||||
<script src="lib/mocha.js"></script>
|
||||
<script> mocha.setup('bdd') </script>
|
||||
<script src="lib/expect.js"></script>
|
||||
|
||||
<script src="lib/sendkeys.js"></script>
|
||||
<script src="lib/jquery.iframe.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<script src="specs_list.js"></script>
|
||||
|
||||
<script src="runner.js"></script>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,39 @@
|
|||
//copied from http://stackoverflow.com/questions/8407946/is-it-possible-to-use-iframes-in-ie-without-memory-leaks
|
||||
(function($) {
|
||||
$.fn.purgeFrame = function() {
|
||||
var deferred;
|
||||
|
||||
if ($.browser.msie && parseFloat($.browser.version, 10) < 9) {
|
||||
deferred = purge(this);
|
||||
} else {
|
||||
this.remove();
|
||||
deferred = $.Deferred();
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
return deferred;
|
||||
};
|
||||
|
||||
function purge($frame) {
|
||||
var sem = $frame.length
|
||||
, deferred = $.Deferred();
|
||||
|
||||
$frame.load(function() {
|
||||
var frame = this;
|
||||
frame.contentWindow.document.innerHTML = '';
|
||||
|
||||
sem -= 1;
|
||||
if (sem <= 0) {
|
||||
$frame.remove();
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
$frame.attr('src', 'about:blank');
|
||||
|
||||
if ($frame.length === 0) {
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
return deferred.promise();
|
||||
}
|
||||
})(jQuery);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,467 @@
|
|||
// Cross-broswer implementation of text ranges and selections
|
||||
// documentation: http://bililite.com/blog/2011/01/11/cross-browser-.and-selections/
|
||||
// Version: 1.1
|
||||
// Copyright (c) 2010 Daniel Wachsstock
|
||||
// MIT license:
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
(function($){
|
||||
|
||||
bililiteRange = function(el, debug){
|
||||
var ret;
|
||||
if (debug){
|
||||
ret = new NothingRange(); // Easier to force it to use the no-selection type than to try to find an old browser
|
||||
}else if (document.selection && !document.addEventListener){
|
||||
// Internet Explorer 8 and lower
|
||||
ret = new IERange();
|
||||
}else if (window.getSelection && el.setSelectionRange){
|
||||
// Standards. Element is an input or textarea
|
||||
ret = new InputRange();
|
||||
}else if (window.getSelection){
|
||||
// Standards, with any other kind of element
|
||||
ret = new W3CRange()
|
||||
}else{
|
||||
// doesn't support selection
|
||||
ret = new NothingRange();
|
||||
}
|
||||
ret._el = el;
|
||||
ret._doc = el.ownerDocument;
|
||||
ret._win = 'defaultView' in ret._doc ? ret._doc.defaultView : ret._doc.parentWindow;
|
||||
ret._textProp = textProp(el);
|
||||
ret._bounds = [0, ret.length()];
|
||||
return ret;
|
||||
}
|
||||
|
||||
function textProp(el){
|
||||
// returns the property that contains the text of the element
|
||||
if (typeof el.value != 'undefined') return 'value';
|
||||
if (typeof el.text != 'undefined') return 'text';
|
||||
if (typeof el.textContent != 'undefined') return 'textContent';
|
||||
return 'innerText';
|
||||
}
|
||||
|
||||
// base class
|
||||
function Range(){}
|
||||
Range.prototype = {
|
||||
length: function() {
|
||||
return this._el[this._textProp].replace(/\r/g, '').length; // need to correct for IE's CrLf weirdness
|
||||
},
|
||||
bounds: function(s){
|
||||
if (s === 'all'){
|
||||
this._bounds = [0, this.length()];
|
||||
}else if (s === 'start'){
|
||||
this._bounds = [0, 0];
|
||||
}else if (s === 'end'){
|
||||
this._bounds = [this.length(), this.length()];
|
||||
}else if (s === 'selection'){
|
||||
this.bounds ('all'); // first select the whole thing for constraining
|
||||
this._bounds = this._nativeSelection();
|
||||
}else if (s){
|
||||
this._bounds = s; // don't error check now; the element may change at any moment, so constrain it when we need it.
|
||||
}else{
|
||||
var b = [
|
||||
Math.max(0, Math.min (this.length(), this._bounds[0])),
|
||||
Math.max(0, Math.min (this.length(), this._bounds[1]))
|
||||
];
|
||||
return b; // need to constrain it to fit
|
||||
}
|
||||
return this; // allow for chaining
|
||||
},
|
||||
select: function(){
|
||||
this._nativeSelect(this._nativeRange(this.bounds()));
|
||||
return this; // allow for chaining
|
||||
},
|
||||
text: function(text, select){
|
||||
if (arguments.length){
|
||||
this._nativeSetText(text, this._nativeRange(this.bounds()));
|
||||
if (select == 'start'){
|
||||
this.bounds ([this._bounds[0], this._bounds[0]]);
|
||||
this.select();
|
||||
}else if (select == 'end'){
|
||||
this.bounds ([this._bounds[0]+text.length, this._bounds[0]+text.length]);
|
||||
this.select();
|
||||
}else if (select == 'all'){
|
||||
this.bounds ([this._bounds[0], this._bounds[0]+text.length]);
|
||||
this.select();
|
||||
}
|
||||
return this; // allow for chaining
|
||||
}else{
|
||||
return this._nativeGetText(this._nativeRange(this.bounds()));
|
||||
}
|
||||
},
|
||||
insertEOL: function (){
|
||||
this._nativeEOL();
|
||||
this._bounds = [this._bounds[0]+1, this._bounds[0]+1]; // move past the EOL marker
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function IERange(){}
|
||||
IERange.prototype = new Range();
|
||||
IERange.prototype._nativeRange = function (bounds){
|
||||
var rng;
|
||||
if (this._el.tagName == 'INPUT'){
|
||||
// IE 8 is very inconsistent; textareas have createTextRange but it doesn't work
|
||||
rng = this._el.createTextRange();
|
||||
}else{
|
||||
rng = this._doc.body.createTextRange ();
|
||||
rng.moveToElementText(this._el);
|
||||
}
|
||||
if (bounds){
|
||||
if (bounds[1] < 0) bounds[1] = 0; // IE tends to run elements out of bounds
|
||||
if (bounds[0] > this.length()) bounds[0] = this.length();
|
||||
if (bounds[1] < rng.text.replace(/\r/g, '').length){ // correct for IE's CrLf wierdness
|
||||
// block-display elements have an invisible, uncounted end of element marker, so we move an extra one and use the current length of the range
|
||||
rng.moveEnd ('character', -1);
|
||||
rng.moveEnd ('character', bounds[1]-rng.text.replace(/\r/g, '').length);
|
||||
}
|
||||
if (bounds[0] > 0) rng.moveStart('character', bounds[0]);
|
||||
}
|
||||
return rng;
|
||||
};
|
||||
IERange.prototype._nativeSelect = function (rng){
|
||||
rng.select();
|
||||
};
|
||||
IERange.prototype._nativeSelection = function (){
|
||||
// returns [start, end] for the selection constrained to be in element
|
||||
var rng = this._nativeRange(); // range of the element to constrain to
|
||||
var len = this.length();
|
||||
if (this._doc.selection.type != 'Text') return [0,0]; // append to the end
|
||||
var sel = this._doc.selection.createRange();
|
||||
try{
|
||||
return [
|
||||
iestart(sel, rng),
|
||||
ieend (sel, rng)
|
||||
];
|
||||
}catch (e){
|
||||
// IE gets upset sometimes about comparing text to input elements, but the selections cannot overlap, so make a best guess
|
||||
return (sel.parentElement().sourceIndex < this._el.sourceIndex) ? [0,0] : [len, len];
|
||||
}
|
||||
};
|
||||
IERange.prototype._nativeGetText = function (rng){
|
||||
return rng.text.replace(/\r/g, ''); // correct for IE's CrLf weirdness
|
||||
};
|
||||
IERange.prototype._nativeSetText = function (text, rng){
|
||||
rng.text = text;
|
||||
};
|
||||
IERange.prototype._nativeEOL = function(){
|
||||
if (typeof this._el.value != 'undefined'){
|
||||
this.text('\n'); // for input and textarea, insert it straight
|
||||
}else{
|
||||
this._nativeRange(this.bounds()).pasteHTML('<br/>');
|
||||
}
|
||||
};
|
||||
// IE internals
|
||||
function iestart(rng, constraint){
|
||||
// returns the position (in character) of the start of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after
|
||||
var len = constraint.text.replace(/\r/g, '').length; // correct for IE's CrLf wierdness
|
||||
if (rng.compareEndPoints ('StartToStart', constraint) <= 0) return 0; // at or before the beginning
|
||||
if (rng.compareEndPoints ('StartToEnd', constraint) >= 0) return len;
|
||||
for (var i = 0; rng.compareEndPoints ('StartToStart', constraint) > 0; ++i, rng.moveStart('character', -1));
|
||||
return i;
|
||||
}
|
||||
function ieend (rng, constraint){
|
||||
// returns the position (in character) of the end of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after
|
||||
var len = constraint.text.replace(/\r/g, '').length; // correct for IE's CrLf wierdness
|
||||
if (rng.compareEndPoints ('EndToEnd', constraint) >= 0) return len; // at or after the end
|
||||
if (rng.compareEndPoints ('EndToStart', constraint) <= 0) return 0;
|
||||
for (var i = 0; rng.compareEndPoints ('EndToStart', constraint) > 0; ++i, rng.moveEnd('character', -1));
|
||||
return i;
|
||||
}
|
||||
|
||||
// an input element in a standards document. "Native Range" is just the bounds array
|
||||
function InputRange(){}
|
||||
InputRange.prototype = new Range();
|
||||
InputRange.prototype._nativeRange = function(bounds) {
|
||||
return bounds || [0, this.length()];
|
||||
};
|
||||
InputRange.prototype._nativeSelect = function (rng){
|
||||
this._el.setSelectionRange(rng[0], rng[1]);
|
||||
};
|
||||
InputRange.prototype._nativeSelection = function(){
|
||||
return [this._el.selectionStart, this._el.selectionEnd];
|
||||
};
|
||||
InputRange.prototype._nativeGetText = function(rng){
|
||||
return this._el.value.substring(rng[0], rng[1]);
|
||||
};
|
||||
InputRange.prototype._nativeSetText = function(text, rng){
|
||||
var val = this._el.value;
|
||||
this._el.value = val.substring(0, rng[0]) + text + val.substring(rng[1]);
|
||||
};
|
||||
InputRange.prototype._nativeEOL = function(){
|
||||
this.text('\n');
|
||||
};
|
||||
|
||||
function W3CRange(){}
|
||||
W3CRange.prototype = new Range();
|
||||
W3CRange.prototype._nativeRange = function (bounds){
|
||||
var rng = this._doc.createRange();
|
||||
rng.selectNodeContents(this._el);
|
||||
if (bounds){
|
||||
w3cmoveBoundary (rng, bounds[0], true, this._el);
|
||||
rng.collapse (true);
|
||||
w3cmoveBoundary (rng, bounds[1]-bounds[0], false, this._el);
|
||||
}
|
||||
return rng;
|
||||
};
|
||||
W3CRange.prototype._nativeSelect = function (rng){
|
||||
this._win.getSelection().removeAllRanges();
|
||||
this._win.getSelection().addRange (rng);
|
||||
};
|
||||
W3CRange.prototype._nativeSelection = function (){
|
||||
// returns [start, end] for the selection constrained to be in element
|
||||
var rng = this._nativeRange(); // range of the element to constrain to
|
||||
if (this._win.getSelection().rangeCount == 0) return [this.length(), this.length()]; // append to the end
|
||||
var sel = this._win.getSelection().getRangeAt(0);
|
||||
return [
|
||||
w3cstart(sel, rng),
|
||||
w3cend (sel, rng)
|
||||
];
|
||||
}
|
||||
W3CRange.prototype._nativeGetText = function (rng){
|
||||
return rng.toString();
|
||||
};
|
||||
W3CRange.prototype._nativeSetText = function (text, rng){
|
||||
rng.deleteContents();
|
||||
rng.insertNode (this._doc.createTextNode(text));
|
||||
this._el.normalize(); // merge the text with the surrounding text
|
||||
};
|
||||
W3CRange.prototype._nativeEOL = function(){
|
||||
var rng = this._nativeRange(this.bounds());
|
||||
rng.deleteContents();
|
||||
var br = this._doc.createElement('br');
|
||||
br.setAttribute ('_moz_dirty', ''); // for Firefox
|
||||
rng.insertNode (br);
|
||||
rng.insertNode (this._doc.createTextNode('\n'));
|
||||
rng.collapse (false);
|
||||
};
|
||||
// W3C internals
|
||||
function nextnode (node, root){
|
||||
// in-order traversal
|
||||
// we've already visited node, so get kids then siblings
|
||||
if (node.firstChild) return node.firstChild;
|
||||
if (node.nextSibling) return node.nextSibling;
|
||||
if (node===root) return null;
|
||||
while (node.parentNode){
|
||||
// get uncles
|
||||
node = node.parentNode;
|
||||
if (node == root) return null;
|
||||
if (node.nextSibling) return node.nextSibling;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function w3cmoveBoundary (rng, n, bStart, el){
|
||||
// move the boundary (bStart == true ? start : end) n characters forward, up to the end of element el. Forward only!
|
||||
// if the start is moved after the end, then an exception is raised
|
||||
if (n <= 0) return;
|
||||
var node = rng[bStart ? 'startContainer' : 'endContainer'];
|
||||
if (node.nodeType == 3){
|
||||
// we may be starting somewhere into the text
|
||||
n += rng[bStart ? 'startOffset' : 'endOffset'];
|
||||
}
|
||||
while (node){
|
||||
if (node.nodeType == 3){
|
||||
if (n <= node.nodeValue.length){
|
||||
rng[bStart ? 'setStart' : 'setEnd'](node, n);
|
||||
// special case: if we end next to a <br>, include that node.
|
||||
if (n == node.nodeValue.length){
|
||||
// skip past zero-length text nodes
|
||||
for (var next = nextnode (node, el); next && next.nodeType==3 && next.nodeValue.length == 0; next = nextnode(next, el)){
|
||||
rng[bStart ? 'setStartAfter' : 'setEndAfter'](next);
|
||||
}
|
||||
if (next && next.nodeType == 1 && next.nodeName == "BR") rng[bStart ? 'setStartAfter' : 'setEndAfter'](next);
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
rng[bStart ? 'setStartAfter' : 'setEndAfter'](node); // skip past this one
|
||||
n -= node.nodeValue.length; // and eat these characters
|
||||
}
|
||||
}
|
||||
node = nextnode (node, el);
|
||||
}
|
||||
}
|
||||
var START_TO_START = 0; // from the w3c definitions
|
||||
var START_TO_END = 1;
|
||||
var END_TO_END = 2;
|
||||
var END_TO_START = 3;
|
||||
// from the Mozilla documentation, for range.compareBoundaryPoints(how, sourceRange)
|
||||
// -1, 0, or 1, indicating whether the corresponding boundary-point of range is respectively before, equal to, or after the corresponding boundary-point of sourceRange.
|
||||
// * Range.END_TO_END compares the end boundary-point of sourceRange to the end boundary-point of range.
|
||||
// * Range.END_TO_START compares the end boundary-point of sourceRange to the start boundary-point of range.
|
||||
// * Range.START_TO_END compares the start boundary-point of sourceRange to the end boundary-point of range.
|
||||
// * Range.START_TO_START compares the start boundary-point of sourceRange to the start boundary-point of range.
|
||||
function w3cstart(rng, constraint){
|
||||
if (rng.compareBoundaryPoints (START_TO_START, constraint) <= 0) return 0; // at or before the beginning
|
||||
if (rng.compareBoundaryPoints (END_TO_START, constraint) >= 0) return constraint.toString().length;
|
||||
rng = rng.cloneRange(); // don't change the original
|
||||
rng.setEnd (constraint.endContainer, constraint.endOffset); // they now end at the same place
|
||||
return constraint.toString().length - rng.toString().length;
|
||||
}
|
||||
function w3cend (rng, constraint){
|
||||
if (rng.compareBoundaryPoints (END_TO_END, constraint) >= 0) return constraint.toString().length; // at or after the end
|
||||
if (rng.compareBoundaryPoints (START_TO_END, constraint) <= 0) return 0;
|
||||
rng = rng.cloneRange(); // don't change the original
|
||||
rng.setStart (constraint.startContainer, constraint.startOffset); // they now start at the same place
|
||||
return rng.toString().length;
|
||||
}
|
||||
|
||||
function NothingRange(){}
|
||||
NothingRange.prototype = new Range();
|
||||
NothingRange.prototype._nativeRange = function(bounds) {
|
||||
return bounds || [0,this.length()];
|
||||
};
|
||||
NothingRange.prototype._nativeSelect = function (rng){ // do nothing
|
||||
};
|
||||
NothingRange.prototype._nativeSelection = function(){
|
||||
return [0,0];
|
||||
};
|
||||
NothingRange.prototype._nativeGetText = function (rng){
|
||||
return this._el[this._textProp].substring(rng[0], rng[1]);
|
||||
};
|
||||
NothingRange.prototype._nativeSetText = function (text, rng){
|
||||
var val = this._el[this._textProp];
|
||||
this._el[this._textProp] = val.substring(0, rng[0]) + text + val.substring(rng[1]);
|
||||
};
|
||||
NothingRange.prototype._nativeEOL = function(){
|
||||
this.text('\n');
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
||||
// insert characters in a textarea or text input field
|
||||
// special characters are enclosed in {}; use {{} for the { character itself
|
||||
// documentation: http://bililite.com/blog/2008/08/20/the-fnsendkeys-plugin/
|
||||
// Version: 2.0
|
||||
// Copyright (c) 2010 Daniel Wachsstock
|
||||
// MIT license:
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
(function($){
|
||||
|
||||
$.fn.sendkeys = function (x, opts){
|
||||
return this.each( function(){
|
||||
var localkeys = $.extend({}, opts, $(this).data('sendkeys')); // allow for element-specific key functions
|
||||
// most elements to not keep track of their selection when they lose focus, so we have to do it for them
|
||||
var rng = $.data (this, 'sendkeys.selection');
|
||||
if (!rng){
|
||||
rng = bililiteRange(this).bounds('selection');
|
||||
$.data(this, 'sendkeys.selection', rng);
|
||||
$(this).bind('mouseup.sendkeys', function(){
|
||||
// we have to update the saved range. The routines here update the bounds with each press, but actual keypresses and mouseclicks do not
|
||||
$.data(this, 'sendkeys.selection').bounds('selection');
|
||||
}).bind('keyup.sendkeys', function(evt){
|
||||
// restore the selection if we got here with a tab (a click should select what was clicked on)
|
||||
if (evt.which == 9){
|
||||
// there's a flash of selection when we restore the focus, but I don't know how to avoid that.
|
||||
$.data(this, 'sendkeys.selection').select();
|
||||
}else{
|
||||
$.data(this, 'sendkeys.selection').bounds('selection');
|
||||
}
|
||||
});
|
||||
}
|
||||
this.focus();
|
||||
if (typeof x === 'undefined') return; // no string, so we just set up the event handlers
|
||||
$.data(this, 'sendkeys.originalText', rng.text());
|
||||
x.replace(/\n/g, '{enter}'). // turn line feeds into explicit break insertions
|
||||
replace(/{[^}]*}|[^{]+/g, function(s){
|
||||
(localkeys[s] || $.fn.sendkeys.defaults[s] || $.fn.sendkeys.defaults.simplechar)(rng, s);
|
||||
});
|
||||
$(this).trigger({type: 'sendkeys', which: x});
|
||||
});
|
||||
}; // sendkeys
|
||||
|
||||
|
||||
// add the functions publicly so they can be overridden
|
||||
$.fn.sendkeys.defaults = {
|
||||
simplechar: function (rng, s){
|
||||
rng.text(s, 'end');
|
||||
for (var i =0; i < s.length; ++i){
|
||||
var x = s.charCodeAt(i);
|
||||
// a bit of cheating: rng._el is the element associated with rng.
|
||||
$(rng._el).trigger({type: 'keypress', keyCode: x, which: x, charCode: x});
|
||||
}
|
||||
},
|
||||
'{{}': function (rng){
|
||||
$.fn.sendkeys.defaults.simplechar (rng, '{')
|
||||
},
|
||||
'{enter}': function (rng){
|
||||
rng.insertEOL();
|
||||
rng.select();
|
||||
var x = '\n'.charCodeAt(0);
|
||||
$(rng._el).trigger({type: 'keypress', keyCode: x, which: x, charCode: x});
|
||||
},
|
||||
'{backspace}': function (rng){
|
||||
var b = rng.bounds();
|
||||
if (b[0] == b[1]) rng.bounds([b[0]-1, b[0]]); // no characters selected; it's just an insertion point. Remove the previous character
|
||||
rng.text('', 'end'); // delete the characters and update the selection
|
||||
},
|
||||
'{del}': function (rng){
|
||||
var b = rng.bounds();
|
||||
if (b[0] == b[1]) rng.bounds([b[0], b[0]+1]); // no characters selected; it's just an insertion point. Remove the next character
|
||||
rng.text('', 'end'); // delete the characters and update the selection
|
||||
},
|
||||
'{rightarrow}': function (rng){
|
||||
var b = rng.bounds();
|
||||
if (b[0] == b[1]) ++b[1]; // no characters selected; it's just an insertion point. Move to the right
|
||||
rng.bounds([b[1], b[1]]).select();
|
||||
},
|
||||
'{leftarrow}': function (rng){
|
||||
var b = rng.bounds();
|
||||
if (b[0] == b[1]) --b[0]; // no characters selected; it's just an insertion point. Move to the left
|
||||
rng.bounds([b[0], b[0]]).select();
|
||||
},
|
||||
'{selectall}' : function (rng){
|
||||
rng.bounds('all').select();
|
||||
},
|
||||
'{selection}': function (rng){
|
||||
$.fn.sendkeys.defaults.simplechar(rng, $.data(rng._el, 'sendkeys.originalText'));
|
||||
},
|
||||
'{mark}' : function (rng){
|
||||
var bounds = rng.bounds();
|
||||
$(rng._el).one('sendkeys', function(){
|
||||
// set up the event listener to change the selection after the sendkeys is done
|
||||
rng.bounds(bounds).select();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,228 @@
|
|||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#console {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#iframe-container {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
float:right;
|
||||
}
|
||||
|
||||
#iframe-container iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#mocha {
|
||||
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
border-right: 2px solid #999;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
overflow: auto;
|
||||
float:left;
|
||||
}
|
||||
|
||||
#mocha #report {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
#mocha ul, #mocha li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#mocha ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#mocha h1, #mocha h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#mocha h1 {
|
||||
margin-top: 15px;
|
||||
font-size: 1em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
#mocha h1 a:visited
|
||||
{
|
||||
color: #00E;
|
||||
}
|
||||
|
||||
#mocha .suite .suite h1 {
|
||||
margin-top: 0;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
#mocha h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#mocha .suite {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#mocha .test {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#mocha .test:hover h2::after {
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: -10px;
|
||||
content: '(view source)';
|
||||
font-size: 12px;
|
||||
font-family: arial;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#mocha .test.pending:hover h2::after {
|
||||
content: '(pending)';
|
||||
font-family: arial;
|
||||
}
|
||||
|
||||
#mocha .test.pass.medium .duration {
|
||||
background: #C09853;
|
||||
}
|
||||
|
||||
#mocha .test.pass.slow .duration {
|
||||
background: #B94A48;
|
||||
}
|
||||
|
||||
#mocha .test.pass::before {
|
||||
content: '✓';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #00d6b2;
|
||||
}
|
||||
|
||||
#mocha .test.pass .duration {
|
||||
font-size: 9px;
|
||||
margin-left: 5px;
|
||||
padding: 2px 5px;
|
||||
color: white;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mocha .test.pass.fast .duration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha .test.pending {
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.pending::before {
|
||||
content: '◦';
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.fail {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test.fail pre {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha .test.fail::before {
|
||||
content: '✖';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test pre.error {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test pre {
|
||||
display: inline-block;
|
||||
font: 12px/1.5 monaco, monospace;
|
||||
margin: 5px;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
border-bottom-color: #ddd;
|
||||
-webkit-border-radius: 3px;
|
||||
-webkit-box-shadow: 0 1px 3px #eee;
|
||||
}
|
||||
|
||||
#report.pass .test.fail {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#report.fail .test.pass {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#error {
|
||||
color: #c00;
|
||||
font-size: 1.5 em;
|
||||
font-weight: 100;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#stats {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 10px;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#stats .progress {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#stats em {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#stats a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#stats a:hover {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#stats li {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
list-style: none;
|
||||
padding-top: 11px;
|
||||
}
|
||||
|
||||
code .comment { color: #ddd }
|
||||
code .init { color: #2F6FAD }
|
||||
code .string { color: #5890AD }
|
||||
code .keyword { color: #8A6343 }
|
||||
code .number { color: #2F6FAD }
|
|
@ -0,0 +1,192 @@
|
|||
$(function(){
|
||||
function Base(runner) {
|
||||
var self = this
|
||||
, stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
|
||||
, failures = this.failures = [];
|
||||
|
||||
if (!runner) return;
|
||||
this.runner = runner;
|
||||
|
||||
runner.on('start', function(){
|
||||
stats.start = new Date;
|
||||
});
|
||||
|
||||
runner.on('suite', function(suite){
|
||||
stats.suites = stats.suites || 0;
|
||||
suite.root || stats.suites++;
|
||||
});
|
||||
|
||||
runner.on('test end', function(test){
|
||||
stats.tests = stats.tests || 0;
|
||||
stats.tests++;
|
||||
});
|
||||
|
||||
runner.on('pass', function(test){
|
||||
stats.passes = stats.passes || 0;
|
||||
|
||||
var medium = test.slow() / 2;
|
||||
test.speed = test.duration > test.slow()
|
||||
? 'slow'
|
||||
: test.duration > medium
|
||||
? 'medium'
|
||||
: 'fast';
|
||||
|
||||
stats.passes++;
|
||||
});
|
||||
|
||||
runner.on('fail', function(test, err){
|
||||
stats.failures = stats.failures || 0;
|
||||
stats.failures++;
|
||||
test.err = err;
|
||||
failures.push(test);
|
||||
});
|
||||
|
||||
runner.on('end', function(){
|
||||
stats.end = new Date;
|
||||
stats.duration = new Date - stats.start;
|
||||
});
|
||||
|
||||
runner.on('pending', function(){
|
||||
stats.pending++;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
This reporter wraps the original html reporter plus reports plain text into a hidden div.
|
||||
This allows the webdriver client to pick up the test results
|
||||
*/
|
||||
var WebdriverAndHtmlReporter = function(html_reporter){
|
||||
return function(runner){
|
||||
Base.call(this, runner);
|
||||
//initalize the html reporter first
|
||||
html_reporter(runner);
|
||||
|
||||
var $console = $("#console");
|
||||
var level = 0;
|
||||
var append = function(){
|
||||
var text = Array.prototype.join.apply(arguments, [" "]);
|
||||
var oldText = $console.text();
|
||||
|
||||
var space = "";
|
||||
for(var i=0;i<level*2;i++){
|
||||
space+=" ";
|
||||
}
|
||||
|
||||
var splitedText = "";
|
||||
_(text.split("\n")).each(function(line){
|
||||
while(line.length > 0){
|
||||
var split = line.substr(0,100);
|
||||
line = line.substr(100);
|
||||
if(splitedText.length > 0) splitedText+="\n";
|
||||
splitedText += split;
|
||||
}
|
||||
});
|
||||
|
||||
//indent all lines with the given amount of space
|
||||
var newText = _(splitedText.split("\n")).map(function(line){
|
||||
return space + line;
|
||||
}).join("\\n");
|
||||
|
||||
$console.text(oldText + newText + "\\n");
|
||||
}
|
||||
|
||||
runner.on('suite', function(suite){
|
||||
if (suite.root) return;
|
||||
|
||||
append(suite.title);
|
||||
level++;
|
||||
});
|
||||
|
||||
runner.on('suite end', function(suite){
|
||||
if (suite.root) return;
|
||||
level--;
|
||||
|
||||
if(level == 0) {
|
||||
append("");
|
||||
}
|
||||
});
|
||||
|
||||
var stringifyException = function(exception){
|
||||
var err = exception.stack || exception.toString();
|
||||
|
||||
// FF / Opera do not add the message
|
||||
if (!~err.indexOf(exception.message)) {
|
||||
err = exception.message + '\n' + err;
|
||||
}
|
||||
|
||||
// <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
|
||||
// check for the result of the stringifying.
|
||||
if ('[object Error]' == err) err = exception.message;
|
||||
|
||||
// Safari doesn't give you a stack. Let's at least provide a source line.
|
||||
if (!exception.stack && exception.sourceURL && exception.line !== undefined) {
|
||||
err += "\n(" + exception.sourceURL + ":" + exception.line + ")";
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
var killTimeout;
|
||||
runner.on('test end', function(test){
|
||||
if ('passed' == test.state) {
|
||||
append("->","[green]PASSED[clear] :", test.title);
|
||||
} else if (test.pending) {
|
||||
append("->","[yellow]PENDING[clear]:", test.title);
|
||||
} else {
|
||||
append("->","[red]FAILED[clear] :", test.title, stringifyException(test.err));
|
||||
}
|
||||
|
||||
if(killTimeout) clearTimeout(killTimeout);
|
||||
killTimeout = setTimeout(function(){
|
||||
append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]");
|
||||
}, 60000 * 3);
|
||||
});
|
||||
|
||||
var total = runner.total;
|
||||
runner.on('end', function(){
|
||||
if(stats.tests >= total){
|
||||
var minutes = Math.floor(stats.duration / 1000 / 60);
|
||||
var seconds = Math.round((stats.duration / 1000) % 60);
|
||||
|
||||
append("FINISHED -", stats.passes, "tests passed,", stats.failures, "tests failed, duration: " + minutes + ":" + seconds);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//allow cross iframe access
|
||||
if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) {
|
||||
document.domain = document.domain; // for comet
|
||||
}
|
||||
|
||||
//http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery
|
||||
var getURLParameter = function (name) {
|
||||
return decodeURI(
|
||||
(RegExp(name + '=' + '(.+?)(&|$)').exec(location.search)||[,null])[1]
|
||||
);
|
||||
}
|
||||
|
||||
//get the list of specs and filter it if requested
|
||||
var specs = specs_list.slice();
|
||||
|
||||
//inject spec scripts into the dom
|
||||
var $body = $('body');
|
||||
$.each(specs, function(i, spec){
|
||||
$body.append('<script src="specs/' + spec + '"></script>')
|
||||
});
|
||||
|
||||
//initalize the test helper
|
||||
helper.init(function(){
|
||||
//configure and start the test framework
|
||||
var grep = getURLParameter("grep");
|
||||
if(grep != "null"){
|
||||
mocha.grep(grep);
|
||||
}
|
||||
|
||||
mocha.ignoreLeaks();
|
||||
|
||||
mocha.reporter(WebdriverAndHtmlReporter(mocha._reporter));
|
||||
|
||||
mocha.run();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
describe("bold button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text bold", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
//get the bold button and click it
|
||||
var $boldButton = chrome$(".buttonicon-bold");
|
||||
$boldButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
// is there a <b> element now?
|
||||
var isBold = $newFirstTextElement.find("b").length === 1;
|
||||
|
||||
//expect it to be bold
|
||||
expect(isBold).to.be(true);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
describe("clear authorship colors button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text clear authorship colors", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
// override the confirm dialogue functioon
|
||||
helper.padChrome$.window.confirm = function(){
|
||||
return true;
|
||||
}
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
// Get the original text
|
||||
var originalText = inner$("div").first().text();
|
||||
|
||||
// Set some new text
|
||||
var sentText = "Hello";
|
||||
|
||||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
$firstTextElement.sendkeys(sentText);
|
||||
$firstTextElement.sendkeys('{rightarrow}');
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div span").first().attr("class").indexOf("author") !== -1; // wait until we have the full value available
|
||||
}).done(function(){
|
||||
//IE hates you if you don't give focus to the inner frame bevore you do a clearAuthorship
|
||||
inner$("div").first().focus();
|
||||
|
||||
//get the clear authorship colors button and click it
|
||||
var $clearauthorshipcolorsButton = chrome$(".buttonicon-clearauthorship");
|
||||
$clearauthorshipcolorsButton.click();
|
||||
|
||||
// does the first divs span include an author class?
|
||||
console.log(inner$("div span").first().attr("class"));
|
||||
var hasAuthorClass = inner$("div span").first().attr("class").indexOf("author") !== -1;
|
||||
//expect(hasAuthorClass).to.be(false);
|
||||
|
||||
// does the first div include an author class?
|
||||
var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1;
|
||||
expect(hasAuthorClass).to.be(false);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -0,0 +1,179 @@
|
|||
describe("indentation button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("indent text", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
var $indentButton = chrome$(".buttonicon-indent");
|
||||
$indentButton.click();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div").first().find("ul li").length === 1;
|
||||
}).done(done);
|
||||
});
|
||||
|
||||
it("keeps the indent on enter for the new line", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
var $indentButton = chrome$(".buttonicon-indent");
|
||||
$indentButton.click();
|
||||
|
||||
//type a bit, make a line break and type again
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
$firstTextElement.sendkeys('line 1');
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
$firstTextElement.sendkeys('line 2');
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div span").first().text().indexOf("line 2") === -1;
|
||||
}).done(function(){
|
||||
var $newSecondLine = inner$("div").first().next();
|
||||
var hasULElement = $newSecondLine.find("ul li").length === 1;
|
||||
|
||||
expect(hasULElement).to.be(true);
|
||||
expect($newSecondLine.text()).to.be("line 2");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
it("makes text indented and outdented", function() {
|
||||
|
||||
//get the inner iframe
|
||||
var $inner = testHelper.$getPadInner();
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var firstTextElement = $inner.find("div").first();
|
||||
|
||||
//select this text element
|
||||
testHelper.selectText(firstTextElement[0], $inner);
|
||||
|
||||
//get the indentation button and click it
|
||||
var $indentButton = testHelper.$getPadChrome().find(".buttonicon-indent");
|
||||
$indentButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var newFirstTextElement = $inner.find("div").first();
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = newFirstTextElement.children(":first");
|
||||
var isUL = firstChild.is('ul');
|
||||
|
||||
//expect it to be the beginning of a list
|
||||
expect(isUL).to.be(true);
|
||||
|
||||
var secondChild = firstChild.children(":first");
|
||||
var isLI = secondChild.is('li');
|
||||
//expect it to be part of a list
|
||||
expect(isLI).to.be(true);
|
||||
|
||||
//indent again
|
||||
$indentButton.click();
|
||||
|
||||
var newFirstTextElement = $inner.find("div").first();
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = newFirstTextElement.children(":first");
|
||||
var hasListIndent2 = firstChild.hasClass('list-indent2');
|
||||
|
||||
//expect it to be part of a list
|
||||
expect(hasListIndent2).to.be(true);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect(newFirstTextElement.text()).to.eql(firstTextElement.text());
|
||||
|
||||
|
||||
// test outdent
|
||||
|
||||
//get the unindentation button and click it twice
|
||||
var $outdentButton = testHelper.$getPadChrome().find(".buttonicon-outdent");
|
||||
$outdentButton.click();
|
||||
$outdentButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var newFirstTextElement = $inner.find("div").first();
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = newFirstTextElement.children(":first");
|
||||
var isUL = firstChild.is('ul');
|
||||
|
||||
//expect it not to be the beginning of a list
|
||||
expect(isUL).to.be(false);
|
||||
|
||||
var secondChild = firstChild.children(":first");
|
||||
var isLI = secondChild.is('li');
|
||||
//expect it to not be part of a list
|
||||
expect(isLI).to.be(false);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect(newFirstTextElement.text()).to.eql(firstTextElement.text());
|
||||
|
||||
|
||||
// Next test tests multiple line indentation
|
||||
|
||||
//select this text element
|
||||
testHelper.selectText(firstTextElement[0], $inner);
|
||||
|
||||
//indent twice
|
||||
$indentButton.click();
|
||||
$indentButton.click();
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var firstTextElement = $inner.find("div").first();
|
||||
|
||||
//select this text element
|
||||
testHelper.selectText(firstTextElement[0], $inner);
|
||||
|
||||
/* this test creates the below content, both should have double indentation
|
||||
line1
|
||||
line2
|
||||
|
||||
|
||||
firstTextElement.sendkeys('{rightarrow}'); // simulate a keypress of enter
|
||||
firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter
|
||||
firstTextElement.sendkeys('line 1'); // simulate writing the first line
|
||||
firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter
|
||||
firstTextElement.sendkeys('line 2'); // simulate writing the second line
|
||||
|
||||
//get the second text element out of the inner iframe
|
||||
setTimeout(function(){ // THIS IS REALLY BAD
|
||||
var secondTextElement = $('iframe').contents().find('iframe').contents().find('iframe').contents().find('body > div').get(1); // THIS IS UGLY
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = secondTextElement.children(":first");
|
||||
var isUL = firstChild.is('ul');
|
||||
|
||||
//expect it to be the beginning of a list
|
||||
expect(isUL).to.be(true);
|
||||
|
||||
var secondChild = secondChild.children(":first");
|
||||
var isLI = secondChild.is('li');
|
||||
//expect it to be part of a list
|
||||
expect(isLI).to.be(true);
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var thirdTextElement = $('iframe').contents().find('iframe').contents().find('iframe').contents().find('body > div').get(2); // THIS IS UGLY TOO
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = thirdTextElement.children(":first");
|
||||
var isUL = firstChild.is('ul');
|
||||
|
||||
//expect it to be the beginning of a list
|
||||
expect(isUL).to.be(true);
|
||||
|
||||
var secondChild = firstChild.children(":first");
|
||||
var isLI = secondChild.is('li');
|
||||
|
||||
//expect it to be part of a list
|
||||
expect(isLI).to.be(true);
|
||||
},1000);
|
||||
});*/
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
describe("italic button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text italic", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
//get the bold button and click it
|
||||
var $boldButton = chrome$(".buttonicon-italic");
|
||||
$boldButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
// is there a <i> element now?
|
||||
var isItalic = $newFirstTextElement.find("i").length === 1;
|
||||
|
||||
//expect it to be bold
|
||||
expect(isItalic).to.be(true);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
describe("assign ordered list", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("insert ordered list text", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
var $insertorderedlistButton = chrome$(".buttonicon-insertorderedlist");
|
||||
$insertorderedlistButton.click();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div").first().find("ol li").length === 1;
|
||||
}).done(done);
|
||||
});
|
||||
|
||||
it("keeps the numbered list on enter for the new line - EMULATES PASTING INTO A PAD", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
var $insertorderedlistButton = chrome$(".buttonicon-insertorderedlist");
|
||||
$insertorderedlistButton.click();
|
||||
|
||||
//type a bit, make a line break and type again
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
$firstTextElement.sendkeys('line 1');
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
$firstTextElement.sendkeys('line 2');
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div span").first().text().indexOf("line 2") === -1;
|
||||
}).done(function(){
|
||||
var $newSecondLine = inner$("div").first().next();
|
||||
var hasOLElement = $newSecondLine.find("ol li").length === 1;
|
||||
console.log($newSecondLine.find("ol"));
|
||||
expect(hasOLElement).to.be(true);
|
||||
expect($newSecondLine.text()).to.be("line 2");
|
||||
var hasLineNumber = $newSecondLine.find("ol").attr("start") === 2;
|
||||
expect(hasLineNumber).to.be(true); // This doesn't work because pasting in content doesn't work
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
describe("undo button then redo button", function(){
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb); // creates a new pad
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("undo some typing", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
// get the first text element inside the editable space
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
var originalValue = $firstTextElement.text(); // get the original value
|
||||
var newString = "Foo";
|
||||
|
||||
$firstTextElement.sendkeys(newString); // send line 1 to the pad
|
||||
var modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
expect(modifiedValue).not.to.be(originalValue); // expect the value to change
|
||||
|
||||
// get undo and redo buttons
|
||||
var $undoButton = chrome$(".buttonicon-undo");
|
||||
var $redoButton = chrome$(".buttonicon-redo");
|
||||
// click the buttons
|
||||
$undoButton.click(); // removes foo
|
||||
$redoButton.click(); // resends foo
|
||||
|
||||
helper.waitFor(function(){
|
||||
console.log(inner$("div span").first().text());
|
||||
return inner$("div span").first().text() === newString;
|
||||
}).done(function(){
|
||||
var finalValue = inner$("div").first().text();
|
||||
expect(finalValue).to.be(modifiedValue); // expect the value to change
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
describe("strikethrough button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text strikethrough", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
//get the strikethrough button and click it
|
||||
var $strikethroughButton = chrome$(".buttonicon-strikethrough");
|
||||
$strikethroughButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
// is there a <i> element now?
|
||||
var isstrikethrough = $newFirstTextElement.find("s").length === 1;
|
||||
|
||||
//expect it to be strikethrough
|
||||
expect(isstrikethrough).to.be(true);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
//deactivated, we need a nice way to get the timeslider, this is ugly
|
||||
xdescribe("timeslider button takes you to the timeslider of a pad", function(){
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb); // creates a new pad
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("timeslider contained in URL", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
// get the first text element inside the editable space
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
var originalValue = $firstTextElement.text(); // get the original value
|
||||
var newValue = "Testing"+originalValue;
|
||||
$firstTextElement.sendkeys("Testing"); // send line 1 to the pad
|
||||
|
||||
var modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
expect(modifiedValue).not.to.be(originalValue); // expect the value to change
|
||||
|
||||
helper.waitFor(function(){
|
||||
return modifiedValue !== originalValue; // The value has changed so we can..
|
||||
}).done(function(){
|
||||
|
||||
var $timesliderButton = chrome$("#timesliderlink");
|
||||
$timesliderButton.click(); // So click the timeslider link
|
||||
|
||||
helper.waitFor(function(){
|
||||
var iFrameURL = chrome$.window.location.href;
|
||||
if(iFrameURL){
|
||||
return iFrameURL.indexOf("timeslider") !== -1;
|
||||
}else{
|
||||
return false; // the URL hasnt been set yet
|
||||
}
|
||||
}).done(function(){
|
||||
// click the buttons
|
||||
var iFrameURL = chrome$.window.location.href; // get the url
|
||||
var inTimeslider = iFrameURL.indexOf("timeslider") !== -1;
|
||||
expect(inTimeslider).to.be(true); // expect the value to change
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
describe("undo button", function(){
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb); // creates a new pad
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("undo some typing", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
// get the first text element inside the editable space
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
var originalValue = $firstTextElement.text(); // get the original value
|
||||
|
||||
$firstTextElement.sendkeys("foo"); // send line 1 to the pad
|
||||
var modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
expect(modifiedValue).not.to.be(originalValue); // expect the value to change
|
||||
|
||||
// get clear authorship button as a variable
|
||||
var $undoButton = chrome$(".buttonicon-undo");
|
||||
// click the button
|
||||
$undoButton.click();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div span").first().text() === originalValue;
|
||||
}).done(function(){
|
||||
var finalValue = inner$("div span").first().text();
|
||||
expect(finalValue).to.be(originalValue); // expect the value to change
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
describe("change username value", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("Remembers the user name after a refresh", function(done) {
|
||||
this.timeout(60000);
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $userButton = chrome$(".buttonicon-showusers");
|
||||
$userButton.click();
|
||||
|
||||
var $usernameInput = chrome$("#myusernameedit");
|
||||
$usernameInput.click();
|
||||
|
||||
$usernameInput.val('John McLear');
|
||||
$usernameInput.blur();
|
||||
|
||||
setTimeout(function(){ //give it a second to save the username on the server side
|
||||
helper.newPad({ // get a new pad, but don't clear the cookies
|
||||
clearCookies: false
|
||||
, cb: function(){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $userButton = chrome$(".buttonicon-showusers");
|
||||
$userButton.click();
|
||||
|
||||
var $usernameInput = chrome$("#myusernameedit");
|
||||
expect($usernameInput.val()).to.be('John McLear')
|
||||
done();
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
|
||||
|
||||
it("Own user name is shown when you enter a chat", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $userButton = chrome$(".buttonicon-showusers");
|
||||
$userButton.click();
|
||||
|
||||
var $usernameInput = chrome$("#myusernameedit");
|
||||
$usernameInput.click();
|
||||
|
||||
$usernameInput.val('John McLear');
|
||||
$usernameInput.blur();
|
||||
|
||||
//click on the chat button to make chat visible
|
||||
var $chatButton = chrome$("#chaticon");
|
||||
$chatButton.click();
|
||||
var $chatInput = chrome$("#chatinput");
|
||||
$chatInput.sendkeys('O hi'); // simulate a keypress of typing JohnMcLear
|
||||
$chatInput.sendkeys('{enter}'); // simulate a keypress of enter actually does evt.which = 10 not 13
|
||||
|
||||
//check if chat shows up
|
||||
helper.waitFor(function(){
|
||||
return chrome$("#chattext").children("p").length !== 0; // wait until the chat message shows up
|
||||
}).done(function(){
|
||||
var $firstChatMessage = chrome$("#chattext").children("p");
|
||||
var containsJohnMcLear = $firstChatMessage.text().indexOf("John McLear") !== -1; // does the string contain John McLear
|
||||
expect(containsJohnMcLear).to.be(true); // expect the first chat message to contain JohnMcLear
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
describe("chat always ons creen select", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes chat stick to right side of the screen", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $settingsButton = chrome$(".buttonicon-settings");
|
||||
$settingsButton.click();
|
||||
|
||||
//get the chat selector
|
||||
var $stickychatCheckbox = chrome$("#options-stickychat");
|
||||
|
||||
//select chat always on screen and fire change event
|
||||
$stickychatCheckbox.attr('selected','selected');
|
||||
$stickychatCheckbox.change();
|
||||
$stickychatCheckbox.click();
|
||||
|
||||
//check if chat changed to get the stickychat Class
|
||||
var $chatbox = chrome$("#chatbox");
|
||||
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
|
||||
expect(hasStickyChatClass).to.be(true);
|
||||
|
||||
//select chat always on screen and fire change event
|
||||
$stickychatCheckbox.attr('selected','selected');
|
||||
$stickychatCheckbox.change();
|
||||
$stickychatCheckbox.click();
|
||||
|
||||
//check if chat changed to remove the stickychat Class
|
||||
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
|
||||
expect(hasStickyChatClass).to.be(false);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,133 @@
|
|||
describe("embed links", function(){
|
||||
var objectify = function (str)
|
||||
{
|
||||
var hash = {};
|
||||
var parts = str.split('&');
|
||||
for(var i = 0; i < parts.length; i++)
|
||||
{
|
||||
var keyValue = parts[i].split('=');
|
||||
hash[keyValue[0]] = keyValue[1];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
var checkiFrameCode = function(embedCode, readonly){
|
||||
//turn the code into an html element
|
||||
var $embediFrame = $(embedCode);
|
||||
|
||||
//read and check the frame attributes
|
||||
var width = $embediFrame.attr("width");
|
||||
var height = $embediFrame.attr("height");
|
||||
var name = $embediFrame.attr("name");
|
||||
expect(width).to.be('600');
|
||||
expect(height).to.be('400');
|
||||
expect(name).to.be(readonly ? "embed_readonly" : "embed_readwrite");
|
||||
|
||||
//parse the url
|
||||
var src = $embediFrame.attr("src");
|
||||
var questionMark = src.indexOf("?");
|
||||
var url = src.substr(0,questionMark);
|
||||
var paramsStr = src.substr(questionMark+1);
|
||||
var params = objectify(paramsStr);
|
||||
|
||||
var expectedParams = {
|
||||
showControls: 'true'
|
||||
, showChat: 'true'
|
||||
, showLineNumbers: 'true'
|
||||
, useMonospaceFont: 'false'
|
||||
}
|
||||
|
||||
//check the url
|
||||
if(readonly){
|
||||
expect(url.indexOf("r.") > 0).to.be(true);
|
||||
} else {
|
||||
expect(url).to.be(helper.padChrome$.window.location.href);
|
||||
}
|
||||
|
||||
//check if all parts of the url are like expected
|
||||
expect(params).to.eql(expectedParams);
|
||||
}
|
||||
|
||||
describe("read and write", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
describe("the share link", function(){
|
||||
it("is the actual pad url", function(done){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//open share dropdown
|
||||
chrome$(".buttonicon-embed").click();
|
||||
|
||||
//get the link of the share field + the actual pad url and compare them
|
||||
var shareLink = chrome$("#linkinput").val();
|
||||
var padURL = chrome$.window.location.href;
|
||||
expect(shareLink).to.be(padURL);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("the embed as iframe code", function(){
|
||||
it("is an iframe with the the correct url parameters and correct size", function(done){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//open share dropdown
|
||||
chrome$(".buttonicon-embed").click();
|
||||
|
||||
//get the link of the share field + the actual pad url and compare them
|
||||
var embedCode = chrome$("#embedinput").val();
|
||||
|
||||
checkiFrameCode(embedCode, false)
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when read only option is set", function(){
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
describe("the share link", function(){
|
||||
it("shows a read only url", function(done){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//open share dropdown
|
||||
chrome$(".buttonicon-embed").click();
|
||||
//check read only checkbox, a bit hacky
|
||||
chrome$('#readonlyinput').attr('checked','checked').click().attr('checked','checked');
|
||||
|
||||
//get the link of the share field + the actual pad url and compare them
|
||||
var shareLink = chrome$("#linkinput").val();
|
||||
var containsReadOnlyLink = shareLink.indexOf("r.") > 0
|
||||
expect(containsReadOnlyLink).to.be(true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("the embed as iframe code", function(){
|
||||
it("is an iframe with the the correct url parameters and correct size", function(done){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//open share dropdown
|
||||
chrome$(".buttonicon-embed").click();
|
||||
//check read only checkbox, a bit hacky
|
||||
chrome$('#readonlyinput').attr('checked','checked').click().attr('checked','checked');
|
||||
|
||||
//get the link of the share field + the actual pad url and compare them
|
||||
var embedCode = chrome$("#embedinput").val();
|
||||
|
||||
checkiFrameCode(embedCode, true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
describe("font select", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text monospace", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $settingsButton = chrome$(".buttonicon-settings");
|
||||
$settingsButton.click();
|
||||
|
||||
//get the font menu and monospace option
|
||||
var $viewfontmenu = chrome$("#viewfontmenu");
|
||||
var $monospaceoption = $viewfontmenu.find("[value=monospace]");
|
||||
|
||||
//select monospace and fire change event
|
||||
$monospaceoption.attr('selected','selected');
|
||||
$viewfontmenu.change();
|
||||
|
||||
//check if font changed to monospace
|
||||
var fontFamily = inner$("body").css("font-family").toLowerCase();
|
||||
expect(fontFamily).to.be("monospace");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,99 @@
|
|||
describe("the test helper", function(){
|
||||
describe("the newPad method", function(){
|
||||
xit("doesn't leak memory if you creates iframes over and over again", function(done){
|
||||
this.timeout(100000);
|
||||
|
||||
var times = 10;
|
||||
|
||||
var loadPad = function(){
|
||||
helper.newPad(function(){
|
||||
times--;
|
||||
if(times > 0){
|
||||
loadPad();
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadPad();
|
||||
});
|
||||
|
||||
it("gives me 3 jquery instances of chrome, outer and inner", function(done){
|
||||
this.timeout(5000);
|
||||
|
||||
helper.newPad(function(){
|
||||
//check if the jquery selectors have the desired elements
|
||||
expect(helper.padChrome$("#editbar").length).to.be(1);
|
||||
expect(helper.padOuter$("#outerdocbody").length).to.be(1);
|
||||
expect(helper.padInner$("#innerdocbody").length).to.be(1);
|
||||
|
||||
//check if the document object was set correctly
|
||||
expect(helper.padChrome$.window.document).to.be(helper.padChrome$.document);
|
||||
expect(helper.padOuter$.window.document).to.be(helper.padOuter$.document);
|
||||
expect(helper.padInner$.window.document).to.be(helper.padInner$.document);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("the waitFor method", function(){
|
||||
it("takes a timeout and waits long enough", function(done){
|
||||
this.timeout(2000);
|
||||
var startTime = new Date().getTime();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return false;
|
||||
}, 1500).fail(function(){
|
||||
var duration = new Date().getTime() - startTime;
|
||||
expect(duration).to.be.greaterThan(1400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("takes an interval and checks on every interval", function(done){
|
||||
this.timeout(4000);
|
||||
var checks = 0;
|
||||
|
||||
helper.waitFor(function(){
|
||||
checks++;
|
||||
return false;
|
||||
}, 2000, 100).fail(function(){
|
||||
expect(checks).to.be.greaterThan(10);
|
||||
expect(checks).to.be.lessThan(30);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("returns a deferred object", function(){
|
||||
it("it calls done after success", function(done){
|
||||
helper.waitFor(function(){
|
||||
return true;
|
||||
}).done(function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("calls fail after failure", function(done){
|
||||
helper.waitFor(function(){
|
||||
return false;
|
||||
},0).fail(function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
xit("throws if you don't listen for fails", function(done){
|
||||
var onerror = window.onerror;
|
||||
window.onerror = function(){
|
||||
window.onerror = onerror;
|
||||
done();
|
||||
}
|
||||
|
||||
helper.waitFor(function(){
|
||||
return false;
|
||||
},100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
describe("send chat message", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("opens chat, sends a message and makes sure it exists on the page", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
var chatValue = "JohnMcLear";
|
||||
|
||||
//click on the chat button to make chat visible
|
||||
var $chatButton = chrome$("#chaticon");
|
||||
$chatButton.click();
|
||||
var $chatInput = chrome$("#chatinput");
|
||||
$chatInput.sendkeys('JohnMcLear'); // simulate a keypress of typing JohnMcLear
|
||||
$chatInput.sendkeys('{enter}'); // simulate a keypress of enter actually does evt.which = 10 not 13
|
||||
|
||||
//check if chat shows up
|
||||
helper.waitFor(function(){
|
||||
return chrome$("#chattext").children("p").length !== 0; // wait until the chat message shows up
|
||||
}).done(function(){
|
||||
var $firstChatMessage = chrome$("#chattext").children("p");
|
||||
var containsMessage = $firstChatMessage.text().indexOf("JohnMcLear") !== -1; // does the string contain JohnMcLear?
|
||||
expect(containsMessage).to.be(true); // expect the first chat message to contain JohnMcLear
|
||||
|
||||
// do a slightly more thorough check
|
||||
var username = $firstChatMessage.children("b");
|
||||
var usernameValue = username.text();
|
||||
var time = $firstChatMessage.children(".time");
|
||||
var timeValue = time.text();
|
||||
var expectedStringIncludingUserNameAndTime = usernameValue + timeValue + " " + "JohnMcLear";
|
||||
expect(expectedStringIncludingUserNameAndTime).to.be($firstChatMessage.text());
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
describe("delete keystroke", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text delete", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
// get the original length of this element
|
||||
var elementLength = $firstTextElement.text().length;
|
||||
|
||||
// get the original string value minus the last char
|
||||
var originalTextValue = $firstTextElement.text();
|
||||
originalTextValueMinusFirstChar = originalTextValue.substring(1, originalTextValue.length );
|
||||
|
||||
// simulate key presses to delete content
|
||||
$firstTextElement.sendkeys('{leftarrow}'); // simulate a keypress of the left arrow key
|
||||
$firstTextElement.sendkeys('{del}'); // simulate a keypress of delete
|
||||
|
||||
//ace creates a new dom element when you press a keystroke, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
// get the new length of this element
|
||||
var newElementLength = $newFirstTextElement.text().length;
|
||||
|
||||
//expect it to be one char less in length
|
||||
expect(newElementLength).to.be((elementLength-1));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
describe("enter keystroke", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("creates a enw line & puts cursor onto a new line", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
// get the original string value minus the last char
|
||||
var originalTextValue = $firstTextElement.text();
|
||||
|
||||
// simulate key presses to enter content
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
|
||||
//ace creates a new dom element when you press a keystroke, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div").first().text() === "";
|
||||
}).done(function(){
|
||||
var $newSecondLine = inner$("div").first().next();
|
||||
var newFirstTextElementValue = inner$("div").first().text();
|
||||
expect(newFirstTextElementValue).to.be(""); // expect the first line to be blank
|
||||
expect($newSecondLine.text()).to.be(originalTextValue); // expect the second line to be the same as the original first line.
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
describe("urls", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("when you enter an url, it becomes clickable", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var firstTextElement = inner$("div").first();
|
||||
|
||||
// simulate key presses to delete content
|
||||
firstTextElement.sendkeys('{selectall}'); // select all
|
||||
firstTextElement.sendkeys('{del}'); // clear the first line
|
||||
firstTextElement.sendkeys('http://etherpad.org'); // insert a URL
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div").first().find("a").length === 1;
|
||||
}, 2000).done(done);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
sauce_connect.log
|
||||
sauce_connect.log.*
|
|
@ -0,0 +1,110 @@
|
|||
var srcFolder = "../../../src/node_modules/";
|
||||
var wd = require(srcFolder + "wd");
|
||||
var async = require(srcFolder + "async");
|
||||
|
||||
var config = {
|
||||
host: "ondemand.saucelabs.com"
|
||||
, port: 80
|
||||
, username: process.env.SAUCE_USER
|
||||
, accessKey: process.env.SAUCE_KEY
|
||||
}
|
||||
|
||||
var allTestsPassed = true;
|
||||
|
||||
var sauceTestWorker = async.queue(function (testSettings, callback) {
|
||||
var browser = wd.remote(config.host, config.port, config.username, config.accessKey);
|
||||
var browserChain = browser.chain();
|
||||
var name = process.env.GIT_HASH + " - " + testSettings.browserName + " " + testSettings.version + ", " + testSettings.platform;
|
||||
testSettings.name = name;
|
||||
testSettings["public"] = true;
|
||||
testSettings["build"] = process.env.GIT_HASH;
|
||||
|
||||
browserChain.init(testSettings).get("http://localhost:9001/tests/frontend/", function(){
|
||||
var url = "https://saucelabs.com/jobs/" + browser.sessionID;
|
||||
console.log("Remote sauce test '" + name + "' started! " + url);
|
||||
|
||||
//tear down the test excecution
|
||||
var stopSauce = function(success){
|
||||
getStatusInterval && clearInterval(getStatusInterval);
|
||||
clearTimeout(timeout);
|
||||
|
||||
browserChain.quit();
|
||||
|
||||
if(!success){
|
||||
allTestsPassed = false;
|
||||
}
|
||||
|
||||
var testResult = knownConsoleText.replace(/\[red\]/g,'\x1B[31m').replace(/\[yellow\]/g,'\x1B[33m')
|
||||
.replace(/\[green\]/g,'\x1B[32m').replace(/\[clear\]/g, '\x1B[39m');
|
||||
testResult = testResult.split("\\n").map(function(line){
|
||||
return "[" + testSettings.browserName + (testSettings.version === "" ? '' : (" " + testSettings.version)) + "] " + line;
|
||||
}).join("\n");
|
||||
|
||||
console.log(testResult);
|
||||
console.log("Remote sauce test '" + name + "' finished! " + url);
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
//timeout for the case the test hangs
|
||||
var timeout = setTimeout(function(){
|
||||
stopSauce(false);
|
||||
}, 60000 * 10);
|
||||
|
||||
var knownConsoleText = "";
|
||||
var getStatusInterval = setInterval(function(){
|
||||
browserChain.eval("$('#console').text()", function(err, consoleText){
|
||||
if(!consoleText || err){
|
||||
return;
|
||||
}
|
||||
knownConsoleText = consoleText;
|
||||
|
||||
if(knownConsoleText.indexOf("FINISHED") > 0){
|
||||
var success = knownConsoleText.indexOf("FAILED") === -1;
|
||||
stopSauce(success);
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
}, 5); //run 5 tests in parrallel
|
||||
|
||||
// Firefox
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Linux'
|
||||
, 'browserName' : 'firefox'
|
||||
, 'version' : ''
|
||||
});
|
||||
|
||||
// Chrome
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Linux'
|
||||
, 'browserName' : 'googlechrome'
|
||||
, 'version' : ''
|
||||
});
|
||||
|
||||
// IE 8
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Windows 2003'
|
||||
, 'browserName' : 'iexplore'
|
||||
, 'version' : '8'
|
||||
});
|
||||
|
||||
// IE 9
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Windows 2008'
|
||||
, 'browserName' : 'iexplore'
|
||||
, 'version' : '9'
|
||||
});
|
||||
|
||||
// IE 10
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Windows 2012'
|
||||
, 'browserName' : 'iexplore'
|
||||
, 'version' : '10'
|
||||
});
|
||||
|
||||
sauceTestWorker.drain = function() {
|
||||
setTimeout(function(){
|
||||
process.exit(allTestsPassed ? 0 : 1);
|
||||
}, 3000);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/sh
|
||||
|
||||
#Move to the base folder
|
||||
cd `dirname $0`
|
||||
|
||||
#start etherpad lite
|
||||
../../../bin/run.sh > /dev/null &
|
||||
sleep 10
|
||||
|
||||
#start remote runner
|
||||
node remote_runner.js
|
||||
exit_code=$?
|
||||
|
||||
kill $!
|
||||
kill $(cat /tmp/sauce.pid)
|
||||
sleep 30
|
||||
|
||||
exit $exit_code
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
# download and unzip the sauce connector
|
||||
curl http://saucelabs.com/downloads/Sauce-Connect-latest.zip > /tmp/sauce.zip
|
||||
unzip /tmp/sauce.zip -d /tmp
|
||||
|
||||
# start the sauce connector in background and make sure it doesn't output the secret key
|
||||
(java -jar /tmp/Sauce-Connect.jar $SAUCE_USER $SAUCE_KEY -f /tmp/tunnel > /dev/null )&
|
||||
|
||||
# save the sauce pid in a file
|
||||
echo $! > /tmp/sauce.pid
|
||||
|
||||
# wait for the tunnel to build up
|
||||
while [ ! -e "/tmp/tunnel" ]
|
||||
do
|
||||
sleep 1
|
||||
done
|
Loading…
Reference in New Issue