Added playwright tests. (#6212)

* Added playwright tests.

* Added clear authorship color.

* Ported enter ts.

* Ported more tests.

* Commented helper tests.

* Fixed admin tests.

* Fixed.

* Fixed admin pages not there.

* Fixed waiting.

* Upload playwright report.

* Remove saucelabs

* Fixed waiting.

* Fixed upload artifact.

* Also install deps.

* Added retry mechanism.

* Added timeout for restart etherpad server.

* Fixed tests.

* Added frontend playwright tests.
This commit is contained in:
SamTV12345 2024-03-10 23:18:50 +01:00 committed by GitHub
parent db46ffb63b
commit c2699e4528
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 1568 additions and 1285 deletions

View file

@ -1,90 +0,0 @@
'use strict';
describe('Admin > Settings', function () {
this.timeout(480000);
before(async function () {
let success = false;
$.ajax({
url: `${location.protocol}//admin:changeme@${location.hostname}:${location.port}/admin/`,
type: 'GET',
success: () => success = true,
});
await helper.waitForPromise(() => success === true);
});
beforeEach(async function () {
helper.newAdmin('settings');
// needed, because the load event is fired to early
await helper.waitForPromise(
() => helper.admin$ && helper.admin$('.settings').val().length > 0, 5000);
});
it('Are Settings visible, populated, does save work', async function () {
const save = async () => {
const p = new Promise((resolve) => {
const observer = new MutationObserver(() => { resolve(); observer.disconnect(); });
observer.observe(
helper.admin$('#response')[0], {attributes: true, childList: false, subtree: false});
});
helper.admin$('#saveSettings').trigger('click');
await p;
};
// save old value
const settings = helper.admin$('.settings').val();
const settingsLength = settings.length;
// set new value
helper.admin$('.settings').val((_, text) => `/* test */\n${text}`);
await helper.waitForPromise(
() => settingsLength + 11 === helper.admin$('.settings').val().length, 5000);
await save();
// new value for settings.json should now be saved
// reset it to the old value
helper.newAdmin('settings');
await helper.waitForPromise(
() => helper.admin$ &&
helper.admin$('.settings').val().length === settingsLength + 11, 20000);
// replace the test value with a line break
helper.admin$('.settings').val((_, text) => text.replace('/* test */\n', ''));
await helper.waitForPromise(() => settingsLength === helper.admin$('.settings').val().length);
await save();
// settings should have the old value
helper.newAdmin('settings');
await helper.waitForPromise(
() => helper.admin$ && helper.admin$('.settings').val().length === settingsLength &&
settings === helper.admin$('.settings').val(), 20000);
});
it('restart works', async function () {
const getStartTime = async () => {
try {
const {httpStartTime} = await $.ajax({
url: new URL('/stats', window.location.href),
method: 'GET',
dataType: 'json',
timeout: 450, // Slightly less than the waitForPromise() interval.
});
return httpStartTime;
} catch (err) {
document.getElementById('console').append(
`an error occurred: ${err.message} of type ${err.name}\n`);
return null;
}
};
let oldStartTime;
await helper.waitForPromise(async () => {
oldStartTime = await getStartTime();
return oldStartTime != null && oldStartTime > 0;
}, 2100, 500);
helper.admin$('#restartEtherpad').trigger('click');
await helper.waitForPromise(async () => {
const startTime = await getStartTime();
return startTime != null && startTime > oldStartTime;
}, 60000, 500);
});
});

View file

@ -1,47 +0,0 @@
'use strict';
describe('Admin Troupbleshooting page', function () {
before(async function () {
let success = false;
$.ajax({
url: `${location.protocol}//admin:changeme@${location.hostname}:${location.port}/admin`,
type: 'GET',
success: () => success = true,
});
await helper.waitForPromise(() => success === true);
});
// create a new pad before each test run
beforeEach(async function () {
helper.newAdmin('plugins/info');
await helper.waitForPromise(
() => helper.admin$ && helper.admin$('.menu').find('li').length >= 3);
});
it('Shows Troubleshooting page Manager', async function () {
helper.admin$('a[data-l10n-id="admin_plugins_info"]')[0].click();
});
it('Shows a version number', async function () {
const content = helper.admin$('span[data-l10n-id="admin_plugins_info.version_number"]')
.parent().text();
const version = content.split(': ')[1].split('.');
if (version.length !== 3) {
throw new Error('Not displaying a semver version number');
}
});
it('Lists installed parts', async function () {
const parts = helper.admin$('pre')[1];
if (parts.textContent.indexOf('ep_etherpad-lite/adminsettings') === -1) {
throw new Error('No admin setting part being displayed...');
}
});
it('Lists installed hooks', async function () {
const parts = helper.admin$('dt');
if (parts.length <= 20) {
throw new Error('Not enough hooks being displayed...');
}
});
});

View file

@ -1,113 +0,0 @@
'use strict';
describe('Plugins page', function () {
function timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
before(async function () {
let success = false;
$.ajax({
url: `${location.protocol}//admin:changeme@${location.hostname}:${location.port}/admin`,
type: 'GET',
success: () => success = true,
});
await helper.waitForPromise(() => success === true);
});
// create a new pad before each test run
beforeEach(async function () {
helper.newAdmin('plugins');
await helper.waitForPromise(
() => helper.admin$ && helper.admin$('.menu').find('li').length >= 3, 30000);
});
it('Lists some plugins', async function () {
await helper.waitForPromise(() => helper.admin$('.results').children().length > 50, 20000);
});
it('Searches for plugin', async function () {
helper.admin$('#search-query').val('ep_font_color');
await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 10000);
await helper.waitForPromise(() => helper.admin$('.results').children().length < 300, 10000);
});
it('Attempt to Update a plugin', async function () {
this.timeout(280000);
await helper.waitForPromise(() => helper.admin$('.results').children().length > 50, 20000);
if (helper.admin$('.ep_align').length === 0) this.skip();
await helper.waitForPromise(
() => helper.admin$('.ep_align .version').text().split('.').length >= 2);
const minorVersionBefore =
parseInt(helper.admin$('.ep_align .version').text().split('.')[1]);
if (!minorVersionBefore) {
throw new Error('Unable to get minor number of plugin, is the plugin installed?');
}
if (minorVersionBefore !== 2) this.skip();
helper.waitForPromise(
() => helper.admin$('.ep_align .do-update').length === 1);
await timeout(500); // HACK! Please submit better fix..
const $doUpdateButton = helper.admin$('.ep_align .do-update');
$doUpdateButton.trigger('click');
// ensure its showing as Updating
await helper.waitForPromise(
() => helper.admin$('.ep_align .message').text() === 'Updating');
// Ensure it's a higher minor version IE 0.3.x as 0.2.x was installed
// Coverage for https://github.com/ether/etherpad-lite/issues/4536
await helper.waitForPromise(() => parseInt(helper.admin$('.ep_align .version')
.text()
.split('.')[1]) > minorVersionBefore, 60000, 1000);
// allow 50 seconds, check every 1 second.
});
it('Attempt to Install a plugin', async function () {
this.timeout(280000);
helper.admin$('#search-query').val('ep_headings2');
await helper.waitForPromise(() => helper.admin$('.results').children().length < 300, 6000);
await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 6000);
// skip if we already have ep_headings2 installed..
if (helper.admin$('.ep_headings2 .do-install').is(':visible') === false) this.skip();
helper.admin$('.ep_headings2 .do-install').trigger('click');
// ensure install has attempted to be started
await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .do-install').length !== 0, 120000);
// ensure its not showing installing any more
await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .message').text() === '', 180000);
// ensure uninstall button is visible
await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .do-uninstall').length !== 0, 120000);
});
it('Attempt to Uninstall a plugin', async function () {
this.timeout(360000);
await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .do-uninstall').length !== 0, 120000);
helper.admin$('.ep_headings2 .do-uninstall').trigger('click');
// ensure its showing uninstalling
await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .message')
.text() === 'Uninstalling', 120000);
// ensure its gone
await helper.waitForPromise(
() => helper.admin$('.ep_headings2').length === 0, 240000);
helper.admin$('#search-query').val('ep_font');
await helper.waitForPromise(() => helper.admin$('.results').children().length < 300, 240000);
await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 1000);
});
});

