mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-28 11:26:16 -04:00
add tests from origin/develop
This commit is contained in:
parent
6a3e4c69b8
commit
2df2d7d60a
71 changed files with 18931 additions and 4032 deletions
76
tests/backend/fuzzImportTest.js
Normal file
76
tests/backend/fuzzImportTest.js
Normal 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;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
79
tests/backend/specs/api/api.js
Normal file
79
tests/backend/specs/api/api.js
Normal 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;
|
||||
}
|
113
tests/backend/specs/api/characterEncoding.js
Normal file
113
tests/backend/specs/api/characterEncoding.js
Normal 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("🇼") === -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;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
var assert = require('assert')
|
||||
supertest = require(__dirname+'/../../../../src/node_modules/supertest'),
|
||||
fs = require('fs'),
|
||||
settings = require(__dirname+'/../../loadSettings').loadSettings(),
|
||||
settings = require(__dirname + '/../../../../src/node/utils/Settings'),
|
||||
api = supertest('http://'+settings.ip+":"+settings.port),
|
||||
path = require('path');
|
||||
|
||||
|
|
10
tests/backend/specs/api/emojis.html
Normal file
10
tests/backend/specs/api/emojis.html
Normal file
File diff suppressed because one or more lines are too long
75
tests/backend/specs/api/fuzzImportTest.js
Normal file
75
tests/backend/specs/api/fuzzImportTest.js
Normal 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;
|
||||
}
|
||||
*/
|
BIN
tests/backend/specs/api/image.png
Normal file
BIN
tests/backend/specs/api/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 355 KiB |
188
tests/backend/specs/api/importexport.js
Normal file
188
tests/backend/specs/api/importexport.js
Normal 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;
|
||||
}
|
410
tests/backend/specs/api/importexportGetPost.js
Normal file
410
tests/backend/specs/api/importexportGetPost.js
Normal 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;
|
||||
}
|
54
tests/backend/specs/api/instance.js
Normal file
54
tests/backend/specs/api/instance.js
Normal 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;
|
||||
}
|
|
@ -1,10 +1,17 @@
|
|||
var assert = require('assert')
|
||||
supertest = require(__dirname+'/../../../../src/node_modules/supertest'),
|
||||
fs = require('fs'),
|
||||
settings = require(__dirname+'/../../loadSettings').loadSettings(),
|
||||
api = supertest('http://'+settings.ip+":"+settings.port),
|
||||
path = require('path'),
|
||||
async = require(__dirname+'/../../../../src/node_modules/async');
|
||||
/*
|
||||
* ACHTUNG: there is a copied & modified version of this file in
|
||||
* <basedir>/tests/container/specs/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 + '/../../../../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');
|
||||
|
||||
|
@ -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
|
||||
* 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(){
|
||||
it('errors if can not connect', function(done) {
|
||||
it('can connect', function(done) {
|
||||
api.get('/api/')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done)
|
||||
|
@ -37,7 +57,7 @@ describe('Connectivity', function(){
|
|||
})
|
||||
|
||||
describe('API Versioning', function(){
|
||||
it('errors if can not connect', function(done) {
|
||||
it('finds the version tag', function(done) {
|
||||
api.get('/api/')
|
||||
.expect(function(res){
|
||||
apiVersion = res.body.currentVersion;
|
||||
|
@ -49,7 +69,7 @@ describe('API Versioning', 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
|
||||
// If your APIKey is password you deserve to fail all tests anyway
|
||||
var permErrorURL = '/api/'+apiVersion+'/createPad?apikey=password&padID=test';
|
||||
|
@ -104,7 +124,7 @@ describe('deletePad', function(){
|
|||
it('deletes a Pad', function(done) {
|
||||
api.get(endPoint('deletePad')+"&padID="+testPadId)
|
||||
.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(){
|
||||
it('deletes a Pad', function(done) {
|
||||
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(){
|
||||
it('get the HTML of a Pad -- Should return a failure', function(done) {
|
||||
api.get(endPoint('getHTML')+"&padID="+testPadId)
|
||||
|
@ -212,7 +258,11 @@ describe('getText', function(){
|
|||
|
||||
describe('setText', function(){
|
||||
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){
|
||||
if(res.body.code !== 0) throw new Error("Pad setting text failed");
|
||||
})
|
||||
|
@ -327,7 +377,11 @@ describe('getLastEdited', function(){
|
|||
|
||||
describe('setText', function(){
|
||||
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){
|
||||
if(res.body.code !== 0) throw new Error("Pad setting text failed");
|
||||
})
|
||||
|
@ -387,7 +441,8 @@ describe('createPad', function(){
|
|||
|
||||
describe('setText', function(){
|
||||
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){
|
||||
if(res.body.code !== 0) throw new Error("Pad Set Text failed")
|
||||
})
|
||||
|
@ -410,7 +465,8 @@ describe('getText', function(){
|
|||
|
||||
describe('setText', function(){
|
||||
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){
|
||||
if(res.body.code !== 0) throw new Error("Pad Set Text failed")
|
||||
})
|
||||
|
@ -524,9 +580,13 @@ describe('getText', function(){
|
|||
describe('setHTML', function(){
|
||||
it('Sets the HTML of a Pad attempting to pass ugly HTML', function(done) {
|
||||
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){
|
||||
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(200, done)
|
||||
|
@ -535,7 +595,11 @@ describe('setHTML', function(){
|
|||
|
||||
describe('setHTML', function(){
|
||||
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){
|
||||
if(res.body.code !== 0) throw new Error("List HTML cant be imported")
|
||||
})
|
||||
|
@ -567,6 +631,39 @@ 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(){
|
||||
it('errors if pad can be created', function(done) {
|
||||
var badUrlChars = ["/", "%23", "%3F", "%26"];
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var assert = require('assert')
|
||||
supertest = require(__dirname+'/../../../../src/node_modules/supertest'),
|
||||
fs = require('fs'),
|
||||
settings = require(__dirname+'/../../loadSettings').loadSettings(),
|
||||
settings = require(__dirname + '/../../../../src/node/utils/Settings'),
|
||||
api = supertest('http://'+settings.ip+":"+settings.port),
|
||||
path = require('path');
|
||||
|
||||
|
|
BIN
tests/backend/specs/api/test.doc
Normal file
BIN
tests/backend/specs/api/test.doc
Normal file
Binary file not shown.
BIN
tests/backend/specs/api/test.docx
Normal file
BIN
tests/backend/specs/api/test.docx
Normal file
Binary file not shown.
1
tests/backend/specs/api/test.etherpad
Normal file
1
tests/backend/specs/api/test.etherpad
Normal 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}}}
|
BIN
tests/backend/specs/api/test.odt
Normal file
BIN
tests/backend/specs/api/test.odt
Normal file
Binary file not shown.
BIN
tests/backend/specs/api/test.pdf
Normal file
BIN
tests/backend/specs/api/test.pdf
Normal file
Binary file not shown.
1
tests/backend/specs/api/test.txt
Normal file
1
tests/backend/specs/api/test.txt
Normal file
|
@ -0,0 +1 @@
|
|||
This is the contents we insert into pads when testing the import functions. Thanks for using Etherpad!
|
|
@ -1,10 +1,12 @@
|
|||
var assert = require('assert')
|
||||
os = require('os'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
TidyHtml = null,
|
||||
Settings = null;
|
||||
|
||||
var npm = require("../../../../src/node_modules/npm/lib/npm.js");
|
||||
var nodeify = require('../../../../src/node_modules/nodeify');
|
||||
|
||||
describe('tidyHtml', function() {
|
||||
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) {
|
||||
// If the user hasn't configured Tidy, we skip this tests as it's required for this test
|
||||
if (!Settings.tidyHtml) {
|
||||
|
@ -23,10 +29,11 @@ describe('tidyHtml', function() {
|
|||
}
|
||||
|
||||
// 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')
|
||||
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);
|
||||
|
||||
// Read the file again
|
||||
|
@ -55,7 +62,7 @@ describe('tidyHtml', function() {
|
|||
this.skip();
|
||||
}
|
||||
|
||||
TidyHtml.tidy('/some/none/existing/file.html', function(err) {
|
||||
tidy('/some/none/existing/file.html', function(err) {
|
||||
assert.ok(err);
|
||||
return done();
|
||||
});
|
||||
|
|
175
tests/backend/specs/contentcollector.js
Normal file
175
tests/backend/specs/contentcollector.js
Normal 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;
|
||||
}
|
87
tests/backend/specs/minify.js
Normal file
87
tests/backend/specs/minify.js
Normal 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;
|
||||
//}
|
||||
//
|
37
tests/container/loadSettings.js
Normal file
37
tests/container/loadSettings.js
Normal 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;
|
38
tests/container/specs/api/pad.js
Normal file
38
tests/container/specs/api/pad.js
Normal 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)
|
||||
});
|
||||
})
|
|
@ -1,11 +1,9 @@
|
|||
var helper = {};
|
||||
|
||||
(function(){
|
||||
var $iframeContainer, $iframe, jsLibraries = {};
|
||||
var $iframe, jsLibraries = {};
|
||||
|
||||
helper.init = function(cb){
|
||||
$iframeContainer = $("#iframe-container");
|
||||
|
||||
$.get('/static/js/jquery.js').done(function(code){
|
||||
// make sure we don't override existing jquery
|
||||
jsLibraries["jquery"] = "if(typeof $ === 'undefined') {\n" + code + "\n}";
|
||||
|
@ -52,10 +50,49 @@ var helper = {};
|
|||
return win.$;
|
||||
}
|
||||
|
||||
helper.clearCookies = function(){
|
||||
window.document.cookie = "";
|
||||
helper.clearSessionCookies = function(){
|
||||
// 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){
|
||||
//build opts object
|
||||
var opts = {clearCookies: true}
|
||||
|
@ -65,26 +102,41 @@ var helper = {};
|
|||
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
|
||||
if(opts.clearCookies){
|
||||
helper.clearCookies();
|
||||
helper.clearSessionCookies();
|
||||
}
|
||||
|
||||
if(!padName)
|
||||
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
|
||||
helper.padChrome$ = helper.padOuter$ = helper.padInner$ = null;
|
||||
|
||||
//clean up iframes properly to prevent IE from memoryleaking
|
||||
$iframeContainer.find("iframe").purgeFrame().done(function(){
|
||||
$iframeContainer.append($iframe);
|
||||
//remove old iframe
|
||||
$("#iframe-container iframe").remove();
|
||||
//set new iframe
|
||||
$("#iframe-container").append($iframe);
|
||||
$iframe.one('load', function(){
|
||||
helper.padChrome$ = getFrameJQuery($('#iframe-container iframe'));
|
||||
if (opts.clearCookies) {
|
||||
helper.clearPadPrefCookie();
|
||||
}
|
||||
if (opts.padPrefs) {
|
||||
helper.setPadPrefCookie(opts.padPrefs);
|
||||
}
|
||||
helper.waitFor(function(){
|
||||
return !$iframe.contents().find("#editorloadingbox").is(":visible");
|
||||
}, 50000).done(function(){
|
||||
helper.padChrome$ = getFrameJQuery( $('#iframe-container iframe'));
|
||||
}, 10000).done(function(){
|
||||
helper.padOuter$ = getFrameJQuery(helper.padChrome$('iframe[name="ace_outer"]'));
|
||||
helper.padInner$ = getFrameJQuery( helper.padOuter$('iframe[name="ace_inner"]'));
|
||||
|
||||
|
@ -95,8 +147,11 @@ var helper = {};
|
|||
|
||||
opts.cb();
|
||||
}).fail(function(){
|
||||
if (helper.retry > 3) {
|
||||
throw new Error("Pad never loaded");
|
||||
});
|
||||
}
|
||||
helper.retry++;
|
||||
helper.newPad(cb,origPadName);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -104,7 +159,7 @@ var helper = {};
|
|||
}
|
||||
|
||||
helper.waitFor = function(conditionFunc, _timeoutTime, _intervalTime){
|
||||
var timeoutTime = _timeoutTime || 1000;
|
||||
var timeoutTime = _timeoutTime || 1900;
|
||||
var intervalTime = _intervalTime || 10;
|
||||
|
||||
var deferred = $.Deferred();
|
||||
|
|
|
@ -14,11 +14,10 @@
|
|||
<script src="lib/underscore.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/sendkeys.js"></script>
|
||||
<script src="lib/jquery.iframe.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<script src="specs_list.js"></script>
|
||||
|
|
|
@ -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
|
@ -6,6 +6,9 @@ body {
|
|||
padding: 0px;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#console {
|
||||
|
@ -13,34 +16,34 @@ body {
|
|||
}
|
||||
|
||||
#iframe-container {
|
||||
width: 50%;
|
||||
width: 80%;
|
||||
min-width: 820px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#iframe-container iframe {
|
||||
height: 100%;
|
||||
position:absolute;
|
||||
min-width:500px;
|
||||
max-width:800px;
|
||||
left:50%;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
#mocha {
|
||||
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
border-right: 2px solid #999;
|
||||
width: 50%;
|
||||
flex: 1 auto;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
overflow: auto;
|
||||
float:left;
|
||||
width:20%;
|
||||
font-size:80%;
|
||||
|
||||
}
|
||||
|
||||
#mocha #report {
|
||||
margin-top: 50px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#mocha ul, #mocha li {
|
||||
#mocha li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -76,11 +79,11 @@ body {
|
|||
}
|
||||
|
||||
#mocha .suite {
|
||||
margin-left: 15px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
#mocha .test {
|
||||
margin-left: 15px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#mocha .test:hover h2::after {
|
||||
|
@ -175,6 +178,10 @@ body {
|
|||
-webkit-box-shadow: 0 1px 3px #eee;
|
||||
}
|
||||
|
||||
#report ul {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#report.pass .test.fail {
|
||||
display: none;
|
||||
}
|
||||
|
@ -191,17 +198,21 @@ body {
|
|||
}
|
||||
|
||||
#stats {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
right: 52%;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#stats .progress {
|
||||
#mocha-stats {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
#mocha-stats .progress {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
margin-right:5px;
|
||||
}
|
||||
|
||||
#stats em {
|
||||
|
@ -229,3 +240,7 @@ code .init { color: #2F6FAD }
|
|||
code .string { color: #5890AD }
|
||||
code .keyword { color: #8A6343 }
|
||||
code .number { color: #2F6FAD }
|
||||
|
||||
ul{
|
||||
padding-left:5px;
|
||||
}
|
||||
|
|
|
@ -1,28 +1,68 @@
|
|||
$(function(){
|
||||
function Base(runner) {
|
||||
var self = this
|
||||
, stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
|
||||
, failures = this.failures = [];
|
||||
|
||||
function stringifyException(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;
|
||||
}
|
||||
|
||||
function CustomRunner(runner) {
|
||||
var stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 };
|
||||
|
||||
if (!runner) return;
|
||||
this.runner = runner;
|
||||
|
||||
runner.on('start', function(){
|
||||
stats.start = new Date;
|
||||
});
|
||||
|
||||
runner.on('suite', function(suite){
|
||||
stats.suites = stats.suites || 0;
|
||||
suite.root || stats.suites++;
|
||||
if (suite.root) return;
|
||||
append(suite.title);
|
||||
level++;
|
||||
});
|
||||
|
||||
runner.on('test end', function(test){
|
||||
stats.tests = stats.tests || 0;
|
||||
runner.on('suite end', function(suite){
|
||||
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++;
|
||||
});
|
||||
|
||||
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;
|
||||
test.speed = test.duration > test.slow()
|
||||
|
@ -32,41 +72,29 @@ $(function(){
|
|||
: 'fast';
|
||||
|
||||
stats.passes++;
|
||||
append("->","[green]PASSED[clear] :", test.title," ",test.duration,"ms");
|
||||
});
|
||||
|
||||
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++;
|
||||
test.err = err;
|
||||
failures.push(test);
|
||||
append("->","[red]FAILED[clear] :", test.title, stringifyException(test.err));
|
||||
});
|
||||
|
||||
runner.on('end', function(){
|
||||
stats.end = new Date;
|
||||
stats.duration = new Date - stats.start;
|
||||
});
|
||||
runner.on('pending', function(test){
|
||||
if(killTimeout) clearTimeout(killTimeout);
|
||||
killTimeout = setTimeout(function(){
|
||||
append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]");
|
||||
}, 60000 * 3);
|
||||
|
||||
runner.on('pending', function(){
|
||||
stats.pending++;
|
||||
append("->","[yellow]PENDING[clear]:", test.title);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
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;
|
||||
|
@ -97,69 +125,24 @@ $(function(){
|
|||
$console.text(oldText + newText + "\\n");
|
||||
}
|
||||
|
||||
runner.on('suite', function(suite){
|
||||
if (suite.root) return;
|
||||
|
||||
append(suite.title);
|
||||
level++;
|
||||
});
|
||||
|
||||
runner.on('suite end', function(suite){
|
||||
if (suite.root) return;
|
||||
level--;
|
||||
|
||||
if(level == 0) {
|
||||
append("");
|
||||
}
|
||||
});
|
||||
|
||||
var stringifyException = function(exception){
|
||||
var err = exception.stack || exception.toString();
|
||||
|
||||
// FF / Opera do not add the message
|
||||
if (!~err.indexOf(exception.message)) {
|
||||
err = exception.message + '\n' + err;
|
||||
}
|
||||
|
||||
// <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
|
||||
// check for the result of the stringifying.
|
||||
if ('[object Error]' == err) err = exception.message;
|
||||
|
||||
// Safari doesn't give you a stack. Let's at least provide a source line.
|
||||
if (!exception.stack && exception.sourceURL && exception.line !== undefined) {
|
||||
err += "\n(" + exception.sourceURL + ":" + exception.line + ")";
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
var killTimeout;
|
||||
runner.on('test end', function(test){
|
||||
if ('passed' == test.state) {
|
||||
append("->","[green]PASSED[clear] :", test.title);
|
||||
} else if (test.pending) {
|
||||
append("->","[yellow]PENDING[clear]:", test.title);
|
||||
} else {
|
||||
append("->","[red]FAILED[clear] :", test.title, stringifyException(test.err));
|
||||
}
|
||||
|
||||
if(killTimeout) clearTimeout(killTimeout);
|
||||
killTimeout = setTimeout(function(){
|
||||
append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]");
|
||||
}, 60000 * 3);
|
||||
});
|
||||
|
||||
var total = runner.total;
|
||||
runner.on('end', function(){
|
||||
if(stats.tests >= total){
|
||||
stats.end = new Date;
|
||||
stats.duration = stats.end - stats.start;
|
||||
var minutes = Math.floor(stats.duration / 1000 / 60);
|
||||
var seconds = Math.round((stats.duration / 1000) % 60);
|
||||
|
||||
append("FINISHED -", stats.passes, "tests passed,", stats.failures, "tests failed, duration: " + minutes + ":" + seconds);
|
||||
var seconds = Math.round((stats.duration / 1000) % 60) // chrome < 57 does not like this .toString().padStart("2","0");
|
||||
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
|
||||
var getURLParameter = function (name) {
|
||||
|
@ -189,10 +172,7 @@ $(function(){
|
|||
mocha.grep(grep);
|
||||
}
|
||||
|
||||
mocha.ignoreLeaks();
|
||||
|
||||
mocha.reporter(WebdriverAndHtmlReporter(mocha._reporter));
|
||||
|
||||
mocha.run();
|
||||
var runner = mocha.run();
|
||||
CustomRunner(runner)
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,13 +44,7 @@ describe("bold button", function(){
|
|||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
if(inner$(window)[0].bowser.modernIE){ // if it's IE
|
||||
var evtType = "keypress";
|
||||
}else{
|
||||
var evtType = "keydown";
|
||||
}
|
||||
|
||||
var e = inner$.Event(evtType);
|
||||
var e = inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = 66; // b
|
||||
inner$("#innerdocbody").trigger(e);
|
||||
|
|
|
@ -297,13 +297,8 @@ function prepareDocument(n, target){ // generates a random document with random
|
|||
}
|
||||
|
||||
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";
|
||||
}else{
|
||||
var evtType = "keydown";
|
||||
}
|
||||
var e = target.Event(evtType);
|
||||
console.log(e);
|
||||
|
||||
var e = target.Event(helper.evtType);
|
||||
if(ctrl){
|
||||
e.ctrlKey = true; // Control key
|
||||
}
|
||||
|
@ -339,6 +334,5 @@ function caretPosition($){
|
|||
var pos = doc.getSelection();
|
||||
pos.y = pos.anchorNode.parentElement.offsetTop;
|
||||
pos.x = pos.anchorNode.parentElement.offsetLeft;
|
||||
console.log(pos);
|
||||
return pos;
|
||||
}
|
||||
|
|
104
tests/frontend/specs/change_user_color.js
Normal file
104
tests/frontend/specs/change_user_color.js
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -75,56 +75,96 @@ describe("Chat messages and UI", function(){
|
|||
//get the chat selector
|
||||
var $stickychatCheckbox = chrome$("#options-stickychat");
|
||||
|
||||
//select chat always on screen and fire change event
|
||||
$stickychatCheckbox.attr('selected','selected');
|
||||
$stickychatCheckbox.change();
|
||||
//select chat always on screen
|
||||
if (!$stickychatCheckbox.is(':checked')) {
|
||||
$stickychatCheckbox.click();
|
||||
}
|
||||
|
||||
// due to animation, we need to make some timeout...
|
||||
setTimeout(function() {
|
||||
//check if chat changed to get the stickychat Class
|
||||
var $chatbox = chrome$("#chatbox");
|
||||
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
|
||||
expect(hasStickyChatClass).to.be(true);
|
||||
|
||||
//select chat always on screen and fire change event
|
||||
$stickychatCheckbox.attr('selected','selected');
|
||||
$stickychatCheckbox.change();
|
||||
// select chat always on screen and fire change event
|
||||
$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)
|
||||
|
||||
|
||||
});
|
||||
|
||||
it("makes chat stick to right side of the screen then makes it one step smaller", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $settingsButton = chrome$(".buttonicon-settings");
|
||||
$settingsButton.click();
|
||||
// open chat
|
||||
chrome$('#chaticon').click();
|
||||
|
||||
//get the chat selector
|
||||
var $stickychatCheckbox = chrome$("#options-stickychat");
|
||||
|
||||
//select chat always on screen and fire change event
|
||||
$stickychatCheckbox.attr('selected','selected');
|
||||
$stickychatCheckbox.change();
|
||||
$stickychatCheckbox.click();
|
||||
// select chat always on screen from chatbox
|
||||
chrome$('.stick-to-screen-btn').click();
|
||||
|
||||
// due to animation, we need to make some timeout...
|
||||
setTimeout(function() {
|
||||
//check if chat changed to get the stickychat Class
|
||||
var $chatbox = chrome$("#chatbox");
|
||||
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
|
||||
expect(hasStickyChatClass).to.be(true);
|
||||
|
||||
//select chat always on screen and fire change event
|
||||
// select chat always on screen and fire change event
|
||||
chrome$('#titlecross').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)
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -39,7 +39,6 @@ describe("clear authorship colors button", function(){
|
|||
$clearauthorshipcolorsButton.click();
|
||||
|
||||
// does the first divs span include an author class?
|
||||
console.log(inner$("div span").first().attr("class"));
|
||||
var hasAuthorClass = inner$("div span").first().attr("class").indexOf("author") !== -1;
|
||||
//expect(hasAuthorClass).to.be(false);
|
||||
|
||||
|
@ -47,13 +46,88 @@ describe("clear authorship colors button", function(){
|
|||
var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1;
|
||||
expect(hasAuthorClass).to.be(false);
|
||||
|
||||
setTimeout(function(){
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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);
|
||||
},1000);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ describe("embed links", function(){
|
|||
var width = $embediFrame.attr("width");
|
||||
var height = $embediFrame.attr("height");
|
||||
var name = $embediFrame.attr("name");
|
||||
expect(width).to.be('600');
|
||||
expect(height).to.be('400');
|
||||
expect(width).to.be('100%');
|
||||
expect(height).to.be('600');
|
||||
expect(name).to.be(readonly ? "embed_readonly" : "embed_readwrite");
|
||||
|
||||
//parse the url
|
||||
|
|
|
@ -5,7 +5,7 @@ describe("font select", function(){
|
|||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text monospace", function(done) {
|
||||
it("makes text RobotoMono", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
|
@ -13,18 +13,19 @@ describe("font select", function(){
|
|||
var $settingsButton = chrome$(".buttonicon-settings");
|
||||
$settingsButton.click();
|
||||
|
||||
//get the font menu and monospace option
|
||||
//get the font menu and RobotoMono option
|
||||
var $viewfontmenu = chrome$("#viewfontmenu");
|
||||
var $monospaceoption = $viewfontmenu.find("[value=monospace]");
|
||||
var $RobotoMonooption = $viewfontmenu.find("[value=RobotoMono]");
|
||||
|
||||
//select monospace and fire change event
|
||||
$monospaceoption.attr('selected','selected');
|
||||
$viewfontmenu.val("monospace");
|
||||
//select RobotoMono and fire change event
|
||||
// $RobotoMonooption.attr('selected','selected');
|
||||
// commenting out above will break safari test
|
||||
$viewfontmenu.val("RobotoMono");
|
||||
$viewfontmenu.change();
|
||||
|
||||
//check if font changed to monospace
|
||||
//check if font changed to RobotoMono
|
||||
var fontFamily = inner$("body").css("font-family").toLowerCase();
|
||||
var containsStr = fontFamily.indexOf("monospace");
|
||||
var containsStr = fontFamily.indexOf("robotomono");
|
||||
expect(containsStr).to.not.be(-1);
|
||||
|
||||
done();
|
||||
|
|
|
@ -20,7 +20,7 @@ describe("the test helper", function(){
|
|||
});
|
||||
|
||||
it("gives me 3 jquery instances of chrome, outer and inner", function(done){
|
||||
this.timeout(5000);
|
||||
this.timeout(10000);
|
||||
|
||||
helper.newPad(function(){
|
||||
//check if the jquery selectors have the desired elements
|
||||
|
@ -36,17 +36,106 @@ describe("the test helper", function(){
|
|||
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(){
|
||||
it("takes a timeout and waits long enough", function(done){
|
||||
this.timeout(2000);
|
||||
var startTime = new Date().getTime();
|
||||
var startTime = Date.now();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return false;
|
||||
}, 1500).fail(function(){
|
||||
var duration = new Date().getTime() - startTime;
|
||||
var duration = Date.now() - startTime;
|
||||
expect(duration).to.be.greaterThan(1400);
|
||||
done();
|
||||
});
|
||||
|
@ -136,7 +225,15 @@ describe("the test helper", function(){
|
|||
helper.selectLines($startLine, $endLine, startOffset, endOffset);
|
||||
|
||||
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();
|
||||
});
|
||||
|
@ -154,7 +251,15 @@ describe("the test helper", function(){
|
|||
helper.selectLines($startLine, $endLine, startOffset, endOffset);
|
||||
|
||||
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();
|
||||
});
|
||||
|
@ -172,7 +277,15 @@ describe("the test helper", function(){
|
|||
helper.selectLines($startLine, $endLine, startOffset, endOffset);
|
||||
|
||||
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();
|
||||
});
|
||||
|
@ -190,7 +303,15 @@ describe("the test helper", function(){
|
|||
helper.selectLines($startLine, $endLine, startOffset, endOffset);
|
||||
|
||||
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();
|
||||
});
|
||||
|
@ -205,7 +326,15 @@ describe("the test helper", function(){
|
|||
helper.selectLines($startLine, $endLine);
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
|
@ -15,13 +15,7 @@ describe("indentation button", function(){
|
|||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
if(inner$(window)[0].bowser.modernIE){ // if it's IE
|
||||
var evtType = "keypress";
|
||||
}else{
|
||||
var evtType = "keydown";
|
||||
}
|
||||
|
||||
var e = inner$.Event(evtType);
|
||||
var e = inner$.Event(helper.evtType);
|
||||
e.keyCode = 9; // tab :|
|
||||
inner$("#innerdocbody").trigger(e);
|
||||
|
||||
|
@ -325,12 +319,7 @@ describe("indentation button", function(){
|
|||
|
||||
function pressEnter(){
|
||||
var inner$ = helper.padInner$;
|
||||
if(inner$(window)[0].bowser.modernIE){ // if it's IE
|
||||
var evtType = "keypress";
|
||||
}else{
|
||||
var evtType = "keydown";
|
||||
}
|
||||
var e = inner$.Event(evtType);
|
||||
var e = inner$.Event(helper.evtType);
|
||||
e.keyCode = 13; // enter :|
|
||||
inner$("#innerdocbody").trigger(e);
|
||||
}
|
||||
|
|
|
@ -44,13 +44,7 @@ describe("italic some text", function(){
|
|||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
if(inner$(window)[0].bowser.modernIE){ // if it's IE
|
||||
var evtType = "keypress";
|
||||
}else{
|
||||
var evtType = "keydown";
|
||||
}
|
||||
|
||||
var e = inner$.Event(evtType);
|
||||
var e = inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = 105; // i
|
||||
inner$("#innerdocbody").trigger(e);
|
||||
|
|
51
tests/frontend/specs/multiple_authors_clear_authorship_colors.js
Executable file
51
tests/frontend/specs/multiple_authors_clear_authorship_colors.js
Executable 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();
|
||||
}
|
||||
});
|
|
@ -100,7 +100,6 @@ describe("assign ordered list", function(){
|
|||
}).done(function(){
|
||||
var $newSecondLine = inner$("div").first().next();
|
||||
var hasOLElement = $newSecondLine.find("ol li").length === 1;
|
||||
console.log($newSecondLine.find("ol"));
|
||||
expect(hasOLElement).to.be(true);
|
||||
expect($newSecondLine.text()).to.be("line 2");
|
||||
var hasLineNumber = $newSecondLine.find("ol").attr("start") === 2;
|
||||
|
@ -111,12 +110,7 @@ describe("assign ordered list", function(){
|
|||
|
||||
var triggerCtrlShiftShortcut = function(shortcutChar) {
|
||||
var inner$ = helper.padInner$;
|
||||
if(inner$(window)[0].bowser.modernIE) { // if it's IE
|
||||
var evtType = "keypress";
|
||||
}else{
|
||||
var evtType = "keydown";
|
||||
}
|
||||
var e = inner$.Event(evtType);
|
||||
var e = inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true;
|
||||
e.shiftKey = true;
|
||||
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);
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
describe('Pad modal', function() {
|
||||
context('when modal is a "force reconnect" message', function() {
|
||||
var MODAL_SELECTOR = '#connectivity .slowcommit';
|
||||
var MODAL_SELECTOR = '#connectivity';
|
||||
|
||||
beforeEach(function(done) {
|
||||
helper.newPad(function() {
|
||||
|
@ -10,7 +10,7 @@ describe('Pad modal', function() {
|
|||
// wait for modal to be displayed
|
||||
var $modal = helper.padChrome$(MODAL_SELECTOR);
|
||||
helper.waitFor(function() {
|
||||
return $modal.is(':visible');
|
||||
return $modal.hasClass('popup-show');
|
||||
}, 50000).done(done);
|
||||
});
|
||||
|
||||
|
@ -30,7 +30,7 @@ describe('Pad modal', function() {
|
|||
|
||||
it('does not close the modal', function(done) {
|
||||
var $modal = helper.padChrome$(MODAL_SELECTOR);
|
||||
var modalIsVisible = $modal.is(':visible');
|
||||
var modalIsVisible = $modal.hasClass('popup-show');
|
||||
|
||||
expect(modalIsVisible).to.be(true);
|
||||
|
||||
|
@ -45,7 +45,7 @@ describe('Pad modal', function() {
|
|||
|
||||
it('does not close the modal', function(done) {
|
||||
var $modal = helper.padChrome$(MODAL_SELECTOR);
|
||||
var modalIsVisible = $modal.is(':visible');
|
||||
var modalIsVisible = $modal.hasClass('popup-show');
|
||||
|
||||
expect(modalIsVisible).to.be(true);
|
||||
|
||||
|
@ -65,12 +65,13 @@ describe('Pad modal', function() {
|
|||
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
// This test breaks safari testing
|
||||
/*
|
||||
it('does not disable editor', function(done) {
|
||||
expect(isEditorDisabled()).to.be(false);
|
||||
done();
|
||||
});
|
||||
|
||||
*/
|
||||
context('and user clicks on editor', function() {
|
||||
beforeEach(function() {
|
||||
clickOnPadInner();
|
||||
|
@ -126,6 +127,7 @@ describe('Pad modal', function() {
|
|||
|
||||
var isModalOpened = function(modalSelector) {
|
||||
var $modal = helper.padChrome$(modalSelector);
|
||||
return $modal.is(':visible');
|
||||
|
||||
return $modal.hasClass('popup-show');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -25,7 +25,6 @@ describe("undo button then redo button", function(){
|
|||
$redoButton.click(); // resends foo
|
||||
|
||||
helper.waitFor(function(){
|
||||
console.log(inner$("div span").first().text());
|
||||
return inner$("div span").first().text() === newString;
|
||||
}).done(function(){
|
||||
var finalValue = inner$("div").first().text();
|
||||
|
@ -47,24 +46,17 @@ describe("undo button then redo button", function(){
|
|||
var modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
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 evtType = "keypress";
|
||||
}else{
|
||||
var evtType = "keydown";
|
||||
}
|
||||
|
||||
var e = inner$.Event(evtType);
|
||||
var e = inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = 90; // z
|
||||
inner$("#innerdocbody").trigger(e);
|
||||
|
||||
var e = inner$.Event(evtType);
|
||||
var e = inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = 121; // y
|
||||
inner$("#innerdocbody").trigger(e);
|
||||
|
||||
helper.waitFor(function(){
|
||||
console.log(inner$("div span").first().text());
|
||||
return inner$("div span").first().text() === newString;
|
||||
}).done(function(){
|
||||
var finalValue = inner$("div").first().text();
|
||||
|
|
|
@ -49,7 +49,7 @@ describe('Responsiveness of Editor', function() {
|
|||
}).done(function(){
|
||||
|
||||
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)
|
||||
var el = inner$('div').first();
|
||||
|
@ -65,11 +65,10 @@ describe('Responsiveness of Editor', function() {
|
|||
helper.waitFor(function(){ // Wait for the ability to process
|
||||
return true; // Ghetto but works for now
|
||||
}).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
|
||||
|
||||
console.log('delay:', delay);
|
||||
expect(delay).to.be.below(200);
|
||||
expect(delay).to.be.below(300);
|
||||
done();
|
||||
}, 1000);
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
});
|
||||
|
|
@ -63,19 +63,23 @@ describe("select formatting buttons when selection has style applied", function(
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
// we have to give some time to Etherpad detects the selection changed
|
||||
setTimeout(function() {
|
||||
helper.waitFor(function(){
|
||||
var lineHTML = inner$.find("div")[line];
|
||||
return lineHTML !== oldLineHTML;
|
||||
});
|
||||
// remove selection from previous line
|
||||
selectLine(line + 1);
|
||||
setTimeout(function() {
|
||||
//setTimeout(function() {
|
||||
// select the text or place the caret on a position that
|
||||
// has the formatting text applied previously
|
||||
selectTarget(line);
|
||||
cb();
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
//}, 1000);
|
||||
}
|
||||
|
||||
var pressFormattingShortcutOnSelection = function(key) {
|
||||
|
@ -88,13 +92,7 @@ describe("select formatting buttons when selection has style applied", function(
|
|||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
if(inner$(window)[0].bowser.modernIE){ // if it's IE
|
||||
var evtType = "keypress";
|
||||
}else{
|
||||
var evtType = "keydown";
|
||||
}
|
||||
|
||||
var e = inner$.Event(evtType);
|
||||
var e = inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = key.charCodeAt(0); // I, U, B, 5
|
||||
inner$("#innerdocbody").trigger(e);
|
||||
|
|
55
tests/frontend/specs/timeslider_follow.js
Normal file
55
tests/frontend/specs/timeslider_follow.js
Normal 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);
|
||||
});
|
||||
|
||||
});
|
||||
|
67
tests/frontend/specs/timeslider_numeric_padID.js
Normal file
67
tests/frontend/specs/timeslider_numeric_padID.js
Normal 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);
|
||||
});
|
||||
});
|
|
@ -11,7 +11,7 @@ describe("timeslider", function(){
|
|||
|
||||
// make some changes to produce 100 revisions
|
||||
var timePerRev = 900
|
||||
, revs = 100;
|
||||
, revs = 99;
|
||||
this.timeout(revs*timePerRev+10000);
|
||||
for(var i=0; i < revs; i++) {
|
||||
setTimeout(function() {
|
||||
|
@ -29,29 +29,31 @@ describe("timeslider", function(){
|
|||
var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
|
||||
var $sliderBar = timeslider$('#ui-slider-bar');
|
||||
|
||||
var latestContents = timeslider$('#padcontent').text();
|
||||
var latestContents = timeslider$('#innerdocbody').text();
|
||||
|
||||
// Click somewhere on the timeslider
|
||||
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.clientY = e.pageY = 45;
|
||||
e.clientY = e.pageY = base+5;
|
||||
$sliderBar.trigger(e);
|
||||
|
||||
e = new jQuery.Event('mousedown');
|
||||
e.clientX = e.pageX = 150;
|
||||
e.clientY = e.pageY = 40;
|
||||
e.clientY = e.pageY = base;
|
||||
$sliderBar.trigger(e);
|
||||
|
||||
e = new jQuery.Event('mousedown');
|
||||
e.clientX = e.pageX = 150;
|
||||
e.clientY = e.pageY = 50;
|
||||
e.clientY = e.pageY = base-5;
|
||||
$sliderBar.trigger(e);
|
||||
|
||||
$sliderBar.trigger('mouseup')
|
||||
|
||||
setTimeout(function() {
|
||||
//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");
|
||||
expect( starIsVisible ).to.eql( true );
|
||||
done();
|
||||
|
@ -86,7 +88,7 @@ describe("timeslider", function(){
|
|||
var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
|
||||
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;
|
||||
|
||||
// Click somewhere on the timeslider
|
||||
|
@ -138,10 +140,10 @@ describe("timeslider", function(){
|
|||
timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
|
||||
} catch(e){}
|
||||
if(timeslider$){
|
||||
return timeslider$('#padcontent').text().length == oldLength;
|
||||
return timeslider$('#innerdocbody').text().length == oldLength;
|
||||
}
|
||||
}, 6000).always(function(){
|
||||
expect( timeslider$('#padcontent').text().length ).to.eql( oldLength );
|
||||
expect( timeslider$('#innerdocbody').text().length ).to.eql( oldLength );
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,6 @@ describe("undo button", function(){
|
|||
this.timeout(60000);
|
||||
});
|
||||
|
||||
/*
|
||||
it("undo some typing by clicking undo button", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
@ -30,7 +29,6 @@ describe("undo button", function(){
|
|||
done();
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
it("undo some typing using a keypress", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
|
@ -44,13 +42,7 @@ describe("undo button", function(){
|
|||
var modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
expect(modifiedValue).not.to.be(originalValue); // expect the value to change
|
||||
|
||||
/*
|
||||
* 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);
|
||||
var e = inner$.Event(helper.evtType);
|
||||
e.ctrlKey = true; // Control key
|
||||
e.which = 90; // z
|
||||
inner$("#innerdocbody").trigger(e);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ describe("urls", function(){
|
|||
// simulate key presses to delete content
|
||||
firstTextElement.sendkeys('{selectall}'); // select all
|
||||
firstTextElement.sendkeys('{del}'); // clear the first line
|
||||
firstTextElement.sendkeys('http://etherpad.org'); // insert a URL
|
||||
firstTextElement.sendkeys('https://etherpad.org'); // insert a URL
|
||||
|
||||
helper.waitFor(function(){
|
||||
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
|
||||
var firstTextElement = inner$("div").first();
|
||||
var url = "http://etherpad.org/!foo";
|
||||
var url = "https://etherpad.org/!foo";
|
||||
|
||||
// simulate key presses to delete content
|
||||
firstTextElement.sendkeys('{selectall}'); // select all
|
||||
|
@ -50,7 +50,7 @@ describe("urls", function(){
|
|||
|
||||
//get the first text element out of the inner iframe
|
||||
var firstTextElement = inner$("div").first();
|
||||
var url = "http://etherpad.org/";
|
||||
var url = "https://etherpad.org/";
|
||||
|
||||
// simulate key presses to delete content
|
||||
firstTextElement.sendkeys('{selectall}'); // select all
|
||||
|
|
|
@ -17,38 +17,49 @@ var sauceTestWorker = async.queue(function (testSettings, callback) {
|
|||
testSettings.name = name;
|
||||
testSettings["public"] = true;
|
||||
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(){
|
||||
var url = "https://saucelabs.com/jobs/" + browser.sessionID;
|
||||
console.log("Remote sauce test '" + name + "' started! " + url);
|
||||
|
||||
//tear down the test excecution
|
||||
var stopSauce = function(success){
|
||||
getStatusInterval && clearInterval(getStatusInterval);
|
||||
var stopSauce = function(success,timesup){
|
||||
clearInterval(getStatusInterval);
|
||||
clearTimeout(timeout);
|
||||
|
||||
browser.quit();
|
||||
|
||||
browser.quit(function(){
|
||||
if(!success){
|
||||
allTestsPassed = false;
|
||||
}
|
||||
|
||||
// if stopSauce is called via timeout (in contrast to via getStatusInterval) than the log of up to the last
|
||||
// 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.version === "" ? '' : (" " + testSettings.version)) + "] " + 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();
|
||||
});
|
||||
}
|
||||
|
||||
//timeout for the case the test hangs
|
||||
/**
|
||||
* timeout if a test hangs or the job exceeds 9.5 minutes
|
||||
* It's necessary because if travis kills the saucelabs session due to inactivity, we don't get any output
|
||||
* @todo this should be configured in testSettings, see https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-Timeouts
|
||||
*/
|
||||
var timeout = setTimeout(function(){
|
||||
stopSauce(false);
|
||||
}, 60000 * 10);
|
||||
stopSauce(false,true);
|
||||
}, 570000); // travis timeout is 10 minutes, set this to a slightly lower value
|
||||
|
||||
var knownConsoleText = "";
|
||||
var getStatusInterval = setInterval(function(){
|
||||
|
@ -59,53 +70,72 @@ var sauceTestWorker = async.queue(function (testSettings, callback) {
|
|||
knownConsoleText = consoleText;
|
||||
|
||||
if(knownConsoleText.indexOf("FINISHED") > 0){
|
||||
var success = knownConsoleText.indexOf("FAILED") === -1;
|
||||
stopSauce(success);
|
||||
let match = knownConsoleText.match(/FINISHED.*([0-9]+) tests passed, ([0-9]+) tests failed/);
|
||||
// finished without failures
|
||||
if (match[2] && match[2] == '0'){
|
||||
stopSauce(true);
|
||||
|
||||
// finished but some tests did not return or some tests failed
|
||||
} else {
|
||||
stopSauce(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
}, 5); //run 5 tests in parrallel
|
||||
|
||||
// Firefox
|
||||
}, 6); //run 6 tests in parrallel
|
||||
|
||||
// 1) Firefox on Linux
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Linux'
|
||||
'platform' : 'Windows 7'
|
||||
, 'browserName' : 'firefox'
|
||||
, 'version' : ''
|
||||
, 'version' : '52.0'
|
||||
});
|
||||
|
||||
// Chrome
|
||||
// 2) Chrome on Linux
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Linux'
|
||||
, 'browserName' : 'googlechrome'
|
||||
, 'version' : ''
|
||||
'platform' : 'Windows 7'
|
||||
, 'browserName' : 'chrome'
|
||||
, '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({
|
||||
'platform' : 'Windows 2003'
|
||||
'platform' : 'Windows 8'
|
||||
, 'browserName' : 'iexplore'
|
||||
, 'version' : '8'
|
||||
, 'version' : '10.0'
|
||||
});
|
||||
*/
|
||||
|
||||
// IE 9
|
||||
// 5) Edge on Win 10
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Windows XP'
|
||||
, 'browserName' : 'iexplore'
|
||||
, 'version' : '9'
|
||||
'platform' : 'Windows 10'
|
||||
, 'browserName' : 'microsoftedge'
|
||||
, 'version' : '83.0'
|
||||
});
|
||||
// 6) Firefox on Win 7
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Windows 7'
|
||||
, 'browserName' : 'firefox'
|
||||
, 'version' : '78.0'
|
||||
});
|
||||
|
||||
// IE 10
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Windows 2012'
|
||||
, 'browserName' : 'iexplore'
|
||||
, 'version' : '10'
|
||||
});
|
||||
|
||||
sauceTestWorker.drain = function() {
|
||||
setTimeout(function(){
|
||||
sauceTestWorker.drain(function() {
|
||||
process.exit(allTestsPassed ? 0 : 1);
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
|
||||
cd `dirname $0`
|
||||
# do not continue if there is an error
|
||||
set -eu
|
||||
|
||||
#start Etherpad
|
||||
../../../bin/run.sh > /dev/null &
|
||||
sleep 10
|
||||
# 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 )"
|
||||
|
||||
#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
|
||||
exit_code=$?
|
||||
|
||||
kill $!
|
||||
kill $(cat /tmp/sauce.pid)
|
||||
sleep 30
|
||||
|
||||
exit $exit_code
|
51
tests/frontend/travis/runnerBackend.sh
Executable file
51
tests/frontend/travis/runnerBackend.sh
Executable 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
|
51
tests/frontend/travis/runnerLoadTest.sh
Executable file
51
tests/frontend/travis/runnerLoadTest.sh
Executable 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
|
|
@ -1,11 +1,20 @@
|
|||
#!/bin/bash
|
||||
# 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
|
||||
mv /tmp/sc-*-linux /tmp/sauce_connect
|
||||
|
||||
# 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
|
||||
while [ ! -e "/tmp/tunnel" ]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue