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
|
cd node_modules
|
||||||
[ -e ep_etherpad-lite ] || ln -s ../src ep_etherpad-lite
|
[ -e ep_etherpad-lite ] || ln -s ../src ep_etherpad-lite
|
||||||
cd ep_etherpad-lite
|
cd ep_etherpad-lite
|
||||||
npm install
|
npm install -s
|
||||||
) || {
|
) || {
|
||||||
rm -rf node_modules
|
rm -rf node_modules
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
{ "name": "importexport", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/importexport:expressCreateServer" } },
|
{ "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": "errorhandling", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling:expressCreateServer" } },
|
||||||
{ "name": "socketio", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio: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": "admin", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/admin:expressCreateServer" } },
|
||||||
{ "name": "adminplugins", "hooks": {
|
{ "name": "adminplugins", "hooks": {
|
||||||
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins:expressCreateServer",
|
"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" },
|
"bin": { "etherpad-lite": "./node/server.js" },
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jshint" : "*"
|
"jshint" : "*",
|
||||||
|
"wd" : "0.0.26"
|
||||||
},
|
},
|
||||||
"engines" : { "node" : ">=0.6.0",
|
"engines" : { "node" : ">=0.6.0",
|
||||||
"npm" : ">=1.0"
|
"npm" : ">=1.0"
|
||||||
|
|
|
@ -150,7 +150,7 @@ var chat = (function()
|
||||||
$("#chatinput").keypress(function(evt)
|
$("#chatinput").keypress(function(evt)
|
||||||
{
|
{
|
||||||
//if the user typed enter, fire the send
|
//if the user typed enter, fire the send
|
||||||
if(evt.which == 13)
|
if(evt.which == 13 || evt.which == 10)
|
||||||
{
|
{
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
self.send();
|
self.send();
|
||||||
|
|
|
@ -389,6 +389,10 @@ function handshake()
|
||||||
});
|
});
|
||||||
// Bind the colorpicker
|
// Bind the colorpicker
|
||||||
var fb = $('#colorpicker').farbtastic({ callback: '#mycolorpickerpreview', width: 220});
|
var fb = $('#colorpicker').farbtastic({ callback: '#mycolorpickerpreview', width: 220});
|
||||||
|
// Bind the read only button
|
||||||
|
$('#readonlyinput').on('click',function(){
|
||||||
|
padeditbar.setEmbedLinks();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var pad = {
|
var pad = {
|
||||||
|
|
|
@ -305,7 +305,7 @@
|
||||||
<div id="embed" class="popup">
|
<div id="embed" class="popup">
|
||||||
<% e.begin_block("embedPopup"); %>
|
<% e.begin_block("embedPopup"); %>
|
||||||
<div id="embedreadonly" class="right acl-write">
|
<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>
|
<label for="readonlyinput">Read only</label>
|
||||||
</div>
|
</div>
|
||||||
<h1>Share this pad</h1>
|
<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