View file

@ -1,24 +0,0 @@
'use strict';
describe('All the alphabet works n stuff', function () {
const expectedString = 'abcdefghijklmnopqrstuvwxyz';
// create a new pad before each test run
beforeEach(async function () {
await helper.aNewPad();
});
it('when you enter any char it appears right', function (done) {
const inner$ = helper.padInner$;
// get the first text element out of the inner iframe
const firstTextElement = inner$('div').first();
// simulate key presses to delete content
firstTextElement.sendkeys('{selectall}'); // select all
firstTextElement.sendkeys('{del}'); // clear the first line
firstTextElement.sendkeys(expectedString); // insert the string
helper.waitFor(() => inner$('div').first().text() === expectedString, 2000).done(done);
});
});

View file

@ -1,64 +0,0 @@
'use strict';
describe('bold button', function () {
// create a new pad before each test run
beforeEach(async function () {
await helper.aNewPad();
});
it('makes text bold on click', function (done) {
const inner$ = helper.padInner$;
const chrome$ = helper.padChrome$;
// get the first text element out of the inner iframe
const $firstTextElement = inner$('div').first();
// select this text element
$firstTextElement.sendkeys('{selectall}');
// get the bold button and click it
const $boldButton = chrome$('.buttonicon-bold');
$boldButton.trigger('click');
const $newFirstTextElement = inner$('div').first();
// is there a <b> element now?
const isBold = $newFirstTextElement.find('b').length === 1;
// expect it to be bold
expect(isBold).to.be(true);
// make sure the text hasn't changed
expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
done();
});
it('makes text bold on keypress', function (done) {
const inner$ = helper.padInner$;
// get the first text element out of the inner iframe
const $firstTextElement = inner$('div').first();
// select this text element
$firstTextElement.sendkeys('{selectall}');
const e = new inner$.Event(helper.evtType);
e.ctrlKey = true; // Control key
e.which = 66; // b
inner$('#innerdocbody').trigger(e);
const $newFirstTextElement = inner$('div').first();
// is there a <b> element now?
const isBold = $newFirstTextElement.find('b').length === 1;
// expect it to be bold
expect(isBold).to.be(true);
// make sure the text hasn't changed
expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
done();
});
});

