add tests from origin/develop

This commit is contained in:
webzwo0i 2020-08-30 14:19:09 +02:00
parent 6a3e4c69b8
commit 2df2d7d60a
71 changed files with 18931 additions and 4032 deletions

View file

@ -0,0 +1,76 @@
/*
* Fuzz testing the import endpoint
* Usage: node fuzzImportTest.js
*/
const fs = require('fs');
const settings = require(__dirname+'/loadSettings').loadSettings();
const host = "http://" + settings.ip + ":" + settings.port;
const path = require('path');
const async = require(__dirname+'/../../src/node_modules/async');
const request = require('request');
const froth = require('mocha-froth');
var filePath = path.join(__dirname, '/../../APIKEY.txt');
var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'});
apiKey = apiKey.replace(/\n$/, "");
var apiVersion = 1;
var testPadId = "TEST_fuzz" + makeid();
var endPoint = function(point, version){
version = version || apiVersion;
return '/api/'+version+'/'+point+'?apikey='+apiKey;
}
console.log("Testing against padID", testPadId);
console.log("To watch the test live visit " + host + "/p/" + testPadId);
console.log("Tests will start in 5 seconds, click the URL now!");
setTimeout(function(){
for (let i=1; i<1000000; i++) { // 1M runs
setTimeout( function timer(){
runTest(i);
}, i*100 ); // 100 ms
}
},5000); // wait 5 seconds
function runTest(number){
request(host + endPoint('createPad') + '&padID=' + testPadId, function(err, res, body){
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
if (err) {
throw new Error("FAILURE", err);
}else{
console.log("Success");
}
});
var fN = '/test.txt';
var cT = 'text/plain';
// To be more agressive every other test we mess with Etherpad
// We provide a weird file name and also set a weird contentType
if (number % 2 == 0) {
fN = froth().toString();
cT = froth().toString();
}
let form = req.form();
form.append('file', froth().toString(), {
filename: fN,
contentType: cT
});
});
}
function makeid()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ ){
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

View file

@ -1,17 +0,0 @@
var jsonminify = require(__dirname+"/../../src/node_modules/jsonminify");
function loadSettings(){
var settingsStr = fs.readFileSync(__dirname+"/../../settings.json").toString();
// try to parse the settings
var settings;
try {
if(settingsStr) {
settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}");
return JSON.parse(settingsStr);
}
}catch(e){
console.error("whoops something is bad with settings");
}
}
exports.loadSettings = loadSettings;

View file

