mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
Merge branch 'develop' of github.com:ether/etherpad-lite into testings
This commit is contained in:
commit
ab43b01174
18 changed files with 232 additions and 196 deletions
|
@ -1,6 +1,15 @@
|
|||
# Develop -- TODO Change to 1.8.x.
|
||||
* ...
|
||||
|
||||
# 1.8.6
|
||||
* IMPORTANT: This fixes a severe problem with postgresql in 1.8.5
|
||||
* SECURITY: Fix authentication and authorization bypass vulnerabilities
|
||||
* API: Update version to 1.2.15
|
||||
* FEATURE: Add copyPadWithoutHistory API (#4295)
|
||||
* FEATURE: Package more asset files to save http requests (#4286)
|
||||
* MINOR: Improve UI when reconnecting
|
||||
* TESTS: Improve tests
|
||||
|
||||
# 1.8.5
|
||||
* IMPORTANT DROP OF SUPPORT: Drop support for IE. Browsers now need async/await.
|
||||
* IMPORTANT SECURITY: Rate limit Commits when env=production
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"pad.settings.fontType.normal": "Normal",
|
||||
"pad.settings.language": "Sprache:",
|
||||
"pad.settings.about": "Über",
|
||||
"pad.settings.poweredBy": "Powered by $1",
|
||||
"pad.settings.poweredBy": "Powered by",
|
||||
"pad.importExport.import_export": "Import/Export",
|
||||
"pad.importExport.import": "Textdatei oder Dokument hochladen",
|
||||
"pad.importExport.importSuccessful": "Erfolgreich!",
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
"pad.settings.fontType": "Leturgerð:",
|
||||
"pad.settings.fontType.normal": "Venjulegt",
|
||||
"pad.settings.language": "Tungumál:",
|
||||
"pad.settings.about": "Um hugbúnaðinn",
|
||||
"pad.settings.poweredBy": "Keyrt með",
|
||||
"pad.importExport.import_export": "Flytja inn/út",
|
||||
"pad.importExport.import": "Settu inn hverskyns texta eða skjal",
|
||||
"pad.importExport.importSuccessful": "Heppnaðist!",
|
||||
|
@ -51,7 +53,7 @@
|
|||
"pad.importExport.exportword": "Microsoft Word",
|
||||
"pad.importExport.exportpdf": "PDF",
|
||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Þú getur aðeins flutt inn úr hreinum texta eða HTML sniðum. Til að geta nýtt \nfleiri þróaðri innflutningssnið <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">settu þá upp AbiWord forritið</a>.",
|
||||
"pad.importExport.abiword.innerHTML": "Þú getur aðeins flutt inn úr hreinum texta eða HTML sniðum. Til að geta nýtt \nfleiri þróaðri innflutningssnið <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">settu þá upp AbiWord forritið eða LibreOffice</a>.",
|
||||
"pad.modals.connected": "Tengt.",
|
||||
"pad.modals.reconnecting": "Endurtengist skrifblokkinni þinni...",
|
||||
"pad.modals.forcereconnect": "Þvinga endurtengingu",
|
||||
|
@ -119,7 +121,7 @@
|
|||
"pad.userlist.guest": "Gestur",
|
||||
"pad.userlist.deny": "Hafna",
|
||||
"pad.userlist.approve": "Samþykkja",
|
||||
"pad.editbar.clearcolors": "Hreinsa liti höfunda á öllu skjalinu?",
|
||||
"pad.editbar.clearcolors": "Hreinsa liti höfunda á öllu skjalinu? Þetta er ekki hægt að afturkalla",
|
||||
"pad.impexp.importbutton": "Flytja inn núna",
|
||||
"pad.impexp.importing": "Flyt inn...",
|
||||
"pad.impexp.confirmimport": "Innflutningur á skrá mun skrifa yfir þann texta sem er á skrifblokkinni núna. \nErtu viss um að þú viljir halda áfram?",
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"Luckas",
|
||||
"Macofe",
|
||||
"Mansil alfalb",
|
||||
"MuratTheTurkish",
|
||||
"Ti4goc",
|
||||
"Tuliouel",
|
||||
"Waldir",
|
||||
|
@ -110,7 +111,7 @@
|
|||
"timeslider.toolbar.exportlink.title": "Exportar",
|
||||
"timeslider.exportCurrent": "Exportar versão atual como:",
|
||||
"timeslider.version": "Versão {{version}}",
|
||||
"timeslider.saved": "Gravado a {{day}} de {{month}} de {{ano}}",
|
||||
"timeslider.saved": "Gravado a {{day}} de {{month}} de {{year}}",
|
||||
"timeslider.playPause": "Reproduzir / pausar conteúdo da nota",
|
||||
"timeslider.backRevision": "Voltar a uma revisão anterior desta nota",
|
||||
"timeslider.forwardRevision": "Avançar para uma revisão posterior desta nota",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Bengtsson96",
|
||||
"Jopparn",
|
||||
"Lokal Profil",
|
||||
"Sabelöga",
|
||||
|
@ -15,7 +16,7 @@
|
|||
"pad.toolbar.underline.title": "Understruken (Ctrl+U)",
|
||||
"pad.toolbar.strikethrough.title": "Genomstruken (Ctrl+5)",
|
||||
"pad.toolbar.ol.title": "Numrerad lista (Ctrl+Shift+N)",
|
||||
"pad.toolbar.ul.title": "Onumrerad lista (Ctrl+Shift+L)",
|
||||
"pad.toolbar.ul.title": "Punktlista (Ctrl+Shift+L)",
|
||||
"pad.toolbar.indent.title": "Öka indrag (TABB)",
|
||||
"pad.toolbar.unindent.title": "Minska indrag (Shift+TABB)",
|
||||
"pad.toolbar.undo.title": "Ångra (Ctrl+Z)",
|
||||
|
@ -29,7 +30,7 @@
|
|||
"pad.toolbar.showusers.title": "Visa användarna på detta block",
|
||||
"pad.colorpicker.save": "Spara",
|
||||
"pad.colorpicker.cancel": "Avbryt",
|
||||
"pad.loading": "Läser in...",
|
||||
"pad.loading": "Läser in …",
|
||||
"pad.noCookie": "Kunde inte hitta några kakor. Var god tillåt kakor i din webbläsare! Din session och inställningar kommer inte sparas mellan dina besök. Detta kan bero på att Etherpad inte ligger inuti en iFrame i vissa webbläsare. Se till att Etherpad är i samma underdomän/domän som det överordnade iFrame-elementet.",
|
||||
"pad.passwordRequired": "Du behöver ett lösenord för att få tillgång till detta block",
|
||||
"pad.permissionDenied": "Du har inte åtkomstbehörighet för detta block",
|
||||
|
@ -58,7 +59,7 @@
|
|||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
||||
"pad.importExport.abiword.innerHTML": "Du kan endast importera från oformaterad text eller HTML-format. För mer avancerade importfunktioner, var god <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">installera AbiWord eller LibreOffice</a>.",
|
||||
"pad.modals.connected": "Ansluten.",
|
||||
"pad.modals.reconnecting": "Återansluter till ditt block...",
|
||||
"pad.modals.reconnecting": "Återansluter till ditt block …",
|
||||
"pad.modals.forcereconnect": "Tvinga återanslutning",
|
||||
"pad.modals.reconnecttimer": "Försöker ansluta igen",
|
||||
"pad.modals.cancel": "Avbryt",
|
||||
|
@ -129,7 +130,7 @@
|
|||
"pad.userlist.approve": "Godkänn",
|
||||
"pad.editbar.clearcolors": "Rensa författarfärger för hela dokumentet? Detta kan inte ångras",
|
||||
"pad.impexp.importbutton": "Importera nu",
|
||||
"pad.impexp.importing": "Importerar...",
|
||||
"pad.impexp.importing": "Importerar …",
|
||||
"pad.impexp.confirmimport": "Att importera en fil kommer att skriva över den aktuella texten i blocket. Är du säker på att du vill fortsätta?",
|
||||
"pad.impexp.convertFailed": "Vi kunde inte importera denna fil. Var god använd ett annat dokumentformat eller kopiera och klistra in den manuellt",
|
||||
"pad.impexp.padHasData": "Vi kunde inte importera denna fil eftersom detta block redan har redigerats. Importera den till ett nytt block.",
|
||||
|
|
|
@ -72,7 +72,7 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
|
|||
return undefined;
|
||||
});
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const isMatch = (si) => (si != null && si.groupID === groupID && si.validUntil <= now);
|
||||
const isMatch = (si) => (si != null && si.groupID === groupID && now < si.validUntil);
|
||||
const sessionInfo = await promises.firstSatisfies(sessionInfoPromises, isMatch);
|
||||
if (sessionInfo == null) return undefined;
|
||||
return sessionInfo.authorID;
|
||||
|
|
|
@ -7,92 +7,37 @@
|
|||
* express-session, which can't actually use promises anyway.
|
||||
*/
|
||||
|
||||
var Store = require('ep_etherpad-lite/node_modules/express-session').Store,
|
||||
db = require('ep_etherpad-lite/node/db/DB').db,
|
||||
log4js = require('ep_etherpad-lite/node_modules/log4js'),
|
||||
messageLogger = log4js.getLogger("SessionStore");
|
||||
const DB = require('ep_etherpad-lite/node/db/DB');
|
||||
const Store = require('ep_etherpad-lite/node_modules/express-session').Store;
|
||||
const log4js = require('ep_etherpad-lite/node_modules/log4js');
|
||||
|
||||
var SessionStore = module.exports = function SessionStore() {};
|
||||
const logger = log4js.getLogger('SessionStore');
|
||||
|
||||
SessionStore.prototype.__proto__ = Store.prototype;
|
||||
|
||||
SessionStore.prototype.get = function(sid, fn) {
|
||||
messageLogger.debug('GET ' + sid);
|
||||
|
||||
var self = this;
|
||||
|
||||
db.get("sessionstorage:" + sid, function(err, sess) {
|
||||
module.exports = class SessionStore extends Store {
|
||||
get(sid, fn) {
|
||||
logger.debug('GET ' + sid);
|
||||
DB.db.get('sessionstorage:' + sid, (err, sess) => {
|
||||
if (sess) {
|
||||
sess.cookie.expires = 'string' == typeof sess.cookie.expires ? new Date(sess.cookie.expires) : sess.cookie.expires;
|
||||
sess.cookie.expires = ('string' == typeof sess.cookie.expires
|
||||
? new Date(sess.cookie.expires) : sess.cookie.expires);
|
||||
if (!sess.cookie.expires || new Date() < sess.cookie.expires) {
|
||||
fn(null, sess);
|
||||
} else {
|
||||
self.destroy(sid, fn);
|
||||
this.destroy(sid, fn);
|
||||
}
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
SessionStore.prototype.set = function(sid, sess, fn) {
|
||||
messageLogger.debug('SET ' + sid);
|
||||
set(sid, sess, fn) {
|
||||
logger.debug('SET ' + sid);
|
||||
DB.db.set('sessionstorage:' + sid, sess, fn);
|
||||
}
|
||||
|
||||
db.set("sessionstorage:" + sid, sess);
|
||||
if (fn) {
|
||||
process.nextTick(fn);
|
||||
}
|
||||
};
|
||||
|
||||
SessionStore.prototype.destroy = function(sid, fn) {
|
||||
messageLogger.debug('DESTROY ' + sid);
|
||||
|
||||
db.remove("sessionstorage:" + sid);
|
||||
if (fn) {
|
||||
process.nextTick(fn);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* RPB: the following methods are optional requirements for a compatible session
|
||||
* store for express-session, but in any case appear to depend on a
|
||||
* non-existent feature of ueberdb2
|
||||
*/
|
||||
if (db.forEach) {
|
||||
SessionStore.prototype.all = function(fn) {
|
||||
messageLogger.debug('ALL');
|
||||
|
||||
var sessions = [];
|
||||
|
||||
db.forEach(function(key, value) {
|
||||
if (key.substr(0,15) === "sessionstorage:") {
|
||||
sessions.push(value);
|
||||
}
|
||||
});
|
||||
fn(null, sessions);
|
||||
};
|
||||
|
||||
SessionStore.prototype.clear = function(fn) {
|
||||
messageLogger.debug('CLEAR');
|
||||
|
||||
db.forEach(function(key, value) {
|
||||
if (key.substr(0,15) === "sessionstorage:") {
|
||||
db.remove("session:" + key);
|
||||
}
|
||||
});
|
||||
if (fn) fn();
|
||||
};
|
||||
|
||||
SessionStore.prototype.length = function(fn) {
|
||||
messageLogger.debug('LENGTH');
|
||||
|
||||
var i = 0;
|
||||
|
||||
db.forEach(function(key, value) {
|
||||
if (key.substr(0,15) === "sessionstorage:") {
|
||||
i++;
|
||||
}
|
||||
});
|
||||
fn(null, i);
|
||||
destroy(sid, fn) {
|
||||
logger.debug('DESTROY ' + sid);
|
||||
DB.db.remove('sessionstorage:' + sid, fn);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -35,27 +35,21 @@ exports.firstSatisfies = (promises, predicate) => {
|
|||
return Promise.race(newPromises);
|
||||
};
|
||||
|
||||
exports.timesLimit = function(ltMax, concurrency, promiseCreator) {
|
||||
var done = 0
|
||||
var current = 0
|
||||
|
||||
function addAnother () {
|
||||
function _internalRun () {
|
||||
done++
|
||||
|
||||
if (done < ltMax) {
|
||||
addAnother()
|
||||
}
|
||||
}
|
||||
|
||||
promiseCreator(current)
|
||||
.then(_internalRun)
|
||||
.catch(_internalRun)
|
||||
|
||||
current++
|
||||
}
|
||||
|
||||
for (var i = 0; i < concurrency && i < ltMax; i++) {
|
||||
addAnother()
|
||||
// Calls `promiseCreator(i)` a total number of `total` times, where `i` is 0 through `total - 1` (in
|
||||
// order). The `concurrency` argument specifies the maximum number of Promises returned by
|
||||
// `promiseCreator` that are allowed to be active (unresolved) simultaneously. (In other words: If
|
||||
// `total` is greater than `concurrency`, then `concurrency` Promises will be created right away,
|
||||
// and each remaining Promise will be created once one of the earlier Promises resolves.) This async
|
||||
// function resolves once all `total` Promises have resolved.
|
||||
exports.timesLimit = async (total, concurrency, promiseCreator) => {
|
||||
if (total > 0 && concurrency <= 0) throw new RangeError('concurrency must be positive');
|
||||
let next = 0;
|
||||
const addAnother = () => promiseCreator(next++).finally(() => {
|
||||
if (next < total) return addAnother();
|
||||
});
|
||||
const promises = [];
|
||||
for (var i = 0; i < concurrency && i < total; i++) {
|
||||
promises.push(addAnother());
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
|
|
@ -92,10 +92,9 @@
|
|||
"url": "https://github.com/ether/etherpad-lite.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "nyc wtfnode node_modules/.bin/_mocha --timeout 5000 --recursive ../tests/backend/specs",
|
||||
"test-contentcollector": "nyc mocha --timeout 5000 ../tests/backend/specs",
|
||||
"test": "nyc wtfnode node_modules/.bin/_mocha --timeout 5000 --recursive ../tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs",
|
||||
"test-container": "nyc mocha --timeout 5000 ../tests/container/specs/api"
|
||||
},
|
||||
"version": "1.8.5",
|
||||
"version": "1.8.6",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
|
|
@ -47,8 +47,6 @@ body {
|
|||
width: 0; /* hide when the container is empty */
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
#editorcontainerbox {
|
||||
.mobile-layout #editorcontainerbox {
|
||||
margin-bottom: 39px; /* Leave space for the bottom toolbar on mobile */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,9 +78,11 @@
|
|||
.popup#users .popup-content {
|
||||
overflow: visible;
|
||||
}
|
||||
/* Move popup to the bottom, except popup linked to left toolbar, like hyperklink popup */
|
||||
.popup:not(.toolbar-popup) {
|
||||
top: auto;
|
||||
bottom: 1rem;
|
||||
}
|
||||
}
|
||||
/* Move popup to the bottom, except popup linked to left toolbar, like hyperklink popup */
|
||||
.mobile-layout .popup:not(.toolbar-popup) {
|
||||
top: auto;
|
||||
left: 1rem;
|
||||
right: auto;
|
||||
bottom: 1rem;
|
||||
}
|
|
@ -98,13 +98,15 @@ input#myusernameedit:not(.editable) {
|
|||
right: calc(100% + 15px);
|
||||
z-index: 101;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
#mycolorpicker.popup {
|
||||
.mobile-layout #users.popup {
|
||||
right: 1rem;
|
||||
left: auto;
|
||||
}
|
||||
.mobile-layout #mycolorpicker.popup {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
left: auto !important;
|
||||
right: 0 !important;
|
||||
}
|
||||
}
|
||||
#mycolorpicker.popup .btn-container {
|
||||
margin-top: 10px;
|
||||
|
|
|
@ -139,8 +139,10 @@
|
|||
.toolbar ul li.separator {
|
||||
width: 5px;
|
||||
}
|
||||
/* menu_right act like a new toolbar on the bottom of the screen */
|
||||
.toolbar .menu_right {
|
||||
}
|
||||
|
||||
/* menu_right act like a new toolbar on the bottom of the screen */
|
||||
.mobile-layout .toolbar .menu_right {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
@ -148,28 +150,29 @@
|
|||
border-top: 1px solid #ccc;
|
||||
background-color: #f4f4f4;
|
||||
padding: 0 5px 5px 5px;
|
||||
}
|
||||
.toolbar ul.menu_right > li {
|
||||
}
|
||||
.mobile-layout .toolbar ul.menu_right > li {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.toolbar ul.menu_right > li.separator {
|
||||
display: none;
|
||||
}
|
||||
.toolbar ul.menu_right > li a {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.toolbar ul.menu_right > li[data-key="showusers"] {
|
||||
}
|
||||
.mobile-layout .toolbar ul.menu_right > li[data-key="showusers"] {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.toolbar ul.menu_right > li[data-key="showusers"] a {
|
||||
}
|
||||
.mobile-layout .toolbar ul.menu_right > li[data-key="showusers"] a {
|
||||
height: 100%;
|
||||
width: 40px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
.mobile-layout .toolbar ul.menu_right > li.separator {
|
||||
display: none;
|
||||
}
|
||||
.mobile-layout .toolbar ul.menu_right > li a {
|
||||
border: none;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.mobile-layout .toolbar ul.menu_right > li a:not(.selected) {
|
||||
background-color: transparent;
|
||||
}
|
|
@ -317,12 +317,14 @@ var padeditbar = (function()
|
|||
{
|
||||
// reset style
|
||||
$('.toolbar').removeClass('cropped')
|
||||
$('body').removeClass('mobile-layout');
|
||||
var menu_left = $('.toolbar .menu_left')[0];
|
||||
|
||||
// on mobile the menu_right get displayed at the bottom of the screen
|
||||
var isMobileLayout = $('.toolbar .menu_right').css('position') === 'fixed';
|
||||
|
||||
if (menu_left && menu_left.scrollWidth > $('.toolbar').width() && isMobileLayout) {
|
||||
var menuRightWidth = 280; // this is approximate, we cannot measure it because on mobileLayour it takes the full width on the bottom of the page
|
||||
if (menu_left && menu_left.scrollWidth > $('.toolbar').width() - menuRightWidth || $('.toolbar').width() < 1000) {
|
||||
$('body').addClass('mobile-layout');
|
||||
}
|
||||
if (menu_left && menu_left.scrollWidth > $('.toolbar').width()) {
|
||||
$('.toolbar').addClass('cropped');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,23 +131,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
|
||||
.toolbar ul li {
|
||||
.mobile-layout .toolbar ul li {
|
||||
margin: 5px 2px;
|
||||
}
|
||||
.mobile-layout .toolbar ul li.separator {
|
||||
margin: 0 5px;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
.mobile-layout .toolbar ul li.separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toolbar .menu_right {
|
||||
}
|
||||
.mobile-layout .toolbar .menu_right {
|
||||
border-top: 1px solid #d2d2d2;
|
||||
border-top: var(--toolbar-border);
|
||||
background-color: #ffffff;
|
||||
background-color: var(--bg-color);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toolbar ul li a:hover { background-color: transparent; }
|
||||
|
||||
.toolbar ul li.separator { margin: 0; display: none; }
|
||||
}
|
||||
|
||||
|
||||
.mobile-layout .toolbar ul li a:hover {
|
||||
/* background-color: transparent; */
|
||||
}
|
||||
|
|
|
@ -46,10 +46,3 @@
|
|||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
#editorcontainerbox {
|
||||
margin-bottom: 39px; /* margin for bottom toolbar */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
85
tests/backend/specs/promises.js
Normal file
85
tests/backend/specs/promises.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
function m(mod) { return __dirname + '/../../../src/' + mod; }
|
||||
|
||||
const assert = require('assert').strict;
|
||||
const promises = require(m('node/utils/promises'));
|
||||
|
||||
describe('promises.timesLimit', async () => {
|
||||
let wantIndex = 0;
|
||||
const testPromises = [];
|
||||
const makePromise = (index) => {
|
||||
// Make sure index increases by one each time.
|
||||
assert.equal(index, wantIndex++);
|
||||
// Save the resolve callback (so the test can trigger resolution)
|
||||
// and the promise itself (to wait for resolve to take effect).
|
||||
const p = {};
|
||||
const promise = new Promise((resolve) => {
|
||||
p.resolve = resolve;
|
||||
});
|
||||
p.promise = promise;
|
||||
testPromises.push(p);
|
||||
return p.promise;
|
||||
};
|
||||
|
||||
const total = 11;
|
||||
const concurrency = 7;
|
||||
const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
|
||||
|
||||
it('honors concurrency', async () => {
|
||||
assert.equal(wantIndex, concurrency);
|
||||
});
|
||||
|
||||
it('creates another when one completes', async () => {
|
||||
const {promise, resolve} = testPromises.shift();
|
||||
resolve();
|
||||
await promise;
|
||||
assert.equal(wantIndex, concurrency + 1);
|
||||
});
|
||||
|
||||
it('creates the expected total number of promises', async () => {
|
||||
while (testPromises.length > 0) {
|
||||
// Resolve them in random order to ensure that the resolution order doesn't matter.
|
||||
const i = Math.floor(Math.random() * Math.floor(testPromises.length));
|
||||
const {promise, resolve} = testPromises.splice(i, 1)[0];
|
||||
resolve();
|
||||
await promise;
|
||||
}
|
||||
assert.equal(wantIndex, total);
|
||||
});
|
||||
|
||||
it('resolves', async () => {
|
||||
await timesLimitPromise;
|
||||
});
|
||||
|
||||
it('does not create too many promises if total < concurrency', async () => {
|
||||
wantIndex = 0;
|
||||
assert.equal(testPromises.length, 0);
|
||||
const total = 7;
|
||||
const concurrency = 11;
|
||||
const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
|
||||
while (testPromises.length > 0) {
|
||||
const {promise, resolve} = testPromises.pop();
|
||||
resolve();
|
||||
await promise;
|
||||
}
|
||||
await timesLimitPromise;
|
||||
assert.equal(wantIndex, total);
|
||||
});
|
||||
|
||||
it('accepts total === 0, concurrency > 0', async () => {
|
||||
wantIndex = 0;
|
||||
assert.equal(testPromises.length, 0);
|
||||
await promises.timesLimit(0, concurrency, makePromise);
|
||||
assert.equal(wantIndex, 0);
|
||||
});
|
||||
|
||||
it('accepts total === 0, concurrency === 0', async () => {
|
||||
wantIndex = 0;
|
||||
assert.equal(testPromises.length, 0);
|
||||
await promises.timesLimit(0, 0, makePromise);
|
||||
assert.equal(wantIndex, 0);
|
||||
});
|
||||
|
||||
it('rejects total > 0, concurrency === 0', async () => {
|
||||
await assert.rejects(promises.timesLimit(total, 0, makePromise), RangeError);
|
||||
});
|
||||
});
|
|
@ -46,6 +46,5 @@ cd src
|
|||
|
||||
failed=0
|
||||
npm run test || failed=1
|
||||
npm run test-contentcollector || failed=1
|
||||
|
||||
exit $failed
|
||||
|
|
Loading…
Reference in a new issue