View file

@ -1,35 +0,0 @@
'use strict';
describe('change username value', function () {
// create a new pad before each test run
beforeEach(async function () {
await helper.aNewPad();
});
it('Remembers the user name after a refresh', async function () {
this.timeout(10000);
await helper.toggleUserList();
await helper.setUserName('😃');
// Give the server an opportunity to write the new name.
await new Promise((resolve) => setTimeout(resolve, 1000));
// get a new pad, but don't clear the cookies
await helper.aNewPad({clearCookies: false});
await helper.toggleUserList();
await helper.waitForPromise(() => helper.usernameField().val() === '😃');
});
it('Own user name is shown when you enter a chat', async function () {
this.timeout(10000);
await helper.toggleUserList();
await helper.setUserName('😃');
await helper.showChat();
await helper.sendChatMessage('O hi{enter}');
await helper.waitForPromise(() => {
// username:hours:minutes text
const chatText = helper.chatTextParagraphs().text();
return chatText.indexOf('😃') === 0;
});
});
});

View file

@ -1,116 +0,0 @@
'use strict';
describe('Chat messages and UI', function () {
// create a new pad before each test run
beforeEach(async function () {
await helper.aNewPad();
});
it('opens chat, sends a message, makes sure it exists ' +
'on the page and hides chat', async function () {
this.timeout(3000);
const chatValue = 'JohnMcLear';
await helper.showChat();
await helper.sendChatMessage(`${chatValue}{enter}`);
expect(helper.chatTextParagraphs().length).to.be(1);
// <p data-authorid="a.qjkwNs4z0pPROphS"
// class="author-a-qjkwz78zs4z122z0pz80zz82zz79zphz83z">
// <b>unnamed:</b>
// <span class="time author-a-qjkwz78zs4z122z0pz80zz82zz79zphz83z">12:38
// </span> JohnMcLear
// </p>
const username = helper.chatTextParagraphs().children('b').text();
const time = helper.chatTextParagraphs().children('.time').text();
// TODO: The '\n' is an artifact of $.sendkeys('{enter}'). Figure out how to get rid of it
// without breaking the other tests that use $.sendkeys().
expect(helper.chatTextParagraphs().text()).to.be(`${username}${time} ${chatValue}\n`);
await helper.hideChat();
});
it("makes sure that an empty message can't be sent", async function () {
const chatValue = 'mluto';
await helper.showChat();
// simulate a keypress of typing enter, mluto and enter (to send 'mluto')
await helper.sendChatMessage(`{enter}${chatValue}{enter}`);
const chat = helper.chatTextParagraphs();
expect(chat.length).to.be(1);
// check that the received message is not the empty one
const username = chat.children('b').text();
const time = chat.children('.time').text();
// TODO: Each '\n' is an artifact of $.sendkeys('{enter}'). Figure out how to get rid of them
// without breaking the other tests that use $.sendkeys().
expect(chat.text()).to.be(`${username}${time} \n${chatValue}\n`);
});
it('makes chat stick to right side of the screen via settings, ' +
'remove sticky via settings, close it', async function () {
this.timeout(5000);
await helper.showSettings();
await helper.enableStickyChatviaSettings();
expect(helper.isChatboxShown()).to.be(true);
expect(helper.isChatboxSticky()).to.be(true);
await helper.disableStickyChatviaSettings();
expect(helper.isChatboxSticky()).to.be(false);
expect(helper.isChatboxShown()).to.be(true);
await helper.hideChat();
expect(helper.isChatboxSticky()).to.be(false);
expect(helper.isChatboxShown()).to.be(false);
});
it('makes chat stick to right side of the screen via icon on the top' +
' right, remove sticky via icon, close it', async function () {
this.timeout(5000);
await helper.showChat();
await helper.enableStickyChatviaIcon();
expect(helper.isChatboxShown()).to.be(true);
expect(helper.isChatboxSticky()).to.be(true);
await helper.disableStickyChatviaIcon();
expect(helper.isChatboxShown()).to.be(true);
expect(helper.isChatboxSticky()).to.be(false);
await helper.hideChat();
expect(helper.isChatboxSticky()).to.be(false);
expect(helper.isChatboxShown()).to.be(false);
});
xit('Checks showChat=false URL Parameter hides chat then' +
' when removed it shows chat', async function () {
// give it a second to save the username on the server side
await new Promise((resolve) => setTimeout(resolve, 3000));
// get a new pad, but don't clear the cookies
await helper.aNewPad({clearCookies: false, params: {showChat: 'false'}});
let chrome$ = helper.padChrome$;
let chaticon = chrome$('#chaticon');
// chat should be hidden.
expect(chaticon.is(':visible')).to.be(false);
// give it a second to save the username on the server side
await new Promise((resolve) => setTimeout(resolve, 1000));
// get a new pad, but don't clear the cookies
await helper.aNewPad({clearCookies: false});
chrome$ = helper.padChrome$;
chaticon = chrome$('#chaticon');
// chat should be visible.
expect(chaticon.is(':visible')).to.be(true);
});
});