@ -0,0 +1,79 @@
/**
* API specs
*
* Tests for generic overarching HTTP API related features not related to any
* specific part of the data model or domain. For example: tests for versioning
* and openapi definitions.
*/
const assert = require('assert');
const supertest = require(__dirname + '/../../../../src/node_modules/supertest');
const fs = require('fs');
const settings = require(__dirname + '/../../../../src/node/utils/Settings');
const api = supertest('http://' + settings.ip + ':' + settings.port);
const path = require('path');
var validateOpenAPI = require(__dirname + '/../../../../src/node_modules/openapi-schema-validation').validate;
var filePath = path.join(__dirname, '../../../../APIKEY.txt');
var apiKey = fs.readFileSync(filePath, { encoding: 'utf-8' });
apiKey = apiKey.replace(/\n$/, '');
var apiVersion = 1;
var testPadId = makeid();
describe('API Versioning', function() {
it('errors if can not connect', function(done) {
api
.get('/api/')
.expect(function(res) {
apiVersion = res.body.currentVersion;
if (!res.body.currentVersion) throw new Error('No version set in API');
return;
})
.expect(200, done);
});
});
describe('OpenAPI definition', function() {
it('generates valid openapi definition document', function(done) {
api
.get('/api/openapi.json')
.expect(function(res) {
const { valid, errors } = validateOpenAPI(res.body, 3);
if (!valid) {
const prettyErrors = JSON.stringify(errors, null, 2);
throw new Error(`Document is not valid OpenAPI. ${errors.length} validation errors:\n${prettyErrors}`);
}
return;
})
.expect(200, done);
});
});
describe('jsonp support', function() {
it('supports jsonp calls', function(done) {
api
.get(endPoint('createPad') + '&jsonp=jsonp_1&padID=' + testPadId)
.expect(function(res) {
if (!res.text.match('jsonp_1')) throw new Error('no jsonp call seen');
})
.expect('Content-Type', /javascript/)
.expect(200, done);
});
});
var endPoint = function(point) {
return '/api/' + apiVersion + '/' + point + '?apikey=' + apiKey;
};
function makeid() {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < 5; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

View file

@ -0,0 +1,113 @@
/*
* This file is copied & modified from <basedir>/tests/backend/specs/api/pad.js
*
* TODO: maybe unify those two files and merge in a single one.
*/
const assert = require('assert');
const supertest = require(__dirname+'/../../../../src/node_modules/supertest');
const fs = require('fs');
const settings = require(__dirname + '/../../../../src/node/utils/Settings');
const api = supertest('http://'+settings.ip+":"+settings.port);
const path = require('path');
const async = require(__dirname+'/../../../../src/node_modules/async');
var filePath = path.join(__dirname, '../../../../APIKEY.txt');
var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'});
apiKey = apiKey.replace(/\n$/, "");
var apiVersion = 1;
var testPadId = makeid();
describe('Connectivity For Character Encoding', function(){
it('can connect', function(done) {
api.get('/api/')
.expect('Content-Type', /json/)
.expect(200, done)
});
})
describe('API Versioning', function(){
it('finds the version tag', function(done) {
api.get('/api/')
.expect(function(res){
apiVersion = res.body.currentVersion;
if (!res.body.currentVersion) throw new Error("No version set in API");
return;
})
.expect(200, done)
});
})
describe('Permission', function(){
it('errors with invalid APIKey', function(done) {
// This is broken because Etherpad doesn't handle HTTP codes properly see #2343
// If your APIKey is password you deserve to fail all tests anyway
var permErrorURL = '/api/'+apiVersion+'/createPad?apikey=password&padID=test';
api.get(permErrorURL)
.expect(401, done)
});
})
describe('createPad', function(){
it('creates a new Pad', function(done) {
api.get(endPoint('createPad')+"&padID="+testPadId)
.expect(function(res){
if(res.body.code !== 0) throw new Error("Unable to create new Pad");
})
.expect('Content-Type', /json/)
.expect(200, done)
});
})
describe('setHTML', function(){
it('Sets the HTML of a Pad attempting to weird utf8 encoded content', function(done) {
fs.readFile('../tests/backend/specs/api/emojis.html', 'utf8', function(err, html) {
api.post(endPoint('setHTML'))
.send({
"padID": testPadId,
"html": html,
})
.expect(function(res){
if(res.body.code !== 0) throw new Error("Can't set HTML properly");
})
.expect('Content-Type', /json/)
.expect(200, done);
});
});
})
describe('getHTML', function(){
it('get the HTML of Pad with emojis', function(done) {
api.get(endPoint('getHTML')+"&padID="+testPadId)
.expect(function(res){
if (res.body.data.html.indexOf("&#127484") === -1) {
throw new Error("Unable to get the HTML");
}
})
.expect('Content-Type', /json/)
.expect(200, done)
});
})
/*
End of test
*/
var endPoint = function(point, version){
version = version || apiVersion;
return '/api/'+version+'/'+point+'?apikey='+apiKey;
}
function makeid()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 10; i++ ){
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

View file

@ -1,7 +1,7 @@
var assert = require('assert') var assert = require('assert')
supertest = require(__dirname+'/../../../../src/node_modules/supertest'), supertest = require(__dirname+'/../../../../src/node_modules/supertest'),
fs = require('fs'), fs = require('fs'),
settings = require(__dirname+'/../../loadSettings').loadSettings(), settings = require(__dirname + '/../../../../src/node/utils/Settings'),
api = supertest('http://'+settings.ip+":"+settings.port), api = supertest('http://'+settings.ip+":"+settings.port),
path = require('path'); path = require('path');

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,75 @@
/*
* Fuzz testing the import endpoint
*/
/*
const fs = require('fs');
const settings = require(__dirname+'/../../../../tests/container/loadSettings.js').loadSettings();
const host = "http://" + settings.ip + ":" + settings.port;
const path = require('path');
const async = require(__dirname+'/../../../../src/node_modules/async');
const request = require(__dirname+'/../../../../src/node_modules/request');
const froth = require(__dirname+'/../../../../src/node_modules/mocha-froth');
var filePath = path.join(__dirname, '../../../../APIKEY.txt');
var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'});
apiKey = apiKey.replace(/\n$/, "");
var apiVersion = 1;
var testPadId = "TEST_fuzz" + makeid();
var endPoint = function(point, version){
version = version || apiVersion;
return '/api/'+version+'/'+point+'?apikey='+apiKey;
}
//console.log("Testing against padID", testPadId);
//console.log("To watch the test live visit " + host + "/p/" + testPadId);
//console.log("Tests will start in 5 seconds, click the URL now!");
setTimeout(function(){
for (let i=1; i<5; i++) { // 5000 runs
setTimeout( function timer(){
runTest(i);
}, i*100 ); // 100 ms
}
process.exit(0);
},5000); // wait 5 seconds
function runTest(number){
request(host + endPoint('createPad') + '&padID=' + testPadId, function(err, res, body){
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
if (err) {
throw new Error("FAILURE", err);
}else{
console.log("Success");
}
});
var fN = '/tmp/fuzztest.txt';
var cT = 'text/plain';
if (number % 2 == 0) {
fN = froth().toString();
cT = froth().toString();
}
let form = req.form();
form.append('file', froth().toString(), {
filename: fN,
contentType: cT
});
console.log("here");
});
}
function makeid()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ ){
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

View file

@ -0,0 +1,188 @@
/*
* ACHTUNG: there is a copied & modified version of this file in
* <basedir>/tests/container/spacs/api/pad.js
*
* TODO: unify those two files, and merge in a single one.
*/
const assert = require('assert');
const supertest = require(__dirname+'/../../../../src/node_modules/supertest');
const fs = require('fs');
const settings = require(__dirname+'/../../../../tests/container/loadSettings.js').loadSettings();
const api = supertest('http://'+settings.ip+":"+settings.port);
const path = require('path');
const async = require(__dirname+'/../../../../src/node_modules/async');
var filePath = path.join(__dirname, '../../../../APIKEY.txt');
var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'});
apiKey = apiKey.replace(/\n$/, "");
var apiVersion = 1;
var lastEdited = "";
var testImports = {
"malformed": {
input: '<html><body><li>wtf</ul></body></html>',
expectedHTML: '<!DOCTYPE HTML><html><body>wtf<br><br></body></html>',
expectedText: 'wtf\n\n'
},
"nonelistiteminlist #3620":{
input: '<html><body><ul>test<li>FOO</li></ul></body></html>',
expectedHTML: '<!DOCTYPE HTML><html><body><ul class="bullet">test<li>FOO</ul><br></body></html>',
expectedText: '\ttest\n\t* FOO\n\n'
},
"whitespaceinlist #3620":{
input: '<html><body><ul> <li>FOO</li></ul></body></html>',
expectedHTML: '<!DOCTYPE HTML><html><body><ul class="bullet"><li>FOO</ul><br></body></html>',
expectedText: '\t* FOO\n\n'
},
"prefixcorrectlinenumber":{
input: '<html><body><ol><li>should be 1</li><li>should be 2</li></ol></body></html>',
expectedHTML: '<!DOCTYPE HTML><html><body><ol start="1" class="number"><li>should be 1</li><li>should be 2</ol><br></body></html>',
expectedText: '\t1. should be 1\n\t2. should be 2\n\n'
},
"prefixcorrectlinenumbernested":{
input: '<html><body><ol><li>should be 1</li><ol><li>foo</li></ol><li>should be 2</li></ol></body></html>',
expectedHTML: '<!DOCTYPE HTML><html><body><ol start="1" class="number"><li>should be 1<ol start="2" class="number"><li>foo</ol><li>should be 2</ol><br></body></html>',
expectedText: '\t1. should be 1\n\t\t1.1. foo\n\t2. should be 2\n\n'
},
/*
"prefixcorrectlinenumber when introduced none list item - currently not supported see #3450":{
input: '<html><body><ol><li>should be 1</li>test<li>should be 2</li></ol></body></html>',
expectedHTML: '<!DOCTYPE HTML><html><body><ol start="1" class="number"><li>should be 1</li>test<li>should be 2</li></ol><br></body></html>',
expectedText: '\t1. should be 1\n\ttest\n\t2. should be 2\n\n'
}
,
"newlinesshouldntresetlinenumber #2194":{
input: '<html><body><ol><li>should be 1</li>test<li>should be 2</li></ol></body></html>',
expectedHTML: '<!DOCTYPE HTML><html><body><ol class="number"><li>should be 1</li>test<li>should be 2</li></ol><br></body></html>',
expectedText: '\t1. should be 1\n\ttest\n\t2. should be 2\n\n'
}
*/
}
Object.keys(testImports).forEach(function (testName) {
var testPadId = makeid();
test = testImports[testName];
describe('createPad', function(){
it('creates a new Pad', function(done) {
api.get(endPoint('createPad')+"&padID="+testPadId)
.expect(function(res){
if(res.body.code !== 0) throw new Error("Unable to create new Pad");
})
.expect('Content-Type', /json/)
.expect(200, done)
});
})
describe('setHTML', function(){
it('Sets the HTML', function(done) {
api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+test.input)
.expect(function(res){
if(res.body.code !== 0) throw new Error("Error:"+testName)
})
.expect('Content-Type', /json/)
.expect(200, done)
});
})
describe('getHTML', function(){
it('Gets back the HTML of a Pad', function(done) {
api.get(endPoint('getHTML')+"&padID="+testPadId)
.expect(function(res){
var receivedHtml = res.body.data.html;
if (receivedHtml !== test.expectedHTML) {
throw new Error(`HTML received from export is not the one we were expecting.
Test Name:
${testName}
Received:
${receivedHtml}
Expected:
${test.expectedHTML}
Which is a different version of the originally imported one:
${test.input}`);
}
})
.expect('Content-Type', /json/)
.expect(200, done)
});
})
describe('getText', function(){
it('Gets back the Text of a Pad', function(done) {
api.get(endPoint('getText')+"&padID="+testPadId)
.expect(function(res){
var receivedText = res.body.data.text;
if (receivedText !== test.expectedText) {
throw new Error(`Text received from export is not the one we were expecting.
Test Name:
${testName}
Received:
${receivedText}
Expected:
${test.expectedText}
Which is a different version of the originally imported one:
${test.input}`);
}
})
.expect('Content-Type', /json/)
.expect(200, done)
});
})
});
var endPoint = function(point, version){
version = version || apiVersion;
return '/api/'+version+'/'+point+'?apikey='+apiKey;
}
function makeid()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ ){
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
function generateLongText(){
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 80000; i++ ){
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
// Need this to compare arrays (listSavedRevisions test)
Array.prototype.equals = function (array) {
// if the other array is a falsy value, return
if (!array)
return false;
// compare lengths - can save a lot of time
if (this.length != array.length)
return false;
for (var i = 0, l=this.length; i < l; i++) {
// Check if we have nested arrays
if (this[i] instanceof Array && array[i] instanceof Array) {
// recurse into the nested arrays
if (!this[i].equals(array[i]))
return false;
} else if (this[i] != array[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}

View file

@ -0,0 +1,410 @@
/*
* Import and Export tests for the /p/whateverPadId/import and /p/whateverPadId/export endpoints.
*/
const assert = require('assert');
const supertest = require(__dirname+'/../../../../src/node_modules/supertest');
const fs = require('fs');
const settings = require(__dirname+'/../../../../src/node/utils/Settings');
const host = 'http://127.0.0.1:'+settings.port;
const api = supertest('http://'+settings.ip+":"+settings.port);
const path = require('path');
const async = require(__dirname+'/../../../../src/node_modules/async');
const request = require(__dirname+'/../../../../src/node_modules/request');
const padText = fs.readFileSync("../tests/backend/specs/api/test.txt");
const etherpadDoc = fs.readFileSync("../tests/backend/specs/api/test.etherpad");
const wordDoc = fs.readFileSync("../tests/backend/specs/api/test.doc");
const wordXDoc = fs.readFileSync("../tests/backend/specs/api/test.docx");
const odtDoc = fs.readFileSync("../tests/backend/specs/api/test.odt");
const pdfDoc = fs.readFileSync("../tests/backend/specs/api/test.pdf");
var filePath = path.join(__dirname, '../../../../APIKEY.txt');
var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'});
apiKey = apiKey.replace(/\n$/, "");
var apiVersion = 1;
var testPadId = makeid();
var lastEdited = "";
var text = generateLongText();
describe('Connectivity', function(){
it('can connect', function(done) {
api.get('/api/')
.expect('Content-Type', /json/)
.expect(200, done)
});
})
describe('API Versioning', function(){
it('finds the version tag', function(done) {
api.get('/api/')
.expect(function(res){
apiVersion = res.body.currentVersion;
if (!res.body.currentVersion) throw new Error("No version set in API");
return;
})
.expect(200, done)
});
})
/*
Tests
-----
Test.
/ Create a pad
/ Set pad contents
/ Try export pad in various formats
/ Get pad contents and ensure it matches imported contents
Test.
/ Try to export a pad that doesn't exist // Expect failure
Test.
/ Try to import an unsupported file to a pad that exists
-- TODO: Test.
Try to import to a file and abort it half way through
Test.
Try to import to files of varying size.
Example Curl command for testing import URI:
curl -s -v --form file=@/home/jose/test.txt http://127.0.0.1:9001/p/foo/import
*/
describe('Imports and Exports', function(){
it('creates a new Pad, imports content to it, checks that content', function(done) {
if(!settings.allowAnyoneToImport){
console.warn("not anyone can import so not testing -- to include this test set allowAnyoneToImport to true in settings.json");
done();
}else{
api.get(endPoint('createPad')+"&padID="+testPadId)
.expect(function(res){
if(res.body.code !== 0) throw new Error("Unable to create new Pad");
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
if (err) {
throw new Error("Failed to import", err);
} else {
api.get(endPoint('getText')+"&padID="+testPadId)
.expect(function(res){
if(res.body.data.text !== padText.toString()){
throw new Error("text is wrong on export");
}
})
}
});
let form = req.form();
form.append('file', padText, {
filename: '/test.txt',
contentType: 'text/plain'
});
})
.expect('Content-Type', /json/)
.expect(200, done)
}
});
// For some reason word import does not work in testing..
// TODO: fix support for .doc files..
xit('Tries to import .doc that uses soffice or abiword', function(done) {
if(!settings.allowAnyoneToImport) return done();
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
if (err) {
throw new Error("Failed to import", err);
} else {
if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){
throw new Error("Failed DOC import", testPadId);
}else{
done();
}
}
});
let form = req.form();
form.append('file', wordDoc, {
filename: '/test.doc',
contentType: 'application/msword'
});
});
it('exports DOC', function(done) {
if(!settings.allowAnyoneToImport) return done();
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
try{
request(host + '/p/'+testPadId+'/export/doc', function (err, res, body) {
// TODO: At some point checking that the contents is correct would be suitable
if(body.length >= 9000){
done();
}else{
throw new Error("Word Document export length is not right");
}
})
}catch(e){
throw new Error(e);
}
})
it('Tries to import .docx that uses soffice or abiword', function(done) {
if(!settings.allowAnyoneToImport) return done();
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
if (err) {
throw new Error("Failed to import", err);
} else {
if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){
throw new Error("Failed DOCX import");
}else{
done();
}
}
});
let form = req.form();
form.append('file', wordXDoc, {
filename: '/test.docx',
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
});
it('exports DOC from imported DOCX', function(done) {
if(!settings.allowAnyoneToImport) return done();
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
request(host + '/p/'+testPadId+'/export/doc', function (err, res, body) {
// TODO: At some point checking that the contents is correct would be suitable
if(body.length >= 9100){
done();
}else{
throw new Error("Word Document export length is not right");
}
})
})
it('Tries to import .pdf that uses soffice or abiword', function(done) {
if(!settings.allowAnyoneToImport) return done();
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
if (err) {
throw new Error("Failed to import", err);
} else {
if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){
throw new Error("Failed PDF import");
}else{
done();
}
}
});
let form = req.form();
form.append('file', pdfDoc, {
filename: '/test.pdf',
contentType: 'application/pdf'
});
});
it('exports PDF', function(done) {
if(!settings.allowAnyoneToImport) return done();
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
request(host + '/p/'+testPadId+'/export/pdf', function (err, res, body) {
// TODO: At some point checking that the contents is correct would be suitable
if(body.length >= 1000){
done();
}else{
throw new Error("PDF Document export length is not right");
}
})
})
it('Tries to import .odt that uses soffice or abiword', function(done) {
if(!settings.allowAnyoneToImport) return done();
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
if (err) {
throw new Error("Failed to import", err);
} else {
if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){
throw new Error("Failed ODT import", testPadId);
}else{
done();
}
}
});
let form = req.form();
form.append('file', odtDoc, {
filename: '/test.odt',
contentType: 'application/odt'
});
});
it('exports ODT', function(done) {
if(!settings.allowAnyoneToImport) return done();
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
request(host + '/p/'+testPadId+'/export/odt', function (err, res, body) {
// TODO: At some point checking that the contents is correct would be suitable
if(body.length >= 7000){
done();
}else{
throw new Error("ODT Document export length is not right");
}
})
})
it('Tries to import .etherpad', function(done) {
if(!settings.allowAnyoneToImport) return done();
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
if (err) {
throw new Error("Failed to import", err);
} else {
if(res.body.indexOf("FrameCall(\'true\', \'ok\');") === -1){
throw new Error("Failed Etherpad import", err, testPadId);
}else{
done();
}
}
});
let form = req.form();
form.append('file', etherpadDoc, {
filename: '/test.etherpad',
contentType: 'application/etherpad'
});
});
it('exports Etherpad', function(done) {
request(host + '/p/'+testPadId+'/export/etherpad', function (err, res, body) {
// TODO: At some point checking that the contents is correct would be suitable
if(body.indexOf("hello") !== -1){
done();
}else{
console.error("body");
throw new Error("Etherpad Document does not include hello");
}
})
})
it('exports HTML for this Etherpad file', function(done) {
request(host + '/p/'+testPadId+'/export/html', function (err, res, body) {
// broken pre fix export -- <ul class="bullet"></li><ul class="bullet"></ul></li></ul>
var expectedHTML = '<ul class="bullet"><li><ul class="bullet"><li>hello</ul></li></ul>';
// expect body to include
if(body.indexOf(expectedHTML) !== -1){
done();
}else{
console.error(body);
throw new Error("Exported HTML nested list items is not right", body);
}
})
})
it('tries to import Plain Text to a pad that does not exist', function(done) {
var req = request.post(host + '/p/'+testPadId+testPadId+testPadId+'/import', function (err, res, body) {
if (res.statusCode === 200) {
throw new Error("Was able to import to a pad that doesn't exist");
}else{
// Wasn't able to write to a pad that doesn't exist, this is expected behavior
api.get(endPoint('getText')+"&padID="+testPadId+testPadId+testPadId)
.expect(function(res){
if(res.body.code !== 1) throw new Error("Pad Exists");
})
.expect(200, done)
}
let form = req.form();
form.append('file', padText, {
filename: '/test.txt',
contentType: 'text/plain'
});
})
});
it('Tries to import unsupported file type', function(done) {
if(settings.allowUnknownFileEnds === true){
console.log("allowing unknown file ends so skipping this test");
return done();
}
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
if (err) {
throw new Error("Failed to import", err);
} else {
if(res.body.indexOf("FrameCall('undefined', 'ok');") !== -1){
console.log("worked");
throw new Error("You shouldn't be able to import this file", testPadId);
}
return done();
}
});
let form = req.form();
form.append('file', padText, {
filename: '/test.xasdasdxx',
contentType: 'weirdness/jobby'
});
});
// end of tests
})
var endPoint = function(point, version){
version = version || apiVersion;
return '/api/'+version+'/'+point+'?apikey='+apiKey;
}
function makeid()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ ){
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
function generateLongText(){
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 80000; i++ ){
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
// Need this to compare arrays (listSavedRevisions test)
Array.prototype.equals = function (array) {
// if the other array is a falsy value, return
if (!array)
return false;
// compare lengths - can save a lot of time
if (this.length != array.length)
return false;
for (var i = 0, l=this.length; i < l; i++) {
// Check if we have nested arrays
if (this[i] instanceof Array && array[i] instanceof Array) {
// recurse into the nested arrays
if (!this[i].equals(array[i]))
return false;
} else if (this[i] != array[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}

View file

@ -0,0 +1,54 @@
/*
* Tests for the instance-level APIs
*
* Section "GLOBAL FUNCTIONS" in src/node/db/API.js
*/
const assert = require('assert');
const supertest = require(__dirname+'/../../../../src/node_modules/supertest');
const fs = require('fs');
const settings = require(__dirname+'/../../../../src/node/utils/Settings');
const api = supertest('http://'+settings.ip+":"+settings.port);
const path = require('path');
var filePath = path.join(__dirname, '../../../../APIKEY.txt');
var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'});
apiKey = apiKey.replace(/\n$/, "");
var apiVersion = '1.2.14';
describe('Connectivity for instance-level API tests', function() {
it('can connect', function(done) {
api.get('/api/')
.expect('Content-Type', /json/)
.expect(200, done)
});
});
describe('getStats', function(){
it('Gets the stats of a running instance', function(done) {
api.get(endPoint('getStats'))
.expect(function(res){
if (res.body.code !== 0) throw new Error("getStats() failed");
if (!(('totalPads' in res.body.data) && (typeof res.body.data.totalPads === 'number'))) {
throw new Error(`Response to getStats() does not contain field totalPads, or it's not a number: ${JSON.stringify(res.body.data)}`);
}
if (!(('totalSessions' in res.body.data) && (typeof res.body.data.totalSessions === 'number'))) {
throw new Error(`Response to getStats() does not contain field totalSessions, or it's not a number: ${JSON.stringify(res.body.data)}`);
}
if (!(('totalActivePads' in res.body.data) && (typeof res.body.data.totalActivePads === 'number'))) {
throw new Error(`Response to getStats() does not contain field totalActivePads, or it's not a number: ${JSON.stringify(res.body.data)}`);
}
})
.expect('Content-Type', /json/)
.expect(200, done);
});
});
var endPoint = function(point, version){
version = version || apiVersion;
return '/api/'+version+'/'+point+'?apikey='+apiKey;
}

View file

@ -1,10 +1,17 @@
var assert = require('assert') /*
supertest = require(__dirname+'/../../../../src/node_modules/supertest'), * ACHTUNG: there is a copied & modified version of this file in
fs = require('fs'), * <basedir>/tests/container/specs/api/pad.js
settings = require(__dirname+'/../../loadSettings').loadSettings(), *
api = supertest('http://'+settings.ip+":"+settings.port), * TODO: unify those two files, and merge in a single one.
path = require('path'), */
async = require(__dirname+'/../../../../src/node_modules/async');
const assert = require('assert');
const supertest = require(__dirname+'/../../../../src/node_modules/supertest');
const fs = require('fs');
const settings = require(__dirname + '/../../../../src/node/utils/Settings');
const api = supertest('http://'+settings.ip+":"+settings.port);
const path = require('path');
const async = require(__dirname+'/../../../../src/node_modules/async');
var filePath = path.join(__dirname, '../../../../APIKEY.txt'); var filePath = path.join(__dirname, '../../../../APIKEY.txt');
@ -26,10 +33,23 @@ var ulHtml = '<!doctype html><html><body><ul class="bullet"><li>one</li><li>two<
* textually, but at least it remains standard compliant and has an equal DOM * textually, but at least it remains standard compliant and has an equal DOM
* structure. * structure.
*/ */
var expectedHtml = '<!doctype html><html><body><ul class="bullet"><li>one</li><li>two</li><li>0</li><li>1</li><li>2<ul class="bullet"><li>3</li><li>4</ul></li></ul><ol class="number"><li>item<ol class="number"><li>item1</li><li>item2</ol></li></ol></body></html>'; var expectedHtml = '<!doctype html><html><body><ul class="bullet"><li>one</li><li>two</li><li>0</li><li>1</li><li>2<ul class="bullet"><li>3</li><li>4</ul></li></ul><ol start="1" class="number"><li>item<ol start="2" class="number"><li>item1</li><li>item2</ol></li></ol></body></html>';
/*
* Html document with space between list items, to test its import and
* verify it is exported back correctly
*/
var ulSpaceHtml = '<!doctype html><html><body><ul class="bullet"> <li>one</li></ul></body></html>';
/*
* When exported back, Etherpad produces an html which is not exactly the same
* textually, but at least it remains standard compliant and has an equal DOM
* structure.
*/
var expectedSpaceHtml = '<!doctype html><html><body><ul class="bullet"><li>one</ul></body></html>';
describe('Connectivity', function(){ describe('Connectivity', function(){
it('errors if can not connect', function(done) { it('can connect', function(done) {
api.get('/api/') api.get('/api/')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200, done) .expect(200, done)
@ -37,7 +57,7 @@ describe('Connectivity', function(){
}) })
describe('API Versioning', function(){ describe('API Versioning', function(){
it('errors if can not connect', function(done) { it('finds the version tag', function(done) {
api.get('/api/') api.get('/api/')
.expect(function(res){ .expect(function(res){
apiVersion = res.body.currentVersion; apiVersion = res.body.currentVersion;
@ -49,7 +69,7 @@ describe('API Versioning', function(){
}) })
describe('Permission', function(){ describe('Permission', function(){
it('errors if can connect without correct APIKey', function(done) { it('errors with invalid APIKey', function(done) {
// This is broken because Etherpad doesn't handle HTTP codes properly see #2343 // This is broken because Etherpad doesn't handle HTTP codes properly see #2343
// If your APIKey is password you deserve to fail all tests anyway // If your APIKey is password you deserve to fail all tests anyway
var permErrorURL = '/api/'+apiVersion+'/createPad?apikey=password&padID=test'; var permErrorURL = '/api/'+apiVersion+'/createPad?apikey=password&padID=test';
@ -104,7 +124,7 @@ describe('deletePad', function(){
it('deletes a Pad', function(done) { it('deletes a Pad', function(done) {
api.get(endPoint('deletePad')+"&padID="+testPadId) api.get(endPoint('deletePad')+"&padID="+testPadId)
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200, done) .expect(200, done) // @TODO: we shouldn't expect 200 here since the pad may not exist
}); });
}) })
@ -166,6 +186,19 @@ describe('getHTML', function(){
}); });
}) })
describe('listAllPads', function () {
it('list all pads', function (done) {
api.get(endPoint('listAllPads'))
.expect(function (res) {
if (res.body.data.padIDs.includes(testPadId) !== true) {
throw new Error('Unable to find pad in pad list')
}
})
.expect('Content-Type', /json/)
.expect(200, done)
})
})
describe('deletePad', function(){ describe('deletePad', function(){
it('deletes a Pad', function(done) { it('deletes a Pad', function(done) {
api.get(endPoint('deletePad')+"&padID="+testPadId) api.get(endPoint('deletePad')+"&padID="+testPadId)
@ -177,6 +210,19 @@ describe('deletePad', function(){
}); });
}) })
describe('listAllPads', function () {
it('list all pads', function (done) {
api.get(endPoint('listAllPads'))
.expect(function (res) {
if (res.body.data.padIDs.includes(testPadId) !== false) {
throw new Error('Test pad should not be in pads list')
}
})
.expect('Content-Type', /json/)
.expect(200, done)
})
})
describe('getHTML', function(){ describe('getHTML', function(){
it('get the HTML of a Pad -- Should return a failure', function(done) { it('get the HTML of a Pad -- Should return a failure', function(done) {
api.get(endPoint('getHTML')+"&padID="+testPadId) api.get(endPoint('getHTML')+"&padID="+testPadId)
@ -204,7 +250,7 @@ describe('getText', function(){
api.get(endPoint('getText')+"&padID="+testPadId) api.get(endPoint('getText')+"&padID="+testPadId)
.expect(function(res){ .expect(function(res){
if(res.body.data.text !== "testText\n") throw new Error("Pad Creation with text") if(res.body.data.text !== "testText\n") throw new Error("Pad Creation with text")
}) })
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200, done) .expect(200, done)
}); });
@ -212,7 +258,11 @@ describe('getText', function(){
describe('setText', function(){ describe('setText', function(){
it('creates a new Pad with text', function(done) { it('creates a new Pad with text', function(done) {
api.get(endPoint('setText')+"&padID="+testPadId+"&text=testTextTwo") api.post(endPoint('setText'))
.send({
"padID": testPadId,
"text": "testTextTwo",
})
.expect(function(res){ .expect(function(res){
if(res.body.code !== 0) throw new Error("Pad setting text failed"); if(res.body.code !== 0) throw new Error("Pad setting text failed");
}) })
@ -327,7 +377,11 @@ describe('getLastEdited', function(){
describe('setText', function(){ describe('setText', function(){
it('creates a new Pad with text', function(done) { it('creates a new Pad with text', function(done) {
api.get(endPoint('setText')+"&padID="+testPadId+"&text=testTextTwo") api.post(endPoint('setText'))
.send({
"padID": testPadId,
"text": "testTextTwo",
})
.expect(function(res){ .expect(function(res){
if(res.body.code !== 0) throw new Error("Pad setting text failed"); if(res.body.code !== 0) throw new Error("Pad setting text failed");
}) })
@ -387,7 +441,8 @@ describe('createPad', function(){
describe('setText', function(){ describe('setText', function(){
it('Sets text on a pad Id', function(done) { it('Sets text on a pad Id', function(done) {
api.get(endPoint('setText')+"&padID="+testPadId+"&text="+text) api.post(endPoint('setText')+"&padID="+testPadId)
.field({text: text})
.expect(function(res){ .expect(function(res){
if(res.body.code !== 0) throw new Error("Pad Set Text failed") if(res.body.code !== 0) throw new Error("Pad Set Text failed")
}) })
@ -410,7 +465,8 @@ describe('getText', function(){
describe('setText', function(){ describe('setText', function(){
it('Sets text on a pad Id including an explicit newline', function(done) { it('Sets text on a pad Id including an explicit newline', function(done) {
api.get(endPoint('setText')+"&padID="+testPadId+"&text="+text+'%0A') api.post(endPoint('setText')+"&padID="+testPadId)
.field({text: text+'\n'})
.expect(function(res){ .expect(function(res){
if(res.body.code !== 0) throw new Error("Pad Set Text failed") if(res.body.code !== 0) throw new Error("Pad Set Text failed")
}) })
@ -524,9 +580,13 @@ describe('getText', function(){
describe('setHTML', function(){ describe('setHTML', function(){
it('Sets the HTML of a Pad attempting to pass ugly HTML', function(done) { it('Sets the HTML of a Pad attempting to pass ugly HTML', function(done) {
var html = "<div><b>Hello HTML</title></head></div>"; var html = "<div><b>Hello HTML</title></head></div>";
api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+html) api.post(endPoint('setHTML'))
.send({
"padID": testPadId,
"html": html,
})
.expect(function(res){ .expect(function(res){
if(res.body.code !== 1) throw new Error("Allowing crappy HTML to be imported") if(res.body.code !== 0) throw new Error("Crappy HTML Can't be Imported[we weren't able to sanitize it']")
}) })
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200, done) .expect(200, done)
@ -535,7 +595,11 @@ describe('setHTML', function(){
describe('setHTML', function(){ describe('setHTML', function(){
it('Sets the HTML of a Pad with complex nested lists of different types', function(done) { it('Sets the HTML of a Pad with complex nested lists of different types', function(done) {
api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+ulHtml) api.post(endPoint('setHTML'))
.send({
"padID": testPadId,
"html": ulHtml,
})
.expect(function(res){ .expect(function(res){
if(res.body.code !== 0) throw new Error("List HTML cant be imported") if(res.body.code !== 0) throw new Error("List HTML cant be imported")
}) })
@ -567,11 +631,44 @@ describe('getHTML', function(){
}); });
}) })
describe('setHTML', function(){
it('Sets the HTML of a Pad with white space between list items', function(done) {
api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+ulSpaceHtml)
.expect(function(res){
if(res.body.code !== 0) throw new Error("List HTML cant be imported")
})
.expect('Content-Type', /json/)
.expect(200, done)
});
})
describe('getHTML', function(){
it('Gets back the HTML of a Pad with complex nested lists of different types', function(done) {
api.get(endPoint('getHTML')+"&padID="+testPadId)
.expect(function(res){
var receivedHtml = res.body.data.html.replace("<br></body>", "</body>").toLowerCase();
if (receivedHtml !== expectedSpaceHtml) {
throw new Error(`HTML received from export is not the one we were expecting.
Received:
${receivedHtml}
Expected:
${expectedSpaceHtml}
Which is a slightly modified version of the originally imported one:
${ulSpaceHtml}`);
}
})
.expect('Content-Type', /json/)
.expect(200, done)
});
})
describe('createPad', function(){ describe('createPad', function(){
it('errors if pad can be created', function(done) { it('errors if pad can be created', function(done) {
var badUrlChars = ["/", "%23", "%3F", "%26"]; var badUrlChars = ["/", "%23", "%3F", "%26"];
async.map( async.map(
badUrlChars, badUrlChars,
function (badUrlChar, cb) { function (badUrlChar, cb) {
api.get(endPoint('createPad')+"&padID="+badUrlChar) api.get(endPoint('createPad')+"&padID="+badUrlChar)
.expect(function(res){ .expect(function(res){

View file

@ -1,7 +1,7 @@
var assert = require('assert') var assert = require('assert')
supertest = require(__dirname+'/../../../../src/node_modules/supertest'), supertest = require(__dirname+'/../../../../src/node_modules/supertest'),
fs = require('fs'), fs = require('fs'),
settings = require(__dirname+'/../../loadSettings').loadSettings(), settings = require(__dirname + '/../../../../src/node/utils/Settings'),
api = supertest('http://'+settings.ip+":"+settings.port), api = supertest('http://'+settings.ip+":"+settings.port),
path = require('path'); path = require('path');

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1 @@
{"pad:Pd4b1Kgvv9qHZZtj8yzl":{"atext":{"text":"*hello\n","attribs":"*0*1*5*3*4+1*0+5|1+1"},"pool":{"numToAttrib":{"0":["author","a.ElbBWNTxmtRrfFqn"],"1":["insertorder","first"],"2":["list","bullet1"],"3":["lmkr","1"],"4":["start","1"],"5":["list","bullet2"]},"nextNum":6},"head":5,"chatHead":-1,"publicStatus":false,"passwordHash":null,"savedRevisions":[]},"globalAuthor:a.ElbBWNTxmtRrfFqn":{"colorId":50,"name":null,"timestamp":1595111151414,"padIDs":"Pd4b1Kgvv9qHZZtj8yzl"},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:0":{"changeset":"Z:1>bj|7+bj$Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n\nWarning: DirtyDB is used. This is fine for testing but not recommended for production. -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n","meta":{"author":"","timestamp":1595111092400,"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0},"atext":{"text":"Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n\nWarning: DirtyDB is used. This is fine for testing but not recommended for production. -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n\n","attribs":"|8+bk"}}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:1":{"changeset":"Z:bk<bj|7-bj$","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111112138}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:2":{"changeset":"Z:1>1*0*1*2*3*4+1$*","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111119434}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:3":{"changeset":"Z:2>0*5*4=1$","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111127471}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:4":{"changeset":"Z:2>2=1*0+2$he","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111128230}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:5":{"changeset":"Z:4>3=3*0+3$llo","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111128727}}}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1 @@
This is the contents we insert into pads when testing the import functions. Thanks for using Etherpad!

View file

@ -1,10 +1,12 @@
var assert = require('assert') var assert = require('assert')
os = require('os'),
fs = require('fs'), fs = require('fs'),
path = require('path'), path = require('path'),
TidyHtml = null, TidyHtml = null,
Settings = null; Settings = null;
var npm = require("../../../../src/node_modules/npm/lib/npm.js"); var npm = require("../../../../src/node_modules/npm/lib/npm.js");
var nodeify = require('../../../../src/node_modules/nodeify');
describe('tidyHtml', function() { describe('tidyHtml', function() {
before(function(done) { before(function(done) {
@ -16,6 +18,10 @@ describe('tidyHtml', function() {
}); });
}); });
function tidy(file, callback) {
return nodeify(TidyHtml.tidy(file), callback);
}
it('Tidies HTML', function(done) { it('Tidies HTML', function(done) {
// If the user hasn't configured Tidy, we skip this tests as it's required for this test // If the user hasn't configured Tidy, we skip this tests as it's required for this test
if (!Settings.tidyHtml) { if (!Settings.tidyHtml) {
@ -23,10 +29,11 @@ describe('tidyHtml', function() {
} }
// Try to tidy up a bad HTML file // Try to tidy up a bad HTML file
var tmpDir = process.env.TEMP || "/tmp"; const tmpDir = os.tmpdir();
var tmpFile = path.join(tmpDir, 'tmp_' + (Math.floor(Math.random() * 1000000)) + '.html') var tmpFile = path.join(tmpDir, 'tmp_' + (Math.floor(Math.random() * 1000000)) + '.html')
fs.writeFileSync(tmpFile, '<html><body><p>a paragraph</p><li>List without outer UL</li>trailing closing p</p></body></html>'); fs.writeFileSync(tmpFile, '<html><body><p>a paragraph</p><li>List without outer UL</li>trailing closing p</p></body></html>');
TidyHtml.tidy(tmpFile, function(err){ tidy(tmpFile, function(err){
assert.ok(!err); assert.ok(!err);
// Read the file again // Read the file again
@ -55,7 +62,7 @@ describe('tidyHtml', function() {
this.skip(); this.skip();
} }
TidyHtml.tidy('/some/none/existing/file.html', function(err) { tidy('/some/none/existing/file.html', function(err) {
assert.ok(err); assert.ok(err);
return done(); return done();
}); });

View file

@ -0,0 +1,175 @@
const Changeset = require("../../../src/static/js/Changeset");
const contentcollector = require("../../../src/static/js/contentcollector");
const AttributePool = require("../../../src/static/js/AttributePool");
const cheerio = require("../../../src/node_modules/cheerio");
const util = require('util');
const tests = {
nestedLi:{
description: "Complex nested Li",
html: '<!doctype html><html><body><ol><li>one</li><li><ol><li>1.1</li></ol></li><li>two</li></ol></body></html>',
expectedLineAttribs : [
'*0*1*2*3+1+3', '*0*4*2*5+1+3', '*0*1*2*5+1+3'
],
expectedText: [
'*one', '*1.1', '*two'
]
},
complexNest:{
description: "Complex list of different types",
html: '<!doctype html><html><body><ul class="bullet"><li>one</li><li>two</li><li>0</li><li>1</li><li>2<ul class="bullet"><li>3</li><li>4</li></ul></li></ul><ol class="number"><li>item<ol class="number"><li>item1</li><li>item2</li></ol></li></ol></body></html>',
expectedLineAttribs : [
'*0*1*2+1+3',
'*0*1*2+1+3',
'*0*1*2+1+1',
'*0*1*2+1+1',
'*0*1*2+1+1',
'*0*3*2+1+1',
'*0*3*2+1+1',
'*0*4*2*5+1+4',
'*0*6*2*7+1+5',
'*0*6*2*7+1+5'
],
expectedText: [
'*one', '*two',
'*0', '*1',
'*2', '*3',
'*4', '*item',
'*item1', '*item2'
]
},
ul: {
description : "Tests if uls properly get attributes",
html : "<html><body><ul><li>a</li><li>b</li></ul><div>div</div><p>foo</p></body></html>",
expectedLineAttribs : [ '*0*1*2+1+1', '*0*1*2+1+1', '+3' , '+3'],
expectedText: ["*a","*b", "div", "foo"]
}
,
ulIndented: {
description : "Tests if indented uls properly get attributes",
html : "<html><body><ul><li>a</li><ul><li>b</li></ul><li>a</li></ul><p>foo</p></body></html>",
expectedLineAttribs : [ '*0*1*2+1+1', '*0*3*2+1+1', '*0*1*2+1+1', '+3' ],
expectedText: ["*a","*b", "*a", "foo"]
},
ol: {
description : "Tests if ols properly get line numbers when in a normal OL",
html : "<html><body><ol><li>a</li><li>b</li><li>c</li></ol><p>test</p></body></html>",
expectedLineAttribs : ['*0*1*2*3+1+1', '*0*1*2*3+1+1', '*0*1*2*3+1+1', '+4'],
expectedText: ["*a","*b","*c", "test"],
noteToSelf: "Ensure empty P does not induce line attribute marker, wont this break the editor?"
}
,
lineDoBreakInOl:{
description : "A single completely empty line break within an ol should reset count if OL is closed off..",
html : "<html><body><ol><li>should be 1</li></ol><p>hello</p><ol><li>should be 1</li><li>should be 2</li></ol><p></p></body></html>",
expectedLineAttribs : [ '*0*1*2*3+1+b', '+5', '*0*1*2*4+1+b', '*0*1*2*4+1+b', '' ],
expectedText: ["*should be 1","hello", "*should be 1","*should be 2", ""],
noteToSelf: "Shouldn't include attribute marker in the <p> line"
},
bulletListInOL:{
description : "A bullet within an OL should not change numbering..",
html : "<html><body><ol><li>should be 1</li><ul><li>should be a bullet</li></ul><li>should be 2</li></ol><p></p></body></html>",
expectedLineAttribs : [ '*0*1*2*3+1+b', '*0*4*2*3+1+i', '*0*1*2*5+1+b', '' ],
expectedText: ["*should be 1","*should be a bullet","*should be 2", ""]
},
testP:{
description : "A single <p></p> should create a new line",
html : "<html><body><p></p><p></p></body></html>",
expectedLineAttribs : [ '', ''],
expectedText: ["", ""],
noteToSelf: "<p></p>should create a line break but not break numbering"
},
nestedOl: {
description : "Tests if ols properly get line numbers when in a normal OL",
html : "<html><body>a<ol><li>b<ol><li>c</li></ol></ol>notlist<p>foo</p></body></html>",
expectedLineAttribs : [ '+1', '*0*1*2*3+1+1', '*0*4*2*5+1+1', '+7', '+3' ],
expectedText: ["a","*b","*c", "notlist", "foo"],
noteToSelf: "Ensure empty P does not induce line attribute marker, wont this break the editor?"
},
nestedOl: {
description : "First item being an UL then subsequent being OL will fail",
html : "<html><body><ul><li>a<ol><li>b</li><li>c</li></ol></li></ul></body></html>",
expectedLineAttribs : [ '+1', '*0*1*2*3+1+1', '*0*4*2*5+1+1' ],
expectedText: ["a","*b","*c"],
noteToSelf: "Ensure empty P does not induce line attribute marker, wont this break the editor?",
disabled: true
},
lineDontBreakOL:{
description : "A single completely empty line break within an ol should NOT reset count",
html : "<html><body><ol><li>should be 1</li><p></p><li>should be 2</li><li>should be 3</li></ol><p></p></body></html>",
expectedLineAttribs : [ ],
expectedText: ["*should be 1","*should be 2","*should be 3"],
noteToSelf: "<p></p>should create a line break but not break numbering -- This is what I can't get working!",
disabled: true
}
}
// For each test..
for (let test in tests){
let testObj = tests[test];
describe(test, function() {
if(testObj.disabled){
return xit("DISABLED:", test, function(done){
done();
})
}
it(testObj.description, function(done) {
var $ = cheerio.load(testObj.html); // Load HTML into Cheerio
var doc = $('html')[0]; // Creates a dom-like representation of HTML
// Create an empty attribute pool
var apool = new AttributePool();
// Convert a dom tree into a list of lines and attribute liens
// using the content collector object
var cc = contentcollector.makeContentCollector(true, null, apool);
cc.collectContent(doc);
var result = cc.finish();
var recievedAttributes = result.lineAttribs;
var expectedAttributes = testObj.expectedLineAttribs;
var recievedText = new Array(result.lines)
var expectedText = testObj.expectedText;
// Check recieved text matches the expected text
if(arraysEqual(recievedText[0], expectedText)){
// console.log("PASS: Recieved Text did match Expected Text\nRecieved:", recievedText[0], "\nExpected:", testObj.expectedText)
}else{
console.error("FAIL: Recieved Text did not match Expected Text\nRecieved:", recievedText[0], "\nExpected:", testObj.expectedText)
throw new Error();
}
// Check recieved attributes matches the expected attributes
if(arraysEqual(recievedAttributes, expectedAttributes)){
// console.log("PASS: Recieved Attributes matched Expected Attributes");
done();
}else{
console.error("FAIL", test, testObj.description);
console.error("FAIL: Recieved Attributes did not match Expected Attributes\nRecieved: ", recievedAttributes, "\nExpected: ", expectedAttributes)
console.error("FAILING HTML", testObj.html);
throw new Error();
}
});
});
};
function arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length != b.length) return false;
// If you don't care about the order of the elements inside
// the array, you should sort both arrays here.
// Please note that calling sort on an array will modify that array.
// you might want to clone your array first.
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}

View file

@ -0,0 +1,87 @@
const request = require(__dirname+'/../../../src/node_modules/supertest')
/**
* types
* woff
* WASM
* html
* woff2
* javascript
*
* require-js stuff
*
* try to require every single file in src/
*/
/**
* test header
* test header with garbage
* if-modified-since
* last-modfied
*
*
*
*/
// ensure minification is true
describe('Minify', function(done) {
it('serves fonts', function(done) {
request('http://localhost:9001')
.get('/p/MINIFY_TEST_123')
//.set('Accept', 'application/json')
//.expect('Content-Type', /json/)
.expect(200, done);
});
});
//const assert = require('assert');
//const supertest = require(__dirname+'/../../../../src/node_modules/supertest');
//const fs = require('fs');
//const settings = require(__dirname+'/../../../../src/node/utils/Settings');
//const host = 'http://127.0.0.1:'+settings.port;
//const api = supertest('http://'+settings.ip+":"+settings.port);
//const path = require('path');
//const async = require(__dirname+'/../../../../src/node_modules/async');
//const request = require(__dirname+'/../../../../src/node_modules/request');
//var filePath = path.join(__dirname, '../../../../APIKEY.txt');
//
//var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'});
//apiKey = apiKey.replace(/\n$/, "");
//var apiVersion = 1;
//var testPadId = makeid();
//var lastEdited = "";
//var text = generateLongText();
//
//describe('Connectivity', function(){
// it('can connect', function(done) {
// api.get('/api/')
// .expect('Content-Type', /json/)
// .expect(200, done)
// });
//})
//
//describe('API Versioning', function(){
// it('finds the version tag', function(done) {
// api.get('/api/')
// .expect(function(res){
// apiVersion = res.body.currentVersion;
// if (!res.body.currentVersion) throw new Error("No version set in API");
// return;
// })
// .expect(200, done)
// });
//})
//
//function makeid()
//{
// var text = "";
// var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
//
// for( var i=0; i < 5; i++ ){
// text += possible.charAt(Math.floor(Math.random() * possible.length));
// }
// return text;
//}
//

View file

@ -0,0 +1,37 @@
/*
* ACHTUNG: this file is a hack used to load "settings.json.docker" instead of
* "settings.json", since in its present form the Settings module does
* not allow it.
* This is a remnant of an analogous file that was placed in
* <basedir>/tests/backend/loadSettings.js
*
* TODO: modify the Settings module:
* 1) no side effects on module load
* 2) write a factory method that loads a configuration file (taking the
* file name from the command line, a function argument, or falling
* back to a default)
*/
var jsonminify = require(__dirname+"/../../src/node_modules/jsonminify");
const fs = require('fs');
function loadSettings(){
var settingsStr = fs.readFileSync(__dirname+"/../../settings.json.docker").toString();
// try to parse the settings
try {
if(settingsStr) {
settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}");
var settings = JSON.parse(settingsStr);
// custom settings for running in a container
settings.ip = 'localhost';
settings.port = '9001';
return settings;
}
}catch(e){
console.error("whoops something is bad with settings");
}
}
exports.loadSettings = loadSettings;

View file

@ -0,0 +1,38 @@
/*
* ACHTUNG: this file was copied & modified from the analogous
* <basedir>/tests/backend/specs/api/pad.js
*
* TODO: unify those two files, and merge in a single one.
*/
const supertest = require(__dirname+'/../../../../src/node_modules/supertest');
const settings = require(__dirname+'/../../loadSettings').loadSettings();
const api = supertest('http://'+settings.ip+":"+settings.port);
var apiVersion = 1;
describe('Connectivity', function(){
it('can connect', function(done) {
api.get('/api/')
.expect('Content-Type', /json/)
.expect(200, done)
});
})
describe('API Versioning', function(){
it('finds the version tag', function(done) {
api.get('/api/')
.expect(function(res){
if (!res.body.currentVersion) throw new Error("No version set in API");
return;
})
.expect(200, done)
});
})
describe('Permission', function(){
it('errors with invalid APIKey', function(done) {
api.get('/api/'+apiVersion+'/createPad?apikey=wrong_password&padID=test')
.expect(401, done)
});
})

View file

@ -1,11 +1,9 @@
var helper = {}; var helper = {};
(function(){ (function(){
var $iframeContainer, $iframe, jsLibraries = {}; var $iframe, jsLibraries = {};
helper.init = function(cb){ helper.init = function(cb){
$iframeContainer = $("#iframe-container");
$.get('/static/js/jquery.js').done(function(code){ $.get('/static/js/jquery.js').done(function(code){
// make sure we don't override existing jquery // make sure we don't override existing jquery
jsLibraries["jquery"] = "if(typeof $ === 'undefined') {\n" + code + "\n}"; jsLibraries["jquery"] = "if(typeof $ === 'undefined') {\n" + code + "\n}";
@ -52,10 +50,49 @@ var helper = {};
return win.$; return win.$;
} }
helper.clearCookies = function(){ helper.clearSessionCookies = function(){
window.document.cookie = ""; // Expire cookies, so author and language are changed after reloading the pad.
// See https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#Example_4_Reset_the_previous_cookie
window.document.cookie = 'token=;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
window.document.cookie = 'language=;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
} }
// Can only happen when the iframe exists, so we're doing it separately from other cookies
helper.clearPadPrefCookie = function(){
helper.padChrome$.document.cookie = 'prefsHttp=;expires=Thu, 01 Jan 1970 00:00:00 GMT';
}
// Overwrite all prefs in pad cookie. Assumes http, not https.
//
// `helper.padChrome$.document.cookie` (the iframe) and `window.document.cookie`
// seem to have independent cookies, UNLESS we put path=/ here (which we don't).
// I don't fully understand it, but this function seems to properly simulate
// padCookie.setPref in the client code
helper.setPadPrefCookie = function(prefs){
helper.padChrome$.document.cookie = ("prefsHttp=" + escape(JSON.stringify(prefs)) + ";expires=Thu, 01 Jan 3000 00:00:00 GMT");
}
// Functionality for knowing what key event type is required for tests
var evtType = "keydown";
// if it's IE require keypress
if(window.navigator.userAgent.indexOf("MSIE") > -1){
evtType = "keypress";
}
// Edge also requires keypress.
if(window.navigator.userAgent.indexOf("Edge") > -1){
evtType = "keypress";
}
// Opera also requires keypress.
if(window.navigator.userAgent.indexOf("OPR") > -1){
evtType = "keypress";
}
helper.evtType = evtType;
// @todo needs fixing asap
// newPad occasionally timeouts, might be a problem with ready/onload code during page setup
// This ensures that tests run regardless of this problem
helper.retry = 0
helper.newPad = function(cb, padName){ helper.newPad = function(cb, padName){
//build opts object //build opts object
var opts = {clearCookies: true} var opts = {clearCookies: true}
@ -65,38 +102,56 @@ var helper = {};
opts = _.defaults(cb, opts); opts = _.defaults(cb, opts);
} }
// if opts.params is set we manipulate the URL to include URL parameters IE ?foo=Bah.
if(opts.params){
var encodedParams = "?" + $.param(opts.params);
}
//clear cookies //clear cookies
if(opts.clearCookies){ if(opts.clearCookies){
helper.clearCookies(); helper.clearSessionCookies();
} }
if(!padName) if(!padName)
padName = "FRONTEND_TEST_" + helper.randomString(20); padName = "FRONTEND_TEST_" + helper.randomString(20);
$iframe = $("<iframe src='/p/" + padName + "'></iframe>"); $iframe = $("<iframe src='/p/" + padName + (encodedParams || '') + "'></iframe>");
// needed for retry
let origPadName = padName;
//clean up inner iframe references //clean up inner iframe references
helper.padChrome$ = helper.padOuter$ = helper.padInner$ = null; helper.padChrome$ = helper.padOuter$ = helper.padInner$ = null;
//clean up iframes properly to prevent IE from memoryleaking //remove old iframe
$iframeContainer.find("iframe").purgeFrame().done(function(){ $("#iframe-container iframe").remove();
$iframeContainer.append($iframe); //set new iframe
$iframe.one('load', function(){ $("#iframe-container").append($iframe);
helper.waitFor(function(){ $iframe.one('load', function(){
return !$iframe.contents().find("#editorloadingbox").is(":visible"); helper.padChrome$ = getFrameJQuery($('#iframe-container iframe'));
}, 50000).done(function(){ if (opts.clearCookies) {
helper.padChrome$ = getFrameJQuery( $('#iframe-container iframe')); helper.clearPadPrefCookie();
helper.padOuter$ = getFrameJQuery(helper.padChrome$('iframe[name="ace_outer"]')); }
helper.padInner$ = getFrameJQuery( helper.padOuter$('iframe[name="ace_inner"]')); if (opts.padPrefs) {
helper.setPadPrefCookie(opts.padPrefs);
}
helper.waitFor(function(){
return !$iframe.contents().find("#editorloadingbox").is(":visible");
}, 10000).done(function(){
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 //disable all animations, this makes tests faster and easier
helper.padChrome$.fx.off = true; helper.padChrome$.fx.off = true;
helper.padOuter$.fx.off = true; helper.padOuter$.fx.off = true;
helper.padInner$.fx.off = true; helper.padInner$.fx.off = true;
opts.cb(); opts.cb();
}).fail(function(){ }).fail(function(){
if (helper.retry > 3) {
throw new Error("Pad never loaded"); throw new Error("Pad never loaded");
}); }
helper.retry++;
helper.newPad(cb,origPadName);
}); });
}); });
@ -104,7 +159,7 @@ var helper = {};
} }
helper.waitFor = function(conditionFunc, _timeoutTime, _intervalTime){ helper.waitFor = function(conditionFunc, _timeoutTime, _intervalTime){
var timeoutTime = _timeoutTime || 1000; var timeoutTime = _timeoutTime || 1900;
var intervalTime = _intervalTime || 10; var intervalTime = _intervalTime || 10;
var deferred = $.Deferred(); var deferred = $.Deferred();
@ -216,4 +271,4 @@ var helper = {};
_it(name, func); _it(name, func);
} }
})() })()

View file

@ -14,11 +14,10 @@
<script src="lib/underscore.js"></script> <script src="lib/underscore.js"></script>
<script src="lib/mocha.js"></script> <script src="lib/mocha.js"></script>
<script> mocha.setup('bdd') </script> <script> mocha.setup({ui: 'bdd', ignoreLeaks: true}) </script>
<script src="lib/expect.js"></script> <script src="lib/expect.js"></script>
<script src="lib/sendkeys.js"></script> <script src="lib/sendkeys.js"></script>
<script src="lib/jquery.iframe.js"></script>
<script src="helper.js"></script> <script src="helper.js"></script>
<script src="specs_list.js"></script> <script src="specs_list.js"></script>

View file

@ -65,7 +65,7 @@
var name = $flags[i] var name = $flags[i]
, assertion = new Assertion(this.obj, name, this) , assertion = new Assertion(this.obj, name, this)
if ('function' == typeof Assertion.prototype[name]) { if ('function' == typeof Assertion.prototype[name]) {
// clone the function, make sure we dont touch the prot reference // clone the function, make sure we dont touch the prot reference
var old = this[name]; var old = this[name];
@ -148,7 +148,7 @@
if ('object' == typeof fn && not) { if ('object' == typeof fn && not) {
// in the presence of a matcher, ensure the `not` only applies to // in the presence of a matcher, ensure the `not` only applies to
// the matching. // the matching.
this.flags.not = false; this.flags.not = false;
} }
var name = this.obj.name || 'fn'; var name = this.obj.name || 'fn';
@ -219,7 +219,7 @@
}; };
/** /**
* Assert within start to finish (inclusive). * Assert within start to finish (inclusive).
* *
* @param {Number} start * @param {Number} start
* @param {Number} finish * @param {Number} finish
@ -298,7 +298,7 @@
, function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }); , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n });
return this; return this;
}; };
/** /**
* Assert string value matches _regexp_. * Assert string value matches _regexp_.
* *
@ -359,13 +359,13 @@
} catch (e) { } catch (e) {
hasProp = undefined !== this.obj[name] hasProp = undefined !== this.obj[name]
} }
this.assert( this.assert(
hasProp hasProp
, function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) } , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) }
, function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) }); , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) });
} }
if (undefined !== val) { if (undefined !== val) {
this.assert( this.assert(
val === this.obj[name] val === this.obj[name]
@ -537,7 +537,7 @@
return html; return html;
} }
}; };
// Returns true if object is a DOM element. // Returns true if object is a DOM element.
var isDOMElement = function (object) { var isDOMElement = function (object) {
if (typeof HTMLElement === 'object') { if (typeof HTMLElement === 'object') {
@ -843,9 +843,9 @@
expect.eql = function eql (actual, expected) { expect.eql = function eql (actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===. // 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) { if (actual === expected) {
return true; return true;
} else if ('undefined' != typeof Buffer } else if ('undefined' != typeof Buffer
&& Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
if (actual.length != expected.length) return false; if (actual.length != expected.length) return false;

View file

@ -1,40 +0,0 @@
//copied from http://stackoverflow.com/questions/8407946/is-it-possible-to-use-iframes-in-ie-without-memory-leaks
(function($) {
$.fn.purgeFrame = function() {
var deferred;
var browser = bowser;
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 one or more lines are too long

View file

@ -305,11 +305,11 @@ var START_TO_END = 1;
var END_TO_END = 2; var END_TO_END = 2;
var END_TO_START = 3; var END_TO_START = 3;
// from the Mozilla documentation, for range.compareBoundaryPoints(how, sourceRange) // 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. // -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_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.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_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. // * Range.START_TO_START compares the start boundary-point of sourceRange to the start boundary-point of range.
function w3cstart(rng, constraint){ function w3cstart(rng, constraint){
if (rng.compareBoundaryPoints (START_TO_START, constraint) <= 0) return 0; // at or before the beginning 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; if (rng.compareBoundaryPoints (END_TO_START, constraint) >= 0) return constraint.toString().length;

View file

@ -1,11 +1,14 @@
html { html {
height: 100%; height: 100%;
} }
body { body {
padding: 0px; padding: 0px;
margin: 0px; margin: 0px;
height: 100%; height: 100%;
display: flex;
flex-direction: row;
overflow: hidden;
} }
#console { #console {
@ -13,34 +16,34 @@ body {
} }
#iframe-container { #iframe-container {
width: 50%; width: 80%;
min-width: 820px;
height: 100%; height: 100%;
} }
#iframe-container iframe { #iframe-container iframe {
height: 100%; height: 100%;
position:absolute;
min-width:500px;
max-width:800px;
left:50%;
width:100%; width:100%;
} }
#mocha { #mocha {
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
border-right: 2px solid #999; border-right: 2px solid #999;
width: 50%; flex: 1 auto;
height: 100%; height: 100%;
position: absolute;
overflow: auto; overflow: auto;
float:left; width:20%;
font-size:80%;
} }
#mocha #report { #mocha #report {
margin-top: 50px; margin: 0;
padding: 0;
margin-top: 10px;
} }
#mocha ul, #mocha li { #mocha li {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@ -60,7 +63,7 @@ body {
} }
#mocha h1 a:visited #mocha h1 a:visited
{ {
color: #00E; color: #00E;
} }
@ -76,11 +79,11 @@ body {
} }
#mocha .suite { #mocha .suite {
margin-left: 15px; margin-left: 0px;
} }
#mocha .test { #mocha .test {
margin-left: 15px; margin-left: 5px;
} }
#mocha .test:hover h2::after { #mocha .test:hover h2::after {
@ -175,6 +178,10 @@ body {
-webkit-box-shadow: 0 1px 3px #eee; -webkit-box-shadow: 0 1px 3px #eee;
} }
#report ul {
padding: 0;
}
#report.pass .test.fail { #report.pass .test.fail {
display: none; display: none;
} }
@ -191,17 +198,21 @@ body {
} }
#stats { #stats {
position: fixed; padding: 10px;
top: 15px;
right: 52%;
font-size: 12px; font-size: 12px;
margin: 0; margin: 0;
color: #888; color: #888;
text-align: right;
} }
#stats .progress { #mocha-stats {
height: 80px;
}
#mocha-stats .progress {
float: right; float: right;
padding-top: 0; padding-top: 0;
margin-right:5px;
} }
#stats em { #stats em {
@ -229,3 +240,7 @@ code .init { color: #2F6FAD }
code .string { color: #5890AD } code .string { color: #5890AD }
code .keyword { color: #8A6343 } code .keyword { color: #8A6343 }
code .number { color: #2F6FAD } code .number { color: #2F6FAD }
ul{
padding-left:5px;
}

View file

@ -1,28 +1,68 @@
$(function(){ $(function(){
function Base(runner) {
var self = this function stringifyException(exception){
, stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } var err = exception.stack || exception.toString();
, failures = this.failures = [];
// 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;
}
function CustomRunner(runner) {
var stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 };
if (!runner) return; if (!runner) return;
this.runner = runner;
runner.on('start', function(){ runner.on('start', function(){
stats.start = new Date; stats.start = new Date;
}); });
runner.on('suite', function(suite){ runner.on('suite', function(suite){
stats.suites = stats.suites || 0;
suite.root || stats.suites++; suite.root || stats.suites++;
if (suite.root) return;
append(suite.title);
level++;
}); });
runner.on('test end', function(test){ runner.on('suite end', function(suite){
stats.tests = stats.tests || 0; if (suite.root) return;
level--;
if(level == 0) {
append("");
}
});
// Scroll down test display after each test
let mochaEl = $('#mocha')[0];
runner.on('test', function(){
mochaEl.scrollTop = mochaEl.scrollHeight;
});
// max time a test is allowed to run
// TODO this should be lowered once timeslider_revision.js is faster
var killTimeout;
runner.on('test end', function(){
stats.tests++; stats.tests++;
}); });
runner.on('pass', function(test){ runner.on('pass', function(test){
stats.passes = stats.passes || 0; if(killTimeout) clearTimeout(killTimeout);
killTimeout = setTimeout(function(){
append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]");
}, 60000 * 3);
var medium = test.slow() / 2; var medium = test.slow() / 2;
test.speed = test.duration > test.slow() test.speed = test.duration > test.slow()
@ -32,43 +72,31 @@ $(function(){
: 'fast'; : 'fast';
stats.passes++; stats.passes++;
append("->","[green]PASSED[clear] :", test.title," ",test.duration,"ms");
}); });
runner.on('fail', function(test, err){ runner.on('fail', function(test, err){
stats.failures = stats.failures || 0; if(killTimeout) clearTimeout(killTimeout);
killTimeout = setTimeout(function(){
append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]");
}, 60000 * 3);
stats.failures++; stats.failures++;
test.err = err; test.err = err;
failures.push(test); append("->","[red]FAILED[clear] :", test.title, stringifyException(test.err));
}); });
runner.on('end', function(){ runner.on('pending', function(test){
stats.end = new Date; if(killTimeout) clearTimeout(killTimeout);
stats.duration = new Date - stats.start; killTimeout = setTimeout(function(){
}); append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]");
}, 60000 * 3);
runner.on('pending', function(){
stats.pending++; stats.pending++;
append("->","[yellow]PENDING[clear]:", test.title);
}); });
}
/* var $console = $("#console");
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);
// Scroll down test display after each test
mocha = $('#mocha')[0];
runner.on('test', function(){
mocha.scrollTop = mocha.scrollHeight;
});
//initalize the html reporter first
html_reporter(runner);
var $console = $("#console");
var level = 0; var level = 0;
var append = function(){ var append = function(){
var text = Array.prototype.join.apply(arguments, [" "]); var text = Array.prototype.join.apply(arguments, [" "]);
@ -97,68 +125,23 @@ $(function(){
$console.text(oldText + newText + "\\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; var total = runner.total;
runner.on('end', function(){ runner.on('end', function(){
if(stats.tests >= total){ stats.end = new Date;
var minutes = Math.floor(stats.duration / 1000 / 60); stats.duration = stats.end - stats.start;
var seconds = Math.round((stats.duration / 1000) % 60); var minutes = Math.floor(stats.duration / 1000 / 60);
var seconds = Math.round((stats.duration / 1000) % 60) // chrome < 57 does not like this .toString().padStart("2","0");
append("FINISHED -", stats.passes, "tests passed,", stats.failures, "tests failed, duration: " + minutes + ":" + seconds); if(stats.tests === total){
append("FINISHED -", stats.passes, "tests passed,", stats.failures, "tests failed,", stats.pending," pending, duration: " + minutes + ":" + seconds);
} else if (stats.tests > total) {
append("FINISHED - but more tests than planned returned", stats.passes, "tests passed,", stats.failures, "tests failed,", stats.pending," pending, duration: " + minutes + ":" + seconds);
append(total,"tests, but",stats.tests,"returned. There is probably a problem with your async code or error handling, see https://github.com/mochajs/mocha/issues/1327");
}
else {
append("FINISHED - but not all tests returned", stats.passes, "tests passed,", stats.failures, "tests failed,", stats.pending, "tests pending, duration: " + minutes + ":" + seconds);
append(total,"tests, but only",stats.tests,"returned. Check for failed before/beforeEach-hooks (no `test end` is called for them and subsequent tests of the same suite are skipped), see https://github.com/mochajs/mocha/pull/1043");
} }
}); });
}
} }
//http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery //http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery
@ -170,7 +153,7 @@ $(function(){
//get the list of specs and filter it if requested //get the list of specs and filter it if requested
var specs = specs_list.slice(); var specs = specs_list.slice();
//inject spec scripts into the dom //inject spec scripts into the dom
var $body = $('body'); var $body = $('body');
$.each(specs, function(i, spec){ $.each(specs, function(i, spec){
@ -189,10 +172,7 @@ $(function(){
mocha.grep(grep); mocha.grep(grep);
} }
mocha.ignoreLeaks(); var runner = mocha.run();
CustomRunner(runner)
mocha.reporter(WebdriverAndHtmlReporter(mocha._reporter));
mocha.run();
}); });
}); });

View file

@ -8,12 +8,12 @@ describe("All the alphabet works n stuff", function(){
}); });
it("when you enter any char it appears right", function(done) { it("when you enter any char it appears right", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var firstTextElement = inner$("div").first(); var firstTextElement = inner$("div").first();
// simulate key presses to delete content // simulate key presses to delete content
firstTextElement.sendkeys('{selectall}'); // select all firstTextElement.sendkeys('{selectall}'); // select all
firstTextElement.sendkeys('{del}'); // clear the first line firstTextElement.sendkeys('{del}'); // clear the first line

View file

@ -6,22 +6,22 @@ describe("bold button", function(){
}); });
it("makes text bold on click", function(done) { it("makes text bold on click", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var $firstTextElement = inner$("div").first(); var $firstTextElement = inner$("div").first();
//select this text element //select this text element
$firstTextElement.sendkeys('{selectall}'); $firstTextElement.sendkeys('{selectall}');
//get the bold button and click it //get the bold button and click it
var $boldButton = chrome$(".buttonicon-bold"); var $boldButton = chrome$(".buttonicon-bold");
$boldButton.click(); $boldButton.click();
//ace creates a new dom element when you press a button, so just get the first text element again //ace creates a new dom element when you press a button, so just get the first text element again
var $newFirstTextElement = inner$("div").first(); var $newFirstTextElement = inner$("div").first();
// is there a <b> element now? // is there a <b> element now?
var isBold = $newFirstTextElement.find("b").length === 1; var isBold = $newFirstTextElement.find("b").length === 1;
@ -44,13 +44,7 @@ describe("bold button", function(){
//select this text element //select this text element
$firstTextElement.sendkeys('{selectall}'); $firstTextElement.sendkeys('{selectall}');
if(inner$(window)[0].bowser.modernIE){ // if it's IE var e = inner$.Event(helper.evtType);
var evtType = "keypress";
}else{
var evtType = "keydown";
}
var e = inner$.Event(evtType);
e.ctrlKey = true; // Control key e.ctrlKey = true; // Control key
e.which = 66; // b e.which = 66; // b
inner$("#innerdocbody").trigger(e); inner$("#innerdocbody").trigger(e);

View file

@ -198,9 +198,9 @@ console.log(inner$);
/* /*
it("Creates N rows, changes height of rows, updates UI by caret key events", function(done){ it("Creates N rows, changes height of rows, updates UI by caret key events", function(done){
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
var numberOfRows = 50; var numberOfRows = 50;
//ace creates a new dom element when you press a keystroke, so just get the first text element again //ace creates a new dom element when you press a keystroke, so just get the first text element again
var $newFirstTextElement = inner$("div").first(); var $newFirstTextElement = inner$("div").first();
var originalDivHeight = inner$("div").first().css("height"); var originalDivHeight = inner$("div").first().css("height");
@ -211,7 +211,7 @@ console.log(inner$);
}).done(function(){ // Once the DOM has registered the items }).done(function(){ // Once the DOM has registered the items
inner$("div").each(function(index){ // Randomize the item heights (replicates images / headings etc) inner$("div").each(function(index){ // Randomize the item heights (replicates images / headings etc)
var random = Math.floor(Math.random() * (50)) + 20; var random = Math.floor(Math.random() * (50)) + 20;
$(this).css("height", random+"px"); $(this).css("height", random+"px");
}); });
console.log(caretPosition(inner$)); console.log(caretPosition(inner$));
@ -253,7 +253,7 @@ console.log(inner$);
keyEvent(inner$, 33, false, false); // doesn't work keyEvent(inner$, 33, false, false); // doesn't work
i++; i++;
} }
// Does scrolling back up the pad with the up arrow show the correct contents? // Does scrolling back up the pad with the up arrow show the correct contents?
helper.waitFor(function(){ // Wait for the new position to be in place helper.waitFor(function(){ // Wait for the new position to be in place
try{ try{
@ -280,7 +280,7 @@ console.log(inner$);
helper.waitFor(function(){ // Wait for the new position to be in place helper.waitFor(function(){ // Wait for the new position to be in place
return isScrolledIntoView(inner$("div:nth-child(1)"), inner$); // Wait for the DOM to scroll into place return isScrolledIntoView(inner$("div:nth-child(1)"), inner$); // Wait for the DOM to scroll into place
}).done(function(){ // Once the DOM has registered the items }).done(function(){ // Once the DOM has registered the items
expect(true).to.be(true); expect(true).to.be(true);
done(); done();
}); });
*/ */
@ -297,20 +297,15 @@ function prepareDocument(n, target){ // generates a random document with random
} }
function keyEvent(target, charCode, ctrl, shift){ // sends a charCode to the window function keyEvent(target, charCode, ctrl, shift){ // sends a charCode to the window
if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE
var evtType = "keypress"; var e = target.Event(helper.evtType);
}else{
var evtType = "keydown";
}
var e = target.Event(evtType);
console.log(e);
if(ctrl){ if(ctrl){
e.ctrlKey = true; // Control key e.ctrlKey = true; // Control key
} }
if(shift){ if(shift){
e.shiftKey = true; // Shift Key e.shiftKey = true; // Shift Key
} }
e.which = charCode; e.which = charCode;
e.keyCode = charCode; e.keyCode = charCode;
target("#innerdocbody").trigger(e); target("#innerdocbody").trigger(e);
} }
@ -339,6 +334,5 @@ function caretPosition($){
var pos = doc.getSelection(); var pos = doc.getSelection();
pos.y = pos.anchorNode.parentElement.offsetTop; pos.y = pos.anchorNode.parentElement.offsetTop;
pos.x = pos.anchorNode.parentElement.offsetLeft; pos.x = pos.anchorNode.parentElement.offsetLeft;
console.log(pos);
return pos; return pos;
} }

View file

@ -0,0 +1,104 @@
describe("change user color", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
this.timeout(60000);
});
it("Color picker matches original color and remembers the user color 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 $userSwatch = chrome$("#myswatch");
$userSwatch.click();
var fb = chrome$.farbtastic('#colorpicker')
var $colorPickerSave = chrome$("#mycolorpickersave");
var $colorPickerPreview = chrome$("#mycolorpickerpreview");
// Same color represented in two different ways
const testColorHash = '#abcdef'
const testColorRGB = 'rgb(171, 205, 239)'
// Check that the color picker matches the automatically assigned random color on the swatch.
// NOTE: This has a tiny chance of creating a false positive for passing in the
// off-chance the randomly assigned color is the same as the test color.
expect($colorPickerPreview.css('background-color')).to.be($userSwatch.css('background-color'))
// The swatch updates as the test color is picked.
fb.setColor(testColorHash)
expect($colorPickerPreview.css('background-color')).to.be(testColorRGB)
$colorPickerSave.click();
expect($userSwatch.css('background-color')).to.be(testColorRGB)
setTimeout(function(){ //give it a second to save the color 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 $userSwatch = chrome$("#myswatch");
$userSwatch.click();
var $colorPickerPreview = chrome$("#mycolorpickerpreview");
expect($colorPickerPreview.css('background-color')).to.be(testColorRGB)
expect($userSwatch.css('background-color')).to.be(testColorRGB)
done();
}
});
}, 1000);
});
it("Own user color is shown when you enter a chat", function(done) {
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
var $colorOption = helper.padChrome$('#options-colorscheck');
if (!$colorOption.is(':checked')) {
$colorOption.click();
}
//click on the settings button to make settings visible
var $userButton = chrome$(".buttonicon-showusers");
$userButton.click();
var $userSwatch = chrome$("#myswatch");
$userSwatch.click();
var fb = chrome$.farbtastic('#colorpicker')
var $colorPickerSave = chrome$("#mycolorpickersave");
// Same color represented in two different ways
const testColorHash = '#abcdef'
const testColorRGB = 'rgb(171, 205, 239)'
fb.setColor(testColorHash)
$colorPickerSave.click();
//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 user
$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");
expect($firstChatMessage.css('background-color')).to.be(testColorRGB); // expect the first chat message to be of the user's color
done();
});
});
});

View file

@ -12,7 +12,7 @@ describe("change username value", function(){
//click on the settings button to make settings visible //click on the settings button to make settings visible
var $userButton = chrome$(".buttonicon-showusers"); var $userButton = chrome$(".buttonicon-showusers");
$userButton.click(); $userButton.click();
var $usernameInput = chrome$("#myusernameedit"); var $usernameInput = chrome$("#myusernameedit");
$usernameInput.click(); $usernameInput.click();
@ -45,7 +45,7 @@ describe("change username value", function(){
//click on the settings button to make settings visible //click on the settings button to make settings visible
var $userButton = chrome$(".buttonicon-showusers"); var $userButton = chrome$(".buttonicon-showusers");
$userButton.click(); $userButton.click();
var $usernameInput = chrome$("#myusernameedit"); var $usernameInput = chrome$("#myusernameedit");
$usernameInput.click(); $usernameInput.click();

View file

@ -6,8 +6,8 @@ describe("Chat messages and UI", function(){
}); });
it("opens chat, sends a message and makes sure it exists on the page", function(done) { it("opens chat, sends a message and makes sure it exists on the page", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
var chatValue = "JohnMcLear"; var chatValue = "JohnMcLear";
//click on the chat button to make chat visible //click on the chat button to make chat visible
@ -39,8 +39,8 @@ describe("Chat messages and UI", function(){
}); });
it("makes sure that an empty message can't be sent", function(done) { it("makes sure that an empty message can't be sent", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//click on the chat button to make chat visible //click on the chat button to make chat visible
var $chatButton = chrome$("#chaticon"); var $chatButton = chrome$("#chaticon");
@ -65,8 +65,8 @@ describe("Chat messages and UI", function(){
}); });
it("makes chat stick to right side of the screen", function(done) { it("makes chat stick to right side of the screen", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//click on the settings button to make settings visible //click on the settings button to make settings visible
var $settingsButton = chrome$(".buttonicon-settings"); var $settingsButton = chrome$(".buttonicon-settings");
@ -75,56 +75,96 @@ describe("Chat messages and UI", function(){
//get the chat selector //get the chat selector
var $stickychatCheckbox = chrome$("#options-stickychat"); var $stickychatCheckbox = chrome$("#options-stickychat");
//select chat always on screen and fire change event //select chat always on screen
$stickychatCheckbox.attr('selected','selected'); if (!$stickychatCheckbox.is(':checked')) {
$stickychatCheckbox.change(); $stickychatCheckbox.click();
$stickychatCheckbox.click(); }
//check if chat changed to get the stickychat Class // due to animation, we need to make some timeout...
var $chatbox = chrome$("#chatbox"); setTimeout(function() {
var hasStickyChatClass = $chatbox.hasClass("stickyChat"); //check if chat changed to get the stickychat Class
expect(hasStickyChatClass).to.be(true); var $chatbox = chrome$("#chatbox");
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
expect(hasStickyChatClass).to.be(true);
//select chat always on screen and fire change event // select chat always on screen and fire change event
$stickychatCheckbox.attr('selected','selected'); $stickychatCheckbox.click();
$stickychatCheckbox.change();
$stickychatCheckbox.click(); setTimeout(function() {
//check if chat changed to remove the stickychat Class
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
expect(hasStickyChatClass).to.be(false);
done();
}, 10)
}, 10)
//check if chat changed to remove the stickychat Class
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
expect(hasStickyChatClass).to.be(false);
done();
}); });
it("makes chat stick to right side of the screen then makes it one step smaller", function(done) { it("makes chat stick to right side of the screen then makes it one step smaller", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//click on the settings button to make settings visible // open chat
var $settingsButton = chrome$(".buttonicon-settings"); chrome$('#chaticon').click();
$settingsButton.click();
//get the chat selector // select chat always on screen from chatbox
var $stickychatCheckbox = chrome$("#options-stickychat"); chrome$('.stick-to-screen-btn').click();
//select chat always on screen and fire change event // due to animation, we need to make some timeout...
$stickychatCheckbox.attr('selected','selected'); setTimeout(function() {
$stickychatCheckbox.change(); //check if chat changed to get the stickychat Class
$stickychatCheckbox.click(); var $chatbox = chrome$("#chatbox");
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
expect(hasStickyChatClass).to.be(true);
//check if chat changed to get the stickychat Class // select chat always on screen and fire change event
var $chatbox = chrome$("#chatbox"); chrome$('#titlecross').click();
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
expect(hasStickyChatClass).to.be(true);
//select chat always on screen and fire change event setTimeout(function() {
chrome$('#titlecross').click(); //check if chat changed to remove the stickychat Class
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
expect(hasStickyChatClass).to.be(false);
//check if chat changed to remove the stickychat Class done();
var hasStickyChatClass = $chatbox.hasClass("stickyChat"); }, 10)
expect(hasStickyChatClass).to.be(false); }, 10)
done();
}); });
xit("Checks showChat=false URL Parameter hides chat then when removed it shows chat", function(done) {
this.timeout(60000);
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
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,
params:{
showChat: "false"
}, cb: function(){
var chrome$ = helper.padChrome$;
var chaticon = chrome$("#chaticon");
// chat should be hidden.
expect(chaticon.is(":visible")).to.be(false);
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$;
var chaticon = chrome$("#chaticon");
// chat should be visible.
expect(chaticon.is(":visible")).to.be(true);
done();
}
});
}, 1000);
}
});
}, 1000);
});
}); });

View file

@ -1,21 +1,21 @@
describe("chat-load-messages", function(){ describe("chat-load-messages", function(){
var padName; var padName;
it("creates a pad", function(done) { it("creates a pad", function(done) {
padName = helper.newPad(done); padName = helper.newPad(done);
this.timeout(60000); this.timeout(60000);
}); });
it("adds a lot of messages", function(done) { it("adds a lot of messages", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
var chatButton = chrome$("#chaticon"); var chatButton = chrome$("#chaticon");
chatButton.click(); chatButton.click();
var chatInput = chrome$("#chatinput"); var chatInput = chrome$("#chatinput");
var chatText = chrome$("#chattext"); var chatText = chrome$("#chattext");
this.timeout(60000); this.timeout(60000);
var messages = 140; var messages = 140;
for(var i=1; i <= messages; i++) { for(var i=1; i <= messages; i++) {
var num = ''+i; var num = ''+i;
@ -33,7 +33,7 @@ describe("chat-load-messages", function(){
helper.newPad(done, padName); helper.newPad(done, padName);
}); });
}); });
it("checks initial message count", function(done) { it("checks initial message count", function(done) {
var chatText; var chatText;
var expectedCount = 101; var expectedCount = 101;
@ -48,7 +48,7 @@ describe("chat-load-messages", function(){
done(); done();
}); });
}); });
it("loads more messages", function(done) { it("loads more messages", function(done) {
var expectedCount = 122; var expectedCount = 122;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
@ -56,7 +56,7 @@ describe("chat-load-messages", function(){
chatButton.click(); chatButton.click();
var chatText = chrome$("#chattext"); var chatText = chrome$("#chattext");
var loadMsgBtn = chrome$("#chatloadmessagesbutton"); var loadMsgBtn = chrome$("#chatloadmessagesbutton");
loadMsgBtn.click(); loadMsgBtn.click();
helper.waitFor(function(){ helper.waitFor(function(){
return chatText.children("p").length == expectedCount; return chatText.children("p").length == expectedCount;
@ -65,7 +65,7 @@ describe("chat-load-messages", function(){
done(); done();
}); });
}); });
it("checks for button vanishing", function(done) { it("checks for button vanishing", function(done) {
var expectedDisplay = 'none'; var expectedDisplay = 'none';
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;

View file

@ -6,8 +6,8 @@ describe("clear authorship colors button", function(){
}); });
it("makes text clear authorship colors", function(done) { it("makes text clear authorship colors", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
// override the confirm dialogue functioon // override the confirm dialogue functioon
helper.padChrome$.window.confirm = function(){ helper.padChrome$.window.confirm = function(){
@ -19,7 +19,7 @@ describe("clear authorship colors button", function(){
// Get the original text // Get the original text
var originalText = inner$("div").first().text(); var originalText = inner$("div").first().text();
// Set some new text // Set some new text
var sentText = "Hello"; var sentText = "Hello";
@ -39,7 +39,6 @@ describe("clear authorship colors button", function(){
$clearauthorshipcolorsButton.click(); $clearauthorshipcolorsButton.click();
// does the first divs span include an author class? // 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; var hasAuthorClass = inner$("div span").first().attr("class").indexOf("author") !== -1;
//expect(hasAuthorClass).to.be(false); //expect(hasAuthorClass).to.be(false);
@ -47,13 +46,88 @@ describe("clear authorship colors button", function(){
var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1; var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1;
expect(hasAuthorClass).to.be(false); expect(hasAuthorClass).to.be(false);
setTimeout(function(){ helper.waitFor(function(){
var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1 var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1
expect(disconnectVisible).to.be(true); return (disconnectVisible === true)
},1000); });
var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1
expect(disconnectVisible).to.be(true);
done();
});
});
it("makes text clear authorship colors and checks it can't be undone", 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?
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);
var e = inner$.Event(helper.evtType);
e.ctrlKey = true; // Control key
e.which = 90; // z
inner$("#innerdocbody").trigger(e); // shouldn't od anything
// does the first div include an author class?
hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1;
expect(hasAuthorClass).to.be(false);
// get undo and redo buttons
var $undoButton = chrome$(".buttonicon-undo");
// click the button
$undoButton.click(); // shouldn't do anything
hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1;
expect(hasAuthorClass).to.be(false);
helper.waitFor(function(){
var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1
return (disconnectVisible === true)
});
var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1
expect(disconnectVisible).to.be(true);
done(); done();
}); });
}); });
}); });

View file

@ -6,12 +6,12 @@ describe("delete keystroke", function(){
}); });
it("makes text delete", function(done) { it("makes text delete", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var $firstTextElement = inner$("div").first(); var $firstTextElement = inner$("div").first();
// get the original length of this element // get the original length of this element
var elementLength = $firstTextElement.text().length; var elementLength = $firstTextElement.text().length;
@ -25,7 +25,7 @@ describe("delete keystroke", function(){
//ace creates a new dom element when you press a keystroke, so just get the first text element again //ace creates a new dom element when you press a keystroke, so just get the first text element again
var $newFirstTextElement = inner$("div").first(); var $newFirstTextElement = inner$("div").first();
// get the new length of this element // get the new length of this element
var newElementLength = $newFirstTextElement.text().length; var newElementLength = $newFirstTextElement.text().length;

View file

@ -16,11 +16,11 @@ describe("embed links", function(){
var $embediFrame = $(embedCode); var $embediFrame = $(embedCode);
//read and check the frame attributes //read and check the frame attributes
var width = $embediFrame.attr("width"); var width = $embediFrame.attr("width");
var height = $embediFrame.attr("height"); var height = $embediFrame.attr("height");
var name = $embediFrame.attr("name"); var name = $embediFrame.attr("name");
expect(width).to.be('600'); expect(width).to.be('100%');
expect(height).to.be('400'); expect(height).to.be('600');
expect(name).to.be(readonly ? "embed_readonly" : "embed_readwrite"); expect(name).to.be(readonly ? "embed_readonly" : "embed_readwrite");
//parse the url //parse the url
@ -43,7 +43,7 @@ describe("embed links", function(){
} else { } else {
expect(url).to.be(helper.padChrome$.window.location.href); expect(url).to.be(helper.padChrome$.window.location.href);
} }
//check if all parts of the url are like expected //check if all parts of the url are like expected
expect(params).to.eql(expectedParams); expect(params).to.eql(expectedParams);
} }
@ -57,7 +57,7 @@ describe("embed links", function(){
describe("the share link", function(){ describe("the share link", function(){
it("is the actual pad url", function(done){ it("is the actual pad url", function(done){
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//open share dropdown //open share dropdown
chrome$(".buttonicon-embed").click(); chrome$(".buttonicon-embed").click();
@ -73,14 +73,14 @@ describe("embed links", function(){
describe("the embed as iframe code", function(){ describe("the embed as iframe code", function(){
it("is an iframe with the the correct url parameters and correct size", function(done){ it("is an iframe with the the correct url parameters and correct size", function(done){
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//open share dropdown //open share dropdown
chrome$(".buttonicon-embed").click(); chrome$(".buttonicon-embed").click();
//get the link of the share field + the actual pad url and compare them //get the link of the share field + the actual pad url and compare them
var embedCode = chrome$("#embedinput").val(); var embedCode = chrome$("#embedinput").val();
checkiFrameCode(embedCode, false) checkiFrameCode(embedCode, false)
done(); done();
@ -96,7 +96,7 @@ describe("embed links", function(){
describe("the share link", function(){ describe("the share link", function(){
it("shows a read only url", function(done){ it("shows a read only url", function(done){
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//open share dropdown //open share dropdown
chrome$(".buttonicon-embed").click(); chrome$(".buttonicon-embed").click();
@ -114,7 +114,7 @@ describe("embed links", function(){
describe("the embed as iframe code", function(){ describe("the embed as iframe code", function(){
it("is an iframe with the the correct url parameters and correct size", function(done){ it("is an iframe with the the correct url parameters and correct size", function(done){
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//open share dropdown //open share dropdown
chrome$(".buttonicon-embed").click(); chrome$(".buttonicon-embed").click();
@ -125,9 +125,9 @@ describe("embed links", function(){
//get the link of the share field + the actual pad url and compare them //get the link of the share field + the actual pad url and compare them
var embedCode = chrome$("#embedinput").val(); var embedCode = chrome$("#embedinput").val();
checkiFrameCode(embedCode, true); checkiFrameCode(embedCode, true);
done(); done();
}); });
}); });

View file

@ -6,12 +6,12 @@ describe("enter keystroke", function(){
}); });
it("creates a new line & puts cursor onto a new line", function(done) { it("creates a new line & puts cursor onto a new line", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var $firstTextElement = inner$("div").first(); var $firstTextElement = inner$("div").first();
// get the original string value minus the last char // get the original string value minus the last char
var originalTextValue = $firstTextElement.text(); var originalTextValue = $firstTextElement.text();
@ -20,7 +20,7 @@ describe("enter keystroke", function(){
//ace creates a new dom element when you press a keystroke, so just get the first text element again //ace creates a new dom element when you press a keystroke, so just get the first text element again
var $newFirstTextElement = inner$("div").first(); var $newFirstTextElement = inner$("div").first();
helper.waitFor(function(){ helper.waitFor(function(){
return inner$("div").first().text() === ""; return inner$("div").first().text() === "";
}).done(function(){ }).done(function(){

View file

@ -5,26 +5,27 @@ describe("font select", function(){
this.timeout(60000); this.timeout(60000);
}); });
it("makes text monospace", function(done) { it("makes text RobotoMono", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//click on the settings button to make settings visible //click on the settings button to make settings visible
var $settingsButton = chrome$(".buttonicon-settings"); var $settingsButton = chrome$(".buttonicon-settings");
$settingsButton.click(); $settingsButton.click();
//get the font menu and monospace option //get the font menu and RobotoMono option
var $viewfontmenu = chrome$("#viewfontmenu"); var $viewfontmenu = chrome$("#viewfontmenu");
var $monospaceoption = $viewfontmenu.find("[value=monospace]"); var $RobotoMonooption = $viewfontmenu.find("[value=RobotoMono]");
//select monospace and fire change event //select RobotoMono and fire change event
$monospaceoption.attr('selected','selected'); // $RobotoMonooption.attr('selected','selected');
$viewfontmenu.val("monospace"); // commenting out above will break safari test
$viewfontmenu.val("RobotoMono");
$viewfontmenu.change(); $viewfontmenu.change();
//check if font changed to monospace //check if font changed to RobotoMono
var fontFamily = inner$("body").css("font-family").toLowerCase(); var fontFamily = inner$("body").css("font-family").toLowerCase();
var containsStr = fontFamily.indexOf("monospace"); var containsStr = fontFamily.indexOf("robotomono");
expect(containsStr).to.not.be(-1); expect(containsStr).to.not.be(-1);
done(); done();

View file

@ -20,7 +20,7 @@ describe("the test helper", function(){
}); });
it("gives me 3 jquery instances of chrome, outer and inner", function(done){ it("gives me 3 jquery instances of chrome, outer and inner", function(done){
this.timeout(5000); this.timeout(10000);
helper.newPad(function(){ helper.newPad(function(){
//check if the jquery selectors have the desired elements //check if the jquery selectors have the desired elements
@ -36,17 +36,106 @@ describe("the test helper", function(){
done(); done();
}); });
}); });
// Make sure the cookies are cleared, and make sure that the cookie
// clearing has taken effect at this point in the code. It has been
// observed that the former can happen without the latter if there
// isn't a timeout (within `newPad`) after clearing the cookies.
// However this doesn't seem to always be easily replicated, so this
// timeout may or may end up in the code. None the less, we test here
// to catch it if the bug comes up again.
it("clears cookies", function(done) {
this.timeout(60000);
// set cookies far into the future to make sure they're not expired yet
window.document.cookie = 'token=foo;expires=Thu, 01 Jan 3030 00:00:00 GMT; path=/';
window.document.cookie = 'language=bar;expires=Thu, 01 Jan 3030 00:00:00 GMT; path=/';
expect(window.document.cookie).to.contain('token=foo');
expect(window.document.cookie).to.contain('language=bar');
helper.newPad(function(){
// helper function seems to have cleared cookies
// NOTE: this doesn't yet mean it's proven to have taken effect by this point in execution
var firstCookie = window.document.cookie
expect(firstCookie).to.not.contain('token=foo');
expect(firstCookie).to.not.contain('language=bar');
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();
// Before refreshing, make sure the name is there
expect($usernameInput.val()).to.be('John McLear');
// Now that we have a chrome, we can set a pad cookie, so we can confirm it gets wiped as well
chrome$.document.cookie = 'prefsHtml=baz;expires=Thu, 01 Jan 3030 00:00:00 GMT';
expect(chrome$.document.cookie).to.contain('prefsHtml=baz');
// Cookies are weird. Because it's attached to chrome$ (as helper.setPadCookies does), AND we
// didn't put path=/, we shouldn't expect it to be visible on window.document.cookie. Let's just
// be sure.
expect(window.document.cookie).to.not.contain('prefsHtml=baz');
setTimeout(function(){ //give it a second to save the username on the server side
helper.newPad(function(){ // get a new pad, let it clear the cookies
var chrome$ = helper.padChrome$;
// helper function seems to have cleared cookies
// NOTE: this doesn't yet mean cookies were cleared effectively.
// We still need to test below that we're in a new session
expect(window.document.cookie).to.not.contain('token=foo');
expect(window.document.cookie).to.not.contain('language=bar');
expect(chrome$.document.cookie).to.contain('prefsHtml=baz');
expect(window.document.cookie).to.not.contain('prefsHtml=baz');
expect(window.document.cookie).to.not.be(firstCookie);
// click on the settings button to make settings visible
var $userButton = chrome$(".buttonicon-showusers");
$userButton.click();
// confirm that the session was actually cleared
var $usernameInput = chrome$("#myusernameedit");
expect($usernameInput.val()).to.be('');
done();
});
}, 1000);
});
});
it("sets pad prefs cookie", function(done) {
this.timeout(60000);
helper.newPad({
padPrefs: {foo:"bar"},
cb: function(){
var chrome$ = helper.padChrome$;
expect(chrome$.document.cookie).to.contain('prefsHttp=%7B%22');
expect(chrome$.document.cookie).to.contain('foo%22%3A%22bar');
done();
}
});
});
}); });
describe("the waitFor method", function(){ describe("the waitFor method", function(){
it("takes a timeout and waits long enough", function(done){ it("takes a timeout and waits long enough", function(done){
this.timeout(2000); this.timeout(2000);
var startTime = new Date().getTime(); var startTime = Date.now();
helper.waitFor(function(){ helper.waitFor(function(){
return false; return false;
}, 1500).fail(function(){ }, 1500).fail(function(){
var duration = new Date().getTime() - startTime; var duration = Date.now() - startTime;
expect(duration).to.be.greaterThan(1400); expect(duration).to.be.greaterThan(1400);
done(); done();
}); });
@ -136,7 +225,15 @@ describe("the test helper", function(){
helper.selectLines($startLine, $endLine, startOffset, endOffset); helper.selectLines($startLine, $endLine, startOffset, endOffset);
var selection = inner$.document.getSelection(); var selection = inner$.document.getSelection();
expect(cleanText(selection.toString())).to.be("ort lines to t");
/*
* replace() is required here because Firefox keeps the line breaks.
*
* I'm not sure this is ideal behavior of getSelection() where the text
* is not consistent between browsers but that's the situation so that's
* how I'm covering it in this test.
*/
expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("ort lines to t");
done(); done();
}); });
@ -154,7 +251,15 @@ describe("the test helper", function(){
helper.selectLines($startLine, $endLine, startOffset, endOffset); helper.selectLines($startLine, $endLine, startOffset, endOffset);
var selection = inner$.document.getSelection(); var selection = inner$.document.getSelection();
expect(cleanText(selection.toString())).to.be("ort lines to test");
/*
* replace() is required here because Firefox keeps the line breaks.
*
* I'm not sure this is ideal behavior of getSelection() where the text
* is not consistent between browsers but that's the situation so that's
* how I'm covering it in this test.
*/
expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("ort lines to test");
done(); done();
}); });
@ -172,7 +277,15 @@ describe("the test helper", function(){
helper.selectLines($startLine, $endLine, startOffset, endOffset); helper.selectLines($startLine, $endLine, startOffset, endOffset);
var selection = inner$.document.getSelection(); var selection = inner$.document.getSelection();
expect(cleanText(selection.toString())).to.be("ort lines ");
/*
* replace() is required here because Firefox keeps the line breaks.
*
* I'm not sure this is ideal behavior of getSelection() where the text
* is not consistent between browsers but that's the situation so that's
* how I'm covering it in this test.
*/
expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("ort lines ");
done(); done();
}); });
@ -190,7 +303,15 @@ describe("the test helper", function(){
helper.selectLines($startLine, $endLine, startOffset, endOffset); helper.selectLines($startLine, $endLine, startOffset, endOffset);
var selection = inner$.document.getSelection(); var selection = inner$.document.getSelection();
expect(cleanText(selection.toString())).to.be("ort lines to test");
/*
* replace() is required here because Firefox keeps the line breaks.
*
* I'm not sure this is ideal behavior of getSelection() where the text
* is not consistent between browsers but that's the situation so that's
* how I'm covering it in this test.
*/
expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("ort lines to test");
done(); done();
}); });
@ -205,7 +326,15 @@ describe("the test helper", function(){
helper.selectLines($startLine, $endLine); helper.selectLines($startLine, $endLine);
var selection = inner$.document.getSelection(); var selection = inner$.document.getSelection();
expect(cleanText(selection.toString())).to.be("short lines to test");
/*
* replace() is required here because Firefox keeps the line breaks.
*
* I'm not sure this is ideal behavior of getSelection() where the text
* is not consistent between browsers but that's the situation so that's
* how I'm covering it in this test.
*/
expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("short lines to test");
done(); done();
}); });

View file

@ -159,7 +159,7 @@ describe("import functionality", function(){
//<ul class="list-bullet4"><li><span class="">bullet4 line 2</span></li></ul>\n\ //<ul class="list-bullet4"><li><span class="">bullet4 line 2</span></li></ul>\n\
//<br>\n') //<br>\n')
}) })
var results = exportfunc(helper.padChrome$.window.location.href) var results = exportfunc(helper.padChrome$.window.location.href)
expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li><ul><ul class="bullet"><li>bullet4 line 2</li><li>bullet4 line 2</li><li>bullet4 line 2</li></ul><li>bullet3 line 1</li></ul></ul><li>bullet2 line 1</li></ul><br>') expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li><ul><ul class="bullet"><li>bullet4 line 2</li><li>bullet4 line 2</li><li>bullet4 line 2</li></ul><li>bullet3 line 1</li></ul></ul><li>bullet2 line 1</li></ul><br>')
expect(results[1][1]).to.be('\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t* bullet3 line 1\n\t* bullet2 line 1\n\n') expect(results[1][1]).to.be('\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t* bullet3 line 1\n\t* bullet2 line 1\n\n')
@ -183,11 +183,11 @@ describe("import functionality", function(){
<br>\n') <br>\n')
}) })
var results = exportfunc(helper.padChrome$.window.location.href) var results = exportfunc(helper.padChrome$.window.location.href)
expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet"><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul></ul><br><ul><ul><ul><ul class="bullet"><li><strong><em><s><u>bullet4 line 2 bisu</u></s></em></strong></li><li><strong><s>bullet4 line 2 bs</s></strong></li><li><u>bullet4 line 2 u<em><s>uis</s></em></u></li><ul><ul><ul><ul class="bullet"><li>foo</li><li><strong><s>foobar bs</s></strong></li></ul></ul></ul><li>foobar</li></ul></ul></ul></ul></ul><br>') expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet"><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul></ul><br><ul><ul><ul><ul class="bullet"><li><strong><em><s><u>bullet4 line 2 bisu</u></s></em></strong></li><li><strong><s>bullet4 line 2 bs</s></strong></li><li><u>bullet4 line 2 u<em><s>uis</s></em></u></li><ul><ul><ul><ul class="bullet"><li>foo</li><li><strong><s>foobar bs</s></strong></li></ul></ul></ul><li>foobar</li></ul></ul></ul></ul></ul><br>')
expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2 bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\t\t\t\t\t\t\t\t* foo\n\t\t\t\t\t\t\t\t* foobar bs\n\t\t\t\t\t* foobar\n\n') expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2 bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\t\t\t\t\t\t\t\t* foo\n\t\t\t\t\t\t\t\t* foobar bs\n\t\t\t\t\t* foobar\n\n')
done() done()
}) })
xit("import a pad with ordered lists from html", function(done){ xit("import a pad with ordered lists from html", function(done){
var importurl = helper.padChrome$.window.location.href+'/import' var importurl = helper.padChrome$.window.location.href+'/import'
var htmlWithBullets = '<html><body><ol class="list-number1" start="1"><li>number 1 line 1</li></ol><ol class="list-number1" start="2"><li>number 2 line 2</li></ol></body></html>' var htmlWithBullets = '<html><body><ol class="list-number1" start="1"><li>number 1 line 1</li></ol><ol class="list-number1" start="2"><li>number 2 line 2</li></ol></body></html>'

View file

@ -66,7 +66,7 @@ describe("import indents functionality", function(){
expect(results[1][1]).to.be('\tindent line 1\n\tindent line 2\n\t\tindent2 line 1\n\t\tindent2 line 2\n\n') expect(results[1][1]).to.be('\tindent line 1\n\tindent line 2\n\t\tindent2 line 1\n\t\tindent2 line 2\n\n')
done() done()
}) })
xit("import a pad with indented lists and newlines from html", function(done){ xit("import a pad with indented lists and newlines from html", function(done){
var importurl = helper.padChrome$.window.location.href+'/import' var importurl = helper.padChrome$.window.location.href+'/import'
var htmlWithIndents = '<html><body><ul class="list-indent1"><li>indent line 1</li></ul><br/><ul class="list-indent1"><li>indent 1 line 2</li><ul class="list-indent2"><li>indent 2 times line 1</li></ul></ul><br/><ul class="list-indent1"><ul class="list-indent2"><li>indent 2 times line 2</li></ul></ul></body></html>' var htmlWithIndents = '<html><body><ul class="list-indent1"><li>indent line 1</li></ul><br/><ul class="list-indent1"><li>indent 1 line 2</li><ul class="list-indent2"><li>indent 2 times line 1</li></ul></ul><br/><ul class="list-indent1"><ul class="list-indent2"><li>indent 2 times line 2</li></ul></ul></body></html>'
@ -104,7 +104,7 @@ describe("import indents functionality", function(){
<br>\n') <br>\n')
}) })
var results = exportfunc(helper.padChrome$.window.location.href) var results = exportfunc(helper.padChrome$.window.location.href)
expect(results[0][1]).to.be('<ul class="indent"><li>indent line 1</li></ul><br><ul class="indent"><li>indent line 2</li><ul class="indent"><li>indent2 line 1</li></ul></ul><br><ul><ul><ul><ul class="indent"><li><strong><em><s><u>indent4 line 2 bisu</u></s></em></strong></li><li><strong><s>indent4 line 2 bs</s></strong></li><li><u>indent4 line 2 u<em><s>uis</s></em></u></li><ul><ul><ul><ul class="indent"><li>foo</li><li><strong><s>foobar bs</s></strong></li></ul></ul></ul><li>foobar</li></ul></ul></ul></ul></ul><br>') expect(results[0][1]).to.be('<ul class="indent"><li>indent line 1</li></ul><br><ul class="indent"><li>indent line 2</li><ul class="indent"><li>indent2 line 1</li></ul></ul><br><ul><ul><ul><ul class="indent"><li><strong><em><s><u>indent4 line 2 bisu</u></s></em></strong></li><li><strong><s>indent4 line 2 bs</s></strong></li><li><u>indent4 line 2 u<em><s>uis</s></em></u></li><ul><ul><ul><ul class="indent"><li>foo</li><li><strong><s>foobar bs</s></strong></li></ul></ul></ul><li>foobar</li></ul></ul></ul></ul></ul><br>')
expect(results[1][1]).to.be('\tindent line 1\n\n\tindent line 2\n\t\tindent2 line 1\n\n\t\t\t\tindent4 line 2 bisu\n\t\t\t\tindent4 line 2 bs\n\t\t\t\tindent4 line 2 uuis\n\t\t\t\t\t\t\t\tfoo\n\t\t\t\t\t\t\t\tfoobar bs\n\t\t\t\t\tfoobar\n\n') expect(results[1][1]).to.be('\tindent line 1\n\n\tindent line 2\n\t\tindent2 line 1\n\n\t\t\t\tindent4 line 2 bisu\n\t\t\t\tindent4 line 2 bs\n\t\t\t\tindent4 line 2 uuis\n\t\t\t\t\t\t\t\tfoo\n\t\t\t\t\t\t\t\tfoobar bs\n\t\t\t\t\tfoobar\n\n')
done() done()
}) })

View file

@ -15,13 +15,7 @@ describe("indentation button", function(){
//select this text element //select this text element
$firstTextElement.sendkeys('{selectall}'); $firstTextElement.sendkeys('{selectall}');
if(inner$(window)[0].bowser.modernIE){ // if it's IE var e = inner$.Event(helper.evtType);
var evtType = "keypress";
}else{
var evtType = "keydown";
}
var e = inner$.Event(evtType);
e.keyCode = 9; // tab :| e.keyCode = 9; // tab :|
inner$("#innerdocbody").trigger(e); inner$("#innerdocbody").trigger(e);
@ -325,12 +319,7 @@ describe("indentation button", function(){
function pressEnter(){ function pressEnter(){
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
if(inner$(window)[0].bowser.modernIE){ // if it's IE var e = inner$.Event(helper.evtType);
var evtType = "keypress";
}else{
var evtType = "keydown";
}
var e = inner$.Event(evtType);
e.keyCode = 13; // enter :| e.keyCode = 13; // enter :|
inner$("#innerdocbody").trigger(e); inner$("#innerdocbody").trigger(e);
} }

View file

@ -6,22 +6,22 @@ describe("italic some text", function(){
}); });
it("makes text italic using button", function(done) { it("makes text italic using button", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var $firstTextElement = inner$("div").first(); var $firstTextElement = inner$("div").first();
//select this text element //select this text element
$firstTextElement.sendkeys('{selectall}'); $firstTextElement.sendkeys('{selectall}');
//get the bold button and click it //get the bold button and click it
var $boldButton = chrome$(".buttonicon-italic"); var $boldButton = chrome$(".buttonicon-italic");
$boldButton.click(); $boldButton.click();
//ace creates a new dom element when you press a button, so just get the first text element again //ace creates a new dom element when you press a button, so just get the first text element again
var $newFirstTextElement = inner$("div").first(); var $newFirstTextElement = inner$("div").first();
// is there a <i> element now? // is there a <i> element now?
var isItalic = $newFirstTextElement.find("i").length === 1; var isItalic = $newFirstTextElement.find("i").length === 1;
@ -44,13 +44,7 @@ describe("italic some text", function(){
//select this text element //select this text element
$firstTextElement.sendkeys('{selectall}'); $firstTextElement.sendkeys('{selectall}');
if(inner$(window)[0].bowser.modernIE){ // if it's IE var e = inner$.Event(helper.evtType);
var evtType = "keypress";
}else{
var evtType = "keydown";
}
var e = inner$.Event(evtType);
e.ctrlKey = true; // Control key e.ctrlKey = true; // Control key
e.which = 105; // i e.which = 105; // i
inner$("#innerdocbody").trigger(e); inner$("#innerdocbody").trigger(e);

View file

@ -0,0 +1,51 @@
describe('author of pad edition', function() {
// author 1 creates a new pad with some content (regular lines and lists)
before(function(done) {
var padId = helper.newPad(function() {
// make sure pad has at least 3 lines
var $firstLine = helper.padInner$('div').first();
$firstLine.html("Hello World");
// wait for lines to be processed by Etherpad
helper.waitFor(function() {
return $firstLine.text() === 'Hello World';
}).done(function() {
// Reload pad, to make changes as a second user. Need a timeout here to make sure
// all changes were saved before reloading
setTimeout(function() {
// Expire cookie, so author is changed after reloading the pad.
// See https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#Example_4_Reset_the_previous_cookie
helper.padChrome$.document.cookie = 'token=foo;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
helper.newPad(done, padId);
}, 1000);
});
});
this.timeout(60000);
});
// author 2 makes some changes on the pad
it('Clears Authorship by second user', function(done) {
clearAuthorship(done);
});
var clearAuthorship = function(done){
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
// override the confirm dialogue functioon
helper.padChrome$.window.confirm = function(){
return true;
}
//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?
var hasAuthorClass = inner$("div span").first().attr("class").indexOf("author") !== -1;
expect(hasAuthorClass).to.be(false)
done();
}
});

View file

@ -100,7 +100,6 @@ describe("assign ordered list", function(){
}).done(function(){ }).done(function(){
var $newSecondLine = inner$("div").first().next(); var $newSecondLine = inner$("div").first().next();
var hasOLElement = $newSecondLine.find("ol li").length === 1; var hasOLElement = $newSecondLine.find("ol li").length === 1;
console.log($newSecondLine.find("ol"));
expect(hasOLElement).to.be(true); expect(hasOLElement).to.be(true);
expect($newSecondLine.text()).to.be("line 2"); expect($newSecondLine.text()).to.be("line 2");
var hasLineNumber = $newSecondLine.find("ol").attr("start") === 2; var hasLineNumber = $newSecondLine.find("ol").attr("start") === 2;
@ -111,12 +110,7 @@ describe("assign ordered list", function(){
var triggerCtrlShiftShortcut = function(shortcutChar) { var triggerCtrlShiftShortcut = function(shortcutChar) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
if(inner$(window)[0].bowser.modernIE) { // if it's IE var e = inner$.Event(helper.evtType);
var evtType = "keypress";
}else{
var evtType = "keydown";
}
var e = inner$.Event(evtType);
e.ctrlKey = true; e.ctrlKey = true;
e.shiftKey = true; e.shiftKey = true;
e.which = shortcutChar.toString().charCodeAt(0); e.which = shortcutChar.toString().charCodeAt(0);
@ -131,3 +125,80 @@ describe("assign ordered list", function(){
} }
}); });
describe("Pressing Tab in an OL increases and decreases indentation", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
this.timeout(60000);
});
it("indent and de-indent list item with keypress", 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}');
var $insertorderedlistButton = chrome$(".buttonicon-insertorderedlist");
$insertorderedlistButton.click();
var e = inner$.Event(helper.evtType);
e.keyCode = 9; // tab
inner$("#innerdocbody").trigger(e);
expect(inner$("div").first().find(".list-number2").length === 1).to.be(true);
e.shiftKey = true; // shift
e.keyCode = 9; // tab
inner$("#innerdocbody").trigger(e);
helper.waitFor(function(){
return inner$("div").first().find(".list-number1").length === 1;
}).done(done);
});
});
describe("Pressing indent/outdent button in an OL increases and decreases indentation and bullet / ol formatting", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
this.timeout(60000);
});
it("indent and de-indent list item with indent button", 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}');
var $insertorderedlistButton = chrome$(".buttonicon-insertorderedlist");
$insertorderedlistButton.click();
var $indentButton = chrome$(".buttonicon-indent");
$indentButton.click(); // make it indented twice
expect(inner$("div").first().find(".list-number2").length === 1).to.be(true);
var $outdentButton = chrome$(".buttonicon-outdent");
$outdentButton.click(); // make it deindented to 1
helper.waitFor(function(){
return inner$("div").first().find(".list-number1").length === 1;
}).done(done);
});
});

View file

@ -1,6 +1,6 @@
describe('Pad modal', function() { describe('Pad modal', function() {
context('when modal is a "force reconnect" message', function() { context('when modal is a "force reconnect" message', function() {
var MODAL_SELECTOR = '#connectivity .slowcommit'; var MODAL_SELECTOR = '#connectivity';
beforeEach(function(done) { beforeEach(function(done) {
helper.newPad(function() { helper.newPad(function() {
@ -10,7 +10,7 @@ describe('Pad modal', function() {
// wait for modal to be displayed // wait for modal to be displayed
var $modal = helper.padChrome$(MODAL_SELECTOR); var $modal = helper.padChrome$(MODAL_SELECTOR);
helper.waitFor(function() { helper.waitFor(function() {
return $modal.is(':visible'); return $modal.hasClass('popup-show');
}, 50000).done(done); }, 50000).done(done);
}); });
@ -30,7 +30,7 @@ describe('Pad modal', function() {
it('does not close the modal', function(done) { it('does not close the modal', function(done) {
var $modal = helper.padChrome$(MODAL_SELECTOR); var $modal = helper.padChrome$(MODAL_SELECTOR);
var modalIsVisible = $modal.is(':visible'); var modalIsVisible = $modal.hasClass('popup-show');
expect(modalIsVisible).to.be(true); expect(modalIsVisible).to.be(true);
@ -45,7 +45,7 @@ describe('Pad modal', function() {
it('does not close the modal', function(done) { it('does not close the modal', function(done) {
var $modal = helper.padChrome$(MODAL_SELECTOR); var $modal = helper.padChrome$(MODAL_SELECTOR);
var modalIsVisible = $modal.is(':visible'); var modalIsVisible = $modal.hasClass('popup-show');
expect(modalIsVisible).to.be(true); expect(modalIsVisible).to.be(true);
@ -65,12 +65,13 @@ describe('Pad modal', function() {
this.timeout(60000); this.timeout(60000);
}); });
// This test breaks safari testing
/*
it('does not disable editor', function(done) { it('does not disable editor', function(done) {
expect(isEditorDisabled()).to.be(false); expect(isEditorDisabled()).to.be(false);
done(); done();
}); });
*/
context('and user clicks on editor', function() { context('and user clicks on editor', function() {
beforeEach(function() { beforeEach(function() {
clickOnPadInner(); clickOnPadInner();
@ -126,6 +127,7 @@ describe('Pad modal', function() {
var isModalOpened = function(modalSelector) { var isModalOpened = function(modalSelector) {
var $modal = helper.padChrome$(modalSelector); var $modal = helper.padChrome$(modalSelector);
return $modal.is(':visible');
return $modal.hasClass('popup-show');
} }
}); });

View file

@ -7,7 +7,7 @@ describe("undo button then redo button", function(){
it("redo some typing with button", function(done){ it("redo some typing with button", function(done){
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
// get the first text element inside the editable space // get the first text element inside the editable space
var $firstTextElement = inner$("div span").first(); var $firstTextElement = inner$("div span").first();
var originalValue = $firstTextElement.text(); // get the original value var originalValue = $firstTextElement.text(); // get the original value
@ -25,7 +25,6 @@ describe("undo button then redo button", function(){
$redoButton.click(); // resends foo $redoButton.click(); // resends foo
helper.waitFor(function(){ helper.waitFor(function(){
console.log(inner$("div span").first().text());
return inner$("div span").first().text() === newString; return inner$("div span").first().text() === newString;
}).done(function(){ }).done(function(){
var finalValue = inner$("div").first().text(); var finalValue = inner$("div").first().text();
@ -47,24 +46,17 @@ describe("undo button then redo button", function(){
var modifiedValue = $firstTextElement.text(); // get the modified value var modifiedValue = $firstTextElement.text(); // get the modified value
expect(modifiedValue).not.to.be(originalValue); // expect the value to change expect(modifiedValue).not.to.be(originalValue); // expect the value to change
if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE var e = inner$.Event(helper.evtType);
var evtType = "keypress";
}else{
var evtType = "keydown";
}
var e = inner$.Event(evtType);
e.ctrlKey = true; // Control key e.ctrlKey = true; // Control key
e.which = 90; // z e.which = 90; // z
inner$("#innerdocbody").trigger(e); inner$("#innerdocbody").trigger(e);
var e = inner$.Event(evtType); var e = inner$.Event(helper.evtType);
e.ctrlKey = true; // Control key e.ctrlKey = true; // Control key
e.which = 121; // y e.which = 121; // y
inner$("#innerdocbody").trigger(e); inner$("#innerdocbody").trigger(e);
helper.waitFor(function(){ helper.waitFor(function(){
console.log(inner$("div span").first().text());
return inner$("div span").first().text() === newString; return inner$("div span").first().text() === newString;
}).done(function(){ }).done(function(){
var finalValue = inner$("div").first().text(); var finalValue = inner$("div").first().text();

View file

@ -49,7 +49,7 @@ describe('Responsiveness of Editor', function() {
}).done(function(){ }).done(function(){
expect( inner$('div').text().length ).to.be.greaterThan( length ); // has the text changed? expect( inner$('div').text().length ).to.be.greaterThan( length ); // has the text changed?
var start = new Date().getTime(); // get the start time var start = Date.now(); // get the start time
// send some new text to the screen (ensure all 3 key events are sent) // send some new text to the screen (ensure all 3 key events are sent)
var el = inner$('div').first(); var el = inner$('div').first();
@ -65,11 +65,10 @@ describe('Responsiveness of Editor', function() {
helper.waitFor(function(){ // Wait for the ability to process helper.waitFor(function(){ // Wait for the ability to process
return true; // Ghetto but works for now return true; // Ghetto but works for now
}).done(function(){ }).done(function(){
var end = new Date().getTime(); // get the current time var end = Date.now(); // get the current time
var delay = end - start; // get the delay as the current time minus the start time var delay = end - start; // get the delay as the current time minus the start time
console.log('delay:', delay); expect(delay).to.be.below(300);
expect(delay).to.be.below(200);
done(); done();
}, 1000); }, 1000);

View file

@ -1,649 +0,0 @@
describe('scroll when focus line is out of viewport', function () {
before(function (done) {
helper.newPad(function(){
cleanPad(function(){
forceUseMonospacedFont();
scrollWhenPlaceCaretInTheLastLineOfViewport();
createPadWithSeveralLines(function(){
resizeEditorHeight();
done();
});
});
});
this.timeout(20000);
});
context('when user presses any arrow keys on a line above the viewport', function(){
context('and scroll percentage config is set to 0.2 on settings.json', function(){
var lineCloseOfTopOfPad = 10;
before(function (done) {
setScrollPercentageWhenFocusLineIsOutOfViewport(0.2, true);
scrollEditorToBottomOfPad();
placeCaretInTheBeginningOfLine(lineCloseOfTopOfPad, function(){ // place caret in the 10th line
// warning: even pressing right arrow, the caret does not change of position
// the column where the caret is, it has not importance, only the line
pressAndReleaseRightArrow();
done();
});
});
it('keeps the focus line scrolled 20% from the top of the viewport', function (done) {
// default behavior is to put the line in the top of viewport, but as
// scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2, we have an extra 20% of lines scrolled
// (2 lines, which are the 20% of the 10 that are visible on viewport)
var firstLineOfViewport = getFirstLineVisibileOfViewport();
expect(lineCloseOfTopOfPad).to.be(firstLineOfViewport + 2);
done();
});
});
});
context('when user presses any arrow keys on a line below the viewport', function(){
context('and scroll percentage config is set to 0.7 on settings.json', function(){
var lineCloseToBottomOfPad = 50;
before(function (done) {
setScrollPercentageWhenFocusLineIsOutOfViewport(0.7);
// firstly, scroll to make the lineCloseToBottomOfPad visible. After that, scroll to make it out of viewport
scrollEditorToTopOfPad();
placeCaretAtTheEndOfLine(lineCloseToBottomOfPad); // place caret in the 50th line
setTimeout(function() {
// warning: even pressing right arrow, the caret does not change of position
pressAndReleaseLeftArrow();
done();
}, 1000);
});
it('keeps the focus line scrolled 70% from the bottom of the viewport', function (done) {
// default behavior is to put the line in the top of viewport, but as
// scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.7, we have an extra 70% of lines scrolled
// (7 lines, which are the 70% of the 10 that are visible on viewport)
var lastLineOfViewport = getLastLineVisibleOfViewport();
expect(lineCloseToBottomOfPad).to.be(lastLineOfViewport - 7);
done();
});
});
});
context('when user presses arrow up on the first line of the viewport', function(){
context('and percentageToScrollWhenUserPressesArrowUp is set to 0.3', function () {
var lineOnTopOfViewportWhenThePadIsScrolledDown;
before(function (done) {
setPercentageToScrollWhenUserPressesArrowUp(0.3);
// we need some room to make the scroll up
scrollEditorToBottomOfPad();
lineOnTopOfViewportWhenThePadIsScrolledDown = 91;
placeCaretAtTheEndOfLine(lineOnTopOfViewportWhenThePadIsScrolledDown);
setTimeout(function() {
// warning: even pressing up arrow, the caret does not change of position
pressAndReleaseUpArrow();
done();
}, 1000);
});
it('keeps the focus line scrolled 30% of the top of the viewport', function (done) {
// default behavior is to put the line in the top of viewport, but as
// PercentageToScrollWhenUserPressesArrowUp is set to 0.3, we have an extra 30% of lines scrolled
// (3 lines, which are the 30% of the 10 that are visible on viewport)
var firstLineOfViewport = getFirstLineVisibileOfViewport();
expect(firstLineOfViewport).to.be(lineOnTopOfViewportWhenThePadIsScrolledDown - 3);
done();
})
});
});
context('when user edits the last line of viewport', function(){
context('and scroll percentage config is set to 0 on settings.json', function(){
var lastLineOfViewportBeforeEnter = 10;
before(function () {
// the default value
resetScrollPercentageWhenFocusLineIsOutOfViewport();
// make sure the last line on viewport is the 10th one
scrollEditorToTopOfPad();
placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter);
pressEnter();
});
it('keeps the focus line on the bottom of the viewport', function (done) {
var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport();
expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 1);
done();
});
});
context('and scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.3', function(){ // this value is arbitrary
var lastLineOfViewportBeforeEnter = 9;
before(function () {
setScrollPercentageWhenFocusLineIsOutOfViewport(0.3);
// make sure the last line on viewport is the 10th one
scrollEditorToTopOfPad();
placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter);
pressBackspace();
});
it('scrolls 30% of viewport up', function (done) {
var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport();
// default behavior is to scroll one line at the bottom of viewport, but as
// scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.3, we have an extra 30% of lines scrolled
// (3 lines, which are the 30% of the 10 that are visible on viewport)
expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 3);
done();
});
});
context('and it is set to a value that overflow the interval [0, 1]', function(){
var lastLineOfViewportBeforeEnter = 10;
before(function(){
var scrollPercentageWhenFocusLineIsOutOfViewport = 1.5;
scrollEditorToTopOfPad();
placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter);
setScrollPercentageWhenFocusLineIsOutOfViewport(scrollPercentageWhenFocusLineIsOutOfViewport);
pressEnter();
});
it('keeps the default behavior of moving the focus line on the bottom of the viewport', function (done) {
var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport();
expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 1);
done();
});
});
});
context('when user edits a line above the viewport', function(){
context('and scroll percentage config is set to 0 on settings.json', function(){
var lineCloseOfTopOfPad = 10;
before(function () {
// the default value
setScrollPercentageWhenFocusLineIsOutOfViewport(0);
// firstly, scroll to make the lineCloseOfTopOfPad visible. After that, scroll to make it out of viewport
scrollEditorToTopOfPad();
placeCaretAtTheEndOfLine(lineCloseOfTopOfPad); // place caret in the 10th line
scrollEditorToBottomOfPad();
pressBackspace(); // edit the line where the caret is, which is above the viewport
});
it('keeps the focus line on the top of the viewport', function (done) {
var firstLineOfViewportAfterEnter = getFirstLineVisibileOfViewport();
expect(firstLineOfViewportAfterEnter).to.be(lineCloseOfTopOfPad);
done();
});
});
context('and scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2', function(){ // this value is arbitrary
var lineCloseToBottomOfPad = 50;
before(function () {
// we force the line edited to be above the top of the viewport
setScrollPercentageWhenFocusLineIsOutOfViewport(0.2, true); // set scroll jump to 20%
scrollEditorToTopOfPad();
placeCaretAtTheEndOfLine(lineCloseToBottomOfPad);
scrollEditorToBottomOfPad();
pressBackspace(); // edit line
});
it('scrolls 20% of viewport down', function (done) {
// default behavior is to scroll one line at the top of viewport, but as
// scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2, we have an extra 20% of lines scrolled
// (2 lines, which are the 20% of the 10 that are visible on viewport)
var firstLineVisibileOfViewport = getFirstLineVisibileOfViewport();
expect(lineCloseToBottomOfPad).to.be(firstLineVisibileOfViewport + 2);
done();
});
});
});
context('when user places the caret at the last line visible of viewport', function(){
var lastLineVisible;
context('and scroll percentage config is set to 0 on settings.json', function(){
before(function (done) {
// reset to the default value
resetScrollPercentageWhenFocusLineIsOutOfViewport();
placeCaretInTheBeginningOfLine(0, function(){ // reset caret position
scrollEditorToTopOfPad();
lastLineVisible = getLastLineVisibleOfViewport();
placeCaretInTheBeginningOfLine(lastLineVisible, done); // place caret in the 9th line
});
});
it('does not scroll', function(done){
setTimeout(function() {
var lastLineOfViewport = getLastLineVisibleOfViewport();
var lineDoesNotScroll = lastLineOfViewport === lastLineVisible;
expect(lineDoesNotScroll).to.be(true);
done();
}, 1000);
});
});
context('and scroll percentage config is set to 0.5 on settings.json', function(){
before(function (done) {
setScrollPercentageWhenFocusLineIsOutOfViewport(0.5);
scrollEditorToTopOfPad();
placeCaretInTheBeginningOfLine(0, function(){ // reset caret position
// this timeout inside a callback is ugly but it necessary to give time to aceSelectionChange
// realizes that the selection has been changed
setTimeout(function() {
lastLineVisible = getLastLineVisibleOfViewport();
placeCaretInTheBeginningOfLine(lastLineVisible, done); // place caret in the 9th line
}, 1000);
});
});
it('scrolls line to 50% of the viewport', function(done){
helper.waitFor(function(){
var lastLineOfViewport = getLastLineVisibleOfViewport();
var lastLinesScrolledFiveLinesUp = lastLineOfViewport - 5 === lastLineVisible;
return lastLinesScrolledFiveLinesUp;
}).done(done);
});
});
});
// This is a special case. When user is selecting a text with arrow down or arrow left we have
// to keep the last line selected on focus
context('when the first line selected is out of the viewport and user presses shift arrow down', function(){
var lastLineOfPad = 99;
before(function (done) {
scrollEditorToTopOfPad();
// make a selection bigger than the viewport height
var $firstLineOfSelection = getLine(0);
var $lastLineOfSelection = getLine(lastLineOfPad);
var lengthOfLastLine = $lastLineOfSelection.text().length;
helper.selectLines($firstLineOfSelection, $lastLineOfSelection, 0, lengthOfLastLine);
// place the last line selected on the viewport
scrollEditorToBottomOfPad();
// press a key to make the selection goes down
// although we can't simulate the extending of selection. It's possible to send a key event
// which is captured on ace2_inner scroll function.
pressAndReleaseLeftArrow(true);
done();
});
it('keeps the last line selected on focus', function (done) {
var lastLineOfSelectionIsVisible = isLineOnViewport(lastLineOfPad);
expect(lastLineOfSelectionIsVisible).to.be(true);
done();
});
});
// In this scenario we avoid the bouncing scroll. E.g Let's suppose we have a big line that is
// the size of the viewport, and its top is above the viewport. When user presses '<-', this line
// will scroll down because the top is out of the viewport. When it scrolls down, the bottom of
// line gets below the viewport so when user presses '<-' again it scrolls up to make the bottom
// of line visible. If user presses arrow keys more than one time, the editor will keep scrolling up and down
context('when the line height is bigger than the scroll amount percentage * viewport height', function(){
var scrollOfEditorBeforePressKey;
var BIG_LINE_NUMBER = 0;
var MIDDLE_OF_BIG_LINE = 51;
before(function (done) {
createPadWithALineHigherThanViewportHeight(this, BIG_LINE_NUMBER, function(){
setScrollPercentageWhenFocusLineIsOutOfViewport(0.5); // set any value to force scroll to outside to viewport
var $bigLine = getLine(BIG_LINE_NUMBER);
// each line has about 5 chars, we place the caret in the middle of the line
helper.selectLines($bigLine, $bigLine, MIDDLE_OF_BIG_LINE, MIDDLE_OF_BIG_LINE);
scrollEditorToLeaveTopAndBottomOfBigLineOutOfViewport($bigLine);
scrollOfEditorBeforePressKey = getEditorScroll();
// press a key to force to scroll
pressAndReleaseRightArrow();
done();
});
});
// reset pad to the original text
after(function (done) {
this.timeout(5000);
cleanPad(function(){
createPadWithSeveralLines(function(){
resetEditorWidth();
done();
});
});
});
// as the editor.line is inside of the viewport, it should not scroll
it('should not scroll', function (done) {
var scrollOfEditorAfterPressKey = getEditorScroll();
expect(scrollOfEditorAfterPressKey).to.be(scrollOfEditorBeforePressKey);
done();
});
});
// Some plugins, for example the ep_page_view, change the editor dimensions. This plugin, for example,
// adds padding-top to the ace_outer, which changes the viewport height
describe('integration with plugins which changes the margin of editor', function(){
context('when editor dimensions changes', function(){
before(function () {
// reset the size of editor. Now we show more than 10 lines as in the other tests
resetResizeOfEditorHeight();
scrollEditorToTopOfPad();
// height of the editor viewport
var editorHeight = getEditorHeight();
// add a big padding-top, 50% of the viewport
var paddingTopOfAceOuter = editorHeight/2;
var chrome$ = helper.padChrome$;
var $outerIframe = chrome$('iframe');
$outerIframe.css('padding-top', paddingTopOfAceOuter);
// we set a big value to check if the scroll is made
setScrollPercentageWhenFocusLineIsOutOfViewport(1);
});
context('and user places the caret in the last line visible of the pad', function(){
var lastLineVisible;
beforeEach(function (done) {
lastLineVisible = getLastLineVisibleOfViewport();
placeCaretInTheBeginningOfLine(lastLineVisible, done);
});
it('scrolls the line where caret is', function(done){
helper.waitFor(function(){
var firstLineVisibileOfViewport = getFirstLineVisibileOfViewport();
var linesScrolled = firstLineVisibileOfViewport !== 0;
return linesScrolled;
}).done(done);
});
});
});
});
/* ********************* Helper functions/constants ********************* */
var TOP_OF_PAGE = 0;
var BOTTOM_OF_PAGE = 5000; // we use a big value to force the page to be scrolled all the way down
var LINES_OF_PAD = 100;
var ENTER = 13;
var BACKSPACE = 8;
var LEFT_ARROW = 37;
var UP_ARROW = 38;
var RIGHT_ARROW = 39;
var LINES_ON_VIEWPORT = 10;
var WIDTH_OF_EDITOR_RESIZED = 100;
var LONG_TEXT_CHARS = 100;
var cleanPad = function(callback) {
var inner$ = helper.padInner$;
var $padContent = inner$('#innerdocbody');
$padContent.html('');
// wait for Etherpad to re-create first line
helper.waitFor(function(){
var lineNumber = inner$('div').length;
return lineNumber === 1;
}, 2000).done(callback);
};
var createPadWithSeveralLines = function(done) {
var line = '<span>a</span><br>';
var $firstLine = helper.padInner$('div').first();
var lines = line.repeat(LINES_OF_PAD); //arbitrary number, we need to create lines that is over the viewport
$firstLine.html(lines);
helper.waitFor(function(){
var linesCreated = helper.padInner$('div').length;
return linesCreated === LINES_OF_PAD;
}, 4000).done(done);
};
var createPadWithALineHigherThanViewportHeight = function(test, line, done) {
var viewportHeight = 160; //10 lines * 16px (height of line)
test.timeout(5000);
cleanPad(function(){
// make the editor smaller to make test easier
// with that width the each line has about 5 chars
resizeEditorWidth();
// we create a line with 100 chars, which makes about 20 lines
setLongTextOnLine(line);
helper.waitFor(function () {
var $firstLine = getLine(line);
var heightOfLine = $firstLine.get(0).getBoundingClientRect().height;
return heightOfLine >= viewportHeight;
}, 4000).done(done);
});
};
var setLongTextOnLine = function(line) {
var $line = getLine(line);
var longText = 'a'.repeat(LONG_TEXT_CHARS);
$line.html(longText);
};
// resize the editor to make the tests easier
var resizeEditorHeight = function() {
var chrome$ = helper.padChrome$;
chrome$('#editorcontainer').css('height', getSizeOfViewport());
};
// this makes about 5 chars per line
var resizeEditorWidth = function() {
var chrome$ = helper.padChrome$;
chrome$('#editorcontainer').css('width', WIDTH_OF_EDITOR_RESIZED);
};
var resetResizeOfEditorHeight = function() {
var chrome$ = helper.padChrome$;
chrome$('#editorcontainer').css('height', '');
};
var resetEditorWidth = function () {
var chrome$ = helper.padChrome$;
chrome$('#editorcontainer').css('width', '');
};
var getEditorHeight = function() {
var chrome$ = helper.padChrome$;
var $editor = chrome$('#editorcontainer');
var editorHeight = $editor.get(0).clientHeight;
return editorHeight;
};
var getSizeOfViewport = function() {
return getLinePositionOnViewport(LINES_ON_VIEWPORT) - getLinePositionOnViewport(0);
};
var scrollPageTo = function(value) {
var outer$ = helper.padOuter$;
var $ace_outer = outer$('#outerdocbody').parent();
$ace_outer.parent().scrollTop(value);
};
var scrollEditorToTopOfPad = function() {
scrollPageTo(TOP_OF_PAGE);
};
var scrollEditorToBottomOfPad = function() {
scrollPageTo(BOTTOM_OF_PAGE);
};
var scrollEditorToLeaveTopAndBottomOfBigLineOutOfViewport = function ($bigLine) {
var lineHeight = $bigLine.get(0).getBoundingClientRect().height;
var middleOfLine = lineHeight/2;
scrollPageTo(middleOfLine);
};
var getLine = function(lineNum) {
var inner$ = helper.padInner$;
var $line = inner$('div').eq(lineNum);
return $line;
};
var placeCaretAtTheEndOfLine = function(lineNum) {
var $targetLine = getLine(lineNum);
var lineLength = $targetLine.text().length;
helper.selectLines($targetLine, $targetLine, lineLength, lineLength);
};
var placeCaretInTheBeginningOfLine = function(lineNum, cb) {
var $targetLine = getLine(lineNum);
helper.selectLines($targetLine, $targetLine, 0, 0);
helper.waitFor(function() {
var $lineWhereCaretIs = getLineWhereCaretIs();
return $targetLine.get(0) === $lineWhereCaretIs.get(0);
}).done(cb);
};
var getLineWhereCaretIs = function() {
var inner$ = helper.padInner$;
var nodeWhereCaretIs = inner$.document.getSelection().anchorNode;
var $lineWhereCaretIs = $(nodeWhereCaretIs).closest('div');
return $lineWhereCaretIs;
};
var getFirstLineVisibileOfViewport = function() {
return _.find(_.range(0, LINES_OF_PAD - 1), isLineOnViewport);
};
var getLastLineVisibleOfViewport = function() {
return _.find(_.range(LINES_OF_PAD - 1, 0, -1), isLineOnViewport);
};
var pressKey = function(keyCode, shiftIsPressed){
var inner$ = helper.padInner$;
var evtType;
if(inner$(window)[0].bowser.modernIE){ // if it's IE
evtType = 'keypress';
}else{
evtType = 'keydown';
}
var e = inner$.Event(evtType);
e.shiftKey = shiftIsPressed;
e.keyCode = keyCode;
e.which = keyCode; // etherpad listens to 'which'
inner$('#innerdocbody').trigger(e);
};
var releaseKey = function(keyCode){
var inner$ = helper.padInner$;
var evtType = 'keyup';
var e = inner$.Event(evtType);
e.keyCode = keyCode;
e.which = keyCode; // etherpad listens to 'which'
inner$('#innerdocbody').trigger(e);
};
var pressEnter = function() {
pressKey(ENTER);
};
var pressBackspace = function() {
pressKey(BACKSPACE);
};
var pressAndReleaseUpArrow = function() {
pressKey(UP_ARROW);
releaseKey(UP_ARROW);
};
var pressAndReleaseRightArrow = function() {
pressKey(RIGHT_ARROW);
releaseKey(RIGHT_ARROW);
};
var pressAndReleaseLeftArrow = function(shiftIsPressed) {
pressKey(LEFT_ARROW, shiftIsPressed);
releaseKey(LEFT_ARROW);
};
var isLineOnViewport = function(lineNumber) {
// in the function scrollNodeVerticallyIntoView from ace2_inner.js, iframePadTop is used to calculate
// how much scroll is needed. Although the name refers to padding-top, this value is not set on the
// padding-top.
var iframePadTop = 8;
var $line = getLine(lineNumber);
var linePosition = $line.get(0).getBoundingClientRect();
// position relative to the current viewport
var linePositionTopOnViewport = linePosition.top - getEditorScroll() + iframePadTop;
var linePositionBottomOnViewport = linePosition.bottom - getEditorScroll();
var lineBellowTop = linePositionBottomOnViewport > 0;
var lineAboveBottom = linePositionTopOnViewport < getClientHeightVisible();
var isVisible = lineBellowTop && lineAboveBottom;
return isVisible;
};
var getEditorScroll = function () {
var outer$ = helper.padOuter$;
var scrollTopFirefox = outer$('#outerdocbody').parent().scrollTop(); // works only on firefox
var scrollTop = outer$('#outerdocbody').scrollTop() || scrollTopFirefox;
return scrollTop;
};
// clientHeight includes padding, so we have to subtract it and consider only the visible viewport
var getClientHeightVisible = function () {
var outer$ = helper.padOuter$;
var $ace_outer = outer$('#outerdocbody').parent();
var ace_outerHeight = $ace_outer.get(0).clientHeight;
var ace_outerPaddingTop = getIntValueOfCSSProperty($ace_outer, 'padding-top');
var paddingAddedWhenPageViewIsEnable = getPaddingAddedWhenPageViewIsEnable();
var clientHeight = ace_outerHeight - ( ace_outerPaddingTop + paddingAddedWhenPageViewIsEnable);
return clientHeight;
};
// ep_page_view changes the dimensions of the editor. We have to guarantee
// the viewport height is calculated right
var getPaddingAddedWhenPageViewIsEnable = function () {
var chrome$ = helper.padChrome$;
var $outerIframe = chrome$('iframe');
var paddingAddedWhenPageViewIsEnable = parseInt($outerIframe.css('padding-top'));
return paddingAddedWhenPageViewIsEnable;
};
var getIntValueOfCSSProperty = function($element, property){
var valueString = $element.css(property);
return parseInt(valueString) || 0;
};
var forceUseMonospacedFont = function () {
helper.padChrome$.window.clientVars.padOptions.useMonospaceFont = true;
};
var setScrollPercentageWhenFocusLineIsOutOfViewport = function(value, editionAboveViewport) {
var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
if (editionAboveViewport) {
scrollSettings.percentage.editionAboveViewport = value;
}else{
scrollSettings.percentage.editionBelowViewport = value;
}
};
var resetScrollPercentageWhenFocusLineIsOutOfViewport = function() {
var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
scrollSettings.percentage.editionAboveViewport = 0;
scrollSettings.percentage.editionBelowViewport = 0;
};
var setPercentageToScrollWhenUserPressesArrowUp = function (value) {
var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
scrollSettings.percentageToScrollWhenUserPressesArrowUp = value;
};
var scrollWhenPlaceCaretInTheLastLineOfViewport = function() {
var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport;
scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport = true;
};
var getLinePositionOnViewport = function(lineNumber) {
var $line = getLine(lineNumber);
var linePosition = $line.get(0).getBoundingClientRect();
// position relative to the current viewport
return linePosition.top - getEditorScroll();
};
});

View file

@ -63,19 +63,23 @@ describe("select formatting buttons when selection has style applied", function(
} }
var applyStyleOnLineOnFullLineAndRemoveSelection = function(line, style, selectTarget, cb) { var applyStyleOnLineOnFullLineAndRemoveSelection = function(line, style, selectTarget, cb) {
// see if line html has changed
var inner$ = helper.padInner$;
var oldLineHTML = inner$.find("div")[line];
applyStyleOnLine(style, line); applyStyleOnLine(style, line);
// we have to give some time to Etherpad detects the selection changed helper.waitFor(function(){
setTimeout(function() { var lineHTML = inner$.find("div")[line];
return lineHTML !== oldLineHTML;
});
// remove selection from previous line // remove selection from previous line
selectLine(line + 1); selectLine(line + 1);
setTimeout(function() { //setTimeout(function() {
// select the text or place the caret on a position that // select the text or place the caret on a position that
// has the formatting text applied previously // has the formatting text applied previously
selectTarget(line); selectTarget(line);
cb(); cb();
}, 1000); //}, 1000);
}, 1000);
} }
var pressFormattingShortcutOnSelection = function(key) { var pressFormattingShortcutOnSelection = function(key) {
@ -88,13 +92,7 @@ describe("select formatting buttons when selection has style applied", function(
//select this text element //select this text element
$firstTextElement.sendkeys('{selectall}'); $firstTextElement.sendkeys('{selectall}');
if(inner$(window)[0].bowser.modernIE){ // if it's IE var e = inner$.Event(helper.evtType);
var evtType = "keypress";
}else{
var evtType = "keydown";
}
var e = inner$.Event(evtType);
e.ctrlKey = true; // Control key e.ctrlKey = true; // Control key
e.which = key.charCodeAt(0); // I, U, B, 5 e.which = key.charCodeAt(0); // I, U, B, 5
inner$("#innerdocbody").trigger(e); inner$("#innerdocbody").trigger(e);

View file

@ -6,22 +6,22 @@ describe("strikethrough button", function(){
}); });
it("makes text strikethrough", function(done) { it("makes text strikethrough", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var $firstTextElement = inner$("div").first(); var $firstTextElement = inner$("div").first();
//select this text element //select this text element
$firstTextElement.sendkeys('{selectall}'); $firstTextElement.sendkeys('{selectall}');
//get the strikethrough button and click it //get the strikethrough button and click it
var $strikethroughButton = chrome$(".buttonicon-strikethrough"); var $strikethroughButton = chrome$(".buttonicon-strikethrough");
$strikethroughButton.click(); $strikethroughButton.click();
//ace creates a new dom element when you press a button, so just get the first text element again //ace creates a new dom element when you press a button, so just get the first text element again
var $newFirstTextElement = inner$("div").first(); var $newFirstTextElement = inner$("div").first();
// is there a <i> element now? // is there a <i> element now?
var isstrikethrough = $newFirstTextElement.find("s").length === 1; var isstrikethrough = $newFirstTextElement.find("s").length === 1;

View file

@ -8,7 +8,7 @@ xdescribe("timeslider button takes you to the timeslider of a pad", function(){
it("timeslider contained in URL", function(done){ it("timeslider contained in URL", function(done){
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
// get the first text element inside the editable space // get the first text element inside the editable space
var $firstTextElement = inner$("div span").first(); var $firstTextElement = inner$("div span").first();
var originalValue = $firstTextElement.text(); // get the original value var originalValue = $firstTextElement.text(); // get the original value

View file

@ -0,0 +1,55 @@
describe("timeslider", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
this.timeout(6000);
});
it("follow content as it's added to timeslider", function(done) { // passes
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
// make some changes to produce 100 revisions
var timePerRev = 900
, revs = 10;
this.timeout(revs*timePerRev+10000);
for(var i=0; i < revs; i++) {
setTimeout(function() {
// enter 'a' in the first text element
inner$("div").last().sendkeys('a\n');
inner$("div").last().sendkeys('{enter}');
inner$("div").last().sendkeys('{enter}');
inner$("div").last().sendkeys('{enter}');
inner$("div").last().sendkeys('{enter}');
}, timePerRev*i);
}
setTimeout(function() {
// go to timeslider
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0');
setTimeout(function() {
var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
var $sliderBar = timeslider$('#ui-slider-bar');
var latestContents = timeslider$('#innerdocbody').text();
// set to follow contents as it arrives
timeslider$('#options-followContents').prop("checked", true);
var originalTop = timeslider$('#innerdocbody').offset();
timeslider$('#playpause_button_icon').click();
setTimeout(function() {
//make sure the text has changed
var newTop = timeslider$('#innerdocbody').offset();
expect( originalTop ).not.to.eql( newTop );
done();
}, 1000);
}, 2000);
}, revs*timePerRev);
});
});

View file

@ -6,9 +6,9 @@ describe("timeslider", function(){
}); });
it("Shows a date and time in the timeslider and make sure it doesn't include NaN", function(done) { it("Shows a date and time in the timeslider and make sure it doesn't include NaN", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
// make some changes to produce 100 revisions // make some changes to produce 100 revisions
var revs = 10; var revs = 10;
this.timeout(60000); this.timeout(60000);
@ -18,15 +18,15 @@ describe("timeslider", function(){
inner$("div").first().sendkeys('a'); inner$("div").first().sendkeys('a');
}, 200); }, 200);
} }
setTimeout(function() { setTimeout(function() {
// go to timeslider // go to timeslider
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider'); $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider');
setTimeout(function() { setTimeout(function() {
var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
var $sliderBar = timeslider$('#ui-slider-bar'); var $sliderBar = timeslider$('#ui-slider-bar');
var latestContents = timeslider$('#padcontent').text(); var latestContents = timeslider$('#padcontent').text();
// Expect the date and time to be shown // Expect the date and time to be shown
@ -36,17 +36,17 @@ describe("timeslider", function(){
e.clientX = e.pageX = 150; e.clientX = e.pageX = 150;
e.clientY = e.pageY = 45; e.clientY = e.pageY = 45;
$sliderBar.trigger(e); $sliderBar.trigger(e);
e = new jQuery.Event('mousedown'); e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150; e.clientX = e.pageX = 150;
e.clientY = e.pageY = 40; e.clientY = e.pageY = 40;
$sliderBar.trigger(e); $sliderBar.trigger(e);
e = new jQuery.Event('mousedown'); e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150; e.clientX = e.pageX = 150;
e.clientY = e.pageY = 50; e.clientY = e.pageY = 50;
$sliderBar.trigger(e); $sliderBar.trigger(e);
$sliderBar.trigger('mouseup') $sliderBar.trigger('mouseup')
setTimeout(function() { setTimeout(function() {

View file

@ -0,0 +1,67 @@
describe("timeslider", function(){
var padId = 735773577357+(Math.round(Math.random()*1000));
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb, padId);
this.timeout(60000);
});
it("Makes sure the export URIs are as expected when the padID is numeric", function(done) {
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
// make some changes to produce 100 revisions
var revs = 10;
this.timeout(60000);
for(var i=0; i < revs; i++) {
setTimeout(function() {
// enter 'a' in the first text element
inner$("div").first().sendkeys('a');
}, 100);
}
setTimeout(function() {
// go to timeslider
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider');
setTimeout(function() {
var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
var $sliderBar = timeslider$('#ui-slider-bar');
var latestContents = timeslider$('#padcontent').text();
// Expect the date and time to be shown
// Click somewhere on the timeslider
var e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150;
e.clientY = e.pageY = 45;
$sliderBar.trigger(e);
e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150;
e.clientY = e.pageY = 40;
$sliderBar.trigger(e);
e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150;
e.clientY = e.pageY = 50;
$sliderBar.trigger(e);
$sliderBar.trigger('mouseup')
setTimeout(function() {
// expect URI to be similar to
// http://192.168.1.48:9001/p/2/2/export/html
// http://192.168.1.48:9001/p/735773577399/0/export/html
var exportLink = timeslider$('#exporthtmla').attr('href');
var checkVal = padId + "/0/export/html";
var includesCorrectURI = exportLink.indexOf(checkVal);
expect(includesCorrectURI).to.not.be(-1);
done();
}, 400);
}, 2000);
}, 2000);
});
});

View file

@ -6,12 +6,12 @@ describe("timeslider", function(){
}); });
it("loads adds a hundred revisions", function(done) { // passes it("loads adds a hundred revisions", function(done) { // passes
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
// make some changes to produce 100 revisions // make some changes to produce 100 revisions
var timePerRev = 900 var timePerRev = 900
, revs = 100; , revs = 99;
this.timeout(revs*timePerRev+10000); this.timeout(revs*timePerRev+10000);
for(var i=0; i < revs; i++) { for(var i=0; i < revs; i++) {
setTimeout(function() { setTimeout(function() {
@ -20,43 +20,45 @@ describe("timeslider", function(){
}, timePerRev*i); }, timePerRev*i);
} }
chrome$('.buttonicon-savedRevision').click(); chrome$('.buttonicon-savedRevision').click();
setTimeout(function() { setTimeout(function() {
// go to timeslider // go to timeslider
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider'); $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider');
setTimeout(function() { setTimeout(function() {
var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
var $sliderBar = timeslider$('#ui-slider-bar'); var $sliderBar = timeslider$('#ui-slider-bar');
var latestContents = timeslider$('#padcontent').text(); var latestContents = timeslider$('#innerdocbody').text();
// Click somewhere on the timeslider // Click somewhere on the timeslider
var e = new jQuery.Event('mousedown'); var e = new jQuery.Event('mousedown');
// sets y co-ordinate of the pad slider modal.
var base = (timeslider$('#ui-slider-bar').offset().top - 24)
e.clientX = e.pageX = 150; e.clientX = e.pageX = 150;
e.clientY = e.pageY = 45; e.clientY = e.pageY = base+5;
$sliderBar.trigger(e); $sliderBar.trigger(e);
e = new jQuery.Event('mousedown'); e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150; e.clientX = e.pageX = 150;
e.clientY = e.pageY = 40; e.clientY = e.pageY = base;
$sliderBar.trigger(e); $sliderBar.trigger(e);
e = new jQuery.Event('mousedown'); e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150; e.clientX = e.pageX = 150;
e.clientY = e.pageY = 50; e.clientY = e.pageY = base-5;
$sliderBar.trigger(e); $sliderBar.trigger(e);
$sliderBar.trigger('mouseup') $sliderBar.trigger('mouseup')
setTimeout(function() { setTimeout(function() {
//make sure the text has changed //make sure the text has changed
expect( timeslider$('#padcontent').text() ).not.to.eql( latestContents ); expect( timeslider$('#innerdocbody').text() ).not.to.eql( latestContents );
var starIsVisible = timeslider$('.star').is(":visible"); var starIsVisible = timeslider$('.star').is(":visible");
expect( starIsVisible ).to.eql( true ); expect( starIsVisible ).to.eql( true );
done(); done();
}, 1000); }, 1000);
}, 6000); }, 6000);
}, revs*timePerRev); }, revs*timePerRev);
}); });
@ -64,9 +66,9 @@ describe("timeslider", function(){
// Disabled as jquery trigger no longer works properly // Disabled as jquery trigger no longer works properly
xit("changes the url when clicking on the timeslider", function(done) { xit("changes the url when clicking on the timeslider", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
// make some changes to produce 7 revisions // make some changes to produce 7 revisions
var timePerRev = 1000 var timePerRev = 1000
, revs = 20; , revs = 20;
@ -77,24 +79,24 @@ describe("timeslider", function(){
inner$("div").first().sendkeys('a'); inner$("div").first().sendkeys('a');
}, timePerRev*i); }, timePerRev*i);
} }
setTimeout(function() { setTimeout(function() {
// go to timeslider // go to timeslider
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider'); $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider');
setTimeout(function() { setTimeout(function() {
var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
var $sliderBar = timeslider$('#ui-slider-bar'); var $sliderBar = timeslider$('#ui-slider-bar');
var latestContents = timeslider$('#padcontent').text(); var latestContents = timeslider$('#innerdocbody').text();
var oldUrl = $('#iframe-container iframe')[0].contentWindow.location.hash; var oldUrl = $('#iframe-container iframe')[0].contentWindow.location.hash;
// Click somewhere on the timeslider // Click somewhere on the timeslider
var e = new jQuery.Event('mousedown'); var e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150; e.clientX = e.pageX = 150;
e.clientY = e.pageY = 60; e.clientY = e.pageY = 60;
$sliderBar.trigger(e); $sliderBar.trigger(e);
helper.waitFor(function(){ helper.waitFor(function(){
return $('#iframe-container iframe')[0].contentWindow.location.hash != oldUrl; return $('#iframe-container iframe')[0].contentWindow.location.hash != oldUrl;
}, 6000).always(function(){ }, 6000).always(function(){
@ -105,7 +107,7 @@ describe("timeslider", function(){
}, revs*timePerRev); }, revs*timePerRev);
}); });
it("jumps to a revision given in the url", function(done) { it("jumps to a revision given in the url", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
this.timeout(20000); this.timeout(20000);
@ -118,7 +120,7 @@ describe("timeslider", function(){
expect( oldLength ).to.not.eql( 0 ); expect( oldLength ).to.not.eql( 0 );
inner$("div").first().sendkeys('a'); inner$("div").first().sendkeys('a');
var timeslider$; var timeslider$;
// wait for our additional revision to be added // wait for our additional revision to be added
helper.waitFor(function(){ helper.waitFor(function(){
// newLines takes the new lines into account which are strippen when using // newLines takes the new lines into account which are strippen when using
@ -131,17 +133,17 @@ describe("timeslider", function(){
}, 6000).always(function() { }, 6000).always(function() {
// go to timeslider with a specific revision set // go to timeslider with a specific revision set
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0'); $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0');
// wait for the timeslider to be loaded // wait for the timeslider to be loaded
helper.waitFor(function(){ helper.waitFor(function(){
try { try {
timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
} catch(e){} } catch(e){}
if(timeslider$){ if(timeslider$){
return timeslider$('#padcontent').text().length == oldLength; return timeslider$('#innerdocbody').text().length == oldLength;
} }
}, 6000).always(function(){ }, 6000).always(function(){
expect( timeslider$('#padcontent').text().length ).to.eql( oldLength ); expect( timeslider$('#innerdocbody').text().length ).to.eql( oldLength );
done(); done();
}); });
}); });
@ -149,17 +151,17 @@ describe("timeslider", function(){
}); });
it("checks the export url", function(done) { it("checks the export url", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
this.timeout(11000); this.timeout(11000);
inner$("div").first().sendkeys('a'); inner$("div").first().sendkeys('a');
setTimeout(function() { setTimeout(function() {
// go to timeslider // go to timeslider
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0'); $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0');
var timeslider$; var timeslider$;
var exportLink; var exportLink;
helper.waitFor(function(){ helper.waitFor(function(){
try{ try{
timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;

View file

@ -4,11 +4,10 @@ describe("undo button", function(){
this.timeout(60000); this.timeout(60000);
}); });
/*
it("undo some typing by clicking undo button", function(done){ it("undo some typing by clicking undo button", function(done){
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
// get the first text element inside the editable space // get the first text element inside the editable space
var $firstTextElement = inner$("div span").first(); var $firstTextElement = inner$("div span").first();
var originalValue = $firstTextElement.text(); // get the original value var originalValue = $firstTextElement.text(); // get the original value
@ -30,7 +29,6 @@ describe("undo button", function(){
done(); done();
}); });
}); });
*/
it("undo some typing using a keypress", function(done){ it("undo some typing using a keypress", function(done){
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
@ -44,13 +42,7 @@ describe("undo button", function(){
var modifiedValue = $firstTextElement.text(); // get the modified value var modifiedValue = $firstTextElement.text(); // get the modified value
expect(modifiedValue).not.to.be(originalValue); // expect the value to change expect(modifiedValue).not.to.be(originalValue); // expect the value to change
/* var e = inner$.Event(helper.evtType);
* ACHTUNG: this is the only place in the test codebase in which a keydown
* is sent for IE. Everywhere else IE uses keypress.
*/
var evtType = "keydown";
var e = inner$.Event(evtType);
e.ctrlKey = true; // Control key e.ctrlKey = true; // Control key
e.which = 90; // z e.which = 90; // z
inner$("#innerdocbody").trigger(e); inner$("#innerdocbody").trigger(e);

View file

@ -6,7 +6,7 @@ describe("assign unordered list", function(){
}); });
it("insert unordered list text then removes by outdent", function(done){ it("insert unordered list text then removes by outdent", function(done){
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
var originalText = inner$("div").first().text(); var originalText = inner$("div").first().text();
@ -33,3 +33,145 @@ describe("assign unordered list", function(){
}); });
}); });
describe("unassign unordered list", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
this.timeout(60000);
});
it("insert unordered list text then remove by clicking list again", function(done){
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
var originalText = inner$("div").first().text();
var $insertunorderedlistButton = chrome$(".buttonicon-insertunorderedlist");
$insertunorderedlistButton.click();
helper.waitFor(function(){
var newText = inner$("div").first().text();
if(newText === originalText){
return inner$("div").first().find("ul li").length === 1;
}
}).done(function(){
// remove indentation by bullet and ensure text string remains the same
$insertunorderedlistButton.click();
helper.waitFor(function(){
var isList = inner$("div").find("ul").length === 1;
// sohuldn't be list
return (isList === false);
}).done(function(){
done();
});
});
});
});
describe("keep unordered list on enter key", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
this.timeout(60000);
});
it("Keeps the unordered list on enter for the new line", function(done){
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
var $insertorderedlistButton = chrome$(".buttonicon-insertunorderedlist");
$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 hasULElement = $newSecondLine.find("ul li").length === 1;
expect(hasULElement).to.be(true);
expect($newSecondLine.text()).to.be("line 2");
done();
});
});
});
describe("Pressing Tab in an UL increases and decreases indentation", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
this.timeout(60000);
});
it("indent and de-indent list item with keypress", 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}');
var $insertorderedlistButton = chrome$(".buttonicon-insertunorderedlist");
$insertorderedlistButton.click();
var e = inner$.Event(helper.evtType);
e.keyCode = 9; // tab
inner$("#innerdocbody").trigger(e);
expect(inner$("div").first().find(".list-bullet2").length === 1).to.be(true);
e.shiftKey = true; // shift
e.keyCode = 9; // tab
inner$("#innerdocbody").trigger(e);
helper.waitFor(function(){
return inner$("div").first().find(".list-bullet1").length === 1;
}).done(done);
});
});
describe("Pressing indent/outdent button in an UL increases and decreases indentation and bullet / ol formatting", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
this.timeout(60000);
});
it("indent and de-indent list item with indent button", 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}');
var $insertunorderedlistButton = chrome$(".buttonicon-insertunorderedlist");
$insertunorderedlistButton.click();
var $indentButton = chrome$(".buttonicon-indent");
$indentButton.click(); // make it indented twice
expect(inner$("div").first().find(".list-bullet2").length === 1).to.be(true);
var $outdentButton = chrome$(".buttonicon-outdent");
$outdentButton.click(); // make it deindented to 1
helper.waitFor(function(){
return inner$("div").first().find(".list-bullet1").length === 1;
}).done(done);
});
});

View file

@ -6,16 +6,16 @@ describe("urls", function(){
}); });
it("when you enter an url, it becomes clickable", function(done) { it("when you enter an url, it becomes clickable", function(done) {
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var firstTextElement = inner$("div").first(); var firstTextElement = inner$("div").first();
// simulate key presses to delete content // simulate key presses to delete content
firstTextElement.sendkeys('{selectall}'); // select all firstTextElement.sendkeys('{selectall}'); // select all
firstTextElement.sendkeys('{del}'); // clear the first line firstTextElement.sendkeys('{del}'); // clear the first line
firstTextElement.sendkeys('http://etherpad.org'); // insert a URL firstTextElement.sendkeys('https://etherpad.org'); // insert a URL
helper.waitFor(function(){ helper.waitFor(function(){
return inner$("div").first().find("a").length === 1; return inner$("div").first().find("a").length === 1;
@ -28,7 +28,7 @@ describe("urls", function(){
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var firstTextElement = inner$("div").first(); var firstTextElement = inner$("div").first();
var url = "http://etherpad.org/!foo"; var url = "https://etherpad.org/!foo";
// simulate key presses to delete content // simulate key presses to delete content
firstTextElement.sendkeys('{selectall}'); // select all firstTextElement.sendkeys('{selectall}'); // select all
@ -50,7 +50,7 @@ describe("urls", function(){
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var firstTextElement = inner$("div").first(); var firstTextElement = inner$("div").first();
var url = "http://etherpad.org/"; var url = "https://etherpad.org/";
// simulate key presses to delete content // simulate key presses to delete content
firstTextElement.sendkeys('{selectall}'); // select all firstTextElement.sendkeys('{selectall}'); // select all

View file

@ -17,95 +17,125 @@ var sauceTestWorker = async.queue(function (testSettings, callback) {
testSettings.name = name; testSettings.name = name;
testSettings["public"] = true; testSettings["public"] = true;
testSettings["build"] = process.env.GIT_HASH; testSettings["build"] = process.env.GIT_HASH;
testSettings["extendedDebugging"] = true; // console.json can be downloaded via saucelabs, don't know how to print them into output of the tests
testSettings["tunnelIdentifier"] = process.env.TRAVIS_JOB_NUMBER;
browser.init(testSettings).get("http://localhost:9001/tests/frontend/", function(){ browser.init(testSettings).get("http://localhost:9001/tests/frontend/", function(){
var url = "https://saucelabs.com/jobs/" + browser.sessionID; var url = "https://saucelabs.com/jobs/" + browser.sessionID;
console.log("Remote sauce test '" + name + "' started! " + url); console.log("Remote sauce test '" + name + "' started! " + url);
//tear down the test excecution //tear down the test excecution
var stopSauce = function(success){ var stopSauce = function(success,timesup){
getStatusInterval && clearInterval(getStatusInterval); clearInterval(getStatusInterval);
clearTimeout(timeout); clearTimeout(timeout);
browser.quit(); browser.quit(function(){
if(!success){
allTestsPassed = false;
}
if(!success){ // if stopSauce is called via timeout (in contrast to via getStatusInterval) than the log of up to the last
allTestsPassed = false; // five seconds may not be available here. It's an error anyway, so don't care about it.
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.platform + (testSettings.version === "" ? '' : (" " + testSettings.version)) + "] " + line;
}).join("\n");
console.log(testResult);
if (timesup) {
console.log("[" + testSettings.browserName + " " + testSettings.platform + (testSettings.version === "" ? '' : (" " + testSettings.version)) + "] allowed test duration exceeded");
}
console.log("Remote sauce test '" + name + "' finished! " + url);
callback();
});
} }
var testResult = knownConsoleText.replace(/\[red\]/g,'\x1B[31m').replace(/\[yellow\]/g,'\x1B[33m') /**
.replace(/\[green\]/g,'\x1B[32m').replace(/\[clear\]/g, '\x1B[39m'); * timeout if a test hangs or the job exceeds 9.5 minutes
testResult = testResult.split("\\n").map(function(line){ * It's necessary because if travis kills the saucelabs session due to inactivity, we don't get any output
return "[" + testSettings.browserName + (testSettings.version === "" ? '' : (" " + testSettings.version)) + "] " + line; * @todo this should be configured in testSettings, see https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-Timeouts
}).join("\n"); */
var timeout = setTimeout(function(){
stopSauce(false,true);
}, 570000); // travis timeout is 10 minutes, set this to a slightly lower value
console.log(testResult); var knownConsoleText = "";
console.log("Remote sauce test '" + name + "' finished! " + url); var getStatusInterval = setInterval(function(){
browser.eval("$('#console').text()", function(err, consoleText){
if(!consoleText || err){
return;
}
knownConsoleText = consoleText;
callback(); if(knownConsoleText.indexOf("FINISHED") > 0){
} let match = knownConsoleText.match(/FINISHED.*([0-9]+) tests passed, ([0-9]+) tests failed/);
// finished without failures
if (match[2] && match[2] == '0'){
stopSauce(true);
//timeout for the case the test hangs // finished but some tests did not return or some tests failed
var timeout = setTimeout(function(){ } else {
stopSauce(false); stopSauce(false);
}, 60000 * 10); }
}
});
}, 5000);
});
var knownConsoleText = ""; }, 6); //run 6 tests in parrallel
var getStatusInterval = setInterval(function(){
browser.eval("$('#console').text()", function(err, consoleText){
if(!consoleText || err){
return;
}
knownConsoleText = consoleText;
if(knownConsoleText.indexOf("FINISHED") > 0){ // 1) Firefox on Linux
var success = knownConsoleText.indexOf("FAILED") === -1;
stopSauce(success);
}
});
}, 5000);
});
}, 5); //run 5 tests in parrallel
// Firefox
sauceTestWorker.push({ sauceTestWorker.push({
'platform' : 'Linux' 'platform' : 'Windows 7'
, 'browserName' : 'firefox' , 'browserName' : 'firefox'
, 'version' : '' , 'version' : '52.0'
}); });
// Chrome // 2) Chrome on Linux
sauceTestWorker.push({ sauceTestWorker.push({
'platform' : 'Linux' 'platform' : 'Windows 7'
, 'browserName' : 'googlechrome' , 'browserName' : 'chrome'
, 'version' : '' , 'version' : '55.0'
, 'args' : ['--use-fake-device-for-media-stream']
}); });
// 3) Safari on OSX 10.15
sauceTestWorker.push({
'platform' : 'OS X 10.15'
, 'browserName' : 'safari'
, 'version' : '13.1'
});
// 4) Safari on OSX 10.14
sauceTestWorker.push({
'platform' : 'OS X 10.14'
, 'browserName' : 'safari'
, 'version' : '12.0'
});
// IE 10 doesn't appear to be working anyway
/* /*
// IE 8 // 4) IE 10 on Win 8
sauceTestWorker.push({ sauceTestWorker.push({
'platform' : 'Windows 2003' 'platform' : 'Windows 8'
, 'browserName' : 'iexplore' , 'browserName' : 'iexplore'
, 'version' : '8' , 'version' : '10.0'
}); });
*/ */
// 5) Edge on Win 10
// IE 9
sauceTestWorker.push({ sauceTestWorker.push({
'platform' : 'Windows XP' 'platform' : 'Windows 10'
, 'browserName' : 'iexplore' , 'browserName' : 'microsoftedge'
, 'version' : '9' , 'version' : '83.0'
});
// 6) Firefox on Win 7
sauceTestWorker.push({
'platform' : 'Windows 7'
, 'browserName' : 'firefox'
, 'version' : '78.0'
}); });
// IE 10 sauceTestWorker.drain(function() {
sauceTestWorker.push({ process.exit(allTestsPassed ? 0 : 1);
'platform' : 'Windows 2012'
, 'browserName' : 'iexplore'
, 'version' : '10'
}); });
sauceTestWorker.drain = function() {
setTimeout(function(){
process.exit(allTestsPassed ? 0 : 1);
}, 3000);
}

View file

@ -1,18 +1,48 @@
#!/bin/sh #!/bin/bash
if [ -z "${SAUCE_USERNAME}" ]; then echo "SAUCE_USERNAME is unset - exiting"; exit 1; fi
if [ -z "${SAUCE_ACCESS_KEY}" ]; then echo "SAUCE_ACCESS_KEY is unset - exiting"; exit 1; fi
#Move to the base folder # do not continue if there is an error
cd `dirname $0` set -eu
#start Etherpad # source: https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself#246128
../../../bin/run.sh > /dev/null & MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
sleep 10
#start remote runner # reliably move to the etherpad base folder before running it
cd "${MY_DIR}/../../../"
# start Etherpad, assuming all dependencies are already installed.
#
# This is possible because the "install" section of .travis.yml already contains
# a call to bin/installDeps.sh
echo "Running Etherpad directly, assuming bin/installDeps.sh has already been run"
node node_modules/ep_etherpad-lite/node/server.js "${@}" &
echo "Now I will try for 15 seconds to connect to Etherpad on http://localhost:9001"
# wait for at most 15 seconds until Etherpad starts accepting connections
#
# modified from:
# https://unix.stackexchange.com/questions/5277/how-do-i-tell-a-script-to-wait-for-a-process-to-start-accepting-requests-on-a-po#349138
#
(timeout 15 bash -c 'until echo > /dev/tcp/localhost/9001; do sleep 0.5; done') || \
(echo "Could not connect to Etherpad on http://localhost:9001" ; exit 1)
echo "Successfully connected to Etherpad on http://localhost:9001"
# On the Travis VM, remote_runner.js is found at
# /home/travis/build/ether/[secure]/tests/frontend/travis/remote_runner.js
# which is the same directory that contains this script.
# Let's move back there.
#
# Probably remote_runner.js is injected by Saucelabs.
cd "${MY_DIR}"
# start the remote runner
echo "Now starting the remote runner"
node remote_runner.js node remote_runner.js
exit_code=$? exit_code=$?
kill $!
kill $(cat /tmp/sauce.pid) kill $(cat /tmp/sauce.pid)
sleep 30
exit $exit_code exit $exit_code

View file

@ -0,0 +1,51 @@
#!/bin/bash
# do not continue if there is an error
set -u
# source: https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself#246128
MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
# reliably move to the etherpad base folder before running it
cd "${MY_DIR}/../../../"
# Set soffice to /usr/bin/soffice
sed 's#\"soffice\": null,#\"soffice\":\"/usr/bin/soffice\",#g' settings.json.template > settings.json.soffice
# Set allowAnyoneToImport to true
sed 's/\"allowAnyoneToImport\": false,/\"allowAnyoneToImport\": true,/g' settings.json.soffice > settings.json.allowImport
# Set "max": 10 to 100 to not agressively rate limit
sed 's/\"max\": 10/\"max\": 100/g' settings.json.allowImport > settings.json.rateLimit
# Set "points": 10 to 1000 to not agressively rate limit commits
sed 's/\"points\": 10/\"points\": 1000/g' settings.json.rateLimit > settings.json
# start Etherpad, assuming all dependencies are already installed.
#
# This is possible because the "install" section of .travis.yml already contains
# a call to bin/installDeps.sh
echo "Running Etherpad directly, assuming bin/installDeps.sh has already been run"
node node_modules/ep_etherpad-lite/node/server.js "${@}" > /dev/null &
echo "Now I will try for 15 seconds to connect to Etherpad on http://localhost:9001"
# wait for at most 15 seconds until Etherpad starts accepting connections
#
# modified from:
# https://unix.stackexchange.com/questions/5277/how-do-i-tell-a-script-to-wait-for-a-process-to-start-accepting-requests-on-a-po#349138
#
(timeout 15 bash -c 'until echo > /dev/tcp/localhost/9001; do sleep 0.5; done') || \
(echo "Could not connect to Etherpad on http://localhost:9001" ; exit 1)
echo "Successfully connected to Etherpad on http://localhost:9001"
# run the backend tests
echo "Now run the backend tests"
cd src
failed=0
npm run test || failed=1
npm run test-contentcollector || failed=1
exit $failed

View file

@ -0,0 +1,51 @@
#!/bin/bash
# do not continue if there is an error
set -eu
# source: https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself#246128
MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
# reliably move to the etherpad base folder before running it
cd "${MY_DIR}/../../../"
# Set "points": 10 to 1000 to not agressively rate limit commits
sed 's/\"points\": 10/\"points\": 1000/g' settings.json.template > settings.json.points
# And enable loadTest
sed 's/\"loadTest\": false,/\"loadTest\": true,/g' settings.json.points > settings.json
# start Etherpad, assuming all dependencies are already installed.
#
# This is possible because the "install" section of .travis.yml already contains
# a call to bin/installDeps.sh
echo "Running Etherpad directly, assuming bin/installDeps.sh has already been run"
node node_modules/ep_etherpad-lite/node/server.js "${@}" > /dev/null &
echo "Now I will try for 15 seconds to connect to Etherpad on http://localhost:9001"
# wait for at most 15 seconds until Etherpad starts accepting connections
#
# modified from:
# https://unix.stackexchange.com/questions/5277/how-do-i-tell-a-script-to-wait-for-a-process-to-start-accepting-requests-on-a-po#349138
#
(timeout 15 bash -c 'until echo > /dev/tcp/localhost/9001; do sleep 0.5; done') || \
(echo "Could not connect to Etherpad on http://localhost:9001" ; exit 1)
echo "Successfully connected to Etherpad on http://localhost:9001"
# Build the minified files?
curl http://localhost:9001/p/minifyme -f -s > /dev/null
# just in case, let's wait for another 10 seconds before going on
sleep 10
# run the backend tests
echo "Now run the load tests for 30 seconds and if it stalls before 100 then error"
etherpad-loadtest -d 30
exit_code=$?
kill $!
sleep 5
exit $exit_code

View file

@ -1,11 +1,20 @@
#!/bin/bash #!/bin/bash
# download and unzip the sauce connector # download and unzip the sauce connector
curl https://saucelabs.com/downloads/sc-latest-linux.tar.gz > /tmp/sauce.tar.gz #
# ACHTUNG: as of 2019-12-21, downloading sc-latest-linux.tar.gz does not work.
# It is necessary to explicitly download a specific version, for
# example https://saucelabs.com/downloads/sc-4.5.4-linux.tar.gz
# Supported versions are currently listed at:
# https://wiki.saucelabs.com/display/DOCS/Downloading+Sauce+Connect+Proxy
if [ -z "${SAUCE_USERNAME}" ]; then echo "SAUCE_USERNAME is unset - exiting"; exit 1; fi
if [ -z "${SAUCE_ACCESS_KEY}" ]; then echo "SAUCE_ACCESS_KEY is unset - exiting"; exit 1; fi
curl https://saucelabs.com/downloads/sc-4.6.2-linux.tar.gz > /tmp/sauce.tar.gz
tar zxf /tmp/sauce.tar.gz --directory /tmp tar zxf /tmp/sauce.tar.gz --directory /tmp
mv /tmp/sc-*-linux /tmp/sauce_connect mv /tmp/sc-*-linux /tmp/sauce_connect
# start the sauce connector in background and make sure it doesn't output the secret key # start the sauce connector in background and make sure it doesn't output the secret key
(/tmp/sauce_connect/bin/sc --user $SAUCE_USERNAME --key $SAUCE_ACCESS_KEY --pidfile /tmp/sauce.pid --readyfile /tmp/tunnel > /dev/null )& (/tmp/sauce_connect/bin/sc --user "${SAUCE_USERNAME}" --key "${SAUCE_ACCESS_KEY}" -i "${TRAVIS_JOB_NUMBER}" --pidfile /tmp/sauce.pid --readyfile /tmp/tunnel > /dev/null )&
# wait for the tunnel to build up # wait for the tunnel to build up
while [ ! -e "/tmp/tunnel" ] while [ ! -e "/tmp/tunnel" ]