restructure: move bin/ and tests/ to src/

Also add symlinks from the old `bin/` and `tests/` locations to avoid
breaking scripts and other tools.

Motivations:

  * Scripts and tests no longer have to do dubious things like:

        require('ep_etherpad-lite/node_modules/foo')

    to access packages installed as dependencies in
    `src/package.json`.

  * Plugins can access the backend test helper library in a non-hacky
    way:

        require('ep_etherpad-lite/tests/backend/common')

  * We can delete the top-level `package.json` without breaking our
    ability to lint the files in `bin/` and `tests/`.

    Deleting the top-level `package.json` has downsides: It will cause
    `npm` to print warnings whenever plugins are installed, npm will
    no longer be able to enforce a plugin's peer dependency on
    ep_etherpad-lite, and npm will keep deleting the
    `node_modules/ep_etherpad-lite` symlink that points to `../src`.

    But there are significant upsides to deleting the top-level
    `package.json`: It will drastically speed up plugin installation
    because `npm` doesn't have to recursively walk the dependencies in
    `src/package.json`. Also, deleting the top-level `package.json`
    avoids npm's horrible dependency hoisting behavior (where it moves
    stuff from `src/node_modules/` to the top-level `node_modules/`
    directory). Dependency hoisting causes numerous mysterious
    problems such as silent failures in `npm outdated` and `npm
    update`. Dependency hoisting also breaks plugins that do:

        require('ep_etherpad-lite/node_modules/foo')
This commit is contained in:
John McLear 2021-02-03 12:08:43 +00:00 committed by Richard Hansen
parent efde0b787a
commit 2ea8ea1275
146 changed files with 191 additions and 1161 deletions

View file

@ -0,0 +1,161 @@
'use strict';
describe('select formatting buttons when selection has style applied', function () {
const STYLES = ['italic', 'bold', 'underline', 'strikethrough'];
const SHORTCUT_KEYS = ['I', 'B', 'U', '5']; // italic, bold, underline, strikethrough
const FIRST_LINE = 0;
before(function (cb) {
helper.newPad(cb);
this.timeout(60000);
});
const applyStyleOnLine = function (style, line) {
const chrome$ = helper.padChrome$;
selectLine(line);
const $formattingButton = chrome$(`.buttonicon-${style}`);
$formattingButton.click();
};
const isButtonSelected = function (style) {
const chrome$ = helper.padChrome$;
const $formattingButton = chrome$(`.buttonicon-${style}`);
return $formattingButton.parent().hasClass('selected');
};
const selectLine = function (lineNumber, offsetStart, offsetEnd) {
const inner$ = helper.padInner$;
const $line = inner$('div').eq(lineNumber);
helper.selectLines($line, $line, offsetStart, offsetEnd);
};
const placeCaretOnLine = function (lineNumber) {
const inner$ = helper.padInner$;
const $line = inner$('div').eq(lineNumber);
$line.sendkeys('{leftarrow}');
};
const undo = function () {
const $undoButton = helper.padChrome$('.buttonicon-undo');
$undoButton.click();
};
const testIfFormattingButtonIsDeselected = function (style) {
it(`deselects the ${style} button`, function (done) {
helper.waitFor(() => isButtonSelected(style) === false).done(done);
});
};
const testIfFormattingButtonIsSelected = function (style) {
it(`selects the ${style} button`, function (done) {
helper.waitFor(() => isButtonSelected(style)).done(done);
});
};
const applyStyleOnLineAndSelectIt = function (line, style, cb) {
applyStyleOnLineOnFullLineAndRemoveSelection(line, style, selectLine, cb);
};
const applyStyleOnLineAndPlaceCaretOnit = function (line, style, cb) {
applyStyleOnLineOnFullLineAndRemoveSelection(line, style, placeCaretOnLine, cb);
};
const applyStyleOnLineOnFullLineAndRemoveSelection = function (line, style, selectTarget, cb) {
// see if line html has changed
const inner$ = helper.padInner$;
const oldLineHTML = inner$.find('div')[line];
applyStyleOnLine(style, line);
helper.waitFor(() => {
const lineHTML = inner$.find('div')[line];
return lineHTML !== oldLineHTML;
});
// remove selection from previous line
selectLine(line + 1);
// setTimeout(function() {
// select the text or place the caret on a position that
// has the formatting text applied previously
selectTarget(line);
cb();
// }, 1000);
};
const pressFormattingShortcutOnSelection = function (key) {
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 = key.charCodeAt(0); // I, U, B, 5
inner$('#innerdocbody').trigger(e);
};
STYLES.forEach((style) => {
context(`when selection is in a text with ${style} applied`, function () {
before(function (done) {
this.timeout(4000);
applyStyleOnLineAndSelectIt(FIRST_LINE, style, done);
});
after(function () {
undo();
});
testIfFormattingButtonIsSelected(style);
});
context(`when caret is in a position with ${style} applied`, function () {
before(function (done) {
this.timeout(4000);
applyStyleOnLineAndPlaceCaretOnit(FIRST_LINE, style, done);
});
after(function () {
undo();
});
testIfFormattingButtonIsSelected(style);
});
});
context('when user applies a style and the selection does not change', function () {
const style = STYLES[0]; // italic
before(function () {
applyStyleOnLine(style, FIRST_LINE);
});
// clean the style applied
after(function () {
applyStyleOnLine(style, FIRST_LINE);
});
it('selects the style button', function (done) {
expect(isButtonSelected(style)).to.be(true);
done();
});
});
SHORTCUT_KEYS.forEach((key, index) => {
const styleOfTheShortcut = STYLES[index]; // italic, bold, ...
context(`when user presses CMD + ${key}`, function () {
before(function () {
pressFormattingShortcutOnSelection(key);
});
testIfFormattingButtonIsSelected(styleOfTheShortcut);
context(`and user presses CMD + ${key} again`, function () {
before(function () {
pressFormattingShortcutOnSelection(key);
});
testIfFormattingButtonIsDeselected(styleOfTheShortcut);
});
});
});
});