View file

@ -1,125 +0,0 @@
'use strict';
describe('clear authorship colors button', function () {
let padId;
// create a new pad before each test run
beforeEach(async function () {
padId = await helper.aNewPad();
});
it('makes text clear authorship colors', async function () {
this.timeout(2500);
const inner$ = helper.padInner$;
const chrome$ = helper.padChrome$;
// override the confirm dialogue functioon
helper.padChrome$.window.confirm = () => true;
// get the first text element out of the inner iframe
const $firstTextElement = inner$('div').first();
// Set some new text
const sentText = 'Hello';
// select this text element
$firstTextElement.sendkeys('{selectall}');
$firstTextElement.sendkeys(sentText);
$firstTextElement.sendkeys('{rightarrow}');
// wait until we have the full value available
await helper.waitForPromise(
() => inner$('div span').first().attr('class').indexOf('author') !== -1);
// IE hates you if you don't give focus to the inner frame bevore you do a clearAuthorship
inner$('div').first().trigger('focus');
// get the clear authorship colors button and click it
const $clearauthorshipcolorsButton = chrome$('.buttonicon-clearauthorship');
$clearauthorshipcolorsButton.trigger('click');
// does the first div include an author class?
const hasAuthorClass = inner$('div').first().attr('class').indexOf('author') !== -1;
expect(hasAuthorClass).to.be(false);
await helper.waitForPromise(
() => chrome$('div.disconnected').attr('class').indexOf('visible') === -1);
});
it("makes text clear authorship colors and checks it can't be undone", async function () {
this.timeout(1500);
const inner$ = helper.padInner$;
const chrome$ = helper.padChrome$;
// override the confirm dialogue functioon
helper.padChrome$.window.confirm = () => true;
// get the first text element out of the inner iframe
const $firstTextElement = inner$('div').first();
// Set some new text
const sentText = 'Hello';
// select this text element
$firstTextElement.sendkeys('{selectall}');
$firstTextElement.sendkeys(sentText);
$firstTextElement.sendkeys('{rightarrow}');
// wait until we have the full value available
await helper.waitForPromise(
() => inner$('div span').first().attr('class').indexOf('author') !== -1);
// IE hates you if you don't give focus to the inner frame bevore you do a clearAuthorship
inner$('div').first().trigger('focus');
// get the clear authorship colors button and click it
const $clearauthorshipcolorsButton = chrome$('.buttonicon-clearauthorship');
$clearauthorshipcolorsButton.trigger('click');
// does the first div include an author class?
let hasAuthorClass = inner$('div').first().attr('class').indexOf('author') !== -1;
expect(hasAuthorClass).to.be(false);
const e = new 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
const $undoButton = chrome$('.buttonicon-undo');
// click the button
$undoButton.trigger('click'); // shouldn't do anything
hasAuthorClass = inner$('div').first().attr('class').indexOf('author') !== -1;
expect(hasAuthorClass).to.be(false);
await helper.waitForPromise(
() => chrome$('div.disconnected').attr('class').indexOf('visible') === -1);
});
// Test for https://github.com/ether/etherpad-lite/issues/5128
it('clears authorship when first line has line attributes', async function () {
// override the confirm dialogue function
helper.padChrome$.window.confirm = () => true;
// Make sure there is text with author info. The first line must have a line attribute.
await helper.clearPad();
await helper.edit('Hello');
helper.padChrome$('.buttonicon-insertunorderedlist').click();
await helper.waitForPromise(() => helper.padInner$('[class*="author-"]').length > 0);
const nCommits = helper.commits.length;
helper.padChrome$('.buttonicon-clearauthorship').click();
await helper.waitForPromise(() => helper.padInner$('[class*="author-"]').length === 0);
// Make sure the change was actually accepted by reloading the pad and looking for authorship.
// Before the pad can be reloaded the server might need some time to accept the change.
await helper.waitForPromise(() => helper.commits.length > nCommits);
await helper.aNewPad({id: padId});
expect(helper.padInner$('[class*="author-"]').length).to.be(0);
});
});

View file

@ -1,102 +0,0 @@
'use strict';
describe('Messages in the COLLABROOM', function () {
const user1Text = 'text created by user 1';
const user2Text = 'text created by user 2';
const triggerEvent = (eventName) => {
const event = new helper.padInner$.Event(eventName);
helper.padInner$('#innerdocbody').trigger(event);
};
const replaceLineText = async (lineNumber, newText) => {
const inner$ = helper.padInner$;
// get the line element
const $line = inner$('div').eq(lineNumber);
// simulate key presses to delete content
$line.sendkeys('{selectall}'); // select all
$line.sendkeys('{del}'); // clear the first line
$line.sendkeys(newText); // insert the string
await helper.waitForPromise(() => inner$('div').eq(lineNumber).text() === newText);
};
before(async function () {
this.timeout(10000);
await helper.aNewPad();
await helper.multipleUsers.init();
});
it('bug #4978 regression test', async function () {
// The bug was triggered by receiving a change from another user while simultaneously composing
// a character and waiting for an acknowledgement of a previously sent change.
// User 1 starts sending a change to the server.
let sendStarted;
const finishSend = (() => {
const socketJsonObj = helper.padChrome$.window.pad.socket;
const sendBackup = socketJsonObj.emit;
let startSend;
sendStarted = new Promise((resolve) => { startSend = resolve; });
let finishSend;
const sendP = new Promise((resolve) => { finishSend = resolve; });
socketJsonObj.send = (...args) => {
startSend();
sendP.then(() => {
socketJsonObj.send = sendBackup;
socketJsonObj.send('message', ...args);
});
};
return finishSend;
})();
await replaceLineText(0, user1Text);
await sendStarted;
// User 1 starts a character composition.
triggerEvent('compositionstart');
// User 1 receives a change from user 2. (User 1 will not incorporate the change until the
// composition is completed.)
const user2ChangeArrivedAtUser1 = new Promise((resolve) => {
const cc = helper.padChrome$.window.pad.collabClient;
const origHM = cc.handleMessageFromServer;
cc.handleMessageFromServer = (evt) => {
if (evt.type === 'COLLABROOM' && evt.data.type === 'NEW_CHANGES') {
cc.handleMessageFromServer = origHM;
resolve();
}
return origHM.call(cc, evt);
};
});
await helper.multipleUsers.performAsOtherUser(async () => await replaceLineText(1, user2Text));
await user2ChangeArrivedAtUser1;
// User 1 finishes sending the change to the server. User 2 should see the changes right away.
finishSend();
await helper.multipleUsers.performAsOtherUser(async () => await helper.waitForPromise(
() => helper.padInner$('div').eq(0).text() === user1Text));
// User 1 finishes the character composition. User 2's change should then become visible.
triggerEvent('compositionend');
await helper.waitForPromise(() => helper.padInner$('div').eq(1).text() === user2Text);
// Users 1 and 2 make some more changes.
await helper.multipleUsers.performAsOtherUser(async () => await replaceLineText(3, user2Text));
await replaceLineText(2, user1Text);
// All changes should appear in both views.
const assertContent = async () => await helper.waitForPromise(() => {
const expectedLines = [
user1Text,
user2Text,
user1Text,
user2Text,
];
return expectedLines.every((txt, i) => helper.padInner$('div').eq(i).text() === txt);
});
await assertContent();
await helper.multipleUsers.performAsOtherUser(assertContent);
});
});

View file

@ -1,30 +0,0 @@
'use strict';
describe('delete keystroke', function () {
// create a new pad before each test run
beforeEach(async function () {
await helper.aNewPad();
});
it('makes text delete', async function () {
const inner$ = helper.padInner$;
// get the first text element out of the inner iframe
const $firstTextElement = inner$('div').first();
// get the original length of this element
const elementLength = $firstTextElement.text().length;
// simulate key presses to delete content
$firstTextElement.sendkeys('{leftarrow}'); // simulate a keypress of the left arrow key
$firstTextElement.sendkeys('{del}'); // simulate a keypress of delete
const $newFirstTextElement = inner$('div').first();
// get the new length of this element
const newElementLength = $newFirstTextElement.text().length;
// expect it to be one char less in length
expect(newElementLength).to.be((elementLength - 1));
});
});

View file

@ -1,125 +0,0 @@
'use strict';
describe('embed links', function () {
const objectify = function (str) {
const hash = {};
const parts = str.split('&');
for (let i = 0; i < parts.length; i++) {
const keyValue = parts[i].split('=');
hash[keyValue[0]] = keyValue[1];
}
return hash;
};
const checkiFrameCode = function (embedCode, readonly) {
// turn the code into an html element
const $embediFrame = $(embedCode);
// read and check the frame attributes
const width = $embediFrame.attr('width');
const height = $embediFrame.attr('height');
const name = $embediFrame.attr('name');
expect(width).to.be('100%');
expect(height).to.be('600');
expect(name).to.be(readonly ? 'embed_readonly' : 'embed_readwrite');
// parse the url
const src = $embediFrame.attr('src');
const questionMark = src.indexOf('?');
const url = src.substr(0, questionMark);
const paramsStr = src.substr(questionMark + 1);
const params = objectify(paramsStr);
const expectedParams = {
showControls: 'true',
showChat: 'true',
showLineNumbers: 'true',
useMonospaceFont: 'false',
};
// check the url
if (readonly) {
expect(url.indexOf('r.') > 0).to.be(true);
} else {
expect(url).to.be(helper.padChrome$.window.location.href);
}
// check if all parts of the url are like expected
expect(params).to.eql(expectedParams);
};
describe('read and write', function () {
// create a new pad before each test run
beforeEach(async function () {
await helper.aNewPad();
});
describe('the share link', function () {
it('is the actual pad url', async function () {
const chrome$ = helper.padChrome$;
// open share dropdown
chrome$('.buttonicon-embed').trigger('click');
// get the link of the share field + the actual pad url and compare them
const shareLink = chrome$('#linkinput').val();
const padURL = chrome$.window.location.href;
expect(shareLink).to.be(padURL);
});
});
describe('the embed as iframe code', function () {
it('is an iframe with the the correct url parameters and correct size', async function () {
const chrome$ = helper.padChrome$;
// open share dropdown
chrome$('.buttonicon-embed').trigger('click');
// get the link of the share field + the actual pad url and compare them
const embedCode = chrome$('#embedinput').val();
checkiFrameCode(embedCode, false);
});
});
});
describe('when read only option is set', function () {
beforeEach(async function () {
await helper.aNewPad();
});
describe('the share link', function () {
it('shows a read only url', async function () {
const chrome$ = helper.padChrome$;
// open share dropdown
chrome$('.buttonicon-embed').trigger('click');
chrome$('#readonlyinput').trigger('click');
chrome$('#readonlyinput:checkbox:not(:checked)').attr('checked', 'checked');
// get the link of the share field + the actual pad url and compare them
const shareLink = chrome$('#linkinput').val();
const containsReadOnlyLink = shareLink.indexOf('r.') > 0;
expect(containsReadOnlyLink).to.be(true);
});
});
describe('the embed as iframe code', function () {
it('is an iframe with the the correct url parameters and correct size', async function () {
const chrome$ = helper.padChrome$;
// open share dropdown
chrome$('.buttonicon-embed').trigger('click');
// check read only checkbox, a bit hacky
chrome$('#readonlyinput').trigger('click');
chrome$('#readonlyinput:checkbox:not(:checked)').attr('checked', 'checked');
// get the link of the share field + the actual pad url and compare them
const embedCode = chrome$('#embedinput').val();
checkiFrameCode(embedCode, true);
});
});
});
});

View file

@ -1,61 +0,0 @@
'use strict';
describe('enter keystroke', function () {
// create a new pad before each test run
beforeEach(async function () {
await helper.aNewPad();
});
it('creates a new line & puts cursor onto a new line', async function () {
this.timeout(2000);
const inner$ = helper.padInner$;
// get the first text element out of the inner iframe
const $firstTextElement = inner$('div').first();
// get the original string value minus the last char
const originalTextValue = $firstTextElement.text();
// simulate key presses to enter content
$firstTextElement.sendkeys('{enter}');
await helper.waitForPromise(() => inner$('div').first().text() === '');
const $newSecondLine = inner$('div').first().next();
const newFirstTextElementValue = inner$('div').first().text();
expect(newFirstTextElementValue).to.be(''); // expect the first line to be blank
// expect the second line to be the same as the original first line.
expect($newSecondLine.text()).to.be(originalTextValue);
});
it('enter is always visible after event', async function () {
const originalLength = helper.padInner$('div').length;
let $lastLine = helper.padInner$('div').last();
// simulate key presses to enter content
let i = 0;
const numberOfLines = 15;
let previousLineLength = originalLength;
while (i < numberOfLines) {
$lastLine = helper.padInner$('div').last();
$lastLine.sendkeys('{enter}');
await helper.waitForPromise(() => helper.padInner$('div').length > previousLineLength);
previousLineLength = helper.padInner$('div').length;
// check we can see the caret..
i++;
}
await helper.waitForPromise(
() => helper.padInner$('div').length === numberOfLines + originalLength);
// is edited line fully visible?
const lastLine = helper.padInner$('div').last();
const bottomOfLastLine = lastLine.offset().top + lastLine.height();
const scrolledWindow = helper.padChrome$('iframe')[0];
await helper.waitForPromise(() => {
const scrolledAmount =
scrolledWindow.contentWindow.pageYOffset + scrolledWindow.contentWindow.innerHeight;
return scrolledAmount >= bottomOfLastLine;
});
});
});

View file

@ -7,7 +7,7 @@ describe('the test helper', function () {
for (let i = 0; i < 10; ++i) await helper.aNewPad();
});
it('gives me 3 jquery instances of chrome, outer and inner', async function () {
xit('gives me 3 jquery instances of chrome, outer and inner', async function () {
this.timeout(10000);
await helper.aNewPad();
// check if the jquery selectors have the desired elements
@ -27,7 +27,7 @@ describe('the test helper', function () {
// 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', async function () {
xit('clears cookies', async function () {
// set cookies far into the future to make sure they're not expired yet
window.Cookies.set('token', 'foo', {expires: 7 /* days */});
window.Cookies.set('language', 'bar', {expires: 7 /* days */});
@ -410,13 +410,13 @@ describe('the test helper', function () {
});
});
it('.edit() defaults to send an edit to the first line', async function () {
xit('.edit() defaults to send an edit to the first line', async function () {
const firstLine = helper.textLines()[0];
await helper.edit('line');
expect(helper.textLines()[0]).to.be(`line${firstLine}`);
});
it('.edit() to the line specified with parameter lineNo', async function () {
xit('.edit() to the line specified with parameter lineNo', async function () {
const firstLine = helper.textLines()[0];
await helper.edit('second line', 2);
@ -425,7 +425,7 @@ describe('the test helper', function () {
expect(text[1]).to.equal('second line');
});
it('.edit() supports sendkeys syntax ({selectall},{del},{enter})', async function () {
xit('.edit() supports sendkeys syntax ({selectall},{del},{enter})', async function () {
expect(helper.textLines()[0]).to.not.equal('');
// select first line

View file

@ -1,310 +0,0 @@
'use strict';
describe('indentation button', function () {
// create a new pad before each test run
beforeEach(async function () {
await helper.aNewPad();
});
it('indent text with keypress', async function () {
const inner$ = helper.padInner$;
// get the first text element out of the inner iframe
const $firstTextElement = inner$('div').first();
// select this text element
$firstTextElement.sendkeys('{selectall}');
const e = new inner$.Event(helper.evtType);
e.keyCode = 9; // tab :|
inner$('#innerdocbody').trigger(e);
await helper.waitForPromise(() => inner$('div').first().find('ul li').length === 1);
});
it('indent text with button', async function () {
const inner$ = helper.padInner$;
const chrome$ = helper.padChrome$;
const $indentButton = chrome$('.buttonicon-indent');
$indentButton.trigger('click');
await helper.waitForPromise(() => inner$('div').first().find('ul li').length === 1);
});
it('keeps the indent on enter for the new line', async function () {
this.timeout(1200);
const inner$ = helper.padInner$;
const chrome$ = helper.padChrome$;
const $indentButton = chrome$('.buttonicon-indent');
$indentButton.trigger('click');
// type a bit, make a line break and type again
const $firstTextElement = inner$('div span').first();
$firstTextElement.sendkeys('line 1');
$firstTextElement.sendkeys('{enter}');
$firstTextElement.sendkeys('line 2');
$firstTextElement.sendkeys('{enter}');
await helper.waitFor(() => inner$('div span').first().text().indexOf('line 2') === -1);
const $newSecondLine = inner$('div').first().next();
const hasULElement = $newSecondLine.find('ul li').length === 1;
expect(hasULElement).to.be(true);
expect($newSecondLine.text()).to.be('line 2');
});
it('indents text with spaces on enter if previous line ends ' +
"with ':', '[', '(', or '{'", async function () {
this.timeout(1200);
const inner$ = helper.padInner$;
// type a bit, make a line break and type again
const $firstTextElement = inner$('div').first();
$firstTextElement.sendkeys("line with ':'{enter}");
$firstTextElement.sendkeys("line with '['{enter}");
$firstTextElement.sendkeys("line with '('{enter}");
$firstTextElement.sendkeys("line with '{{}'{enter}");
await helper.waitForPromise(() => {
// wait for Etherpad to split four lines into separated divs
const $fourthLine = inner$('div').first().next().next().next();
return $fourthLine.text().indexOf("line with '{'") === 0;
});
// we validate bottom to top for easier implementation
// curly braces
const $lineWithCurlyBraces = inner$('div').first().next().next().next();
$lineWithCurlyBraces.sendkeys('{{}');
// cannot use sendkeys('{enter}') here, browser does not read the command properly
pressEnter();
const $lineAfterCurlyBraces = inner$('div').first().next().next().next().next();
expect($lineAfterCurlyBraces.text()).to.match(/\s{4}/); // tab === 4 spaces
// parenthesis
const $lineWithParenthesis = inner$('div').first().next().next();
$lineWithParenthesis.sendkeys('(');
pressEnter();
const $lineAfterParenthesis = inner$('div').first().next().next().next();
expect($lineAfterParenthesis.text()).to.match(/\s{4}/);
// bracket
const $lineWithBracket = inner$('div').first().next();
$lineWithBracket.sendkeys('[');
pressEnter();
const $lineAfterBracket = inner$('div').first().next().next();
expect($lineAfterBracket.text()).to.match(/\s{4}/);
// colon
const $lineWithColon = inner$('div').first();
$lineWithColon.sendkeys(':');
pressEnter();
const $lineAfterColon = inner$('div').first().next();
expect($lineAfterColon.text()).to.match(/\s{4}/);
});
it('appends indentation to the indent of previous line if previous line ends ' +
"with ':', '[', '(', or '{'", async function () {
this.timeout(1200);
const inner$ = helper.padInner$;
// type a bit, make a line break and type again
const $firstTextElement = inner$('div').first();
$firstTextElement.sendkeys(" line with some indentation and ':'{enter}");
$firstTextElement.sendkeys('line 2{enter}');
await helper.waitForPromise(() => {
// wait for Etherpad to split two lines into separated divs
const $secondLine = inner$('div').first().next();
return $secondLine.text().indexOf('line 2') === 0;
});
const $lineWithColon = inner$('div').first();
$lineWithColon.sendkeys(':');
pressEnter();
const $lineAfterColon = inner$('div').first().next();
// previous line indentation + regular tab (4 spaces)
expect($lineAfterColon.text()).to.match(/\s{6}/);
});
it("issue #2772 shows '*' when multiple indented lines " +
' receive a style and are outdented', async function () {
this.timeout(1200);
const inner$ = helper.padInner$;
const chrome$ = helper.padChrome$;
// make sure pad has more than one line
inner$('div').first().sendkeys('First{enter}Second{enter}');
await helper.waitForPromise(() => inner$('div').first().text().trim() === 'First');
// indent first 2 lines
const $lines = inner$('div');
const $firstLine = $lines.first();
let $secondLine = $lines.slice(1, 2);
helper.selectLines($firstLine, $secondLine);
const $indentButton = chrome$('.buttonicon-indent');
$indentButton.trigger('click');
await helper.waitForPromise(() => inner$('div').first().find('ul li').length === 1);
// apply bold
const $boldButton = chrome$('.buttonicon-bold');
$boldButton.trigger('click');
await helper.waitForPromise(() => inner$('div').first().find('b').length === 1);
// outdent first 2 lines
const $outdentButton = chrome$('.buttonicon-outdent');
$outdentButton.trigger('click');
await helper.waitForPromise(() => inner$('div').first().find('ul li').length === 0);
// check if '*' is displayed
$secondLine = inner$('div').slice(1, 2);
expect($secondLine.text().trim()).to.be('Second');
});
xit('makes text indented and outdented', async function () {
// get the inner iframe
const $inner = helper.$getPadInner();
// get the first text element out of the inner iframe
let firstTextElement = $inner.find('div').first();
// select this text element
helper.selectText(firstTextElement[0], $inner);
// get the indentation button and click it
const $indentButton = helper.$getPadChrome().find('.buttonicon-indent');
$indentButton.trigger('click');
let newFirstTextElement = $inner.find('div').first();
// is there a list-indent class element now?
let firstChild = newFirstTextElement.children(':first');
let isUL = firstChild.is('ul');
// expect it to be the beginning of a list
expect(isUL).to.be(true);
let secondChild = firstChild.children(':first');
let isLI = secondChild.is('li');
// expect it to be part of a list
expect(isLI).to.be(true);
// indent again
$indentButton.trigger('click');
newFirstTextElement = $inner.find('div').first();
// is there a list-indent class element now?
firstChild = newFirstTextElement.children(':first');
const hasListIndent2 = firstChild.hasClass('list-indent2');
// expect it to be part of a list
expect(hasListIndent2).to.be(true);
// make sure the text hasn't changed
expect(newFirstTextElement.text()).to.eql(firstTextElement.text());
// test outdent
// get the unindentation button and click it twice
const $outdentButton = helper.$getPadChrome().find('.buttonicon-outdent');
$outdentButton.trigger('click');
$outdentButton.trigger('click');
newFirstTextElement = $inner.find('div').first();
// is there a list-indent class element now?
firstChild = newFirstTextElement.children(':first');
isUL = firstChild.is('ul');
// expect it not to be the beginning of a list
expect(isUL).to.be(false);
secondChild = firstChild.children(':first');
isLI = secondChild.is('li');
// expect it to not be part of a list
expect(isLI).to.be(false);
// make sure the text hasn't changed
expect(newFirstTextElement.text()).to.eql(firstTextElement.text());
// Next test tests multiple line indentation
// select this text element
helper.selectText(firstTextElement[0], $inner);
// indent twice
$indentButton.trigger('click');
$indentButton.trigger('click');
// get the first text element out of the inner iframe
firstTextElement = $inner.find('div').first();
// select this text element
helper.selectText(firstTextElement[0], $inner);
/* this test creates the below content, both should have double indentation
line1
line2
*/
firstTextElement.sendkeys('{rightarrow}'); // simulate a keypress of enter
firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter
firstTextElement.sendkeys('line 1'); // simulate writing the first line
firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter
firstTextElement.sendkeys('line 2'); // simulate writing the second line
// get the second text element out of the inner iframe
await new Promise((resolve) => setTimeout(resolve, 1000)); // THIS IS REALLY BAD
const secondTextElement = $('iframe').contents()
.find('iframe').contents()
.find('iframe').contents().find('body > div').get(1); // THIS IS UGLY
// is there a list-indent class element now?
firstChild = secondTextElement.children(':first');
isUL = firstChild.is('ul');
// expect it to be the beginning of a list
expect(isUL).to.be(true);
secondChild = secondChild.children(':first');
isLI = secondChild.is('li');
// expect it to be part of a list
expect(isLI).to.be(true);
// get the first text element out of the inner iframe
const thirdTextElement = $('iframe').contents()
.find('iframe').contents()
.find('iframe').contents()
.find('body > div').get(2); // THIS IS UGLY TOO
// is there a list-indent class element now?
firstChild = thirdTextElement.children(':first');
isUL = firstChild.is('ul');
// expect it to be the beginning of a list
expect(isUL).to.be(true);
secondChild = firstChild.children(':first');
isLI = secondChild.is('li');
// expect it to be part of a list
expect(isLI).to.be(true);
});
});
const pressEnter = () => {
const inner$ = helper.padInner$;
const e = new inner$.Event(helper.evtType);
e.keyCode = 13; // enter :|
inner$('#innerdocbody').trigger(e);
};