diff --git a/CHANGES.md b/CHANGES.md index 194e7d158..3c8da1fa6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -30,6 +30,7 @@ - Documentation includes utf-8 charset to make minfied versions compatible across platforms. #1017 - #1026 Typing in MUC shows "Typing from another device" - #1039 Multi-option data form elements not shown and saved correctly +- #1143 Able to send blank message ### API changes @@ -39,6 +40,9 @@ - New API method `_converse.api.vcard.update`. - The `contactStatusChanged` event has been renamed to `contactPresenceChanged` and a event `presenceChanged` is now also triggered on the contact. +- `_converse.api.chats.open` and `_converse.api.rooms.open` now returns a + `Presence` which resolves with the `Backbone.Model` representing the chat + object. ## UI changes diff --git a/Makefile b/Makefile index cdff95931..021b62c38 100644 --- a/Makefile +++ b/Makefile @@ -227,6 +227,7 @@ check: eslint html: rm -rf $(BUILDDIR)/html $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + make apidoc @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." diff --git a/dist/converse.js b/dist/converse.js index 7dc40591b..7ed1550f1 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -4529,7 +4529,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { -/* WEBPACK VAR INJECTION */(function(global) {var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Native Javascript for Bootstrap 4 v2.0.22 | © dnp_theme | MIT-License +/* WEBPACK VAR INJECTION */(function(global) {var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Native Javascript for Bootstrap 4 v2.0.23 | © dnp_theme | MIT-License (function (root, factory) { if (true) { // AMD support: @@ -4612,7 +4612,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod clickEvent = 'click', hoverEvent = 'hover', keydownEvent = 'keydown', - keyupEvent = 'keyup', + keyupEvent = 'keyup', resizeEvent = 'resize', scrollEvent = 'scroll', // originalEvents @@ -4632,18 +4632,20 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod hasAttribute = 'hasAttribute', createElement = 'createElement', appendChild = 'appendChild', - innerHTML = 'innerHTML', + innerHTML = 'innerHTML', getElementsByTagName = 'getElementsByTagName', preventDefault = 'preventDefault', getBoundingClientRect = 'getBoundingClientRect', querySelectorAll = 'querySelectorAll', getElementsByCLASSNAME = 'getElementsByClassName', + getComputedStyle = 'getComputedStyle', indexOf = 'indexOf', parentNode = 'parentNode', length = 'length', toLowerCase = 'toLowerCase', Transition = 'Transition', + Duration = 'Duration', Webkit = 'Webkit', style = 'style', push = 'push', @@ -4663,15 +4665,16 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod // tooltip / popover mouseHover = ('onmouseleave' in DOC) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ], tipPositions = /\b(top|bottom|left|right)+/, - + // modal modalOverlay = 0, fixedTop = 'fixed-top', fixedBottom = 'fixed-bottom', - + // transitionEnd since 2.0.4 supportTransitions = Webkit+Transition in HTML[style] || Transition[toLowerCase]() in HTML[style], transitionEndEvent = Webkit+Transition in HTML[style] ? Webkit[toLowerCase]()+Transition+'End' : Transition[toLowerCase]()+'end', + transitionDuration = Webkit+Duration in HTML[style] ? Webkit[toLowerCase]()+Transition+Duration : Transition[toLowerCase]()+Duration, // set new focus element since 2.0.3 setFocus = function(element){ @@ -4725,9 +4728,16 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod off(element, event, handlerWrapper); }); }, + getTransitionDurationFromElement = function(element) { + var duration = globalObject[getComputedStyle](element)[transitionDuration]; + duration = parseFloat(duration); + duration = typeof duration === 'number' && !isNaN(duration) ? duration * 1000 : 0; + return duration + 50; // we take a short offset to make sure we fire on the next frame after animation + }, emulateTransitionEnd = function(element,handler){ // emulateTransitionEnd since 2.0.4 - if (supportTransitions) { one(element, transitionEndEvent, function(e){ handler(e); }); } - else { handler(); } + var called = 0, duration = getTransitionDurationFromElement(element); + supportTransitions && one(element, transitionEndEvent, function(e){ handler(e); called = 1; }); + setTimeout(function() { !called && handler(); }, duration); }, bootstrapCustomEvent = function (eventName, componentName, related) { var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName); @@ -4750,8 +4760,8 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod scroll = parent === DOC[body] ? getScroll() : { x: parent[offsetLeft] + parent[scrollLeft], y: parent[offsetTop] + parent[scrollTop] }, linkDimensions = { w: rect[right] - rect[left], h: rect[bottom] - rect[top] }, isPopover = hasClass(element,'popover'), - topPosition, leftPosition, - + topPosition, leftPosition, + arrow = queryElement('.arrow',element), arrowTop, arrowLeft, arrowWidth, arrowHeight, @@ -4770,7 +4780,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod position = position === bottom && bottomExceed ? top : position; position = position === left && leftExceed ? right : position; position = position === right && rightExceed ? left : position; - + // update tooltip/popover class element.className[indexOf](position) === -1 && (element.className = element.className.replace(tipPositions,position)); @@ -4823,7 +4833,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod arrowLeft && (arrow[style][left] = arrowLeft + 'px'); }; - BSN.version = '2.0.22'; + BSN.version = '2.0.23'; /* Native Javascript for Bootstrap 4 | Alert -------------------------------------------*/ @@ -4993,7 +5003,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod // DATA API var intervalAttribute = element[getAttribute](dataInterval), intervalOption = options[interval], - intervalData = intervalAttribute === 'false' ? 0 : parseInt(intervalAttribute) || 5000, // bootstrap carousel default interval + intervalData = intervalAttribute === 'false' ? 0 : parseInt(intervalAttribute), pauseData = element[getAttribute](dataPause) === hoverEvent || false, keyboardData = element[getAttribute](dataKeyboard) === 'true' || false, @@ -5008,8 +5018,8 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod this[pause] = (options[pause] === hoverEvent || pauseData) ? hoverEvent : false; // false / hover this[interval] = typeof intervalOption === 'number' ? intervalOption - : intervalData === 0 ? 0 - : intervalData; + : intervalOption === false || intervalData === 0 || intervalData === false ? 0 + : 5000; // bootstrap carousel default interval // bind, event targets var self = this, index = element.index = 0, timer = element.timer = 0, @@ -5128,10 +5138,10 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod addClass(slides[next],carouselItem +'-'+ slideDirection); addClass(slides[activeItem],carouselItem +'-'+ slideDirection); - one(slides[activeItem], transitionEndEvent, function(e) { - var timeout = e[target] !== slides[activeItem] ? e.elapsedTime*1000 : 0; + one(slides[next], transitionEndEvent, function(e) { + var timeout = e[target] !== slides[next] ? e.elapsedTime*1000+100 : 20; - setTimeout(function(){ + isSliding && setTimeout(function(){ isSliding = false; addClass(slides[next],active); @@ -5146,7 +5156,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod if ( !DOC.hidden && self[interval] && !hasClass(element,paused) ) { self.cycle(); } - },timeout+100); + }, timeout); }); } else { @@ -5211,23 +5221,24 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod // event targets and constants var accordion = null, collapse = null, self = this, - isAnimating = false, // when true it will prevent click handlers accordionData = element[getAttribute]('data-parent'), + activeCollapse, activeElement, // component strings component = 'collapse', collapsed = 'collapsed', + isAnimating = 'isAnimating', // private methods openAction = function(collapseElement,toggle) { bootstrapCustomEvent.call(collapseElement, showEvent, component); - isAnimating = true; + collapseElement[isAnimating] = true; addClass(collapseElement,collapsing); removeClass(collapseElement,component); collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; emulateTransitionEnd(collapseElement, function() { - isAnimating = false; + collapseElement[isAnimating] = false; collapseElement[setAttribute](ariaExpanded,'true'); toggle[setAttribute](ariaExpanded,'true'); removeClass(collapseElement,collapsing); @@ -5239,7 +5250,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod }, closeAction = function(collapseElement,toggle) { bootstrapCustomEvent.call(collapseElement, hideEvent, component); - isAnimating = true; + collapseElement[isAnimating] = true; collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; // set height first removeClass(collapseElement,component); removeClass(collapseElement,showClass); @@ -5248,7 +5259,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod collapseElement[style][height] = '0px'; emulateTransitionEnd(collapseElement, function() { - isAnimating = false; + collapseElement[isAnimating] = false; collapseElement[setAttribute](ariaExpanded,'false'); toggle[setAttribute](ariaExpanded,'false'); removeClass(collapseElement,collapsing); @@ -5267,29 +5278,29 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod // public methods this.toggle = function(e) { e[preventDefault](); - if (isAnimating) return; if (!hasClass(collapse,showClass)) { self.show(); } else { self.hide(); } }; this.hide = function() { + if ( collapse[isAnimating] ) return; closeAction(collapse,element); addClass(element,collapsed); }; this.show = function() { if ( accordion ) { - var activeCollapse = queryElement('.'+component+'.'+showClass,accordion), - toggle = activeCollapse && (queryElement('['+dataToggle+'="'+component+'"]['+dataTarget+'="#'+activeCollapse.id+'"]',accordion) - || queryElement('['+dataToggle+'="'+component+'"][href="#'+activeCollapse.id+'"]',accordion) ), - correspondingCollapse = toggle && (toggle[getAttribute](dataTarget) || toggle.href); - if ( activeCollapse && toggle && activeCollapse !== collapse ) { - closeAction(activeCollapse,toggle); - if ( correspondingCollapse.split('#')[1] !== collapse.id ) { addClass(toggle,collapsed); } - else { removeClass(toggle,collapsed); } - } + activeCollapse = queryElement('.'+component+'.'+showClass,accordion); + activeElement = activeCollapse && (queryElement('['+dataToggle+'="'+component+'"]['+dataTarget+'="#'+activeCollapse.id+'"]',accordion) + || queryElement('['+dataToggle+'="'+component+'"][href="#'+activeCollapse.id+'"]',accordion) ); } - openAction(collapse,element); - removeClass(element,collapsed); + if ( !collapse[isAnimating] || activeCollapse && !activeCollapse[isAnimating] ) { + if ( activeElement && activeCollapse !== collapse ) { + closeAction(activeCollapse,activeElement); + addClass(activeElement,collapsed); + } + openAction(collapse,element); + removeClass(element,collapsed); + } }; // init @@ -5297,6 +5308,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod on(element, clickEvent, self.toggle); } collapse = getTarget(); + collapse[isAnimating] = false; // when true it will prevent click handlers accordion = queryElement(options.parent) || accordionData && getClosest(element, accordionData); element[stringCollapse] = self; }; @@ -5454,6 +5466,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod var btnCheck = element[getAttribute](dataTarget)||element[getAttribute]('href'), checkModal = queryElement( btnCheck ), modal = hasClass(element,'modal') ? element : checkModal, + overlayDelay, // strings component = 'modal', @@ -5487,13 +5500,13 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod return globalObject[innerWidth] || (htmlRect[right] - Math.abs(htmlRect[left])); }, setScrollbar = function () { - var bodyStyle = globalObject.getComputedStyle(DOC[body]), + var bodyStyle = globalObject[getComputedStyle](DOC[body]), bodyPad = parseInt((bodyStyle[paddingRight]), 10), itemPad; if (bodyIsOverflowing) { DOC[body][style][paddingRight] = (bodyPad + scrollbarWidth) + 'px'; if (fixedItems[length]){ for (var i = 0; i < fixedItems[length]; i++) { - itemPad = globalObject.getComputedStyle(fixedItems[i])[paddingRight]; + itemPad = globalObject[getComputedStyle](fixedItems[i])[paddingRight]; fixedItems[i][style][paddingRight] = ( parseInt(itemPad) + scrollbarWidth) + 'px'; } } @@ -5635,6 +5648,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod if ( overlay && modalOverlay && !hasClass(overlay,showClass)) { overlay[offsetWidth]; // force reflow to enable trasition + overlayDelay = getTransitionDurationFromElement(overlay); addClass(overlay, showClass); } @@ -5654,18 +5668,19 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod keydownHandlerToggle(); hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow(); - }, supportTransitions ? 150 : 0); + }, supportTransitions && overlay ? overlayDelay : 0); }; this.hide = function() { bootstrapCustomEvent.call(modal, hideEvent, component); overlay = queryElement('.'+modalBackdropString); + overlayDelay = overlay && getTransitionDurationFromElement(overlay); removeClass(modal,showClass); modal[setAttribute](ariaHidden, true); - (function(){ + setTimeout(function(){ hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerHide) : triggerHide(); - }()); + }, supportTransitions && overlay ? overlayDelay : 0); }; this.setContent = function( content ) { queryElement('.'+component+'-content',modal)[innerHTML] = content; @@ -6021,7 +6036,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod tabsContentContainer[style][height] = nextHeight + 'px'; // height animation tabsContentContainer[offsetWidth]; emulateTransitionEnd(tabsContentContainer, triggerEnd); - },1); + },50); } } else { tabs[isAnimating] = false; @@ -6048,7 +6063,7 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod tabsContentContainer[style][height] = containerHeight + 'px'; // height animation tabsContentContainer[offsetHeight]; activeContent[style][float] = ''; - nextContent[style][float] = ''; + nextContent[style][float] = ''; } if ( hasClass(nextContent, 'fade') ) { @@ -62132,9 +62147,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ return _converse.log(`Invalid JID "${jid}" provided in URL fragment`, Strophe.LogLevel.WARN); } - Promise.all([_converse.api.waitUntil('rosterContactsFetched'), _converse.api.waitUntil('chatBoxesFetched')]).then(() => { - _converse.api.chats.open(jid); - }); + _converse.api.chats.open(jid); } _converse.router.route('converse/chat?jid=:jid', openChat); @@ -62589,7 +62602,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ attrs.from = stanza.getAttribute('from'); attrs.nick = Strophe.unescapeNode(Strophe.getResourceFromJid(attrs.from)); - if (attrs.from === this.get('nick')) { + if (Strophe.getResourceFromJid(attrs.from) === this.get('nick')) { attrs.sender = 'me'; } else { attrs.sender = 'them'; @@ -63023,6 +63036,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ _.extend(_converse.api, { + /** + * The "chats" grouping (used for one-on-one chats) + * + * @namespace + */ 'chats': { 'create'(jids, attrs) { if (_.isUndefined(jids)) { @@ -63053,21 +63071,78 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ }); }, + /** + * Opens a new one-on-one chat. + * + * @function + * + * @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com'] + * @returns {Promise} Promise which resolves with the Backbone.Model representing the chat. + * + * @example + * // To open a single chat, provide the JID of the contact you're chatting with in that chat: + * converse.plugins.add('myplugin', { + * initialize: function() { + * var _converse = this._converse; + * // Note, buddy@example.org must be in your contacts roster! + * _converse.api.chats.open('buddy@example.com').then((chat) => { + * // Now you can do something with the chat model + * }); + * } + * }); + * + * @example + * // To open an array of chats, provide an array of JIDs: + * converse.plugins.add('myplugin', { + * initialize: function () { + * var _converse = this._converse; + * // Note, these users must first be in your contacts roster! + * _converse.api.chats.open(['buddy1@example.com', 'buddy2@example.com']).then((chats) => { + * // Now you can do something with the chat models + * }); + * } + * }); + * + */ 'open'(jids, attrs) { - if (_.isUndefined(jids)) { - _converse.log("chats.open: You need to provide at least one JID", Strophe.LogLevel.ERROR); + return new Promise((resolve, reject) => { + Promise.all([_converse.api.waitUntil('rosterContactsFetched'), _converse.api.waitUntil('chatBoxesFetched')]).then(() => { + if (_.isUndefined(jids)) { + const err_msg = "chats.open: You need to provide at least one JID"; - return null; - } else if (_.isString(jids)) { - const chatbox = _converse.api.chats.create(jids, attrs); + _converse.log(err_msg, Strophe.LogLevel.ERROR); - chatbox.trigger('show'); - return chatbox; - } - - return _.map(jids, jid => _converse.api.chats.create(jid, attrs).trigger('show')); + reject(new Error(err_msg)); + } else if (_.isString(jids)) { + resolve(_converse.api.chats.create(jids, attrs).trigger('show')); + } else { + resolve(_.map(jids, jid => _converse.api.chats.create(jid, attrs).trigger('show'))); + } + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }); }, + /** + * Returns a chat model. The chat should already be open. + * + * @function + * + * @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com'] + * @returns {Backbone.Model} + * + * @example + * // To return a single chat, provide the JID of the contact you're chatting with in that chat: + * const model = _converse.api.chats.get('buddy@example.com'); + * + * @example + * // To return an array of chats, provide an array of JIDs: + * const models = _converse.api.chats.get(['buddy1@example.com', 'buddy2@example.com']); + * + * @example + * // To return all open chats, call the method without any parameters:: + * const models = _converse.api.chats.get(); + * + */ 'get'(jids) { if (_.isUndefined(jids)) { const result = []; @@ -63975,6 +64050,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ev.preventDefault(); const textarea = this.el.querySelector('.chat-textarea'), message = textarea.value; + + if (!message.replace(/\s/g, '').length) { + return; + } + let spoiler_hint; if (this.model.get('composing_spoiler')) { @@ -63989,12 +64069,9 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ const event = document.createEvent('Event'); event.initEvent('input', true, true); textarea.dispatchEvent(event); + this.onMessageSubmitted(message, spoiler_hint); - if (message !== '') { - this.onMessageSubmitted(message, spoiler_hint); - - _converse.emit('messageSend', message); - } + _converse.emit('messageSend', message); this.setChatState(_converse.ACTIVE); }, @@ -65155,7 +65232,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ _.extend(_converse, Backbone.Events); // Core plugins are whitelisted automatically - _converse.core_plugins = ['converse-bookmarks', 'converse-caps', 'converse-chatboxes', 'converse-chatview', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-omemo', 'converse-oauth', 'converse-ping', 'converse-profile', 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Make converse pluggable + _converse.core_plugins = ['converse-bookmarks', 'converse-caps', 'converse-chatboxes', 'converse-chatview', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-omemo', 'converse-oauth', 'converse-ping', 'converse-profile', 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Setting wait to 59 instead of 60 to avoid timing conflicts with the + // webserver, which is often also set to 60 and might therefore sometimes + // return a 504 error page instead of passing through to the BOSH proxy. + + const BOSH_WAIT = 59; // Make converse pluggable pluggable.enable(_converse, '_converse', 'pluggable'); // Module-level constants @@ -66159,7 +66240,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ this.connection.reset(); } - this.connection.connect(this.jid.toLowerCase(), null, this.onConnectStatusChanged); + this.connection.connect(this.jid.toLowerCase(), null, this.onConnectStatusChanged, BOSH_WAIT); } else if (this.authentication === _converse.LOGIN) { const password = _.isNil(credentials) ? _converse.connection.pass || this.password : credentials.password; @@ -66187,7 +66268,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ this.connection.reset(); } - this.connection.connect(this.jid, password, this.onConnectStatusChanged); + this.connection.connect(this.jid, password, this.onConnectStatusChanged, BOSH_WAIT); } }; @@ -66850,6 +66931,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ }); } + iqresult.c('query', attrs); + _.each(plugin._identities, identity => { const attrs = { 'category': identity.category, @@ -67605,8 +67688,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ throw new Error("converse-embedded: auto_join_rooms must be an Array"); } - if (_converse.auto_join_rooms.length !== 1 && _converse.auto_join_private_chats.length !== 1) { - throw new Error("converse-embedded: It doesn't make " + "sense to have the auto_join_rooms setting to zero or " + "more then one, since only one chat room can be open " + "at any time."); + if (_converse.auto_join_rooms.length > 1 && _converse.auto_join_private_chats.length > 1) { + throw new Error("converse-embedded: It doesn't make " + "sense to have the auto_join_rooms setting more then one, " + "since only one chat room can be open at any time."); } } @@ -69911,17 +69994,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ this.model.on('show', this.show, this); this.model.occupants.on('add', this.showJoinNotification, this); this.model.occupants.on('remove', this.showLeaveNotification, this); - this.model.occupants.on('change:show', occupant => { - if (!occupant.isMember() || _.includes(occupant.get('states'), '303')) { - return; - } - - if (occupant.get('show') === 'offline') { - this.showLeaveNotification(occupant); - } else if (occupant.get('show') === 'online') { - this.showJoinNotification(occupant); - } - }); + this.model.occupants.on('change:show', this.showJoinOrLeaveNotification, this); this.createEmojiPicker(); this.createOccupantsView(); this.render().insertIntoDOM(); @@ -69931,8 +70004,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ const handler = () => { if (!u.isPersistableModel(this.model)) { // Happens during tests, nothing to do if this - // is a hanging chatbox (i.e. not in the - // collection anymore). + // is a hanging chatbox (i.e. not in the collection anymore). return; } @@ -70809,6 +70881,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ } }, + showJoinOrLeaveNotification(occupant) { + if (!occupant.isMember() || _.includes(occupant.get('states'), '303')) { + return; + } + + if (occupant.get('show') === 'offline') { + this.showLeaveNotification(occupant); + } else if (occupant.get('show') === 'online') { + this.showJoinNotification(occupant); + } + }, + showJoinNotification(occupant) { if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { return; @@ -70856,10 +70940,9 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ showLeaveNotification(occupant) { const nick = occupant.get('nick'), stat = occupant.get('status'), - last_el = this.content.lastElementChild, - last_msg_date = last_el.getAttribute('data-isodate'); + last_el = this.content.lastElementChild; - if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && moment(last_msg_date).isSame(new Date(), "day") && _.get(last_el, 'dataset', {}).join === `"${nick}"`) { + if (last_el && _.includes(_.get(last_el, 'classList', []), 'chat-info') && moment(last_el.getAttribute('data-isodate')).isSame(new Date(), "day") && _.get(last_el, 'dataset', {}).join === `"${nick}"`) { let message; if (_.isNil(stat)) { @@ -70890,7 +70973,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ 'data': `data-leave="${nick}"` }; - if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && _.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) { + if (last_el && _.includes(_.get(last_el, 'classList', []), 'chat-info') && _.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) { last_el.outerHTML = tpl_info(data); } else { const el = u.stringToElement(tpl_info(data)); @@ -72789,13 +72872,21 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ }, 'open'(jids, attrs) { - if (_.isUndefined(jids)) { - throw new TypeError('rooms.open: You need to provide at least one JID'); - } else if (_.isString(jids)) { - return _converse.api.rooms.create(jids, attrs).trigger('show'); - } + return new Promise((resolve, reject) => { + _converse.api.waitUntil('chatBoxesFetched').then(() => { + if (_.isUndefined(jids)) { + const err_msg = 'rooms.open: You need to provide at least one JID'; - return _.map(jids, jid => _converse.api.rooms.create(jid, attrs).trigger('show')); + _converse.log(err_msg, Strophe.LogLevel.ERROR); + + reject(new TypeError(err_msg)); + } else if (_.isString(jids)) { + resolve(_converse.api.rooms.create(jids, attrs).trigger('show')); + } else { + resolve(_.map(jids, jid => _converse.api.rooms.create(jid, attrs).trigger('show'))); + } + }); + }); }, 'get'(jids, attrs, create) { @@ -73262,7 +73353,12 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ getBundlesAndBuildSessions() { const _converse = this.__super__._converse; return new Promise((resolve, reject) => { - _converse.getDevicesForContact(this.get('jid')).then(devices => { + _converse.getDevicesForContact(this.get('jid')).then(their_devices => { + const device_id = _converse.omemo_store.get('device_id'), + devicelist = _converse.devicelists.get(_converse.bare_jid), + own_devices = devicelist.devices.filter(device => device.get('id') !== device_id), + devices = _.concat(own_devices, their_devices.models); + Promise.all(devices.map(device => device.getBundle())).then(() => this.buildSessions(devices)).then(() => resolve(devices)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }); @@ -73329,32 +73425,34 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ const _converse = this.__super__._converse, address = new libsignal.SignalProtocolAddress(this.get('jid'), device.get('id')), sessionCipher = new window.libsignal.SessionCipher(_converse.omemo_store, address); - return sessionCipher.encrypt(plaintext); + return new Promise((resolve, reject) => { + sessionCipher.encrypt(plaintext).then(payload => resolve({ + 'payload': payload, + 'device': device + })).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + }); }, - addKeysToMessageStanza(stanza, devices, payloads) { - for (var i in payloads) { - if (Object.prototype.hasOwnProperty.call(payloads, i)) { - const payload = btoa(JSON.stringify(payloads[i])); - const prekey = 3 == parseInt(payloads[i].type, 10); + addKeysToMessageStanza(stanza, dicts, iv) { + for (var i in dicts) { + if (Object.prototype.hasOwnProperty.call(dicts, i)) { + const payload = dicts[i].payload, + device = dicts[i].device, + prekey = 3 == parseInt(payload.type, 10); + stanza.c('key', { + 'rid': device.get('id') + }).t(btoa(JSON.stringify(dicts[i].payload))); - if (i == payloads.length - 1) { - stanza.c('key', { - 'rid': devices.get('id') - }).t(payload); + if (prekey) { + stanza.attrs({ + 'prekey': prekey + }); + } - if (prekey) { - stanza.attrs({ - 'prekey': prekey - }); - } + stanza.up(); - stanza.up().c('iv').t(payloads[0].iv).up().up(); - } else { - stanza.c('key', { - prekey: prekey, - rid: devices.get('id') - }).t(payload).up(); + if (i == dicts.length - 1) { + stanza.c('iv').t(iv).up().up(); } } } @@ -73389,8 +73487,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ // concatenation is encrypted using the corresponding // long-standing SignalProtocol session. // TODO: need to include own devices here as well (and filter out distrusted devices) - const promises = devices.map(device => this.encryptKey(payload.key_str + payload.tag, device)); - return Promise.all(promises).then(payloads => this.addKeysToMessageStanza(stanza, devices, payloads)); + const promises = devices.filter(device => device.get('trusted') != UNTRUSTED).map(device => this.encryptKey(payload.key_str + payload.tag, device)); + return Promise.all(promises).then(dicts => this.addKeysToMessageStanza(stanza, dicts, payload.iv)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }); }, @@ -78295,8 +78393,13 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ xhr.onload = function () { if (xhr.status >= 200 && xhr.status < 400) { - jed_instance = new Jed(window.JSON.parse(xhr.responseText)); - resolve(); + try { + const data = window.JSON.parse(xhr.responseText); + jed_instance = new Jed(data); + resolve(); + } catch (e) { + xhr.onerror(e); + } } else { xhr.onerror(); } diff --git a/docs/source/conf.py b/docs/source/conf.py index b96619ec9..8c7ca6003 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Converse.js documentation build configuration file, created by +# Converse documentation build configuration file, created by # sphinx-quickstart on Fri Apr 26 20:48:03 2013. # # This file is execfile()d with the current directory set to its containing dir. @@ -40,8 +40,8 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'Converse.js' -copyright = u'2017, JC Brand' +project = u'Converse' +copyright = u'2018, JC Brand' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -108,7 +108,7 @@ html_logo = "_static/conversejs_small.png" # theme further. html_theme_options = { # Navigation bar title. (Default: ``project`` value) - 'navbar_title': "Converse.js", + 'navbar_title': "Converse", # Tab name for entire site. (Default: "Site") 'navbar_site_name': "Table of Contents", # A list of tuples containing pages or urls to link to. @@ -229,7 +229,7 @@ latex_elements = { # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'Conversejs.tex', u'Converse.js Documentation', + ('index', 'Conversejs.tex', u'Converse Documentation', u'JC Brand', 'manual'), ] @@ -259,7 +259,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'conversejs', u'Converse.js Documentation', + ('index', 'conversejs', u'Converse Documentation', [u'JC Brand'], 1) ] @@ -273,8 +273,8 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Conversejs', u'Converse.js Documentation', - u'JC Brand', 'Conversejs', 'Open Source XMPP webchat', + ('index', 'Conversejs', u'Converse Documentation', + u'JC Brand', 'Converse', 'Open Source XMPP webchat', 'Miscellaneous'), ] diff --git a/docs/source/developer_api.rst b/docs/source/developer_api.rst index 69eb7f0c7..75c3be1a3 100644 --- a/docs/source/developer_api.rst +++ b/docs/source/developer_api.rst @@ -2,12 +2,12 @@ -============================= -The converse.js developer API -============================= +========================= +The old API documentation +========================= -.. note:: The API documented here is available in Converse.js 0.8.4 and higher. - Earlier versions of Converse.js might have different API methods or none at all. +.. note:: The API documented here is available in Converse 0.8.4 and higher. + Earlier versions of Converse might have different API methods or none at all. .. note:: From version 3.0.0 and onwards many API methods have been made private and available to plugins only. This means that if you want to @@ -15,7 +15,7 @@ The converse.js developer API access it. This change is done to avoid leakage of sensitive data to malicious or non-whitelisted scripts. -The Converse.js API is broken up into different logical "groupings" (for +The Converse API is broken up into different logical "groupings" (for example ``converse.plugins`` or ``converse.contacts``). There are some exceptions to this, like ``converse.initialize``, which aren't @@ -58,8 +58,8 @@ initialize .. note:: This method is the one exception of a method which is not logically grouped as explained above. -Publich API method which initializes converse.js. -This method must always be called when using converse.js. +Publich API method which initializes Converse. +This method must always be called when using Converse. The `initialize` method takes a map of :ref:`configuration-settings`. @@ -105,7 +105,7 @@ Registers a new plugin. // Inside this method, you have access to the closured // _converse object, which contains the core logic and data - // structures of converse.js + // structures of Converse } } converse.plugins.add('myplugin', plugin); @@ -182,7 +182,7 @@ two important ways: * A handler registered for a promise, will still fire *after* the promise has been resolved, which is not the case with an event handler. -Converse.js has the following promises: +Converse has the following promises: * :ref:`cachedRoster` * :ref:`chatBoxesFetched` @@ -210,7 +210,7 @@ already by that time. The **archive** grouping ------------------------ -Converse.js supports the *Message Archive Management* +Converse supports the *Message Archive Management* (`XEP-0313 `_) protocol, through which it is able to query an XMPP server for archived messages. @@ -263,12 +263,12 @@ the returned messages. **Waiting until server support has been determined** -The query method will only work if converse.js has been able to determine that +The query method will only work if Converse has been able to determine that the server supports MAM queries, otherwise the following error will be raised: - *This server does not support XEP-0313, Message Archive Management* -The very first time converse.js loads in a browser tab, if you call the query +The very first time Converse loads in a browser tab, if you call the query API too quickly, the above error might appear because service discovery has not yet been completed. @@ -453,7 +453,7 @@ Paramters: get *** -Returns all of the identities registered for this client (i.e. instance of Converse.js). +Returns all of the identities registered for this client (i.e. instance of Converse). .. code-block:: javascript @@ -473,17 +473,17 @@ Paramters: * (String) name * (String) lang -Lets you add new identities for this client (i.e. instance of Converse.js). +Lets you add new identities for this client (i.e. instance of Converse). .. code-block:: javascript - _converse.api.disco.own.identities.add('client', 'web', 'Converse.js'); + _converse.api.disco.own.identities.add('client', 'web', 'Converse'); get *** -Returns all of the identities registered for this client (i.e. instance of Converse.js). +Returns all of the identities registered for this client (i.e. instance of Converse). .. code-block:: javascript @@ -602,7 +602,7 @@ Logs the user in. This method can accept a map with the credentials, like this: } }); -or it can be called without any parameters, in which case converse.js will try +or it can be called without any parameters, in which case Converse will try to log the user in by calling the `prebind_url` or `credentials_url` depending on whether prebinding is used or not. @@ -816,112 +816,67 @@ Note, for MUC chatrooms, you need to use the "rooms" grouping instead. get ~~~ -Returns an object representing a chatbox. The chatbox should already be open. +Returns an object representing a chat. The chat should already be open. -To return a single chatbox, provide the JID of the contact you're chatting -with in that chatbox: +To return a single chat, provide the JID of the contact you're chatting +with in that chat: .. code-block:: javascript _converse.api.chats.get('buddy@example.com') -To return an array of chatboxes, provide an array of JIDs: +To return an array of chats, provide an array of JIDs: .. code-block:: javascript _converse.api.chats.get(['buddy1@example.com', 'buddy2@example.com']) -To return all open chatboxes, call the method without any JIDs:: +To return all open chats, call the method without any JIDs:: _converse.api.chats.get() open ~~~~ -Opens a chatbox and returns a `Backbone.View `_ object -representing a chatbox. +Opens a new chat. + +It returns an promise which will resolve with a `Backbone.Model `_ representing the chat. Note that converse doesn't allow opening chats with users who aren't in your roster (unless you have set :ref:`allow_non_roster_messaging` to ``true``). -Before opening a chat, you should first wait until the roster has been populated. -This is the :ref:`rosterContactsFetched` event/promise. - -Besides that, it's a good idea to also first wait until already opened chatboxes -(which are cached in sessionStorage) have also been fetched from the cache. -This is the :ref:`chatBoxesFetched` event/promise. - These two events fire only once per session, so they're also available as promises. -So, to open a single chatbox: +So, to open a single chat: .. code-block:: javascript converse.plugins.add('myplugin', { - initialize: function() { - var _converse = this._converse; - Promise.all([ - _converse.api.waitUntil('rosterContactsFetched'), - _converse.api.waitUntil('chatBoxesFetched') - ]).then(function() { + initialize: function() { + var _converse = this._converse; + // Note, buddy@example.org must be in your contacts roster! - _converse.api.chats.open('buddy@example.com') - }); - } + _converse.api.chats.open('buddy@example.com').then((chat) => { + // Now you can do something with the chat model + }); + } }); -To return an array of chatboxes, provide an array of JIDs: +To return an array of chats, provide an array of JIDs: .. code-block:: javascript converse.plugins.add('myplugin', { initialize: function () { var _converse = this._converse; - Promise.all([ - _converse.api.waitUntil('rosterContactsFetched'), - _converse.api.waitUntil('chatBoxesFetched') - ]).then(function() { - // Note, these users must first be in your contacts roster! - _converse.api.chats.open(['buddy1@example.com', 'buddy2@example.com']) + // Note, these users must first be in your contacts roster! + _converse.api.chats.open(['buddy1@example.com', 'buddy2@example.com']).then((chats) => { + // Now you can do something with the chat models }); } }); -*The returned chatbox object contains the following methods:* - -+-------------------+------------------------------------------+ -| Method | Description | -+===================+==========================================+ -| close | Close the chatbox. | -+-------------------+------------------------------------------+ -| focus | Focuses the chatbox textarea | -+-------------------+------------------------------------------+ -| model.endOTR | End an OTR (Off-the-record) session. | -+-------------------+------------------------------------------+ -| model.get | Get an attribute (i.e. accessor). | -+-------------------+------------------------------------------+ -| model.initiateOTR | Start an OTR (off-the-record) session. | -+-------------------+------------------------------------------+ -| model.maximize | Minimize the chatbox. | -+-------------------+------------------------------------------+ -| model.minimize | Maximize the chatbox. | -+-------------------+------------------------------------------+ -| model.set | Set an attribute (i.e. mutator). | -+-------------------+------------------------------------------+ -| show | Opens/shows the chatbox. | -+-------------------+------------------------------------------+ - -*The get and set methods can be used to retrieve and change the following attributes:* - -+-------------+-----------------------------------------------------+ -| Attribute | Description | -+=============+=====================================================+ -| height | The height of the chatbox. | -+-------------+-----------------------------------------------------+ -| url | The URL of the chatbox heading. | -+-------------+-----------------------------------------------------+ - The **chatviews** grouping -------------------------- @@ -952,7 +907,7 @@ To return an array of views, provide an array of JIDs: The **listen** grouping ----------------------- -Converse.js emits events to which you can subscribe from your own JavaScript. +Converse emits events to which you can subscribe from your own JavaScript. Concerning events, the following methods are available under the "listen" grouping: @@ -1014,7 +969,7 @@ The **rooms** grouping get ~~~ -Returns an object representing a multi user chatbox (room). +Returns an object representing a multi user chat (room). It takes 3 parameters: * the room JID (if not specified, all rooms will be returned). @@ -1046,7 +1001,7 @@ It takes 3 parameters: open ~~~~ -Opens a multi user chatbox and returns an object representing it. +Opens a multi user chat and returns an object representing it. Similar to the ``chats.get`` API. It takes 2 parameters: @@ -1055,7 +1010,7 @@ It takes 2 parameters: * A map (object) containing any extra room attributes. For example, if you want to specify the nickname, use ``{'nick': 'bloodninja'}``. -To open a single multi user chatbox, provide the JID of the room: +To open a single multi user chat, provide the JID of the room: .. code-block:: javascript @@ -1150,7 +1105,7 @@ JIDs. The **promises** grouping ------------------------- -Converse.js and its plugins emit various events which you can listen to via the +Converse and its plugins emit various events which you can listen to via the :ref:`listen-grouping`. Some of these events are also available as `ES2015 Promises `_, @@ -1209,7 +1164,7 @@ For example: The **settings** grouping ------------------------- -This grouping allows access to the configuration settings of converse.js. +This grouping allows access to the configuration settings of Converse. .. _`settings-update`: @@ -1290,7 +1245,7 @@ or : }); Note, this is not an alternative to calling ``converse.initialize``, which still needs -to be called. Generally, you'd use this method after converse.js is already +to be called. Generally, you'd use this method after Converse is already running and you want to change the configuration on-the-fly. The **tokens** grouping @@ -1328,7 +1283,7 @@ Parameters: Returns a Promise which results with the VCard data for a particular JID or for a `Backbone.Model` instance which represents an entity with a JID (such as a roster contact, -chatbox or chatroom occupant). +chat or chatroom occupant). If a `Backbone.Model` instance is passed in, then it must have either a `jid` attribute or a `muc_jid` attribute. diff --git a/docs/source/development.rst b/docs/source/development.rst index 1f116a860..3c7a7487d 100644 --- a/docs/source/development.rst +++ b/docs/source/development.rst @@ -8,13 +8,13 @@ Development =========== -Welcome to the developer documentation of converse.js. Read the documentation +Welcome to the developer documentation of Converse. Read the documentation linked to below, if you want to add new features or create your own customized -version of converse.js. +version of Converse. -Converse.js itself composed of plugins, and exposes an API with which you can +Converse itself composed of plugins, and exposes an API with which you can create and register your own plugins. This is the recommended way to customize -or add new functionality to converse.js. +or add new functionality to Converse. .. toctree:: :maxdepth: 2 @@ -22,6 +22,7 @@ or add new functionality to converse.js. developer_guidelines style_guide plugin_development + api/index developer_api events other_frameworks diff --git a/docs/source/setup.rst b/docs/source/setup.rst index d8675172b..cd52bdc62 100644 --- a/docs/source/setup.rst +++ b/docs/source/setup.rst @@ -8,19 +8,19 @@ Setup and integration ===================== -This page documents what you'll need to do to be able to connect Converse.js with +This page documents what you'll need to do to be able to connect Converse with your own XMPP server and to better integrate it into your website. -At the very least you'll need Converse.js and an :ref:`XMPP server` with +At the very least you'll need Converse and an :ref:`XMPP server` with :ref:`websocket-section` or :ref:`BOSH-section` enabled. That's definitely -enough to simply demo Converse.js or to do development work on it. +enough to simply demo Converse or to do development work on it. -However, if you want to more fully integrate it into a website or intranet, +However, if you want to more fully integrate it into a website then you'll likely need to set up more services and components. The diagram below shows a fairly common setup for a website or intranet: -* Converse.js runs in the web-browser on the user's device. +* Converse runs in the web-browser on the user's device. * It communicates with the XMPP server via BOSH or websocket which is usually reverse-proxied by a web-server in order to overcome cross-site scripting @@ -34,13 +34,12 @@ The diagram below shows a fairly common setup for a website or intranet: the XMPP server is configured to use, so that users can log in with those credentials. - * Usually (but optionally) there is a backend web application which hosts a - website in which Converse.js appears. + website in which Converse appears. .. figure:: images/diagram.png :align: center - :alt: A diagram of a possible setup, consisting of Converse.js, a web server, a backend web application, an XMPP server, a user directory such as LDAP and an XMPP server. + :alt: A diagram of a possible setup, consisting of Converse, a web server, a backend web application, an XMPP server, a user directory such as LDAP and an XMPP server. This diagram shows the various services in a fairly common setup (image generated with `draw.io `_). @@ -53,11 +52,11 @@ The various components An XMPP server ============== -*Converse.js* implements `XMPP `_ as its +*Converse* uses `XMPP `_ as its messaging protocol, and therefore needs to connect to an XMPP/Jabber server (Jabber® is an older and more user-friendly synonym for XMPP). -You can connect to public XMPP servers like ``jabber.org`` but if you want to +You can connect to public XMPP servers like ``conversejs.org`` but if you want to have :ref:`session support ` you'll have to set up your own XMPP server. You can find a list of public XMPP servers/providers on `xmpp.net `_ @@ -79,7 +78,7 @@ stanzas to be sent over an HTTP connection. HTTP connections are stateless and usually shortlived. XMPP connections on the other hand are stateful and usually last much longer. -So to enable a web application like *Converse.js* to communicate with an XMPP +So to enable a web application like *Converse* to communicate with an XMPP server, we need a proxy which acts as a bridge between these two protocols. This is the job of a BOSH connection manager. BOSH (Bidirectional-streams Over @@ -87,7 +86,7 @@ Synchronous HTTP) is a protocol for allowing XMPP communication over HTTP. The protocol is defined in `XEP-0206: XMPP Over BOSH `_. Popular XMPP servers such as `Ejabberd `_, -prosody `(mod_bosh) `_ and +Prosody `(mod_bosh) `_ and `OpenFire `_ all include their own BOSH connection managers (but you usually have to enable them in the configuration). @@ -98,14 +97,15 @@ https://conversejs.org does), then you'll need a standalone connection manager. For a standalone manager, see for example `Punjab `_ and `node-xmpp-bosh `_. -The demo on the `Converse.js homepage `_ uses a connection +The demo on the `Converse homepage `_ uses a connection manager located at https://conversejs.org/http-bind. This connection manager is available for testing purposes only, please don't use it in production. Refer to the :ref:`bosh-service-url` configuration setting for information on -how to configure Converse.js to connect to a BOSH URL. +how to configure Converse to connect to a BOSH URL. + .. _`websocket-section`: @@ -122,7 +122,7 @@ HTTP. Therefore BOSH, which operates over HTTP, doesn't apply to websockets. does the node-xmpp-bosh connection manager. Refer to the :ref:`websocket-url` configuration setting for information on how to -configure Converse.js to connect to a websocket URL. +configure Converse to connect to a websocket URL. The Webserver ============= @@ -133,7 +133,7 @@ Overcoming cross-domain request restrictions Lets say your domain is *example.org*, but the domain of your connection manager is *example.com*. -HTTP requests are made by *Converse.js* to the BOSH connection manager via +HTTP requests are made by *Converse* to the BOSH connection manager via XmlHttpRequests (XHR). Until recently, it was not possible to make such requests to a different domain than the one currently being served (to prevent XSS attacks). @@ -159,7 +159,7 @@ Examples: Assuming your site is accessible on port ``80`` for the domain ``mysite.com`` and your connection manager manager is running at ``someothersite.com/http-bind``. -The *bosh_service_url* value you want to give Converse.js to overcome +The *bosh_service_url* value you want to give Converse to overcome the cross-domain restriction is ``mysite.com/http-bind`` and not ``someothersite.com/http-bind``. @@ -192,6 +192,32 @@ Apache +.. note:: + + If you're getting XML parsing errors for your BOSH endpoint, for + example:: + + XML Parsing Error: mismatched tag. Expected: . + Location: https://example.org/http-bind/ + Line Number 6, Column 3: bosh-anon:6:3 + Also ERROR: request id 12.2 error 504 happened + + Then your BOSH proxy is returning an HTML error page (for a 504 error in + the above example). + + This might be because your webserver and BOSH proxy have the same timeout + for BOSH requests. Because the webserver receives the request slightly earlier, + it gives up a few microseconds before the XMPP server’s empty result and thus returns a + 504 error page containing HTML to browser, which then gets parsed as if its + XML. + + To fix this, make sure that the webserver's timeout is slightly higher. + In Nginx you can do this by adding ``proxy_read_timeout 61;``; + + From Converse 4.0.0 onwards the default ``wait`` time is set to 59 seconds, to avoid + this problem. + + .. _`session-support`: Single Session Support @@ -215,7 +241,7 @@ authenticated BOSH session with the XMPP server or a standalone `BOSH `_. Other XMPP servers have similar plugin modules. If your web-application has access to the same credentials, it can send those -credentials to Converse.js so that user's are automatically logged in when the +credentials to Converse so that user's are automatically logged in when the page loads. This is can be done by setting :ref:`auto_login` to true and configuring the @@ -316,7 +342,7 @@ The first option has the drawback that your web-application needs to know the XMPP credentials of your users and that they need to be stored in the clear. The second option has that same drawback and it also needs to pass those -credentials to Converse.js. +credentials to Converse. To avoid these drawbacks, you can instead let your backend web application generate temporary authentication tokens which are then sent to the XMPP server diff --git a/locale/ar/LC_MESSAGES/converse.json b/locale/ar/LC_MESSAGES/converse.json index 3ac3cfa92..cc3548a41 100644 --- a/locale/ar/LC_MESSAGES/converse.json +++ b/locale/ar/LC_MESSAGES/converse.json @@ -1 +1 @@ -{"domain":"converse","locale_data":{"converse":{"":{"domain":"converse","plural_forms":"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;","lang":"ar"},"The name for this bookmark:":["تسمية الفاصلة المرجعية :"],"Save":["حفظ"],"Cancel":["إلغاء"],"Are you sure you want to remove the bookmark \"%1$s\"?":["هل أنت متأكد أنك تريد إزالة الفاصلة المرجعية \"%1$s\" ؟"],"Sorry, something went wrong while trying to save your bookmark.":["المعذرة، لقد طرأ هناك خطأ أثناء محاولة الإحتفاظ بالفواصل المرجعية."],"Remove this bookmark":["إزالة هذه الفاصلة المرجعية"],"Click to toggle the bookmarks list":["أنقر للإنتقال إلى قائمة الإشارات المرجعية"],"Bookmarks":["الفواصل المرجعية"],"Sorry, could not determine file upload URL.":[""],"Sorry, could not determine upload URL.":[""],"Sorry, could not succesfully upload your file.":["للأسف لم نتمكّن مِن القيام برفع ملفك بنجاح."],"Sorry, looks like file upload is not supported by your server.":["للأسف يبدو أن خاصية رفع الملفات لا يدعمها خادومكم."],"The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.":[""],"Sorry, an error occurred:":[""],"Close this chat box":["إغلق نافذة المحادثة هذه"],"The User's Profile Image":[""],"Close":["إغلاق"],"Email":[""],"Full Name":[""],"Nickname":["الإسم المُستعار"],"Refresh":[""],"Role":[""],"URL":[""],"Are you sure you want to remove this contact?":["هل أنت متأكد أنك تريد حذف هذا المراسل ؟"],"Error":[""],"Sorry, there was an error while trying to remove %1$s as a contact.":[""],"You have unread messages":["لقد ورَدَت إليك رسائل غير مقروءة"],"Hidden message":["رسالة مخفية"],"Personal message":["رسالة خاصة"],"Send":["إرسل"],"Optional hint":["دليل إختياري"],"Choose a file to send":["إختر الملف الذي تود إرساله"],"Click to write as a normal (non-spoiler) message":[""],"Click to write your message as a spoiler":[""],"Clear all messages":["تنظيف كافة الرسائل"],"Start a call":["إبدأ مكالمة"],"Remove messages":["حذف الرسائل"],"Write in the third person":["كتب كأنه شخص ثالث"],"Show this menu":["إظهار هذه القائمة"],"Are you sure you want to clear the messages from this conversation?":["هل أنت متأكد أنك تود مسح الرسائل مِن نافذة المحادثة هذه ؟"],"Username":["إسم المستخدِم"],"user@domain":["user@domain"],"Please enter a valid XMPP address":["يرجى إدخال عنوان XMPP صالح"],"Chat Contacts":["جهات الإتصال"],"Toggle chat":["الإنتقال إلى الدردشة"],"The connection has dropped, attempting to reconnect.":["لقد إنقطع الإتصال، عملية إعادة الربط جارية."],"An error occurred while connecting to the chat server.":["طرأ هناك خطأ أنثاء الربط بخادوم المحادثة."],"Your Jabber ID and/or password is incorrect. Please try again.":["مُعرِّف جابر الخاص بك أو كلمتك السرية خاطئة. يرجى إعادة المحاولة ثانية."],"Sorry, we could not connect to the XMPP host with domain: %1$s":["عذرا، لم نتمكن مِن الإتصال بخادوم XMPP عبر النطاق : %1$s"],"The XMPP server did not offer a supported authentication mechanism":[""],"Show more":["عرض المزيد"],"Typing from another device":["يكتب عبر جهاز آخَر"],"Stopped typing on the other device":["توقّف عن الكتابة عبر الجهاز الآخَر"],"Minimize this chat box":["تصغير نافذة المحادثة هذه"],"Click to restore this chat":["أنقر لاستعادة هذه المحادثة"],"Minimized":["تصغير"],"You have been removed from this groupchat because of an affiliation change":[""],"You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member":[""],"You have been removed from this groupchat because the MUC (Multi-user chat) service is being shut down":[""],"%1$s has been banned":["لقد تم طرد %1$s"],"%1$s's nickname has changed":["لقد قام %1$s بتغيير إسمه المُستعار"],"%1$s has been kicked out":["لقد تم طرد %1$s مِن غرفة المحادثة مؤقتًا"],"%1$s has been removed because of an affiliation change":[""],"%1$s has been removed for not being a member":["تمت إزالة %1$s لأنه ليس عضو مُنتم إلى الغرفة"],"Your nickname has been automatically set to %1$s":["لقد تم تغيير إسمك المستعار آليا إلى %1$s"],"Your nickname has been changed to %1$s":["لقد تم تغيير إسمك المُستعار إلى %1$s"],"Description:":["التفاصيل :"],"Features:":["الميزات :"],"Requires authentication":["يتطلّب المصادقة"],"Hidden":["خفية"],"Requires an invitation":["تستلزم دعوة"],"Moderated":["تحت الإشراف"],"Non-anonymous":["غير مجهولة"],"Open":["مفتوحة"],"Public":["عمومية"],"Semi-anonymous":["مجهولة نسبيًا"],"Temporary":["مُؤقّتة"],"Unmoderated":["ليست تحت الإشراف"],"Server address":["عنوان الخادوم"],"conference.example.org":["conference.example.org"],"Optional nickname":["إسم مستعار اختياري"],"name@conference.example.org":["name@conference.example.org"],"Join":["الإلتحاق بالغرفة"],"Message":["رسالة"],"%1$s has been muted":["تم كتم %1$s"],"%1$s is now a moderator":["أصبح %1$s مُشرفًا"],"Error: the \"%1$s\" command takes two arguments, the user's nickname and optionally a reason.":[""],"Sorry, an error happened while running the command. Check your browser's developer console for details.":[""],"Change user's affiliation to admin":[""],"Change user role to participant":["تغيير دور المستخدِم إلى مُشترِك"],"Write in 3rd person":[""],"Grant membership to a user":["منح صفة العضوية لمستخدِم"],"Remove user's ability to post messages":["منع المستخدم مِن بعث رسائل"],"Change your nickname":["غيّر إسمك المُستعار"],"Grant moderator role to user":["ترقية المستخدِم إلى رتبة مشرف"],"Revoke user's membership":["إسقاط صفة العضوية مِن المستخدِم"],"Set groupchat subject (alias for /subject)":[""],"Allow muted user to post messages":["السماح للمستخدم المكتوم نشر رسائل"],"The nickname you chose is reserved or currently in use, please choose a different one.":["إنّ الإسم المستعار الذي قمت باختياره محجوز أو مُستعمَل حاليا مِن طرف شخص آخَر، يُرجى اختيار إسمٍ آخَر."],"Please choose your nickname":["يرجى اختيار إسمك المُستعار"],"Password: ":["كلمة السر : "],"Submit":["إرسال"],"This action was done by %1$s.":["قام %1$s بهذا الإجراء."],"The reason given is: \"%1$s\".":["السبب : \"%1$s\"."],"No nickname was specified.":["لم تقم باختيار أي إسم مستعار."],"Remote server not found":[""],"Topic set by %1$s":["قام %1$s بتحديد الموضوع"],"Click to mention %1$s in your message.":["أنقر للإشارة إلى %1$s في رسالتك."],"This user is a moderator.":["إنّ هذا المستخدِم مشرف في الغرفة."],"Visitor":[""],"Owner":[""],"Admin":[""],"Participants":[""],"Invite":["دعوة"],"You are about to invite %1$s to the groupchat \"%2$s\". You may optionally include a message, explaining the reason for the invitation.":[""],"Please enter a valid XMPP username":["يُرجى إدخال إسم مستخدِم XMPP صحيح"],"Notification from %1$s":["إشعار مِن %1$s"],"%1$s says":["%1$s قال"],"has gone offline":["قد قطع الإتصال"],"has gone away":["قد غاب"],"is busy":["مشغول"],"has come online":["صار مُتّصلا الآن"],"wants to be your contact":["يُريد أن يُصبح مُراسلك"],"Log in with %1$s":[""],"Your Profile":["ملفك الشخصي"],"Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.":[""],"Your avatar image":[""],"You can check your browser's developer console for any error output.":[""],"Away":["غائب"],"Busy":["مشغول"],"Custom status":["حالتك الخاصة"],"Offline":["غير متصل"],"Online":["مُتّصل"],"Away for long":["غائب لمدة قد تطول"],"Change chat status":["أنقر لتغيير حالة الدردشة"],"Personal status message":["رسالة الحالة الخاصة"],"I am %1$s":["أنا %1$s"],"Change settings":["تغيير الإعدادات"],"Click to change your chat status":["أنقر لتغيير حالتك للدردشة"],"Log out":["الخروج"],"Your profile":["ملفك الشخصي"],"Are you sure you want to log out?":["هل أنت متأكد أنك تريد الخروج ؟"],"online":["متصل"],"busy":["مشغول"],"away for long":["غائب لمدة قد تطول"],"away":["غائب"],"offline":["غير متصل"]," e.g. conversejs.org":[" مثال conversejs.org"],"Fetch registration form":["جارٍ جلب استمارة التسجيل"],"Tip: A list of public XMPP providers is available":[""],"here":["هنا"],"Sorry, we're unable to connect to your chosen provider.":["المعذرة، لم نتمكن بربطك بموفر خدمة المحادثة الذي قمت باختياره."],"Sorry, the given provider does not support in band account registration. Please try with a different provider.":[""],"Something went wrong while establishing a connection with \"%1$s\". Are you sure it exists?":[""],"Now logging you in":["جارٍ تسجيل دخولك الآن"],"Registered successfully":["تم تسجيل حسابك بنجاح"],"The provider rejected your registration attempt. Please check the values you entered for correctness.":[""],"Open Groupchats":[""],"Sorry, there was an error while trying to add %1$s as a contact.":["المعذرة، لقد حدث هناك خطأ أثناء محاولة إضافة %1$s كمُراسِل."],"This client does not allow presence subscriptions":[""],"Click to hide these contacts":["أْنقُر لإخفاء هؤلاء المراسلين"],"This contact is busy":["إنّ المُراسَل مشغول"],"This contact is online":["إنّ هذا المُراسَل غير مُتصل"],"This contact is offline":["هذا المراسل غير متصل"],"This contact is unavailable":["إنّ هذا المراسَل غير متوفر"],"This contact is away for an extended period":["لقد غاب هذا المستخدِم ثانية لفترة أطوَل"],"This contact is away":["إنّ هذا المراسَل غائب"],"Groups":["الفِرَق"],"My contacts":["جهات إتصالي"],"Pending contacts":["المُراسلون المُعلّقون"],"Contact requests":["طلبات التراسل"],"Ungrouped":[""],"Contact name":["إسم المراسل"],"Add a Contact":["إضافة مراسل"],"XMPP Address":["عنوان XMPP"],"name@example.org":["name@example.org"],"Add":["إضافة"],"Filter":["عامل التصفية"],"Filter by contact name":["فرز حسب اسم جهة الاتصال"],"Filter by group name":["فرز حسب اسم المجموعة"],"Filter by status":["تصنيف حسب الحالة"],"Any":["الكل"],"Unread":["غير مقروءة"],"Chatty":["كثيرة الدردشة"],"Extended Away":[""],"Click to remove %1$s as a contact":["أنقر لإزالة %1$s مِن قائمة مراسليك"],"Click to accept the contact request from %1$s":["أنقر لقبول طلب التراسل مع %1$s"],"Click to decline the contact request from %1$s":["أنقر لرفض طلب التراسل مع %1$s"],"Click to chat with %1$s (JID: %2$s)":["أنقر للتحدث مع %1$s (JID : %2$s)"],"Are you sure you want to decline this contact request?":["هل أنت متأكد أنك تود رفض طلب التراسل مع هذا المستخدِم ؟"],"Contacts":["جهات الإتصال"],"Add a contact":["إضافة مراسل"],"Name":[""],"Topic":[""],"Topic author":[""],"Features":["الميزات"],"Password protected":["مؤمَّنة بكلمة سرية"],"Members only":["الأعضاء فقط"],"Persistent":["دائمة"],"Only moderators can see your XMPP username":["بإمكان المشرفين فقط رؤية إسم XMPP الخاص بك"],"Message archiving":["أرشفة الرسائل"],"Messages are archived on the server":["الرسائل محفوظة على الخادوم"],"No password":["بدون كلمة سرية"],"XMPP Username:":["إسم المستخدِم :"],"Password:":["كلمة السر :"],"password":["كلمة السر"],"This is a trusted device":[""],"To improve performance, we cache your data in this browser. Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. It's important that you explicitly log out, otherwise not all cached data might be deleted.":[""],"Click here to log in anonymously":["أُنقُر لتسجيل الدخول كشخص مجهول"],"Don't have a chat account?":["لا تمتلك حسابًا للمحادثة بعدُ ؟"],"Create an account":["أنشئ حسابًا"],"Create your account":["إنشئ حسابك"],"Please enter the XMPP provider to register with:":["يرجى إدخال مزود خدمة XMPP الذي تود إنشاء حسابك فيه :"],"Already have a chat account?":["عندك حساب مُحادثة ؟"],"Log in here":["قم بتسجيل الدخول هنا"],"Account Registration:":["إنشاء حساب :"],"Register":["تسجيل حساب"],"Choose a different provider":["إختر مزود خدمة آخَر"],"Hold tight, we're fetching the registration form…":["تحلى بالصبر، جارٍ جلب استمارة التسجيل …"],"Download":["تنزيل"],"Download video file":["تنزيل ملف الفيديو"],"Download audio file":["تنزيل ملف صوتي"]}}} \ No newline at end of file +{"domain":"converse","locale_data":{"converse":{"":{"domain":"converse","plural_forms":"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;","lang":"ar"},"Bookmark this groupchat":["إضافة فريق المحادثة هذا إلى الفواصل المرجعية"],"The name for this bookmark:":["تسمية الفاصلة المرجعية :"],"Would you like this groupchat to be automatically joined upon startup?":["هل تريد الإلتحاق آليًا بفريق المحادثة هذا مباشَرةً بعد الإتصال ؟"],"What should your nickname for this groupchat be?":["ما هو الإسم المُستعار الذي تريد استخدامه في فريق المحادثة هذا ؟"],"Save":["حفظ"],"Cancel":["إلغاء"],"Are you sure you want to remove the bookmark \"%1$s\"?":["هل أنت متأكد أنك تريد إزالة الفاصلة المرجعية \"%1$s\" ؟"],"Sorry, something went wrong while trying to save your bookmark.":["المعذرة، لقد طرأ هناك خطأ أثناء محاولة الإحتفاظ بالفواصل المرجعية."],"Leave this groupchat":["مغادرة فريق المحادثة"],"Remove this bookmark":["إزالة هذه الفاصلة المرجعية"],"Unbookmark this groupchat":["تنحية فريق المحادثة مِن الفواصل المرجعية"],"Show more information on this groupchat":["عرض المزيد مِن التفاصيل عن فريق المحادثة هذا"],"Click to open this groupchat":["أنقر لفتح فريق المحادثة هذا"],"Click to toggle the bookmarks list":["أنقر للإنتقال إلى قائمة الإشارات المرجعية"],"Bookmarks":["الفواصل المرجعية"],"Sorry, could not determine file upload URL.":[""],"Sorry, could not determine upload URL.":[""],"Sorry, could not succesfully upload your file. Your server’s response: \"%1$s\"":["للأسف لم نتمكّن مِن القيام برفع ملفك بنجاح. أجاب خادومك : \"%1$s\""],"Sorry, could not succesfully upload your file.":["للأسف لم نتمكّن مِن القيام برفع ملفك بنجاح."],"Sorry, looks like file upload is not supported by your server.":["للأسف يبدو أن خاصية رفع الملفات لا يدعمها خادومكم."],"The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.":[""],"Sorry, an error occurred:":[""],"Close this chat box":["إغلق نافذة المحادثة هذه"],"The User's Profile Image":[""],"Close":["إغلاق"],"Email":[""],"Full Name":[""],"Jabber ID":["مُعرَّف حساب جابر"],"Nickname":["الإسم المُستعار"],"Remove as contact":["إزالة مِن المراسِلين"],"Refresh":[""],"Role":[""],"URL":[""],"Are you sure you want to remove this contact?":["هل أنت متأكد أنك تريد حذف هذا المراسل ؟"],"Error":[""],"Sorry, there was an error while trying to remove %1$s as a contact.":[""],"You have unread messages":["لقد ورَدَت إليك رسائل غير مقروءة"],"Hidden message":["رسالة مخفية"],"Personal message":["رسالة خاصة"],"Send":["إرسل"],"Optional hint":["دليل إختياري"],"Choose a file to send":["إختر الملف الذي تود إرساله"],"Click to write as a normal (non-spoiler) message":[""],"Click to write your message as a spoiler":[""],"Clear all messages":["تنظيف كافة الرسائل"],"Insert emojis":["إدراج وجه مبتسم"],"Start a call":["إبدأ مكالمة"],"Remove messages":["حذف الرسائل"],"Write in the third person":["كتب كأنه شخص ثالث"],"Show this menu":["إظهار هذه القائمة"],"Are you sure you want to clear the messages from this conversation?":["هل أنت متأكد أنك تود مسح الرسائل مِن نافذة المحادثة هذه ؟"],"%1$s has gone offline":["%1$s قد قطع الإتصال"],"%1$s has gone away":["%1$s قد غاب"],"%1$s is busy":["%1$s مشغول"],"%1$s is online":["%1$s متصل"],"Username":["إسم المستخدِم"],"user@domain":["user@domain"],"Please enter a valid XMPP address":["يرجى إدخال عنوان XMPP صالح"],"Chat Contacts":["جهات الإتصال"],"Toggle chat":["الإنتقال إلى الدردشة"],"The connection has dropped, attempting to reconnect.":["لقد إنقطع الإتصال، عملية إعادة الربط جارية."],"An error occurred while connecting to the chat server.":["طرأ هناك خطأ أنثاء الربط بخادوم المحادثة."],"Your Jabber ID and/or password is incorrect. Please try again.":["مُعرِّف جابر الخاص بك أو كلمتك السرية خاطئة. يرجى إعادة المحاولة ثانية."],"Sorry, we could not connect to the XMPP host with domain: %1$s":["عذرا، لم نتمكن مِن الإتصال بخادوم XMPP عبر النطاق : %1$s"],"The XMPP server did not offer a supported authentication mechanism":[""],"Show more":["عرض المزيد"],"Typing from another device":["يكتب عبر جهاز آخَر"],"%1$s is typing":["%1$s يكتب حاليا"],"Stopped typing on the other device":["توقّف عن الكتابة عبر الجهاز الآخَر"],"%1$s has stopped typing":["%1$s توقّفَ عن الكتابة"],"Minimize this chat box":["تصغير نافذة المحادثة هذه"],"Click to restore this chat":["أنقر لاستعادة هذه المحادثة"],"Minimized":["تصغير"],"This groupchat is not anonymous":["فريق المحادثة هذا ليس مجهولًا"],"This groupchat now shows unavailable members":[""],"This groupchat does not show unavailable members":["فريق المحادثة هذا لا يعرض الأعضاء المشغولين"],"The groupchat configuration has changed":["تم تعديل خيارات فريق المحادثة"],"groupchat logging is now enabled":["الإلتحاق بفريق المحادثة مسموح الآن للجميع"],"This groupchat is now no longer anonymous":["لم يَعُد فريق المحادثة مجهولا بعد الآن"],"This groupchat is now semi-anonymous":["أصبح فريق المحادثة مجهولا نسبيًا"],"This groupchat is now fully-anonymous":["أصبح فريق المحادثة الآن مجهولا تمامًا"],"A new groupchat has been created":["تم إنشاء فريق محادثة جديد"],"You have been banned from this groupchat":["لقد تم طردُك مِن فريق المحادثة هذا"],"You have been kicked from this groupchat":["لقد تم طردُك مؤقتًا مِن فريق المحادثة هذا"],"You have been removed from this groupchat because of an affiliation change":[""],"You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member":[""],"You have been removed from this groupchat because the MUC (Multi-user chat) service is being shut down":[""],"%1$s has been banned":["لقد تم طرد %1$s"],"%1$s's nickname has changed":["لقد قام %1$s بتغيير إسمه المُستعار"],"%1$s has been kicked out":["لقد تم طرد %1$s مِن غرفة المحادثة مؤقتًا"],"%1$s has been removed because of an affiliation change":[""],"%1$s has been removed for not being a member":["تمت إزالة %1$s لأنه ليس عضو مُنتم إلى الغرفة"],"Your nickname has been automatically set to %1$s":["لقد تم تغيير إسمك المستعار آليا إلى %1$s"],"Your nickname has been changed to %1$s":["لقد تم تغيير إسمك المُستعار إلى %1$s"],"Description:":["التفاصيل :"],"Groupchat Address (JID):":["عنوان فريق المحادثة (JID) :"],"Participants:":["المشتركون :"],"Features:":["الميزات :"],"Requires authentication":["يتطلّب المصادقة"],"Hidden":["خفية"],"Requires an invitation":["تستلزم دعوة"],"Moderated":["تحت الإشراف"],"Non-anonymous":["غير مجهولة"],"Open":["مفتوحة"],"Permanent":["دائم"],"Public":["عمومية"],"Semi-anonymous":["مجهولة نسبيًا"],"Temporary":["مُؤقّتة"],"Unmoderated":["ليست تحت الإشراف"],"Query for Groupchats":["البحث عن فِرق محادثة"],"Server address":["عنوان الخادوم"],"Show groupchats":["عرض فِرَق المحادثة"],"conference.example.org":["conference.example.org"],"No groupchats found":["لم يتم العثور على أي فريق محادثة"],"groupchats found:":["تم العثور على فِرَق المحادثة :"],"Groupchat address":["عنوان فريق المحادثة"],"Optional nickname":["إسم مستعار اختياري"],"name@conference.example.org":["name@conference.example.org"],"Join":["الإلتحاق بالغرفة"],"Groupchat info for %1$s":[""],"Message":["رسالة"],"%1$s is no longer a moderator":["لم يعُد %1$s مِن المُشْرِفين"],"%1$s has been muted":["تم كتم %1$s"],"%1$s is now a moderator":["أصبح %1$s مُشرفًا"],"Error: the \"%1$s\" command takes two arguments, the user's nickname and optionally a reason.":[""],"Sorry, an error happened while running the command. Check your browser's developer console for details.":[""],"Change user's affiliation to admin":[""],"Change user role to participant":["تغيير دور المستخدِم إلى مُشترِك"],"Write in 3rd person":[""],"Grant membership to a user":["منح صفة العضوية لمستخدِم"],"Remove user's ability to post messages":["منع المستخدم مِن بعث رسائل"],"Change your nickname":["غيّر إسمك المُستعار"],"Grant moderator role to user":["ترقية المستخدِم إلى رتبة مشرف"],"Revoke user's membership":["إسقاط صفة العضوية مِن المستخدِم"],"Set groupchat subject (alias for /subject)":[""],"Allow muted user to post messages":["السماح للمستخدم المكتوم نشر رسائل"],"The nickname you chose is reserved or currently in use, please choose a different one.":["إنّ الإسم المستعار الذي قمت باختياره محجوز أو مُستعمَل حاليا مِن طرف شخص آخَر، يُرجى اختيار إسمٍ آخَر."],"Please choose your nickname":["يرجى اختيار إسمك المُستعار"],"Password: ":["كلمة السر : "],"Submit":["إرسال"],"This action was done by %1$s.":["قام %1$s بهذا الإجراء."],"The reason given is: \"%1$s\".":["السبب : \"%1$s\"."],"No nickname was specified.":["لم تقم باختيار أي إسم مستعار."],"Remote server not found":[""],"Topic set by %1$s":["قام %1$s بتحديد الموضوع"],"Click to mention %1$s in your message.":["أنقر للإشارة إلى %1$s في رسالتك."],"This user is a moderator.":["إنّ هذا المستخدِم مشرف في الغرفة."],"Visitor":[""],"Owner":[""],"Admin":[""],"Participants":[""],"Invite":["دعوة"],"You are about to invite %1$s to the groupchat \"%2$s\". You may optionally include a message, explaining the reason for the invitation.":[""],"Please enter a valid XMPP username":["يُرجى إدخال إسم مستخدِم XMPP صحيح"],"Notification from %1$s":["إشعار مِن %1$s"],"%1$s says":["%1$s قال"],"has gone offline":["قد قطع الإتصال"],"has gone away":["قد غاب"],"is busy":["مشغول"],"has come online":["صار مُتّصلا الآن"],"wants to be your contact":["يُريد أن يُصبح مُراسلك"],"Log in with %1$s":[""],"Your Profile":["ملفك الشخصي"],"Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.":[""],"Your avatar image":[""],"You can check your browser's developer console for any error output.":[""],"Away":["غائب"],"Busy":["مشغول"],"Custom status":["حالتك الخاصة"],"Offline":["غير متصل"],"Online":["مُتّصل"],"Away for long":["غائب لمدة قد تطول"],"Change chat status":["أنقر لتغيير حالة الدردشة"],"Personal status message":["رسالة الحالة الخاصة"],"I am %1$s":["أنا %1$s"],"Change settings":["تغيير الإعدادات"],"Click to change your chat status":["أنقر لتغيير حالتك للدردشة"],"Log out":["الخروج"],"Your profile":["ملفك الشخصي"],"Are you sure you want to log out?":["هل أنت متأكد أنك تريد الخروج ؟"],"online":["متصل"],"busy":["مشغول"],"away for long":["غائب لمدة قد تطول"],"away":["غائب"],"offline":["غير متصل"]," e.g. conversejs.org":[" مثال conversejs.org"],"Fetch registration form":["جارٍ جلب استمارة التسجيل"],"Tip: A list of public XMPP providers is available":[""],"here":["هنا"],"Sorry, we're unable to connect to your chosen provider.":["المعذرة، لم نتمكن بربطك بموفر خدمة المحادثة الذي قمت باختياره."],"Sorry, the given provider does not support in band account registration. Please try with a different provider.":[""],"Something went wrong while establishing a connection with \"%1$s\". Are you sure it exists?":[""],"Now logging you in":["جارٍ تسجيل دخولك الآن"],"Registered successfully":["تم تسجيل حسابك بنجاح"],"The provider rejected your registration attempt. Please check the values you entered for correctness.":[""],"Open Groupchats":[""],"Sorry, there was an error while trying to add %1$s as a contact.":["المعذرة، لقد حدث هناك خطأ أثناء محاولة إضافة %1$s كمُراسِل."],"This client does not allow presence subscriptions":[""],"Click to hide these contacts":["أْنقُر لإخفاء هؤلاء المراسلين"],"This contact is busy":["إنّ المُراسَل مشغول"],"This contact is online":["إنّ هذا المُراسَل غير مُتصل"],"This contact is offline":["هذا المراسل غير متصل"],"This contact is unavailable":["إنّ هذا المراسَل غير متوفر"],"This contact is away for an extended period":["لقد غاب هذا المستخدِم ثانية لفترة أطوَل"],"This contact is away":["إنّ هذا المراسَل غائب"],"Groups":["الفِرَق"],"My contacts":["جهات إتصالي"],"Pending contacts":["المُراسلون المُعلّقون"],"Contact requests":["طلبات التراسل"],"Ungrouped":[""],"Contact name":["إسم المراسل"],"Add a Contact":["إضافة مراسل"],"XMPP Address":["عنوان XMPP"],"name@example.org":["name@example.org"],"Add":["إضافة"],"Filter":["عامل التصفية"],"Filter by contact name":["فرز حسب اسم جهة الاتصال"],"Filter by group name":["فرز حسب اسم المجموعة"],"Filter by status":["تصنيف حسب الحالة"],"Any":["الكل"],"Unread":["غير مقروءة"],"Chatty":["كثيرة الدردشة"],"Extended Away":[""],"Click to remove %1$s as a contact":["أنقر لإزالة %1$s مِن قائمة مراسليك"],"Click to accept the contact request from %1$s":["أنقر لقبول طلب التراسل مع %1$s"],"Click to decline the contact request from %1$s":["أنقر لرفض طلب التراسل مع %1$s"],"Click to chat with %1$s (JID: %2$s)":["أنقر للتحدث مع %1$s (JID : %2$s)"],"Are you sure you want to decline this contact request?":["هل أنت متأكد أنك تود رفض طلب التراسل مع هذا المستخدِم ؟"],"Contacts":["جهات الإتصال"],"Add a contact":["إضافة مراسل"],"Name":[""],"Topic":[""],"Topic author":[""],"Features":["الميزات"],"Password protected":["مؤمَّنة بكلمة سرية"],"Members only":["الأعضاء فقط"],"Persistent":["دائمة"],"Only moderators can see your XMPP username":["بإمكان المشرفين فقط رؤية إسم XMPP الخاص بك"],"Message archiving":["أرشفة الرسائل"],"Messages are archived on the server":["الرسائل محفوظة على الخادوم"],"No password":["بدون كلمة سرية"],"XMPP Username:":["إسم المستخدِم :"],"Password:":["كلمة السر :"],"password":["كلمة السر"],"This is a trusted device":[""],"To improve performance, we cache your data in this browser. Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. It's important that you explicitly log out, otherwise not all cached data might be deleted.":[""],"Click here to log in anonymously":["أُنقُر لتسجيل الدخول كشخص مجهول"],"Don't have a chat account?":["لا تمتلك حسابًا للمحادثة بعدُ ؟"],"Create an account":["أنشئ حسابًا"],"Create your account":["إنشئ حسابك"],"Please enter the XMPP provider to register with:":["يرجى إدخال مزود خدمة XMPP الذي تود إنشاء حسابك فيه :"],"Already have a chat account?":["عندك حساب مُحادثة ؟"],"Log in here":["قم بتسجيل الدخول هنا"],"Account Registration:":["إنشاء حساب :"],"Register":["تسجيل حساب"],"Choose a different provider":["إختر مزود خدمة آخَر"],"Hold tight, we're fetching the registration form…":["تحلى بالصبر، جارٍ جلب استمارة التسجيل …"],"Download":["تنزيل"],"Download video file":["تنزيل ملف الفيديو"],"Download audio file":["تنزيل ملف صوتي"]}}} \ No newline at end of file diff --git a/locale/ar/LC_MESSAGES/converse.po b/locale/ar/LC_MESSAGES/converse.po index fb1b3463d..c15e587a8 100644 --- a/locale/ar/LC_MESSAGES/converse.po +++ b/locale/ar/LC_MESSAGES/converse.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Converse.js 3.3.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-07-22 11:17+0200\n" -"PO-Revision-Date: 2018-07-22 11:49+0200\n" +"PO-Revision-Date: 2018-08-02 05:34+0000\n" "Last-Translator: ButterflyOfFire \n" "Language-Team: Arabic \n" @@ -18,28 +18,25 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" -"X-Generator: Weblate 3.0\n" +"X-Generator: Weblate 3.1.1\n" #: dist/converse-no-dependencies.js:40690 #: dist/converse-no-dependencies.js:40775 #: dist/converse-no-dependencies.js:53689 -#, fuzzy msgid "Bookmark this groupchat" -msgstr "إضافة هذه الغرفة إلى الفواصل المرجعية" +msgstr "إضافة فريق المحادثة هذا إلى الفواصل المرجعية" #: dist/converse-no-dependencies.js:40776 msgid "The name for this bookmark:" msgstr "تسمية الفاصلة المرجعية :" #: dist/converse-no-dependencies.js:40777 -#, fuzzy msgid "Would you like this groupchat to be automatically joined upon startup?" -msgstr "هل تود الإلتحاق بهذه الغرفة آليا مباشَرةً بعد الإتصال ؟" +msgstr "هل تريد الإلتحاق آليًا بفريق المحادثة هذا مباشَرةً بعد الإتصال ؟" #: dist/converse-no-dependencies.js:40778 -#, fuzzy msgid "What should your nickname for this groupchat be?" -msgstr "ما هو الإسم المُستعار الذي تريد استخدامه في غرفة المحادثة هذه ؟" +msgstr "ما هو الإسم المُستعار الذي تريد استخدامه في فريق المحادثة هذا ؟" #: dist/converse-no-dependencies.js:40780 #: dist/converse-no-dependencies.js:49483 @@ -66,9 +63,8 @@ msgstr "المعذرة، لقد طرأ هناك خطأ أثناء محاولة #: dist/converse-no-dependencies.js:41055 #: dist/converse-no-dependencies.js:53687 -#, fuzzy msgid "Leave this groupchat" -msgstr "الخروج مِن هذه الغرفة" +msgstr "مغادرة فريق المحادثة" #: dist/converse-no-dependencies.js:41056 msgid "Remove this bookmark" @@ -76,23 +72,20 @@ msgstr "إزالة هذه الفاصلة المرجعية" #: dist/converse-no-dependencies.js:41057 #: dist/converse-no-dependencies.js:53688 -#, fuzzy msgid "Unbookmark this groupchat" -msgstr "تنحية غرفة المحادثة مِن الفواصل المرجعية" +msgstr "تنحية فريق المحادثة مِن الفواصل المرجعية" #: dist/converse-no-dependencies.js:41058 #: dist/converse-no-dependencies.js:48755 #: dist/converse-no-dependencies.js:53690 -#, fuzzy msgid "Show more information on this groupchat" -msgstr "عرض المزيد مِن التفاصيل عن هذه الغرفة" +msgstr "عرض المزيد مِن التفاصيل عن فريق المحادثة هذا" #: dist/converse-no-dependencies.js:41061 #: dist/converse-no-dependencies.js:48754 #: dist/converse-no-dependencies.js:53692 -#, fuzzy msgid "Click to open this groupchat" -msgstr "أنقر لفتح غرفة المحادثة هذه" +msgstr "أنقر لفتح فريق المحادثة هذا" #: dist/converse-no-dependencies.js:41097 msgid "Click to toggle the bookmarks list" @@ -111,11 +104,11 @@ msgid "Sorry, could not determine upload URL." msgstr "" #: dist/converse-no-dependencies.js:41573 -#, fuzzy, javascript-format +#, javascript-format msgid "" "Sorry, could not succesfully upload your file. Your server’s response: \"%1$s" "\"" -msgstr "للأسف لم نتمكّن مِن القيام برفع ملفك بنجاح." +msgstr "للأسف لم نتمكّن مِن القيام برفع ملفك بنجاح. أجاب خادومك : \"%1$s\"" #: dist/converse-no-dependencies.js:41575 msgid "Sorry, could not succesfully upload your file." @@ -163,9 +156,8 @@ msgid "Full Name" msgstr "" #: dist/converse-no-dependencies.js:42572 -#, fuzzy msgid "Jabber ID" -msgstr "مُعرَّف حساب جابر :" +msgstr "مُعرَّف حساب جابر" #: dist/converse-no-dependencies.js:42573 #: dist/converse-no-dependencies.js:49639 @@ -174,9 +166,8 @@ msgid "Nickname" msgstr "الإسم المُستعار" #: dist/converse-no-dependencies.js:42574 -#, fuzzy msgid "Remove as contact" -msgstr "إضافة مراسل" +msgstr "إزالة مِن المراسِلين" #: dist/converse-no-dependencies.js:42575 msgid "Refresh" @@ -248,7 +239,6 @@ msgid "Clear all messages" msgstr "تنظيف كافة الرسائل" #: dist/converse-no-dependencies.js:42815 -#, fuzzy msgid "Insert emojis" msgstr "إدراج وجه مبتسم" @@ -275,25 +265,25 @@ msgid "Are you sure you want to clear the messages from this conversation?" msgstr "هل أنت متأكد أنك تود مسح الرسائل مِن نافذة المحادثة هذه ؟" #: dist/converse-no-dependencies.js:43413 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has gone offline" -msgstr "قد قطع الإتصال" +msgstr "%1$s قد قطع الإتصال" #: dist/converse-no-dependencies.js:43415 #: dist/converse-no-dependencies.js:47662 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has gone away" -msgstr "قد غاب" +msgstr "%1$s قد غاب" #: dist/converse-no-dependencies.js:43417 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is busy" -msgstr "مشغول" +msgstr "%1$s مشغول" #: dist/converse-no-dependencies.js:43419 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is online" -msgstr "متصل" +msgstr "%1$s متصل" #: dist/converse-no-dependencies.js:44042 msgid "Username" @@ -346,18 +336,18 @@ msgid "Typing from another device" msgstr "يكتب عبر جهاز آخَر" #: dist/converse-no-dependencies.js:47653 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is typing" -msgstr "يكتب حاليا" +msgstr "%1$s يكتب حاليا" #: dist/converse-no-dependencies.js:47657 msgid "Stopped typing on the other device" msgstr "توقّف عن الكتابة عبر الجهاز الآخَر" #: dist/converse-no-dependencies.js:47659 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has stopped typing" -msgstr "توقّفَ عن الكتابة" +msgstr "%1$s توقّفَ عن الكتابة" #: dist/converse-no-dependencies.js:47905 #: dist/converse-no-dependencies.js:47948 @@ -373,29 +363,24 @@ msgid "Minimized" msgstr "تصغير" #: dist/converse-no-dependencies.js:48597 -#, fuzzy msgid "This groupchat is not anonymous" -msgstr "غرفة المحادثة هذه ليست مجهولة" +msgstr "فريق المحادثة هذا ليس مجهولًا" #: dist/converse-no-dependencies.js:48598 -#, fuzzy msgid "This groupchat now shows unavailable members" -msgstr "هذه القاعة لا تقوم بعرض الأعضاء المشغولين" +msgstr "" #: dist/converse-no-dependencies.js:48599 -#, fuzzy msgid "This groupchat does not show unavailable members" -msgstr "هذه القاعة لا تقوم بعرض الأعضاء المشغولين" +msgstr "فريق المحادثة هذا لا يعرض الأعضاء المشغولين" #: dist/converse-no-dependencies.js:48600 -#, fuzzy msgid "The groupchat configuration has changed" -msgstr "تم تعديل إعدادات غرفة المحادثة" +msgstr "تم تعديل خيارات فريق المحادثة" #: dist/converse-no-dependencies.js:48601 -#, fuzzy msgid "groupchat logging is now enabled" -msgstr "الإلتحاق بالغرفة مسموح للجميع الآن" +msgstr "الإلتحاق بفريق المحادثة مسموح الآن للجميع" #: dist/converse-no-dependencies.js:48602 #, fuzzy @@ -403,34 +388,28 @@ msgid "groupchat logging is now disabled" msgstr "مُنِع الآن الإلتحاق بغرفة المحادثة" #: dist/converse-no-dependencies.js:48603 -#, fuzzy msgid "This groupchat is now no longer anonymous" -msgstr "لم تَعُد غرفة المحادثة مجهولة الآن" +msgstr "لم يَعُد فريق المحادثة مجهولا بعد الآن" #: dist/converse-no-dependencies.js:48604 -#, fuzzy msgid "This groupchat is now semi-anonymous" -msgstr "أصبحت غرفة المحادثة مجهولة نسبيًا" +msgstr "أصبح فريق المحادثة مجهولا نسبيًا" #: dist/converse-no-dependencies.js:48605 -#, fuzzy msgid "This groupchat is now fully-anonymous" -msgstr "أصبحت غرفة المحادثة الآن مجهولة تمامًا" +msgstr "أصبح فريق المحادثة الآن مجهولا تمامًا" #: dist/converse-no-dependencies.js:48606 -#, fuzzy msgid "A new groupchat has been created" -msgstr "تم إنشاء غرفة محادثة جديدة" +msgstr "تم إنشاء فريق محادثة جديد" #: dist/converse-no-dependencies.js:48609 -#, fuzzy msgid "You have been banned from this groupchat" -msgstr "لقد تم طردُك مِن غرفة المحادثة هذه" +msgstr "لقد تم طردُك مِن فريق المحادثة هذا" #: dist/converse-no-dependencies.js:48610 -#, fuzzy msgid "You have been kicked from this groupchat" -msgstr "لقد تم طردُك مؤقتًا مِن غرفة المحادثة هذه" +msgstr "لقد تم طردُك مؤقتًا مِن فريق المحادثة هذا" #: dist/converse-no-dependencies.js:48611 msgid "" @@ -499,14 +478,12 @@ msgid "Description:" msgstr "التفاصيل :" #: dist/converse-no-dependencies.js:48666 -#, fuzzy msgid "Groupchat Address (JID):" -msgstr "عنوان غرفة المحادثة (JID) :" +msgstr "عنوان فريق المحادثة (JID) :" #: dist/converse-no-dependencies.js:48667 -#, fuzzy msgid "Participants:" -msgstr "المستخدِمون المُقِيمون :" +msgstr "المشتركون :" #: dist/converse-no-dependencies.js:48668 msgid "Features:" @@ -543,9 +520,8 @@ msgid "Open" msgstr "مفتوحة" #: dist/converse-no-dependencies.js:48675 -#, fuzzy msgid "Permanent" -msgstr "غرفة محادثة دائمة" +msgstr "دائم" #: dist/converse-no-dependencies.js:48676 #: dist/converse-no-dependencies.js:57167 @@ -570,32 +546,28 @@ msgid "Unmoderated" msgstr "ليست تحت الإشراف" #: dist/converse-no-dependencies.js:48715 -#, fuzzy msgid "Query for Groupchats" -msgstr "البحث عن قاعات" +msgstr "البحث عن فِرق محادثة" #: dist/converse-no-dependencies.js:48716 msgid "Server address" msgstr "عنوان الخادوم" #: dist/converse-no-dependencies.js:48717 -#, fuzzy msgid "Show groupchats" -msgstr "عرض غُرف المحادثة" +msgstr "عرض فِرَق المحادثة" #: dist/converse-no-dependencies.js:48718 msgid "conference.example.org" msgstr "conference.example.org" #: dist/converse-no-dependencies.js:48767 -#, fuzzy msgid "No groupchats found" -msgstr "لم يتم العثور على أية غُرفة محادثة" +msgstr "لم يتم العثور على أي فريق محادثة" #: dist/converse-no-dependencies.js:48784 -#, fuzzy msgid "groupchats found:" -msgstr "تم العثور على غرف المحادثة :" +msgstr "تم العثور على فِرَق المحادثة :" #: dist/converse-no-dependencies.js:48836 #, fuzzy @@ -603,9 +575,8 @@ msgid "Enter a new Groupchat" msgstr "الدخول إلى غرفة محادثة جديدة" #: dist/converse-no-dependencies.js:48837 -#, fuzzy msgid "Groupchat address" -msgstr "عنوان غرفة المحادثة" +msgstr "عنوان فريق المحادثة" #: dist/converse-no-dependencies.js:48838 #: dist/converse-no-dependencies.js:55005 @@ -621,18 +592,18 @@ msgid "Join" msgstr "الإلتحاق بالغرفة" #: dist/converse-no-dependencies.js:48884 -#, fuzzy, javascript-format +#, javascript-format msgid "Groupchat info for %1$s" -msgstr "إشعار مِن %1$s" +msgstr "" #: dist/converse-no-dependencies.js:48990 msgid "Message" msgstr "رسالة" #: dist/converse-no-dependencies.js:49036 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is no longer a moderator" -msgstr "لم يعُد %1$s مِن مُشْرِفي غرفة المحادثة" +msgstr "لم يعُد %1$s مِن المُشْرِفين" #: dist/converse-no-dependencies.js:49040 #, fuzzy, javascript-format diff --git a/locale/de/LC_MESSAGES/converse.json b/locale/de/LC_MESSAGES/converse.json index 9fef57d98..f446dd186 100644 --- a/locale/de/LC_MESSAGES/converse.json +++ b/locale/de/LC_MESSAGES/converse.json @@ -1 +1 @@ -{"domain":"converse","locale_data":{"converse":{"":{"domain":"converse","plural_forms":"nplurals=2; plural=n != 1;","lang":"de"},"The name for this bookmark:":["Name des Lesezeichens:"],"Save":["Speichern"],"Cancel":["Abbrechen"],"Are you sure you want to remove the bookmark \"%1$s\"?":["Möchten Sie das Lesezeichen „%1$s” wirklich löschen?"],"Sorry, something went wrong while trying to save your bookmark.":["Leider konnte das Lesezeichen nicht gespeichert werden."],"Remove this bookmark":["Dieses Lesezeichen entfernen"],"Click to toggle the bookmarks list":["Liste der Lesezeichen umschalten"],"Bookmarks":["Lesezeichen"],"Sorry, could not determine file upload URL.":[""],"Sorry, could not determine upload URL.":[""],"Sorry, could not succesfully upload your file. Your server’s response: \"%1$s\"":[""],"Sorry, could not succesfully upload your file.":[""],"Sorry, looks like file upload is not supported by your server.":[""],"The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.":[""],"Sorry, an error occurred:":[""],"Close this chat box":["Dieses Chat-Fenster schließen"],"The User's Profile Image":[""],"Close":[""],"Email":[""],"Nickname":["Spitzname"],"Refresh":[""],"Role":[""],"URL":[""],"Are you sure you want to remove this contact?":["Möchten Sie diesen Kontakt wirklich entfernen?"],"Error":["Fehler"],"Sorry, there was an error while trying to remove %1$s as a contact.":["Leider gab es einen Fehler beim Versuch, %1$s als Kontakt zu entfernen."],"You have unread messages":["Sie haben ungelesene Nachrichten"],"Hidden message":["Versteckte Nachricht"],"Personal message":["Persönliche Nachricht"],"Send":["Senden"],"Optional hint":["Optionaler Hinweis"],"Choose a file to send":["Datei versenden"],"Click to write as a normal (non-spoiler) message":["Hier klicken, um Statusnachricht zu ändern (ohne Spoiler)"],"Clear all messages":["Alle Nachrichten löschen"],"Start a call":["Beginne eine Unterhaltung"],"Remove messages":["Nachrichten entfernen"],"Write in the third person":["In der dritten Person schreiben"],"Show this menu":["Dieses Menü anzeigen"],"Username":["Benutzername"],"user@domain":["user@domain"],"Please enter a valid XMPP address":["Bitte eine gültige XMPP/Jabber-ID eingeben"],"Toggle chat":["Chat ein-/ausblenden"],"The connection has dropped, attempting to reconnect.":["Die Verbindung ist abgebrochen und es wird versucht, die Verbindung wiederherzustellen."],"An error occurred while connecting to the chat server.":["Beim Verbinden mit dem Chatserver ist ein Fehler aufgetreten."],"Your Jabber ID and/or password is incorrect. Please try again.":["Ihre Jabber-ID und/oder Ihr Passwort ist falsch. Bitte versuchen Sie es erneut."],"Sorry, we could not connect to the XMPP host with domain: %1$s":["Leider konnten wir keine Verbindung zum XMPP-Host mit der Domain „%1$s” herstellen"],"The XMPP server did not offer a supported authentication mechanism":["Der XMPP-Server hat keinen unterstützten Authentifizierungsmechanismus angeboten"],"Show more":["Mehr anzeigen"],"Typing from another device":["Schreibt von einem anderen Gerät"],"Stopped typing on the other device":["Schreibt nicht mehr auf dem anderen Gerät"],"Minimize this chat box":["Minimiere dieses Gesprächsfenster"],"Click to restore this chat":["Hier klicken, um diesen Chat wiederherzustellen"],"Minimized":["Minimiert"],"%1$s has been banned":["%1$s wurde verbannt"],"%1$s's nickname has changed":["Der Spitzname von %1$s hat sich geändert"],"%1$s has been kicked out":["%1$s wurde hinausgeworfen"],"%1$s has been removed because of an affiliation change":["%1$s wurde wegen einer Zugehörigkeitsänderung entfernt"],"%1$s has been removed for not being a member":["%1$s ist kein Mitglied und wurde daher entfernt"],"Your nickname has been automatically set to %1$s":["Ihr Spitzname wurde automatisch geändert zu: %1$s"],"Your nickname has been changed to %1$s":["Ihr Spitzname wurde geändert zu: %1$s"],"Description:":["Beschreibung:"],"Features:":["Funktionen:"],"Requires authentication":["Authentifizierung erforderlich"],"Hidden":["Ausblenden"],"Requires an invitation":["Einladung erforderlich"],"Moderated":["Moderiert"],"Non-anonymous":["Nicht anonym"],"Open":["Offen"],"Public":["Öffentlich"],"Semi-anonymous":["Teilweise anonym"],"Temporary":["Vorübergehend"],"Unmoderated":["Nicht moderiert"],"name@conference.example.org":[""],"Message":["Nachricht"],"Error: the \"%1$s\" command takes two arguments, the user's nickname and optionally a reason.":["Fehler: Das „%1$s”-Kommando benötigt zwei Argumente: Den Benutzernamen und einen Grund."],"Sorry, an error happened while running the command. Check your browser's developer console for details.":[""],"Change user's affiliation to admin":["Zugehörigkeit des Benutzers zu Administrator ändern"],"Change user role to participant":["Rolle zu Teilnehmer ändern"],"Write in 3rd person":["In der dritten Person schreiben"],"Grant membership to a user":["Einem Benutzer die Mitgliedschaft gewähren"],"Remove user's ability to post messages":["Die Möglichkeit des Benutzers, Nachrichten zu senden, entfernen"],"Change your nickname":["Eigenen Spitznamen ändern"],"Grant moderator role to user":["Benutzer Moderatorrechte gewähren"],"Revoke user's membership":["Mitgliedschaft des Benutzers widerrufen"],"Allow muted user to post messages":["Stummgeschaltetem Benutzer erlauben Nachrichten zu senden"],"The nickname you chose is reserved or currently in use, please choose a different one.":["Der gewählte Spitzname ist reserviert oder derzeit in Gebrauch. Bitte wähle einen Anderen."],"Please choose your nickname":["Wählen Sie Ihren Spitznamen"],"Password: ":["Passwort: "],"Submit":["Senden"],"This action was done by %1$s.":["Diese Aktion wurde durch %1$s ausgeführt."],"The reason given is: \"%1$s\".":["Angegebene Grund: „%1$s”"],"No nickname was specified.":["Kein Spitzname festgelegt."],"Remote server not found":[""],"Click to mention %1$s in your message.":["Klicken Sie hier, um %1$s in Ihrer Nachricht zu erwähnen."],"This user is a moderator.":["Dieser Benutzer ist ein Moderator."],"Visitor":[""],"Owner":[""],"Admin":[""],"Participants":[""],"Invite":["Einladen"],"Please enter a valid XMPP username":["Bitte eine gültige XMPP/Jabber-ID angeben"],"Notification from %1$s":["Benachrichtigung von %1$s"],"%1$s says":["%1$s sagt"],"has gone offline":["hat sich abgemeldet"],"has gone away":["ist jetzt abwesend"],"is busy":["ist beschäftigt"],"has come online":["kam online"],"wants to be your contact":["möchte Ihr Kontakt sein"],"Log in with %1$s":[""],"Your Profile":[""],"Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.":[""],"Your avatar image":[""],"You can check your browser's developer console for any error output.":[""],"Away":["Abwesend"],"Busy":["Beschäftigt"],"Custom status":["Statusnachricht"],"Offline":["Abgemeldet"],"Online":["Online"],"I am %1$s":["Ich bin %1$s"],"Change settings":[""],"Click to change your chat status":["Hier klicken, um Ihren Status zu ändern"],"Log out":["Abmelden"],"Your profile":[""],"online":["online"],"busy":["beschäftigt"],"away for long":["länger abwesend"],"away":["abwesend"],"offline":["abgemeldet"]," e.g. conversejs.org":[" z. B. conversejs.org"],"Fetch registration form":["Anmeldeformular wird abgerufen"],"Tip: A list of public XMPP providers is available":["Tipp: Eine Liste öffentlicher Provider ist verfügbar"],"here":["hier"],"Sorry, we're unable to connect to your chosen provider.":["Leider können wir keine Verbindung zu dem von Ihnen gewählten Provider herstellen."],"Sorry, the given provider does not support in band account registration. Please try with a different provider.":["Entschuldigung: Dieser Provider erlaubt keine direkte Benutzer- Registrierung. Versuchen Sie einen anderen Provider oder erstellen Sie einen Zugang beim Provider direkt."],"Something went wrong while establishing a connection with \"%1$s\". Are you sure it exists?":["Die Verbindung zu „%1$s” konnte nicht hergestellt werden. Sind Sie sicher, dass „%1$s” existiert?"],"Now logging you in":["Sie werden angemeldet"],"Registered successfully":["Registrierung erfolgreich"],"The provider rejected your registration attempt. Please check the values you entered for correctness.":["Der Provider hat die Registrierung abgelehnt. Bitte überprüfen Sie Ihre Angaben auf Richtigkeit."],"Open Groupchats":[""],"Sorry, there was an error while trying to add %1$s as a contact.":["Leider gab es einen Fehler beim Versuch, %1$s als Kontakt hinzuzufügen."],"This client does not allow presence subscriptions":["Dieser Client erlaubt keine Anwesenheitsabonnements"],"Click to hide these contacts":["Hier klicken, um diese Kontakte auszublenden"],"This contact is busy":["Dieser Kontakt ist beschäftigt"],"This contact is online":["Dieser Kontakt ist online"],"This contact is offline":["Dieser Kontakt ist offline"],"This contact is unavailable":["Dieser Kontakt ist nicht verfügbar"],"This contact is away for an extended period":["Dieser Kontakt ist für längere Zeit abwesend"],"This contact is away":["Dieser Kontakt ist abwesend"],"Groups":["Gruppen"],"My contacts":["Meine Kontakte"],"Pending contacts":["Unbestätigte Kontakte"],"Contact requests":["Kontaktanfragen"],"Ungrouped":["Ungruppiert"],"Contact name":["Name des Kontakts"],"XMPP Address":[""],"Add":["Hinzufügen"],"Filter":["Filter"],"Filter by group name":[""],"Filter by status":[""],"Any":["Jeder"],"Unread":["Ungelesen"],"Chatty":["Gesprächsbereit"],"Extended Away":["Länger nicht anwesend"],"Click to remove %1$s as a contact":["Hier klicken, um %1$s als Kontakt zu entfernen"],"Click to accept the contact request from %1$s":["Hier klicken, um die Kontaktanfrage von %1$s zu akzeptieren"],"Click to decline the contact request from %1$s":["Hier klicken, um die Kontaktanfrage von %1$s abzulehnen"],"Are you sure you want to decline this contact request?":["Möchten Sie diese Kontaktanfrage wirklich ablehnen?"],"Contacts":["Kontakte"],"Add a contact":["Kontakt hinzufügen"],"Topic":[""],"Topic author":[""],"Features":["Funktionen"],"Password protected":["Passwortgeschützt"],"Members only":["Nur Mitglieder"],"Persistent":["Dauerhaft"],"Only moderators can see your XMPP username":["Nur Moderatoren können deine XMPP/Jabber-ID sehen"],"Message archiving":["Nachrichtenarchivierung"],"Messages are archived on the server":["Nachrichten werden auf dem Server archiviert"],"No password":["Kein Passwort"],"XMPP Username:":["XMPP Benutzername:"],"Password:":["Passwort:"],"password":["passwort"],"This is a trusted device":[""],"To improve performance, we cache your data in this browser. Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. It's important that you explicitly log out, otherwise not all cached data might be deleted.":[""],"Click here to log in anonymously":["Hier klicken, um sich anonym anzumelden"],"Don't have a chat account?":["Sie haben noch kein Chat-Konto?"],"Create an account":["Konto erstellen"],"Create your account":["Erstellen Sie Ihr Konto"],"Please enter the XMPP provider to register with:":["Bitte geben Sie den XMPP-Provider ein, bei dem Sie sich anmelden möchten:"],"Already have a chat account?":["Sie haben bereits ein Chat-Konto?"],"Log in here":["Hier anmelden"],"Account Registration:":["Konto-Registrierung:"],"Register":["Registrierung"],"Choose a different provider":["Wählen Sie einen anderen Anbieter"],"Hold tight, we're fetching the registration form…":["Bitte warten, das Anmeldeformular wird geladen …"],"Download":["Herunterladen"],"Download video file":["Video Datei Herunterladen"],"Download audio file":[""]}}} \ No newline at end of file +{"domain":"converse","locale_data":{"converse":{"":{"domain":"converse","plural_forms":"nplurals=2; plural=n != 1;","lang":"de"},"Bookmark this groupchat":["Diesen Raum als Lesezeichen speichern"],"The name for this bookmark:":["Name des Lesezeichens:"],"Would you like this groupchat to be automatically joined upon startup?":["Beim Anmelden diesen Raum automatisch betreten?"],"What should your nickname for this groupchat be?":["Welcher Spitzname soll in diesem Raum verwendet werden?"],"Save":["Speichern"],"Cancel":["Abbrechen"],"Are you sure you want to remove the bookmark \"%1$s\"?":["Möchten Sie das Lesezeichen „%1$s” wirklich löschen?"],"Sorry, something went wrong while trying to save your bookmark.":["Leider konnte das Lesezeichen nicht gespeichert werden."],"Leave this groupchat":["Diesen Raum verlassen"],"Remove this bookmark":["Dieses Lesezeichen entfernen"],"Unbookmark this groupchat":["Lesezeichen für diesen Raum entfernen"],"Show more information on this groupchat":["Zeige mehr Informationen über diesen Raum"],"Click to open this groupchat":["Hier klicken, um diesen Raum zu öffnen"],"Click to toggle the bookmarks list":["Liste der Lesezeichen umschalten"],"Bookmarks":["Lesezeichen"],"Sorry, could not determine file upload URL.":["Die URL für das Hochladen der Datei konnte nicht ermittelt werden."],"Sorry, could not determine upload URL.":["Die URL für das Hochladen der Datei konnte nicht ermittelt werden."],"Sorry, could not succesfully upload your file. Your server’s response: \"%1$s\"":["Die Datei konnte nicht hochgeladen werden. Der Server antwortete: \"%1$s1\""],"Sorry, could not succesfully upload your file.":["Konnte die Datei leider nicht erfolgreich hochladen."],"Sorry, looks like file upload is not supported by your server.":["Scheint als würde das Hochladen von Dateien auf dem Server nicht unterstützt."],"The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.":["Die Größe deiner Datei, %1$s1, überschreitet das erlaubte Maximum vom Server, welches bei %2$s2 liegt."],"Sorry, an error occurred:":["Es ist leider ein Fehler aufgetreten:"],"Close this chat box":["Dieses Chat-Fenster schließen"],"The User's Profile Image":["Benutzerprofilbild"],"Close":["Schließen"],"Email":["E-Mail"],"Full Name":["Name"],"Jabber ID":["Jabber-ID"],"Nickname":["Spitzname"],"Remove as contact":["Kontakt entfernen"],"Refresh":["Aktualisieren"],"Role":["Rolle"],"URL":["URL"],"Are you sure you want to remove this contact?":["Möchten Sie diesen Kontakt wirklich entfernen?"],"Error":["Fehler"],"Sorry, there was an error while trying to remove %1$s as a contact.":["Leider gab es einen Fehler beim Versuch, %1$s als Kontakt zu entfernen."],"You have unread messages":["Sie haben ungelesene Nachrichten"],"Hidden message":["Versteckte Nachricht"],"Personal message":["Persönliche Nachricht"],"Send":["Senden"],"Optional hint":["Optionaler Hinweis"],"Choose a file to send":["Datei versenden"],"Click to write as a normal (non-spoiler) message":["Hier klicken, um Statusnachricht zu ändern (ohne Spoiler)"],"Click to write your message as a spoiler":["Hier klicken, um die Nachricht als Spoiler zu kennzeichnen"],"Clear all messages":["Alle Nachrichten löschen"],"Insert emojis":["Emojis einfügen"],"Start a call":["Beginne eine Unterhaltung"],"Remove messages":["Nachrichten entfernen"],"Write in the third person":["In der dritten Person schreiben"],"Show this menu":["Dieses Menü anzeigen"],"Are you sure you want to clear the messages from this conversation?":["Sind Sie sicher, dass Sie alle Nachrichten dieses Chats löschen möchten?"],"%1$s has gone offline":["%1$s1 hat sich abgemeldet"],"%1$s has gone away":["%1$s1 ist jetzt abwesend"],"%1$s is busy":["%1$s1 ist beschäftigt"],"%1$s is online":["%1$s1 ist jetzt online"],"Username":["Benutzername"],"user@domain":["user@domain"],"Please enter a valid XMPP address":["Bitte eine gültige XMPP/Jabber-ID eingeben"],"Chat Contacts":["Kontakte"],"Toggle chat":["Chat ein-/ausblenden"],"The connection has dropped, attempting to reconnect.":["Die Verbindung ist abgebrochen und es wird versucht, die Verbindung wiederherzustellen."],"An error occurred while connecting to the chat server.":["Beim Verbinden mit dem Chatserver ist ein Fehler aufgetreten."],"Your Jabber ID and/or password is incorrect. Please try again.":["Ihre Jabber-ID und/oder Ihr Passwort ist falsch. Bitte versuchen Sie es erneut."],"Sorry, we could not connect to the XMPP host with domain: %1$s":["Leider konnten wir keine Verbindung zum XMPP-Host mit der Domain „%1$s” herstellen"],"The XMPP server did not offer a supported authentication mechanism":["Der XMPP-Server hat keinen unterstützten Authentifizierungsmechanismus angeboten"],"Show more":["Mehr anzeigen"],"Typing from another device":["Schreibt von einem anderen Gerät"],"%1$s is typing":["%1$s schreibt …"],"Stopped typing on the other device":["Schreibt nicht mehr auf dem anderen Gerät"],"%1$s has stopped typing":["%1$s1 tippt nicht mehr"],"Minimize this chat box":["Minimiere dieses Gesprächsfenster"],"Click to restore this chat":["Hier klicken, um diesen Chat wiederherzustellen"],"Minimized":["Minimiert"],"This groupchat is not anonymous":["Dieser Raum ist nicht anonym"],"This groupchat now shows unavailable members":["Dieser Raum zeigt jetzt nicht verfügbare Mitglieder an"],"This groupchat does not show unavailable members":["In diesem Raum werden keine nicht verfügbaren Mitglieder angezeigt"],"The groupchat configuration has changed":["Die Raumkonfiguration hat sich geändert"],"groupchat logging is now enabled":["Nachrichten in diesem Raum werden ab jetzt protokolliert"],"groupchat logging is now disabled":["Nachrichten in diesem Raum werden nicht mehr protokolliert"],"This groupchat is now no longer anonymous":["Dieses Raum ist jetzt nicht mehr anonym"],"This groupchat is now semi-anonymous":["Dieser Raum ist jetzt nur teilweise anonym"],"This groupchat is now fully-anonymous":["Dieser Raum ist jetzt vollständig anonym"],"A new groupchat has been created":["Ein neuer Raum wurde erstellt"],"You have been banned from this groupchat":["Sie wurden aus diesem Raum verbannt"],"You have been kicked from this groupchat":["Sie wurden aus diesem Raum hinausgeworfen"],"You have been removed from this groupchat because of an affiliation change":["Sie wurden wegen einer Zugehörigkeitsänderung entfernt"],"You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member":["Sie wurden aus diesem Raum ausgeschlossen, da der Raum jetzt nur noch Mitglieder erlaubt und Sie kein Mitglied sind"],"You have been removed from this groupchat because the MUC (Multi-user chat) service is being shut down":["Sie wurden aus diesem Raum entfernt, weil der MUC-Dienst (Multi-User-Chat) heruntergefahren wird"],"%1$s has been banned":["%1$s wurde verbannt"],"%1$s's nickname has changed":["Der Spitzname von %1$s hat sich geändert"],"%1$s has been kicked out":["%1$s wurde hinausgeworfen"],"%1$s has been removed because of an affiliation change":["%1$s wurde wegen einer Zugehörigkeitsänderung entfernt"],"%1$s has been removed for not being a member":["%1$s ist kein Mitglied und wurde daher entfernt"],"Your nickname has been automatically set to %1$s":["Ihr Spitzname wurde automatisch geändert zu: %1$s"],"Your nickname has been changed to %1$s":["Ihr Spitzname wurde geändert zu: %1$s"],"Description:":["Beschreibung:"],"Groupchat Address (JID):":["XMPP/Jabber-ID (JID) dieses Raumes:"],"Participants:":["Teilnehmer:"],"Features:":["Funktionen:"],"Requires authentication":["Authentifizierung erforderlich"],"Hidden":["Ausblenden"],"Requires an invitation":["Einladung erforderlich"],"Moderated":["Moderiert"],"Non-anonymous":["Nicht anonym"],"Open":["Offen"],"Permanent":["Dauerhafter Raum"],"Public":["Öffentlich"],"Semi-anonymous":["Teilweise anonym"],"Temporary":["Vorübergehend"],"Unmoderated":["Nicht moderiert"],"Query for Groupchats":["Benutzer aus dem Raum verbannen"],"Server address":["Server"],"Show groupchats":["Gruppen"],"conference.example.org":["z.B. conference.example.tld"],"No groupchats found":["Keine Räume gefunden"],"groupchats found:":["Raum gefunden:"],"Enter a new Groupchat":["Raum betreten"],"Groupchat address":["XMPP/Jabber-ID (JID) dieses Raumes"],"Optional nickname":["Optionaler Spitzname"],"name@conference.example.org":["name@conference.beispiel.tld"],"Join":["Betreten"],"Groupchat info for %1$s":["Benachrichtigung für %1$s"],"Message":["Nachricht"],"%1$s is no longer a moderator":["%1$s ist kein Moderator mehr"],"%1$s has been given a voice again":["%1$s darf nun wieder schreiben"],"%1$s has been muted":["%1$s wurde das Schreibrecht entzogen"],"%1$s is now a moderator":["%1$s ist jetzt ein Moderator"],"Close and leave this groupchat":["Schließen und diesen Raum verlassen"],"Configure this groupchat":["Einstellungsänderungen an diesem Raum vornehmen"],"Show more details about this groupchat":["Mehr Information über diesen Raum anzeigen"],"Hide the list of participants":["Teilnehmerliste ausblenden"],"Error: the \"%1$s\" command takes two arguments, the user's nickname and optionally a reason.":["Fehler: Das „%1$s”-Kommando benötigt zwei Argumente: Den Benutzernamen und einen Grund."],"Sorry, an error happened while running the command. Check your browser's developer console for details.":["Leider ist ein Fehler während dem Ausführen des Kommandos aufgetreten. Überprüfe die Entwicklerkonsole des Browsers."],"Change user's affiliation to admin":["Zugehörigkeit des Benutzers zu Administrator ändern"],"Ban user from groupchat":["Benutzer aus dem Raum verbannen"],"Change user role to participant":["Rolle zu Teilnehmer ändern"],"Kick user from groupchat":["Benutzer aus dem Raum hinauswerfen"],"Write in 3rd person":["In der dritten Person schreiben"],"Grant membership to a user":["Einem Benutzer die Mitgliedschaft gewähren"],"Remove user's ability to post messages":["Die Möglichkeit des Benutzers, Nachrichten zu senden, entfernen"],"Change your nickname":["Eigenen Spitznamen ändern"],"Grant moderator role to user":["Benutzer Moderatorrechte gewähren"],"Grant ownership of this groupchat":["Besitzrechte an diesem Raum vergeben"],"Revoke user's membership":["Mitgliedschaft des Benutzers widerrufen"],"Set groupchat subject":["Thema des Chatraums festlegen"],"Set groupchat subject (alias for /subject)":["Raumthema (alias für /subject) festlegen"],"Allow muted user to post messages":["Stummgeschaltetem Benutzer erlauben Nachrichten zu senden"],"The nickname you chose is reserved or currently in use, please choose a different one.":["Der gewählte Spitzname ist reserviert oder derzeit in Gebrauch. Bitte wähle einen Anderen."],"Please choose your nickname":["Wählen Sie Ihren Spitznamen"],"Enter groupchat":["Raum betreten"],"This groupchat requires a password":["Dieser Raum erfordert ein Passwort"],"Password: ":["Passwort: "],"Submit":["Senden"],"This action was done by %1$s.":["Diese Aktion wurde durch %1$s ausgeführt."],"The reason given is: \"%1$s\".":["Angegebene Grund: „%1$s”"],"%1$s has left and re-entered the groupchat":["%1$s hat den Raum erneut betreten"],"%1$s has entered the groupchat":["%1$s hat den Raum betreten"],"%1$s has entered the groupchat. \"%2$s\"":["%1$s hat den Raum betreten. „%2$s”"],"%1$s has entered and left the groupchat":["%1$s hat den Raum wieder verlassen"],"%1$s has entered and left the groupchat. \"%2$s\"":["%1$s hat den Raum wieder verlassen. „%2$s”"],"%1$s has left the groupchat":["%1$s hat den Raum verlassen"],"%1$s has left the groupchat. \"%2$s\"":["%1$s hat den Raum verlassen. „%2$s”"],"You are not on the member list of this groupchat.":["Sie sind nicht auf der Mitgliederliste dieses Raums."],"You have been banned from this groupchat.":["Sie wurden aus diesem Raum verbannt."],"No nickname was specified.":["Kein Spitzname festgelegt."],"You are not allowed to create new groupchats.":["Es ist Ihnen nicht erlaubt neue Räume anzulegen."],"Your nickname doesn't conform to this groupchat's policies.":["Ihr Spitzname entspricht nicht den Richtlinien dieses Raumes."],"This groupchat does not (yet) exist.":["Dieser Raum existiert (noch) nicht."],"This groupchat has reached its maximum number of participants.":["Maximale Anzahl an Teilnehmern für diesen Raum erreicht."],"Remote server not found":["Server wurde nicht gefunden"],"The explanation given is: \"%1$s\".":["Angegebene Grund: „%1$s”."],"Topic set by %1$s":["Das Thema wurde von %1$s gesetzt"],"Groupchats":["Gruppenchat"],"Add a new groupchat":["Neuen Gruppenchat hinzufügen"],"Query for groupchats":["Gruppenchats abfragen"],"Click to mention %1$s in your message.":["Klicken Sie hier, um %1$s in Ihrer Nachricht zu erwähnen."],"This user is a moderator.":["Dieser Benutzer ist ein Moderator."],"This user can send messages in this groupchat.":["Dieser Benutzer kann Nachrichten in diesem Raum senden."],"This user can NOT send messages in this groupchat.":["Dieser Benutzer kann keine Nachrichten in diesem Raum senden."],"Moderator":["Moderator"],"Visitor":["Besucher"],"Owner":["Eigentümer"],"Member":["Mitglieder"],"Admin":["Administrator"],"Participants":["Teilnehmer"],"Invite":["Einladen"],"You are about to invite %1$s to the groupchat \"%2$s\". You may optionally include a message, explaining the reason for the invitation.":["Sie sind dabei, %1$s in den Chatraum „%2$s” einzuladen. Optional können Sie eine Nachricht anfügen, in der Sie den Grund für die Einladung erläutern."],"Please enter a valid XMPP username":["Bitte eine gültige XMPP/Jabber-ID angeben"],"%1$s has invited you to join a groupchat: %2$s":["%1$s hat Sie in den Raum „%2$s” eingeladen"],"%1$s has invited you to join a groupchat: %2$s, and left the following reason: \"%3$s\"":["%1$s hat Sie in den Raum „%2$s” eingeladen. Begründung: „%3$s”"],"Notification from %1$s":["Benachrichtigung von %1$s"],"%1$s says":["%1$s sagt"],"has gone offline":["hat sich abgemeldet"],"has gone away":["ist jetzt abwesend"],"is busy":["ist beschäftigt"],"has come online":["kam online"],"wants to be your contact":["möchte Ihr Kontakt sein"],"Log in with %1$s":["Login mit %1$s"],"Your Profile":["Dein Profil"],"XMPP Address (JID)":["XMPP/Jabber-ID (JID)"],"Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.":["Benutze Kommas um die Rollen zu separieren. Die Rollen erscheinen neben deinem Namen."],"Your avatar image":["Dein Avatarbild"],"Sorry, an error happened while trying to save your profile data.":["Leider konnte das Lesezeichen nicht gespeichert werden."],"You can check your browser's developer console for any error output.":["Schau in die Entwicklerkonsole des Browsers um mögliche Fehlerausgaben zu sehen."],"Away":["Abwesend"],"Busy":["Beschäftigt"],"Custom status":["Statusnachricht"],"Offline":["Abgemeldet"],"Online":["Online"],"Away for long":["länger abwesend"],"Change chat status":["Hier klicken, um Ihren Status zu ändern"],"Personal status message":["Persönliche Nachricht"],"I am %1$s":["Ich bin %1$s"],"Change settings":["Einstellungen ändern"],"Click to change your chat status":["Hier klicken, um Ihren Status zu ändern"],"Log out":["Abmelden"],"Your profile":["Dein Profil"],"Are you sure you want to log out?":["Möchten Sie sich wirklich abmelden?"],"online":["online"],"busy":["beschäftigt"],"away for long":["länger abwesend"],"away":["abwesend"],"offline":["abgemeldet"]," e.g. conversejs.org":[" z. B. conversejs.org"],"Fetch registration form":["Anmeldeformular wird abgerufen"],"Tip: A list of public XMPP providers is available":["Tipp: Eine Liste öffentlicher Provider ist verfügbar"],"here":["hier"],"Sorry, we're unable to connect to your chosen provider.":["Leider können wir keine Verbindung zu dem von Ihnen gewählten Provider herstellen."],"Sorry, the given provider does not support in band account registration. Please try with a different provider.":["Entschuldigung: Dieser Provider erlaubt keine direkte Benutzer- Registrierung. Versuchen Sie einen anderen Provider oder erstellen Sie einen Zugang beim Provider direkt."],"Something went wrong while establishing a connection with \"%1$s\". Are you sure it exists?":["Die Verbindung zu „%1$s” konnte nicht hergestellt werden. Sind Sie sicher, dass „%1$s” existiert?"],"Now logging you in":["Sie werden angemeldet"],"Registered successfully":["Registrierung erfolgreich"],"The provider rejected your registration attempt. Please check the values you entered for correctness.":["Der Provider hat die Registrierung abgelehnt. Bitte überprüfen Sie Ihre Angaben auf Richtigkeit."],"Click to toggle the list of open groupchats":["Gruppenteilnehmer anzeigen"],"Open Groupchats":["Öffne Gruppenchats"],"Are you sure you want to leave the groupchat %1$s?":["Möchten Sie den Raum „%1$s” wirklich verlassen?"],"Sorry, there was an error while trying to add %1$s as a contact.":["Leider gab es einen Fehler beim Versuch, %1$s als Kontakt hinzuzufügen."],"This client does not allow presence subscriptions":["Dieser Client erlaubt keine Anwesenheitsabonnements"],"Click to hide these contacts":["Hier klicken, um diese Kontakte auszublenden"],"This contact is busy":["Dieser Kontakt ist beschäftigt"],"This contact is online":["Dieser Kontakt ist online"],"This contact is offline":["Dieser Kontakt ist offline"],"This contact is unavailable":["Dieser Kontakt ist nicht verfügbar"],"This contact is away for an extended period":["Dieser Kontakt ist für längere Zeit abwesend"],"This contact is away":["Dieser Kontakt ist abwesend"],"Groups":["Gruppen"],"My contacts":["Meine Kontakte"],"Pending contacts":["Unbestätigte Kontakte"],"Contact requests":["Kontaktanfragen"],"Ungrouped":["Ungruppiert"],"Contact name":["Name des Kontakts"],"Add a Contact":["Kontakt hinzufügen"],"XMPP Address":["XMPP Adresse"],"name@example.org":["z.B. benutzer@example.tld"],"Add":["Hinzufügen"],"Filter":["Filter"],"Filter by contact name":["Name des Kontakts"],"Filter by group name":["Suche per Gruppenname"],"Filter by status":["Suche via Status"],"Any":["Jeder"],"Unread":["Ungelesen"],"Chatty":["Gesprächsbereit"],"Extended Away":["Länger nicht anwesend"],"Click to remove %1$s as a contact":["Hier klicken, um %1$s als Kontakt zu entfernen"],"Click to accept the contact request from %1$s":["Hier klicken, um die Kontaktanfrage von %1$s zu akzeptieren"],"Click to decline the contact request from %1$s":["Hier klicken, um die Kontaktanfrage von %1$s abzulehnen"],"Click to chat with %1$s (JID: %2$s)":["Hier klicken, um mit %1$s (JID: %2$s) eine Unterhaltung zu beginnen"],"Are you sure you want to decline this contact request?":["Möchten Sie diese Kontaktanfrage wirklich ablehnen?"],"Contacts":["Kontakte"],"Add a contact":["Kontakt hinzufügen"],"Name":["Name"],"Groupchat address (JID)":["Raumadresse (JID)"],"Description":["Beschreibung"],"Topic":["Thema"],"Topic author":["Author des Themas"],"Online users":["Online"],"Features":["Funktionen"],"Password protected":["Passwortgeschützt"],"This groupchat requires a password before entry":["Dieser Raum erfordert ein Passwort"],"No password required":["Kein Passwort benötigt"],"This groupchat does not require a password upon entry":["Dieser Raum erfordert kein Passwort"],"This groupchat is not publicly searchable":["Dieser Raum ist nicht öffentlich auffindbar"],"This groupchat is publicly searchable":["Dieser Raum ist öffentlich auffindbar"],"Members only":["Nur Mitglieder"],"This groupchat is restricted to members only":["Dieser Raum ist nur für Mitglieder zugänglich"],"Anyone can join this groupchat":["Jeder kann diesen Raum betreten"],"Persistent":["Dauerhaft"],"This groupchat persists even if it's unoccupied":["Dieser Raum bleibt bestehen, auch wenn er nicht besetzt ist"],"This groupchat will disappear once the last person leaves":["Dieser Raum verschwindet, sobald die letzte Person den Raum verlässt"],"Not anonymous":["Nicht anonym"],"All other groupchat participants can see your XMPP username":["Jeder in dem Raum kann deine XMPP/Jabber-ID sehen"],"Only moderators can see your XMPP username":["Nur Moderatoren können deine XMPP/Jabber-ID sehen"],"This groupchat is being moderated":["Dieser Raum ist moderiert"],"Not moderated":["Nicht moderiert"],"This groupchat is not being moderated":["Dieser Raum wird nicht moderiert"],"Message archiving":["Nachrichtenarchivierung"],"Messages are archived on the server":["Nachrichten werden auf dem Server archiviert"],"No password":["Kein Passwort"],"this groupchat is restricted to members only":["Dieser Raum ist nur für Mitglieder zugänglich"],"XMPP Username:":["XMPP Benutzername:"],"Password:":["Passwort:"],"password":["passwort"],"This is a trusted device":["Diesem Gerät wird vertraut"],"To improve performance, we cache your data in this browser. Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. It's important that you explicitly log out, otherwise not all cached data might be deleted.":["Um die Performanz zu verbessern, werden Daten im Browser zwischengespeichert. Entkreuze diese Box, wenn du an einem öffentlichen PC bist oder du die Daten löschen willst, sobald du dich ausloggst. Es ist wichtig, dass du dich expilzit ausloggst, ansonsten werden die gespeicherten Daten möglicherweise nicht gelöscht."],"Log in":["Anmelden"],"Click here to log in anonymously":["Hier klicken, um sich anonym anzumelden"],"This message has been edited":["Diese Nachricht wurde geändert"],"Edit this message":["Nachricht bearbeiten"],"Message versions":["Nachrichtenarchivierung"],"Don't have a chat account?":["Sie haben noch kein Chat-Konto?"],"Create an account":["Konto erstellen"],"Create your account":["Erstellen Sie Ihr Konto"],"Please enter the XMPP provider to register with:":["Bitte geben Sie den XMPP-Provider ein, bei dem Sie sich anmelden möchten:"],"Already have a chat account?":["Sie haben bereits ein Chat-Konto?"],"Log in here":["Hier anmelden"],"Account Registration:":["Konto-Registrierung:"],"Register":["Registrierung"],"Choose a different provider":["Wählen Sie einen anderen Anbieter"],"Hold tight, we're fetching the registration form…":["Bitte warten, das Anmeldeformular wird geladen …"],"Download":["Herunterladen"],"Download \"%1$s\"":["Lade \"%1$s\""],"Download video file":["Video Datei Herunterladen"],"Download audio file":["Lade Audiodatei herunter"]}}} \ No newline at end of file diff --git a/locale/de/LC_MESSAGES/converse.po b/locale/de/LC_MESSAGES/converse.po index 299a8e25f..49a0e81d5 100644 --- a/locale/de/LC_MESSAGES/converse.po +++ b/locale/de/LC_MESSAGES/converse.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: Converse.js 0.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-07-22 11:17+0200\n" -"PO-Revision-Date: 2018-07-02 15:33+0200\n" -"Last-Translator: anonymous <>\n" +"PO-Revision-Date: 2018-07-31 18:02+0000\n" +"Last-Translator: The Lacks \n" "Language-Team: German \n" "Language: de\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 3.1-dev\n" +"X-Generator: Weblate 3.1.1\n" "domain: converse\n" "lang: de\n" "plural_forms: nplurals=2; plural=(n != 1);\n" @@ -25,23 +25,20 @@ msgstr "" #: dist/converse-no-dependencies.js:40690 #: dist/converse-no-dependencies.js:40775 #: dist/converse-no-dependencies.js:53689 -#, fuzzy msgid "Bookmark this groupchat" -msgstr "Raum als Lesezeichen speichern" +msgstr "Diesen Raum als Lesezeichen speichern" #: dist/converse-no-dependencies.js:40776 msgid "The name for this bookmark:" msgstr "Name des Lesezeichens:" #: dist/converse-no-dependencies.js:40777 -#, fuzzy msgid "Would you like this groupchat to be automatically joined upon startup?" msgstr "Beim Anmelden diesen Raum automatisch betreten?" #: dist/converse-no-dependencies.js:40778 -#, fuzzy msgid "What should your nickname for this groupchat be?" -msgstr "Welchen Spitznamen möchten Sie in diesem Raum verwenden?" +msgstr "Welcher Spitzname soll in diesem Raum verwendet werden?" #: dist/converse-no-dependencies.js:40780 #: dist/converse-no-dependencies.js:49483 @@ -68,7 +65,6 @@ msgstr "Leider konnte das Lesezeichen nicht gespeichert werden." #: dist/converse-no-dependencies.js:41055 #: dist/converse-no-dependencies.js:53687 -#, fuzzy msgid "Leave this groupchat" msgstr "Diesen Raum verlassen" @@ -78,21 +74,18 @@ msgstr "Dieses Lesezeichen entfernen" #: dist/converse-no-dependencies.js:41057 #: dist/converse-no-dependencies.js:53688 -#, fuzzy msgid "Unbookmark this groupchat" msgstr "Lesezeichen für diesen Raum entfernen" #: dist/converse-no-dependencies.js:41058 #: dist/converse-no-dependencies.js:48755 #: dist/converse-no-dependencies.js:53690 -#, fuzzy msgid "Show more information on this groupchat" -msgstr "Mehr Information über diesen Raum anzeigen" +msgstr "Zeige mehr Informationen über diesen Raum" #: dist/converse-no-dependencies.js:41061 #: dist/converse-no-dependencies.js:48754 #: dist/converse-no-dependencies.js:53692 -#, fuzzy msgid "Click to open this groupchat" msgstr "Hier klicken, um diesen Raum zu öffnen" @@ -106,11 +99,11 @@ msgstr "Lesezeichen" #: dist/converse-no-dependencies.js:41530 msgid "Sorry, could not determine file upload URL." -msgstr "" +msgstr "Die URL für das Hochladen der Datei konnte nicht ermittelt werden." #: dist/converse-no-dependencies.js:41538 msgid "Sorry, could not determine upload URL." -msgstr "" +msgstr "Die URL für das Hochladen der Datei konnte nicht ermittelt werden." #: dist/converse-no-dependencies.js:41573 #, javascript-format @@ -118,14 +111,16 @@ msgid "" "Sorry, could not succesfully upload your file. Your server’s response: \"%1$s" "\"" msgstr "" +"Die Datei konnte nicht hochgeladen werden. Der Server antwortete: \"%1$s1\"" #: dist/converse-no-dependencies.js:41575 msgid "Sorry, could not succesfully upload your file." -msgstr "" +msgstr "Konnte die Datei leider nicht erfolgreich hochladen." #: dist/converse-no-dependencies.js:41793 msgid "Sorry, looks like file upload is not supported by your server." msgstr "" +"Scheint als würde das Hochladen von Dateien auf dem Server nicht unterstützt." #: dist/converse-no-dependencies.js:41803 #, javascript-format @@ -133,10 +128,12 @@ msgid "" "The size of your file, %1$s, exceeds the maximum allowed by your server, " "which is %2$s." msgstr "" +"Die Größe deiner Datei, %1$s1, überschreitet das erlaubte Maximum vom " +"Server, welches bei %2$s2 liegt." #: dist/converse-no-dependencies.js:41996 msgid "Sorry, an error occurred:" -msgstr "" +msgstr "Es ist leider ein Fehler aufgetreten:" #: dist/converse-no-dependencies.js:42538 msgid "Close this chat box" @@ -144,7 +141,7 @@ msgstr "Dieses Chat-Fenster schließen" #: dist/converse-no-dependencies.js:42566 msgid "The User's Profile Image" -msgstr "" +msgstr "Benutzerprofilbild" #: dist/converse-no-dependencies.js:42569 #: dist/converse-no-dependencies.js:52477 @@ -152,23 +149,21 @@ msgstr "" #: dist/converse-no-dependencies.js:57245 #: dist/converse-no-dependencies.js:58439 msgid "Close" -msgstr "" +msgstr "Schließen" #: dist/converse-no-dependencies.js:42570 #: dist/converse-no-dependencies.js:52478 msgid "Email" -msgstr "" +msgstr "E-Mail" #: dist/converse-no-dependencies.js:42571 #: dist/converse-no-dependencies.js:52479 -#, fuzzy msgid "Full Name" msgstr "Name" #: dist/converse-no-dependencies.js:42572 -#, fuzzy msgid "Jabber ID" -msgstr "Jabber-ID:" +msgstr "Jabber-ID" #: dist/converse-no-dependencies.js:42573 #: dist/converse-no-dependencies.js:49639 @@ -177,23 +172,22 @@ msgid "Nickname" msgstr "Spitzname" #: dist/converse-no-dependencies.js:42574 -#, fuzzy msgid "Remove as contact" -msgstr "Kontakt hinzufügen" +msgstr "Kontakt entfernen" #: dist/converse-no-dependencies.js:42575 msgid "Refresh" -msgstr "" +msgstr "Aktualisieren" #: dist/converse-no-dependencies.js:42576 #: dist/converse-no-dependencies.js:52482 msgid "Role" -msgstr "" +msgstr "Rolle" #: dist/converse-no-dependencies.js:42577 #: dist/converse-no-dependencies.js:52485 msgid "URL" -msgstr "" +msgstr "URL" #: dist/converse-no-dependencies.js:42616 #: dist/converse-no-dependencies.js:55404 @@ -244,18 +238,16 @@ msgid "Click to write as a normal (non-spoiler) message" msgstr "Hier klicken, um Statusnachricht zu ändern (ohne Spoiler)" #: dist/converse-no-dependencies.js:42810 -#, fuzzy msgid "Click to write your message as a spoiler" -msgstr "Hier klicken, um Statusnachricht zu ändern" +msgstr "Hier klicken, um die Nachricht als Spoiler zu kennzeichnen" #: dist/converse-no-dependencies.js:42814 msgid "Clear all messages" msgstr "Alle Nachrichten löschen" #: dist/converse-no-dependencies.js:42815 -#, fuzzy msgid "Insert emojis" -msgstr "Smiley einfügen" +msgstr "Emojis einfügen" #: dist/converse-no-dependencies.js:42816 msgid "Start a call" @@ -276,31 +268,30 @@ msgid "Show this menu" msgstr "Dieses Menü anzeigen" #: dist/converse-no-dependencies.js:43317 -#, fuzzy msgid "Are you sure you want to clear the messages from this conversation?" msgstr "" "Sind Sie sicher, dass Sie alle Nachrichten dieses Chats löschen möchten?" #: dist/converse-no-dependencies.js:43413 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has gone offline" -msgstr "hat sich abgemeldet" +msgstr "%1$s1 hat sich abgemeldet" #: dist/converse-no-dependencies.js:43415 #: dist/converse-no-dependencies.js:47662 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has gone away" -msgstr "ist jetzt abwesend" +msgstr "%1$s1 ist jetzt abwesend" #: dist/converse-no-dependencies.js:43417 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is busy" -msgstr "ist beschäftigt" +msgstr "%1$s1 ist beschäftigt" #: dist/converse-no-dependencies.js:43419 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is online" -msgstr "online" +msgstr "%1$s1 ist jetzt online" #: dist/converse-no-dependencies.js:44042 msgid "Username" @@ -316,7 +307,6 @@ msgid "Please enter a valid XMPP address" msgstr "Bitte eine gültige XMPP/Jabber-ID eingeben" #: dist/converse-no-dependencies.js:44145 -#, fuzzy msgid "Chat Contacts" msgstr "Kontakte" @@ -362,18 +352,18 @@ msgid "Typing from another device" msgstr "Schreibt von einem anderen Gerät" #: dist/converse-no-dependencies.js:47653 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is typing" -msgstr "schreibt …" +msgstr "%1$s schreibt …" #: dist/converse-no-dependencies.js:47657 msgid "Stopped typing on the other device" msgstr "Schreibt nicht mehr auf dem anderen Gerät" #: dist/converse-no-dependencies.js:47659 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has stopped typing" -msgstr "tippt nicht mehr" +msgstr "%1$s1 tippt nicht mehr" #: dist/converse-no-dependencies.js:47905 #: dist/converse-no-dependencies.js:47948 @@ -389,88 +379,73 @@ msgid "Minimized" msgstr "Minimiert" #: dist/converse-no-dependencies.js:48597 -#, fuzzy msgid "This groupchat is not anonymous" msgstr "Dieser Raum ist nicht anonym" #: dist/converse-no-dependencies.js:48598 -#, fuzzy msgid "This groupchat now shows unavailable members" -msgstr "Dieser Raum zeigt jetzt nicht verfügbare Mitglieder an." +msgstr "Dieser Raum zeigt jetzt nicht verfügbare Mitglieder an" #: dist/converse-no-dependencies.js:48599 -#, fuzzy msgid "This groupchat does not show unavailable members" -msgstr "In diesem Raum werden keine nicht verfügbaren Mitglieder angezeigt." +msgstr "In diesem Raum werden keine nicht verfügbaren Mitglieder angezeigt" #: dist/converse-no-dependencies.js:48600 -#, fuzzy msgid "The groupchat configuration has changed" msgstr "Die Raumkonfiguration hat sich geändert" #: dist/converse-no-dependencies.js:48601 -#, fuzzy msgid "groupchat logging is now enabled" msgstr "Nachrichten in diesem Raum werden ab jetzt protokolliert" #: dist/converse-no-dependencies.js:48602 -#, fuzzy msgid "groupchat logging is now disabled" msgstr "Nachrichten in diesem Raum werden nicht mehr protokolliert" #: dist/converse-no-dependencies.js:48603 -#, fuzzy msgid "This groupchat is now no longer anonymous" msgstr "Dieses Raum ist jetzt nicht mehr anonym" #: dist/converse-no-dependencies.js:48604 -#, fuzzy msgid "This groupchat is now semi-anonymous" msgstr "Dieser Raum ist jetzt nur teilweise anonym" #: dist/converse-no-dependencies.js:48605 -#, fuzzy msgid "This groupchat is now fully-anonymous" msgstr "Dieser Raum ist jetzt vollständig anonym" #: dist/converse-no-dependencies.js:48606 -#, fuzzy msgid "A new groupchat has been created" msgstr "Ein neuer Raum wurde erstellt" #: dist/converse-no-dependencies.js:48609 -#, fuzzy msgid "You have been banned from this groupchat" msgstr "Sie wurden aus diesem Raum verbannt" #: dist/converse-no-dependencies.js:48610 -#, fuzzy msgid "You have been kicked from this groupchat" msgstr "Sie wurden aus diesem Raum hinausgeworfen" #: dist/converse-no-dependencies.js:48611 -#, fuzzy msgid "" "You have been removed from this groupchat because of an affiliation change" msgstr "Sie wurden wegen einer Zugehörigkeitsänderung entfernt" #: dist/converse-no-dependencies.js:48612 -#, fuzzy msgid "" "You have been removed from this groupchat because the groupchat has changed " "to members-only and you're not a member" msgstr "" "Sie wurden aus diesem Raum ausgeschlossen, da der Raum jetzt nur noch " -"Mitglieder erlaubt und Sie kein Mitglied sind." +"Mitglieder erlaubt und Sie kein Mitglied sind" #: dist/converse-no-dependencies.js:48613 -#, fuzzy msgid "" "You have been removed from this groupchat because the MUC (Multi-user chat) " "service is being shut down" msgstr "" "Sie wurden aus diesem Raum entfernt, weil der MUC-Dienst (Multi-User-Chat) " -"heruntergefahren wird." +"heruntergefahren wird" #. XXX: Note the triple underscore function and not double #. * underscore. @@ -522,12 +497,10 @@ msgid "Description:" msgstr "Beschreibung:" #: dist/converse-no-dependencies.js:48666 -#, fuzzy msgid "Groupchat Address (JID):" msgstr "XMPP/Jabber-ID (JID) dieses Raumes:" #: dist/converse-no-dependencies.js:48667 -#, fuzzy msgid "Participants:" msgstr "Teilnehmer:" @@ -566,7 +539,6 @@ msgid "Open" msgstr "Offen" #: dist/converse-no-dependencies.js:48675 -#, fuzzy msgid "Permanent" msgstr "Dauerhafter Raum" @@ -593,106 +565,92 @@ msgid "Unmoderated" msgstr "Nicht moderiert" #: dist/converse-no-dependencies.js:48715 -#, fuzzy msgid "Query for Groupchats" msgstr "Benutzer aus dem Raum verbannen" #: dist/converse-no-dependencies.js:48716 -#, fuzzy msgid "Server address" msgstr "Server" #: dist/converse-no-dependencies.js:48717 -#, fuzzy msgid "Show groupchats" msgstr "Gruppen" #: dist/converse-no-dependencies.js:48718 -#, fuzzy msgid "conference.example.org" -msgstr "z.B. benutzer@example.org" +msgstr "z.B. conference.example.tld" #: dist/converse-no-dependencies.js:48767 -#, fuzzy msgid "No groupchats found" msgstr "Keine Räume gefunden" #: dist/converse-no-dependencies.js:48784 -#, fuzzy msgid "groupchats found:" -msgstr "Gruppen" +msgstr "Raum gefunden:" #: dist/converse-no-dependencies.js:48836 -#, fuzzy msgid "Enter a new Groupchat" msgstr "Raum betreten" #: dist/converse-no-dependencies.js:48837 -#, fuzzy msgid "Groupchat address" -msgstr "XMPP/Jabber-ID (JID) dieses Raumes:" +msgstr "XMPP/Jabber-ID (JID) dieses Raumes" #: dist/converse-no-dependencies.js:48838 #: dist/converse-no-dependencies.js:55005 -#, fuzzy msgid "Optional nickname" -msgstr "Optionaler Hinweis" +msgstr "Optionaler Spitzname" #: dist/converse-no-dependencies.js:48839 msgid "name@conference.example.org" -msgstr "" +msgstr "name@conference.beispiel.tld" #: dist/converse-no-dependencies.js:48840 -#, fuzzy msgid "Join" -msgstr "Raum betreten" +msgstr "Betreten" #: dist/converse-no-dependencies.js:48884 -#, fuzzy, javascript-format +#, javascript-format msgid "Groupchat info for %1$s" -msgstr "Benachrichtigung von %1$s" +msgstr "Benachrichtigung für %1$s" #: dist/converse-no-dependencies.js:48990 msgid "Message" msgstr "Nachricht" #: dist/converse-no-dependencies.js:49036 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is no longer a moderator" -msgstr "%1$s ist kein Moderator mehr." +msgstr "%1$s ist kein Moderator mehr" #: dist/converse-no-dependencies.js:49040 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has been given a voice again" -msgstr "%1$s darf nun wieder schreiben." +msgstr "%1$s darf nun wieder schreiben" #: dist/converse-no-dependencies.js:49044 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has been muted" -msgstr "%1$s wurde das Schreibrecht entzogen." +msgstr "%1$s wurde das Schreibrecht entzogen" #: dist/converse-no-dependencies.js:49048 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is now a moderator" -msgstr "%1$s ist jetzt ein Moderator." +msgstr "%1$s ist jetzt ein Moderator" #: dist/converse-no-dependencies.js:49056 -#, fuzzy msgid "Close and leave this groupchat" msgstr "Schließen und diesen Raum verlassen" #: dist/converse-no-dependencies.js:49057 -#, fuzzy msgid "Configure this groupchat" msgstr "Einstellungsänderungen an diesem Raum vornehmen" #: dist/converse-no-dependencies.js:49058 -#, fuzzy msgid "Show more details about this groupchat" msgstr "Mehr Information über diesen Raum anzeigen" #: dist/converse-no-dependencies.js:49098 -#, fuzzy msgid "Hide the list of participants" msgstr "Teilnehmerliste ausblenden" @@ -710,13 +668,14 @@ msgid "" "Sorry, an error happened while running the command. Check your browser's " "developer console for details." msgstr "" +"Leider ist ein Fehler während dem Ausführen des Kommandos aufgetreten. " +"Überprüfe die Entwicklerkonsole des Browsers." #: dist/converse-no-dependencies.js:49282 msgid "Change user's affiliation to admin" msgstr "Zugehörigkeit des Benutzers zu Administrator ändern" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Ban user from groupchat" msgstr "Benutzer aus dem Raum verbannen" @@ -725,7 +684,6 @@ msgid "Change user role to participant" msgstr "Rolle zu Teilnehmer ändern" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Kick user from groupchat" msgstr "Benutzer aus dem Raum hinauswerfen" @@ -750,7 +708,6 @@ msgid "Grant moderator role to user" msgstr "Benutzer Moderatorrechte gewähren" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Grant ownership of this groupchat" msgstr "Besitzrechte an diesem Raum vergeben" @@ -759,12 +716,10 @@ msgid "Revoke user's membership" msgstr "Mitgliedschaft des Benutzers widerrufen" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Set groupchat subject" msgstr "Thema des Chatraums festlegen" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Set groupchat subject (alias for /subject)" msgstr "Raumthema (alias für /subject) festlegen" @@ -785,12 +740,10 @@ msgid "Please choose your nickname" msgstr "Wählen Sie Ihren Spitznamen" #: dist/converse-no-dependencies.js:49640 -#, fuzzy msgid "Enter groupchat" msgstr "Raum betreten" #: dist/converse-no-dependencies.js:49661 -#, fuzzy msgid "This groupchat requires a password" msgstr "Dieser Raum erfordert ein Passwort" @@ -814,47 +767,45 @@ msgid "The reason given is: \"%1$s\"." msgstr "Angegebene Grund: „%1$s”" #: dist/converse-no-dependencies.js:49828 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has left and re-entered the groupchat" -msgstr "%1$s hat den Raum erneut betreten." +msgstr "%1$s hat den Raum erneut betreten" #: dist/converse-no-dependencies.js:49834 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has entered the groupchat" -msgstr "%1$s hat den Raum betreten." +msgstr "%1$s hat den Raum betreten" #: dist/converse-no-dependencies.js:49836 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has entered the groupchat. \"%2$s\"" msgstr "%1$s hat den Raum betreten. „%2$s”" #: dist/converse-no-dependencies.js:49867 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has entered and left the groupchat" -msgstr "%1$s hat den Raum wieder verlassen." +msgstr "%1$s hat den Raum wieder verlassen" #: dist/converse-no-dependencies.js:49869 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has entered and left the groupchat. \"%2$s\"" msgstr "%1$s hat den Raum wieder verlassen. „%2$s”" #: dist/converse-no-dependencies.js:49882 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has left the groupchat" -msgstr "%1$s hat den Raum verlassen." +msgstr "%1$s hat den Raum verlassen" #: dist/converse-no-dependencies.js:49884 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has left the groupchat. \"%2$s\"" msgstr "%1$s hat den Raum verlassen. „%2$s”" #: dist/converse-no-dependencies.js:49930 -#, fuzzy msgid "You are not on the member list of this groupchat." msgstr "Sie sind nicht auf der Mitgliederliste dieses Raums." #: dist/converse-no-dependencies.js:49932 -#, fuzzy msgid "You have been banned from this groupchat." msgstr "Sie wurden aus diesem Raum verbannt." @@ -863,53 +814,46 @@ msgid "No nickname was specified." msgstr "Kein Spitzname festgelegt." #: dist/converse-no-dependencies.js:49940 -#, fuzzy msgid "You are not allowed to create new groupchats." msgstr "Es ist Ihnen nicht erlaubt neue Räume anzulegen." #: dist/converse-no-dependencies.js:49942 -#, fuzzy msgid "Your nickname doesn't conform to this groupchat's policies." msgstr "Ihr Spitzname entspricht nicht den Richtlinien dieses Raumes." #: dist/converse-no-dependencies.js:49946 -#, fuzzy msgid "This groupchat does not (yet) exist." msgstr "Dieser Raum existiert (noch) nicht." #: dist/converse-no-dependencies.js:49948 -#, fuzzy msgid "This groupchat has reached its maximum number of participants." msgstr "Maximale Anzahl an Teilnehmern für diesen Raum erreicht." #: dist/converse-no-dependencies.js:49950 msgid "Remote server not found" -msgstr "" +msgstr "Server wurde nicht gefunden" #: dist/converse-no-dependencies.js:49955 -#, fuzzy, javascript-format +#, javascript-format msgid "The explanation given is: \"%1$s\"." -msgstr "Angegebene Grund: „%1$s”" +msgstr "Angegebene Grund: „%1$s”." #: dist/converse-no-dependencies.js:50008 -#, fuzzy, javascript-format +#, javascript-format msgid "Topic set by %1$s" -msgstr "%1$s hat das Thema zu „%2$s” geändert" +msgstr "Das Thema wurde von %1$s gesetzt" #: dist/converse-no-dependencies.js:50031 -#, fuzzy msgid "Groupchats" -msgstr "Gruppen" +msgstr "Gruppenchat" #: dist/converse-no-dependencies.js:50032 -#, fuzzy msgid "Add a new groupchat" -msgstr "Raum betreten" +msgstr "Neuen Gruppenchat hinzufügen" #: dist/converse-no-dependencies.js:50033 -#, fuzzy msgid "Query for groupchats" -msgstr "Benutzer aus dem Raum verbannen" +msgstr "Gruppenchats abfragen" #: dist/converse-no-dependencies.js:50071 #, javascript-format @@ -921,40 +865,36 @@ msgid "This user is a moderator." msgstr "Dieser Benutzer ist ein Moderator." #: dist/converse-no-dependencies.js:50073 -#, fuzzy msgid "This user can send messages in this groupchat." msgstr "Dieser Benutzer kann Nachrichten in diesem Raum senden." #: dist/converse-no-dependencies.js:50074 -#, fuzzy msgid "This user can NOT send messages in this groupchat." msgstr "Dieser Benutzer kann keine Nachrichten in diesem Raum senden." #: dist/converse-no-dependencies.js:50075 -#, fuzzy msgid "Moderator" -msgstr "Moderiert" +msgstr "Moderator" #: dist/converse-no-dependencies.js:50076 msgid "Visitor" -msgstr "" +msgstr "Besucher" #: dist/converse-no-dependencies.js:50077 msgid "Owner" -msgstr "" +msgstr "Eigentümer" #: dist/converse-no-dependencies.js:50078 -#, fuzzy msgid "Member" -msgstr "Nur Mitglieder" +msgstr "Mitglieder" #: dist/converse-no-dependencies.js:50079 msgid "Admin" -msgstr "" +msgstr "Administrator" #: dist/converse-no-dependencies.js:50121 msgid "Participants" -msgstr "" +msgstr "Teilnehmer" #: dist/converse-no-dependencies.js:50138 #: dist/converse-no-dependencies.js:50219 @@ -962,7 +902,7 @@ msgid "Invite" msgstr "Einladen" #: dist/converse-no-dependencies.js:50196 -#, fuzzy, javascript-format +#, javascript-format msgid "" "You are about to invite %1$s to the groupchat \"%2$s\". You may optionally " "include a message, explaining the reason for the invitation." @@ -975,12 +915,12 @@ msgid "Please enter a valid XMPP username" msgstr "Bitte eine gültige XMPP/Jabber-ID angeben" #: dist/converse-no-dependencies.js:51591 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has invited you to join a groupchat: %2$s" msgstr "%1$s hat Sie in den Raum „%2$s” eingeladen" #: dist/converse-no-dependencies.js:51593 -#, fuzzy, javascript-format +#, javascript-format msgid "" "%1$s has invited you to join a groupchat: %2$s, and left the following " "reason: \"%3$s\"" @@ -1023,35 +963,37 @@ msgstr "möchte Ihr Kontakt sein" #: dist/converse-no-dependencies.js:52229 #, javascript-format msgid "Log in with %1$s" -msgstr "" +msgstr "Login mit %1$s" #: dist/converse-no-dependencies.js:52476 msgid "Your Profile" -msgstr "" +msgstr "Dein Profil" #: dist/converse-no-dependencies.js:52481 -#, fuzzy msgid "XMPP Address (JID)" -msgstr "XMPP/Jabber-ID (JID) dieses Raumes:" +msgstr "XMPP/Jabber-ID (JID)" #: dist/converse-no-dependencies.js:52483 msgid "" "Use commas to separate multiple roles. Your roles are shown next to your " "name on your chat messages." msgstr "" +"Benutze Kommas um die Rollen zu separieren. Die Rollen erscheinen neben " +"deinem Namen." #: dist/converse-no-dependencies.js:52486 msgid "Your avatar image" -msgstr "" +msgstr "Dein Avatarbild" #: dist/converse-no-dependencies.js:52513 -#, fuzzy msgid "Sorry, an error happened while trying to save your profile data." msgstr "Leider konnte das Lesezeichen nicht gespeichert werden." #: dist/converse-no-dependencies.js:52513 msgid "You can check your browser's developer console for any error output." msgstr "" +"Schau in die Entwicklerkonsole des Browsers um mögliche Fehlerausgaben zu " +"sehen." #: dist/converse-no-dependencies.js:52561 #: dist/converse-no-dependencies.js:55131 @@ -1078,17 +1020,14 @@ msgid "Online" msgstr "Online" #: dist/converse-no-dependencies.js:52569 -#, fuzzy msgid "Away for long" msgstr "länger abwesend" #: dist/converse-no-dependencies.js:52570 -#, fuzzy msgid "Change chat status" msgstr "Hier klicken, um Ihren Status zu ändern" #: dist/converse-no-dependencies.js:52571 -#, fuzzy msgid "Personal status message" msgstr "Persönliche Nachricht" @@ -1099,7 +1038,7 @@ msgstr "Ich bin %1$s" #: dist/converse-no-dependencies.js:52618 msgid "Change settings" -msgstr "" +msgstr "Einstellungen ändern" #: dist/converse-no-dependencies.js:52619 msgid "Click to change your chat status" @@ -1111,12 +1050,11 @@ msgstr "Abmelden" #: dist/converse-no-dependencies.js:52621 msgid "Your profile" -msgstr "" +msgstr "Dein Profil" #: dist/converse-no-dependencies.js:52644 -#, fuzzy msgid "Are you sure you want to log out?" -msgstr "Möchten Sie diesen Kontakt wirklich entfernen?" +msgstr "Möchten Sie sich wirklich abmelden?" #: dist/converse-no-dependencies.js:52652 #: dist/converse-no-dependencies.js:52662 @@ -1196,16 +1134,15 @@ msgstr "" "Angaben auf Richtigkeit." #: dist/converse-no-dependencies.js:53748 -#, fuzzy msgid "Click to toggle the list of open groupchats" -msgstr "Umschalten der Raumlisten" +msgstr "Gruppenteilnehmer anzeigen" #: dist/converse-no-dependencies.js:53749 msgid "Open Groupchats" -msgstr "" +msgstr "Öffne Gruppenchats" #: dist/converse-no-dependencies.js:53793 -#, fuzzy, javascript-format +#, javascript-format msgid "Are you sure you want to leave the groupchat %1$s?" msgstr "Möchten Sie den Raum „%1$s” wirklich verlassen?" @@ -1272,18 +1209,16 @@ msgid "Contact name" msgstr "Name des Kontakts" #: dist/converse-no-dependencies.js:55008 -#, fuzzy msgid "Add a Contact" msgstr "Kontakt hinzufügen" #: dist/converse-no-dependencies.js:55009 msgid "XMPP Address" -msgstr "" +msgstr "XMPP Adresse" #: dist/converse-no-dependencies.js:55011 -#, fuzzy msgid "name@example.org" -msgstr "z.B. benutzer@example.org" +msgstr "z.B. benutzer@example.tld" #: dist/converse-no-dependencies.js:55012 msgid "Add" @@ -1294,17 +1229,16 @@ msgid "Filter" msgstr "Filter" #: dist/converse-no-dependencies.js:55123 -#, fuzzy msgid "Filter by contact name" msgstr "Name des Kontakts" #: dist/converse-no-dependencies.js:55124 msgid "Filter by group name" -msgstr "" +msgstr "Suche per Gruppenname" #: dist/converse-no-dependencies.js:55125 msgid "Filter by status" -msgstr "" +msgstr "Suche via Status" #: dist/converse-no-dependencies.js:55126 msgid "Any" @@ -1339,9 +1273,9 @@ msgid "Click to decline the contact request from %1$s" msgstr "Hier klicken, um die Kontaktanfrage von %1$s abzulehnen" #: dist/converse-no-dependencies.js:55357 -#, fuzzy, javascript-format +#, javascript-format msgid "Click to chat with %1$s (JID: %2$s)" -msgstr "Hier klicken, um mit diesem Kontakt eine Unterhaltung zu beginnen" +msgstr "Hier klicken, um mit %1$s (JID: %2$s) eine Unterhaltung zu beginnen" #: dist/converse-no-dependencies.js:55434 msgid "Are you sure you want to decline this contact request?" @@ -1356,30 +1290,26 @@ msgid "Add a contact" msgstr "Kontakt hinzufügen" #: dist/converse-no-dependencies.js:57111 -#, fuzzy msgid "Name" msgstr "Name" #: dist/converse-no-dependencies.js:57115 -#, fuzzy msgid "Groupchat address (JID)" -msgstr "XMPP/Jabber-ID (JID) dieses Raumes:" +msgstr "Raumadresse (JID)" #: dist/converse-no-dependencies.js:57119 -#, fuzzy msgid "Description" -msgstr "Beschreibung:" +msgstr "Beschreibung" #: dist/converse-no-dependencies.js:57125 msgid "Topic" -msgstr "" +msgstr "Thema" #: dist/converse-no-dependencies.js:57129 msgid "Topic author" -msgstr "" +msgstr "Author des Themas" #: dist/converse-no-dependencies.js:57135 -#, fuzzy msgid "Online users" msgstr "Online" @@ -1395,30 +1325,25 @@ msgstr "Passwortgeschützt" #: dist/converse-no-dependencies.js:57145 #: dist/converse-no-dependencies.js:57297 -#, fuzzy msgid "This groupchat requires a password before entry" msgstr "Dieser Raum erfordert ein Passwort" #: dist/converse-no-dependencies.js:57151 -#, fuzzy msgid "No password required" -msgstr "Kein Passwort" +msgstr "Kein Passwort benötigt" #: dist/converse-no-dependencies.js:57153 #: dist/converse-no-dependencies.js:57305 -#, fuzzy msgid "This groupchat does not require a password upon entry" msgstr "Dieser Raum erfordert kein Passwort" #: dist/converse-no-dependencies.js:57161 #: dist/converse-no-dependencies.js:57313 -#, fuzzy msgid "This groupchat is not publicly searchable" msgstr "Dieser Raum ist nicht öffentlich auffindbar" #: dist/converse-no-dependencies.js:57169 #: dist/converse-no-dependencies.js:57321 -#, fuzzy msgid "This groupchat is publicly searchable" msgstr "Dieser Raum ist öffentlich auffindbar" @@ -1428,13 +1353,11 @@ msgid "Members only" msgstr "Nur Mitglieder" #: dist/converse-no-dependencies.js:57177 -#, fuzzy msgid "This groupchat is restricted to members only" msgstr "Dieser Raum ist nur für Mitglieder zugänglich" #: dist/converse-no-dependencies.js:57185 #: dist/converse-no-dependencies.js:57337 -#, fuzzy msgid "Anyone can join this groupchat" msgstr "Jeder kann diesen Raum betreten" @@ -1445,25 +1368,21 @@ msgstr "Dauerhaft" #: dist/converse-no-dependencies.js:57193 #: dist/converse-no-dependencies.js:57345 -#, fuzzy msgid "This groupchat persists even if it's unoccupied" -msgstr "Dieser Raum bleibt bestehen, auch wenn er nicht besetzt ist." +msgstr "Dieser Raum bleibt bestehen, auch wenn er nicht besetzt ist" #: dist/converse-no-dependencies.js:57201 #: dist/converse-no-dependencies.js:57353 -#, fuzzy msgid "This groupchat will disappear once the last person leaves" -msgstr "Dieser Raum verschwindet, sobald die letzte Person den Raum verlässt." +msgstr "Dieser Raum verschwindet, sobald die letzte Person den Raum verlässt" #: dist/converse-no-dependencies.js:57207 #: dist/converse-no-dependencies.js:57363 -#, fuzzy msgid "Not anonymous" msgstr "Nicht anonym" #: dist/converse-no-dependencies.js:57209 #: dist/converse-no-dependencies.js:57361 -#, fuzzy msgid "All other groupchat participants can see your XMPP username" msgstr "Jeder in dem Raum kann deine XMPP/Jabber-ID sehen" @@ -1474,19 +1393,16 @@ msgstr "Nur Moderatoren können deine XMPP/Jabber-ID sehen" #: dist/converse-no-dependencies.js:57225 #: dist/converse-no-dependencies.js:57377 -#, fuzzy msgid "This groupchat is being moderated" msgstr "Dieser Raum ist moderiert" #: dist/converse-no-dependencies.js:57231 #: dist/converse-no-dependencies.js:57387 -#, fuzzy msgid "Not moderated" msgstr "Nicht moderiert" #: dist/converse-no-dependencies.js:57233 #: dist/converse-no-dependencies.js:57385 -#, fuzzy msgid "This groupchat is not being moderated" msgstr "Dieser Raum wird nicht moderiert" @@ -1505,7 +1421,6 @@ msgid "No password" msgstr "Kein Passwort" #: dist/converse-no-dependencies.js:57329 -#, fuzzy msgid "this groupchat is restricted to members only" msgstr "Dieser Raum ist nur für Mitglieder zugänglich" @@ -1523,7 +1438,7 @@ msgstr "passwort" #: dist/converse-no-dependencies.js:58283 msgid "This is a trusted device" -msgstr "" +msgstr "Diesem Gerät wird vertraut" #: dist/converse-no-dependencies.js:58285 msgid "" @@ -1532,9 +1447,13 @@ msgid "" "log out. It's important that you explicitly log out, otherwise not all " "cached data might be deleted." msgstr "" +"Um die Performanz zu verbessern, werden Daten im Browser " +"zwischengespeichert. Entkreuze diese Box, wenn du an einem öffentlichen PC " +"bist oder du die Daten löschen willst, sobald du dich ausloggst. Es ist " +"wichtig, dass du dich expilzit ausloggst, ansonsten werden die gespeicherten " +"Daten möglicherweise nicht gelöscht." #: dist/converse-no-dependencies.js:58287 -#, fuzzy msgid "Log in" msgstr "Anmelden" @@ -1543,17 +1462,14 @@ msgid "Click here to log in anonymously" msgstr "Hier klicken, um sich anonym anzumelden" #: dist/converse-no-dependencies.js:58376 -#, fuzzy msgid "This message has been edited" -msgstr "Dieser Raum ist moderiert" +msgstr "Diese Nachricht wurde geändert" #: dist/converse-no-dependencies.js:58402 -#, fuzzy msgid "Edit this message" -msgstr "Versteckte Nachrichten verstecken" +msgstr "Nachricht bearbeiten" #: dist/converse-no-dependencies.js:58427 -#, fuzzy msgid "Message versions" msgstr "Nachrichtenarchivierung" @@ -1604,9 +1520,9 @@ msgid "Download" msgstr "Herunterladen" #: dist/converse-no-dependencies.js:59996 -#, fuzzy, javascript-format +#, javascript-format msgid "Download \"%1$s\"" -msgstr "Läd:\"%1$s\"herunter" +msgstr "Lade \"%1$s\"" #: dist/converse-no-dependencies.js:60019 msgid "Download video file" @@ -1614,7 +1530,7 @@ msgstr "Video Datei Herunterladen" #: dist/converse-no-dependencies.js:60032 msgid "Download audio file" -msgstr "" +msgstr "Lade Audiodatei herunter" #, fuzzy #~ msgid "Room address (JID)" diff --git a/locale/fr/LC_MESSAGES/converse.json b/locale/fr/LC_MESSAGES/converse.json index 5e22c9804..37d0cf896 100644 --- a/locale/fr/LC_MESSAGES/converse.json +++ b/locale/fr/LC_MESSAGES/converse.json @@ -1 +1 @@ -{"domain":"converse","locale_data":{"converse":{"":{"domain":"converse","plural_forms":"nplurals=2; plural=n > 1;","lang":"fr"},"The name for this bookmark:":["Nom de ce marque-page :"],"Would you like this groupchat to be automatically joined upon startup?":[""],"What should your nickname for this groupchat be?":[""],"Save":[""],"Cancel":[""],"Leave this groupchat":[""],"Click to toggle the bookmarks list":["Cliquer pour ouvrir la liste des salons"],"Bookmarks":["Marques-page"],"Sorry, could not determine file upload URL.":["Désolé, impossible de déterminer l’URL pour envoyer le fichier."],"Sorry, could not determine upload URL.":["Désolé, impossible de déterminer l’URL d’envoi de fichier."],"Sorry, could not succesfully upload your file.":["Désolé, l’envoi de fichier a échoué."],"Sorry, looks like file upload is not supported by your server.":["Désolé, votre serveur semble ne pas proposer l’envoi de fichier."],"The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.":["La taille de votre fichier, %1$s, dépasse le maximum autorisé par votre serveur, qui est %2$s."],"Sorry, an error occurred:":[""],"Close this chat box":["Fermer cette fenêtre de discussion"],"The User's Profile Image":["Image de profil de l’utilisateur"],"Close":["Fermer"],"Email":["E-mail"],"Full Name":["Nom complet"],"Jabber ID":["Identifiant Jabber"],"Nickname":["Alias"],"Remove as contact":["Supprimer ce contact"],"Refresh":["Rafraîchir"],"Role":["Rôle"],"URL":["URL"],"Are you sure you want to remove this contact?":["Voulez-vous vraiment retirer ce contact ?"],"Error":["Erreur"],"Sorry, there was an error while trying to remove %1$s as a contact.":["Désolé, il y a eu une erreur lors de la tentative de retrait de %1$s comme contact."],"You have unread messages":["Vous avez de nouveaux messages"],"Hidden message":["Message caché"],"Personal message":["Message personnel"],"Send":["Envoyer"],"Optional hint":["Indice optionnel"],"Choose a file to send":["Choisir un fichier à envoyer"],"Click to write as a normal (non-spoiler) message":["Cliquez pour écrire un message sans spoiler"],"Click to write your message as a spoiler":["Cliquez pour écrire votre message en tant que spoiler"],"Clear all messages":["Supprimer tous les messages"],"Insert emojis":["Insérer un emoji"],"Start a call":["Démarrer un appel"],"Remove messages":["Effacer les messages"],"Write in the third person":["Écrire à la troisième personne"],"Show this menu":["Afficher ce menu"],"Are you sure you want to clear the messages from this conversation?":["Voulez-vous vraiment effacer les messages de cette conversation ?"],"Username":["Nom"],"user@domain":["utilisateur@domaine"],"Please enter a valid XMPP address":["Veuillez saisir une adresse XMPP valide"],"Chat Contacts":["Contacts de chat"],"Toggle chat":["Ouvrir la discussion"],"The connection has dropped, attempting to reconnect.":["La connexion a été perdue, tentative de reconnexion en cours."],"An error occurred while connecting to the chat server.":["Une erreur est survenue lors de la connexion au serveur de discussion."],"Your Jabber ID and/or password is incorrect. Please try again.":["Votre identifiant Jabber ou votre mot de passe est incorrect. Veuillez réessayer."],"Sorry, we could not connect to the XMPP host with domain: %1$s":["Désolé, nous n’avons pas pu nous connecter à l’hôte XMPP avec le domaine : %1$s"],"The XMPP server did not offer a supported authentication mechanism":["Le serveur XMPP n’a pas proposé un mécanisme d’authentification pris en charge"],"Show more":["Afficher plus"],"Typing from another device":[""],"Stopped typing on the other device":[""],"%1$s has stopped typing":[""],"Minimize this chat box":["Réduire cette fenêtre de discussion"],"Click to restore this chat":["Cliquez pour afficher cette discussion"],"Minimized":["Réduit(s)"],"%1$s has been banned":["%1$s a été banni"],"%1$s's nickname has changed":["L’alias de %1$s a changé"],"%1$s has been kicked out":["%1$s a été expulsé"],"%1$s has been removed because of an affiliation change":["%1$s a été supprimé à cause d’un changement d’affiliation"],"%1$s has been removed for not being a member":["%1$s a été supprimé car il n’est pas membre"],"Your nickname has been automatically set to %1$s":["Votre alias a été automatiquement défini à : %1$s"],"Your nickname has been changed to %1$s":["Votre alias a été modifié en : %1$s"],"Description:":["Description :"],"Features:":["Caractéristiques :"],"Requires authentication":["Nécessite une authentification"],"Hidden":["Caché"],"Requires an invitation":["Nécessite une invitation"],"Moderated":["Modéré"],"Non-anonymous":["Non-anonyme"],"Open":["Ouvert"],"Public":["Public"],"Semi-anonymous":["Semi-anonyme"],"Temporary":["Temporaire"],"Unmoderated":["Non modéré"],"Server address":["Adresse du serveur"],"conference.example.org":["chat.exemple.org"],"Optional nickname":["Pseudonyme optionnel"],"name@conference.example.org":["nom@chat.example.org"],"Join":["Rejoindre"],"Message":["Message"],"%1$s is no longer a moderator":["%1$s n’est plus un modérateur"],"%1$s has been given a voice again":["%1$s peut de nouveau parler"],"%1$s has been muted":["%1$s ne peut plus parler"],"%1$s is now a moderator":["%1$s est désormais un modérateur"],"Error: the \"%1$s\" command takes two arguments, the user's nickname and optionally a reason.":["Erreur : la commande « %1$s » prend deux paramètres, le pseudo de l’utilisateur et une raison optionnelle."],"Sorry, an error happened while running the command. Check your browser's developer console for details.":["Désolé, une erreur s'est produite lors de l'exécution de la commande. Vérifiez la console de développement de votre navigateur pour plus de détails."],"Change user's affiliation to admin":["Changer le rôle de l’utilisateur en administrateur"],"Change user role to participant":["Changer le rôle de l’utilisateur en participant"],"Write in 3rd person":["Écrire à la troisième personne"],"Grant membership to a user":["Autoriser l’utilisateur à être membre"],"Remove user's ability to post messages":["Retirer le droit d’envoyer des messages"],"Change your nickname":["Changer votre alias"],"Grant moderator role to user":["Changer le rôle de l’utilisateur en modérateur"],"Revoke user's membership":["Révoquer l’utilisateur des membres"],"Allow muted user to post messages":["Autoriser les utilisateurs muets à poster des messages"],"The nickname you chose is reserved or currently in use, please choose a different one.":["L’alias choisi est réservé ou actuellment utilisé, veuillez en choisir un différent."],"Please choose your nickname":["Veuillez choisir votre alias"],"Password: ":["Mot de passe : "],"Submit":["Soumettre"],"This action was done by %1$s.":["Cette action a été réalisée par %1$s."],"The reason given is: \"%1$s\".":["La raison indiquée est : « %1$s »."],"No nickname was specified.":["Aucun alias n’a été indiqué."],"Remote server not found":[""],"Topic set by %1$s":["Le sujet a été défini par %1$s"],"Click to mention %1$s in your message.":["Cliquer pour citer %1$s dans votre message."],"This user is a moderator.":["Cet utilisateur est un modérateur."],"Moderator":["Modérateur"],"Visitor":["Visiteur"],"Owner":["Propriétaire"],"Member":["Membre"],"Admin":["Administrateur"],"Participants":[""],"Invite":["Inviter"],"Please enter a valid XMPP username":["Veuillez saisir un identifiant utilisateur XMPP valide"],"Notification from %1$s":["Notification depuis %1$s"],"%1$s says":["%1$s dit"],"has gone offline":["s’est déconnecté"],"is busy":["est occupé"],"has come online":["s’est déconnecté"],"wants to be your contact":["veut être votre contact"],"Log in with %1$s":[""],"Your Profile":["Votre profil"],"XMPP Address (JID)":["Adresse XMPP (JID)"],"Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.":["Utilisez une virgule pour séparer plusieurs rôles. Vos rôles sont affichés à côté de votre nom dans vos messages."],"Your avatar image":["Votre image d’avatar"],"Sorry, an error happened while trying to save your profile data.":["Désolé, quelque chose s’est mal passé pendant la sauvegarde de votre profil."],"You can check your browser's developer console for any error output.":["Vous pouvez surveiller toute erreur qui apparaîtrait dans la console de développement de votre navigateur."],"Away":["Absent"],"Busy":["Occupé"],"Custom status":["Statut personnel"],"Offline":["Déconnecté"],"Online":["En ligne"],"Away for long":["Absent pour une longue durée"],"Change chat status":["changer votre statut de chat"],"Personal status message":["Message de statut personnel"],"I am %1$s":["Je suis %1$s"],"Change settings":["Changer les préférences"],"Click to change your chat status":["Cliquez pour changer votre statut"],"Log out":["Se déconnecter"],"Your profile":["Votre profil"],"Are you sure you want to log out?":["Voulez-vous vraiment vous déconnecter ?"],"online":["en ligne"],"busy":["occupé"],"away for long":["absent pour une longue durée"],"away":["absent"],"offline":["Déconnecté"]," e.g. conversejs.org":[" par exemple conversejs.org"],"Fetch registration form":["Récupération du formulaire d’enregistrement"],"Tip: A list of public XMPP providers is available":["Astuce : une liste publique de fournisseurs XMPP est disponible"],"here":["ici"],"Sorry, we're unable to connect to your chosen provider.":["Désolé, nous n’avons pas pu nous connecter à votre fournisseur."],"Sorry, the given provider does not support in band account registration. Please try with a different provider.":["Désolé, le fournisseur indiqué ne supporte pas l’enregistrement de compte en ligne. Merci d’essayer avec un autre fournisseur."],"Something went wrong while establishing a connection with \"%1$s\". Are you sure it exists?":["Quelque chose a échoué lors de l’établissement de la connexion avec « %1$s ». Existe-t-il vraiment ?"],"Now logging you in":["En cours de connexion"],"Registered successfully":["Enregistré avec succès"],"The provider rejected your registration attempt. Please check the values you entered for correctness.":["Le fournisseur a rejeté votre demande d’inscription. Merci de vérifier que les données que vous avez fournies sont correctes."],"Open Groupchats":[""],"Sorry, there was an error while trying to add %1$s as a contact.":["Désolé, il y a eu une erreur lors de la tentative d’ajout de %1$s comme contact."],"This client does not allow presence subscriptions":["Ce client ne permet pas les mises à jour de disponibilité"],"Click to hide these contacts":["Cliquez pour cacher ces contacts"],"This contact is busy":["Ce contact est occupé"],"This contact is online":["Ce contact est connecté"],"This contact is offline":["Ce contact est déconnecté"],"This contact is unavailable":["Ce contact est indisponible"],"This contact is away for an extended period":["Ce contact est absent"],"This contact is away":["Ce contact est absent"],"Groups":["Groupes"],"My contacts":["Mes contacts"],"Pending contacts":["Contacts en attente"],"Contact requests":["Demandes de contacts"],"Ungrouped":["Sans groupe"],"Contact name":["Nom du contact"],"Add a Contact":["Ajouter un contact"],"XMPP Address":["Adresse XMPP"],"name@example.org":["nom@exemple.org"],"Add":["Ajouter"],"Filter":["Filtrer"],"Filter by contact name":["Filtrer par nom de contact"],"Filter by group name":["Filtrer par nom de groupe"],"Filter by status":["Filtrer par statut"],"Any":["Aucun"],"Unread":["Non lu"],"Chatty":["Bavard"],"Extended Away":["Absence longue durée"],"Click to remove %1$s as a contact":["Cliquez pour retirer le contact %1$s"],"Click to accept the contact request from %1$s":["Cliquez pour accepter la demande d’ajout de contact de %1$s"],"Click to decline the contact request from %1$s":["Cliquez pour décliner la demande d’ajout de contact de %1$s"],"Click to chat with %1$s (JID: %2$s)":["Cliquez pour discuter avec %1$s (JID : %2$s)"],"Are you sure you want to decline this contact request?":["Voulez-vous vraiment rejeter cette demande d’ajout de contact ?"],"Contacts":["Contacts"],"Add a contact":["Ajouter un contact"],"Topic":[""],"Topic author":[""],"Features":["Caractéristiques"],"Password protected":["Protégé par mot de passe"],"Members only":["Membres uniquement"],"Persistent":["Persistant"],"Only moderators can see your XMPP username":["Seuls les modérateurs peuvent voir votre identifiant XMPP"],"Message archiving":["Archivage des messages"],"Messages are archived on the server":["Les messages sont archivés sur le serveur"],"No password":["Pas de mot de passe"],"XMPP Username:":["Nom d’utilisateur XMPP :"],"Password:":["Mot de passe :"],"password":["Mot de passe"],"This is a trusted device":[""],"To improve performance, we cache your data in this browser. Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. It's important that you explicitly log out, otherwise not all cached data might be deleted.":[""],"Click here to log in anonymously":["Cliquez ici pour se connecter anonymement"],"Don't have a chat account?":["Vous n’avez pas de compte ?"],"Create an account":["Créer un compte"],"Create your account":["Créer votre compte"],"Please enter the XMPP provider to register with:":["Veuillez saisir le fournisseur XMPP auprès duquel s’inscrire :"],"Already have a chat account?":["Vous avez déjà un compte ?"],"Log in here":["Connectez-vous ici"],"Account Registration:":["Création de compte :"],"Register":["S’inscrire"],"Choose a different provider":["Choisir un autre fournisseur"],"Hold tight, we're fetching the registration form…":["Ne bougez pas, on va chercher le formulaire d’inscription…"],"Download":["Télécharger"],"Download video file":["Télécharger le fichier vidéo"],"Download audio file":["Télécharger le fichier audio"]}}} \ No newline at end of file +{"domain":"converse","locale_data":{"converse":{"":{"domain":"converse","plural_forms":"nplurals=2; plural=n > 1;","lang":"fr"},"Bookmark this groupchat":["Mettre ce salon en marque-page"],"The name for this bookmark:":["Nom de ce marque-page :"],"Would you like this groupchat to be automatically joined upon startup?":["Voulez-vous que ce salon soit automatiquement rejoint au démarrage ?"],"What should your nickname for this groupchat be?":["Que devrait être votre pseudo sur ce salon ?"],"Save":["Sauvegarder"],"Cancel":["Annuler"],"Are you sure you want to remove the bookmark \"%1$s\"?":["Voulez-vous vraiment supprimer le marque-page « %1$s » ?"],"Sorry, something went wrong while trying to save your bookmark.":["Désolé, quelque chose s’est mal passé pendant la sauvegarde de ce marque-page."],"Leave this groupchat":["Quitter ce salon"],"Remove this bookmark":["Supprimer ce marque-page"],"Unbookmark this groupchat":["Retirer ce salon des marque-pages"],"Show more information on this groupchat":["Afficher davantage d’informations sur ce salon"],"Click to open this groupchat":["Cliquer pour ouvrir ce salon"],"Click to toggle the bookmarks list":["Cliquer pour ouvrir la liste des salons"],"Bookmarks":["Marques-page"],"Sorry, could not determine file upload URL.":["Désolé, impossible de déterminer l’URL pour envoyer le fichier."],"Sorry, could not determine upload URL.":["Désolé, impossible de déterminer l’URL d’envoi de fichier."],"Sorry, could not succesfully upload your file. Your server’s response: \"%1$s\"":["Désolé, l’envoi de fichier a échoué. Votre serveur a répondu : « %1$s »"],"Sorry, could not succesfully upload your file.":["Désolé, l’envoi de fichier a échoué."],"Sorry, looks like file upload is not supported by your server.":["Désolé, votre serveur semble ne pas proposer l’envoi de fichier."],"The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.":["La taille de votre fichier, %1$s, dépasse le maximum autorisé par votre serveur, qui est %2$s."],"Sorry, an error occurred:":["Désolé, une erreur s’est produite :"],"Close this chat box":["Fermer cette fenêtre de discussion"],"The User's Profile Image":["Image de profil de l’utilisateur"],"Close":["Fermer"],"Email":["E-mail"],"Full Name":["Nom complet"],"Jabber ID":["Identifiant Jabber"],"Nickname":["Alias"],"Remove as contact":["Supprimer ce contact"],"Refresh":["Rafraîchir"],"Role":["Rôle"],"URL":["URL"],"Are you sure you want to remove this contact?":["Voulez-vous vraiment retirer ce contact ?"],"Error":["Erreur"],"Sorry, there was an error while trying to remove %1$s as a contact.":["Désolé, il y a eu une erreur lors de la tentative de retrait de %1$s comme contact."],"You have unread messages":["Vous avez de nouveaux messages"],"Hidden message":["Message caché"],"Personal message":["Message personnel"],"Send":["Envoyer"],"Optional hint":["Indice optionnel"],"Choose a file to send":["Choisir un fichier à envoyer"],"Click to write as a normal (non-spoiler) message":["Cliquez pour écrire un message sans spoiler"],"Click to write your message as a spoiler":["Cliquez pour écrire votre message en tant que spoiler"],"Clear all messages":["Supprimer tous les messages"],"Insert emojis":["Insérer un emoji"],"Start a call":["Démarrer un appel"],"Remove messages":["Effacer les messages"],"Write in the third person":["Écrire à la troisième personne"],"Show this menu":["Afficher ce menu"],"Are you sure you want to clear the messages from this conversation?":["Voulez-vous vraiment effacer les messages de cette conversation ?"],"%1$s has gone offline":["%1$s s’est déconnecté"],"%1$s has gone away":["%1$s n’est plus disponible"],"%1$s is busy":["%1$s est occupé"],"%1$s is online":["%1$s est en ligne"],"Username":["Nom"],"user@domain":["utilisateur@domaine"],"Please enter a valid XMPP address":["Veuillez saisir une adresse XMPP valide"],"Chat Contacts":["Contacts de chat"],"Toggle chat":["Ouvrir la discussion"],"The connection has dropped, attempting to reconnect.":["La connexion a été perdue, tentative de reconnexion en cours."],"An error occurred while connecting to the chat server.":["Une erreur est survenue lors de la connexion au serveur de discussion."],"Your Jabber ID and/or password is incorrect. Please try again.":["Votre identifiant Jabber ou votre mot de passe est incorrect. Veuillez réessayer."],"Sorry, we could not connect to the XMPP host with domain: %1$s":["Désolé, nous n’avons pas pu nous connecter à l’hôte XMPP avec le domaine : %1$s"],"The XMPP server did not offer a supported authentication mechanism":["Le serveur XMPP n’a pas proposé un mécanisme d’authentification pris en charge"],"Show more":["Afficher plus"],"Typing from another device":["En train d’écrire depuis un autre client"],"%1$s is typing":["%1$s est en train d’écrire"],"Stopped typing on the other device":["A arrêté d’écrire sur l’autre client"],"%1$s has stopped typing":["%1$s a arrêté d’écrire"],"Minimize this chat box":["Réduire cette fenêtre de discussion"],"Click to restore this chat":["Cliquez pour afficher cette discussion"],"Minimized":["Réduit(s)"],"This groupchat is not anonymous":["Ce salon n’est pas anonyme"],"This groupchat now shows unavailable members":["Ce salon affiche maintenant les membres indisponibles"],"This groupchat does not show unavailable members":["Ce salon n’affiche pas les membres indisponibles"],"The groupchat configuration has changed":["Les paramètres de ce salon ont été modifiés"],"groupchat logging is now enabled":["L’enregistrement des logs de ce salon est maintenant activé"],"groupchat logging is now disabled":["L’enregistrement des logs de ce salon est maintenant désactivé"],"This groupchat is now no longer anonymous":["Ce salon n’est plus anonyme"],"This groupchat is now semi-anonymous":["Ce salon est maintenant semi-anonyme"],"This groupchat is now fully-anonymous":["Ce salon est maintenant entièrement anonyme"],"A new groupchat has been created":["Un nouveau salon a été créé"],"You have been banned from this groupchat":["Vous avez été banni de ce salon"],"You have been kicked from this groupchat":["Vous avez été expulsé de ce salon"],"You have been removed from this groupchat because of an affiliation change":["Vous avez été retiré de ce salon du fait d’un changement d’affiliation"],"You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member":["Vous avez été retiré de ce salon parce que ce salon est devenu réservé aux membres et vous n’êtes pas membre"],"You have been removed from this groupchat because the MUC (Multi-user chat) service is being shut down":["Vous avez été retiré de ce salon parce que le service sur lequel il est hébergé est en cours d’extinction"],"%1$s has been banned":["%1$s a été banni"],"%1$s's nickname has changed":["L’alias de %1$s a changé"],"%1$s has been kicked out":["%1$s a été expulsé"],"%1$s has been removed because of an affiliation change":["%1$s a été supprimé à cause d’un changement d’affiliation"],"%1$s has been removed for not being a member":["%1$s a été supprimé car il n’est pas membre"],"Your nickname has been automatically set to %1$s":["Votre alias a été automatiquement défini à : %1$s"],"Your nickname has been changed to %1$s":["Votre alias a été modifié en : %1$s"],"Description:":["Description :"],"Groupchat Address (JID):":["Adresse du salon (JID) :"],"Participants:":["Participants :"],"Features:":["Caractéristiques :"],"Requires authentication":["Nécessite une authentification"],"Hidden":["Caché"],"Requires an invitation":["Nécessite une invitation"],"Moderated":["Modéré"],"Non-anonymous":["Non-anonyme"],"Open":["Ouvert"],"Permanent":["Permanent"],"Public":["Public"],"Semi-anonymous":["Semi-anonyme"],"Temporary":["Temporaire"],"Unmoderated":["Non modéré"],"Query for Groupchats":["Chercher un salon"],"Server address":["Adresse du serveur"],"Show groupchats":["Afficher les salons"],"conference.example.org":["chat.exemple.org"],"No groupchats found":["Aucun salon trouvé"],"groupchats found:":["Salons trouvés :"],"Enter a new Groupchat":["Entrer dans un nouveau salon"],"Groupchat address":["Adresse du salon"],"Optional nickname":["Pseudonyme optionnel"],"name@conference.example.org":["nom@chat.example.org"],"Join":["Rejoindre"],"Groupchat info for %1$s":["Informations sur le salon %1$s"],"Message":["Message"],"%1$s is no longer a moderator":["%1$s n’est plus un modérateur"],"%1$s has been given a voice again":["%1$s peut de nouveau parler"],"%1$s has been muted":["%1$s ne peut plus parler"],"%1$s is now a moderator":["%1$s est désormais un modérateur"],"Close and leave this groupchat":["Fermer et quitter ce salon"],"Configure this groupchat":["Configurer ce salon"],"Show more details about this groupchat":["Afficher davantage d’informations sur ce salon"],"Hide the list of participants":["Cacher la liste des participants"],"Error: the \"%1$s\" command takes two arguments, the user's nickname and optionally a reason.":["Erreur : la commande « %1$s » prend deux paramètres, le pseudo de l’utilisateur et une raison optionnelle."],"Sorry, an error happened while running the command. Check your browser's developer console for details.":["Désolé, une erreur s'est produite lors de l'exécution de la commande. Vérifiez la console de développement de votre navigateur pour plus de détails."],"Change user's affiliation to admin":["Changer le rôle de l’utilisateur en administrateur"],"Ban user from groupchat":["Bannir l’utilisateur du salon"],"Change user role to participant":["Changer le rôle de l’utilisateur en participant"],"Kick user from groupchat":["Expulser l’utilisateur du salon"],"Write in 3rd person":["Écrire à la troisième personne"],"Grant membership to a user":["Autoriser l’utilisateur à être membre"],"Remove user's ability to post messages":["Retirer le droit d’envoyer des messages"],"Change your nickname":["Changer votre alias"],"Grant moderator role to user":["Changer le rôle de l’utilisateur en modérateur"],"Grant ownership of this groupchat":["Accorder la propriété à ce salon"],"Revoke user's membership":["Révoquer l’utilisateur des membres"],"Set groupchat subject":["Définir le sujet du salon"],"Set groupchat subject (alias for /subject)":["Définir le sujet du salon (alias pour /subject)"],"Allow muted user to post messages":["Autoriser les utilisateurs muets à poster des messages"],"The nickname you chose is reserved or currently in use, please choose a different one.":["L’alias choisi est réservé ou actuellment utilisé, veuillez en choisir un différent."],"Please choose your nickname":["Veuillez choisir votre alias"],"Enter groupchat":["Entrer dans le salon"],"This groupchat requires a password":["Ce salon nécessite un mot de passe"],"Password: ":["Mot de passe : "],"Submit":["Soumettre"],"This action was done by %1$s.":["Cette action a été réalisée par %1$s."],"The reason given is: \"%1$s\".":["La raison indiquée est : « %1$s »."],"%1$s has left and re-entered the groupchat":["%1$s a quitté puis rejoint le salon"],"%1$s has entered the groupchat":["%1$s a rejoint le salon"],"%1$s has entered the groupchat. \"%2$s\"":["%1$s a rejoint le salon. « %2$s »"],"%1$s has entered and left the groupchat":["%1$s a rejoint puis quitté le salon"],"%1$s has entered and left the groupchat. \"%2$s\"":["%1$s a rejoint puis quitté le salon. « %2$s »"],"%1$s has left the groupchat":["%1$s a quitté le salon"],"%1$s has left the groupchat. \"%2$s\"":["%1$s a quitté le salon. « %2$s »"],"You are not on the member list of this groupchat.":["Vous n’êtes pas dans la liste des membres de ce salon."],"You have been banned from this groupchat.":["Vous avez été banni de ce salon."],"No nickname was specified.":["Aucun alias n’a été indiqué."],"You are not allowed to create new groupchats.":["Vous n’êtes pas autorisé à créer des salons."],"Your nickname doesn't conform to this groupchat's policies.":["Votre pseudo n’est pas conforme à la politique de ce salon."],"This groupchat does not (yet) exist.":["Ce salon n’existe pas (pour l’instant)."],"This groupchat has reached its maximum number of participants.":["Ce salon a atteint sa limite maximale d’occupants."],"Remote server not found":["Serveur distant introuvable"],"The explanation given is: \"%1$s\".":["La raison indiquée est : « %1$s »."],"Topic set by %1$s":["Le sujet a été défini par %1$s"],"Groupchats":["Salons"],"Add a new groupchat":["Ajouter un nouveau salon"],"Query for groupchats":["Chercher un salon"],"Click to mention %1$s in your message.":["Cliquer pour citer %1$s dans votre message."],"This user is a moderator.":["Cet utilisateur est un modérateur."],"This user can send messages in this groupchat.":["Cet utilisateur peut envoyer des messages dans ce salon."],"This user can NOT send messages in this groupchat.":["Cet utilisateur ne peut PAS envoyer de messages dans ce salon."],"Moderator":["Modérateur"],"Visitor":["Visiteur"],"Owner":["Propriétaire"],"Member":["Membre"],"Admin":["Administrateur"],"Participants":["Participants"],"Invite":["Inviter"],"You are about to invite %1$s to the groupchat \"%2$s\". You may optionally include a message, explaining the reason for the invitation.":["Vous allez inviter %1$s dans le salon %2$s. Vous pouvez facultativement ajouter un message expliquant la raison de cette invitation."],"Please enter a valid XMPP username":["Veuillez saisir un identifiant utilisateur XMPP valide"],"%1$s has invited you to join a groupchat: %2$s":["%1$s vous invite à rejoindre le salon : %2$s"],"%1$s has invited you to join a groupchat: %2$s, and left the following reason: \"%3$s\"":["%1$s vous invite à rejoindre le salon : %2$s, avec le message suivant: « %3$s »"],"Notification from %1$s":["Notification depuis %1$s"],"%1$s says":["%1$s dit"],"has gone offline":["s’est déconnecté"],"has gone away":["est absent"],"is busy":["est occupé"],"has come online":["s’est déconnecté"],"wants to be your contact":["veut être votre contact"],"Log in with %1$s":["Se connecter avec %1$s"],"Your Profile":["Votre profil"],"XMPP Address (JID)":["Adresse XMPP (JID)"],"Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.":["Utilisez une virgule pour séparer plusieurs rôles. Vos rôles sont affichés à côté de votre nom dans vos messages."],"Your avatar image":["Votre image d’avatar"],"Sorry, an error happened while trying to save your profile data.":["Désolé, quelque chose s’est mal passé pendant la sauvegarde de votre profil."],"You can check your browser's developer console for any error output.":["Vous pouvez surveiller toute erreur qui apparaîtrait dans la console de développement de votre navigateur."],"Away":["Absent"],"Busy":["Occupé"],"Custom status":["Statut personnel"],"Offline":["Déconnecté"],"Online":["En ligne"],"Away for long":["Absent pour une longue durée"],"Change chat status":["changer votre statut de chat"],"Personal status message":["Message de statut personnel"],"I am %1$s":["Je suis %1$s"],"Change settings":["Changer les préférences"],"Click to change your chat status":["Cliquez pour changer votre statut"],"Log out":["Se déconnecter"],"Your profile":["Votre profil"],"Are you sure you want to log out?":["Voulez-vous vraiment vous déconnecter ?"],"online":["en ligne"],"busy":["occupé"],"away for long":["absent pour une longue durée"],"away":["absent"],"offline":["Déconnecté"]," e.g. conversejs.org":[" par exemple conversejs.org"],"Fetch registration form":["Récupération du formulaire d’enregistrement"],"Tip: A list of public XMPP providers is available":["Astuce : une liste publique de fournisseurs XMPP est disponible"],"here":["ici"],"Sorry, we're unable to connect to your chosen provider.":["Désolé, nous n’avons pas pu nous connecter à votre fournisseur."],"Sorry, the given provider does not support in band account registration. Please try with a different provider.":["Désolé, le fournisseur indiqué ne supporte pas l’enregistrement de compte en ligne. Merci d’essayer avec un autre fournisseur."],"Something went wrong while establishing a connection with \"%1$s\". Are you sure it exists?":["Quelque chose a échoué lors de l’établissement de la connexion avec « %1$s ». Existe-t-il vraiment ?"],"Now logging you in":["En cours de connexion"],"Registered successfully":["Enregistré avec succès"],"The provider rejected your registration attempt. Please check the values you entered for correctness.":["Le fournisseur a rejeté votre demande d’inscription. Merci de vérifier que les données que vous avez fournies sont correctes."],"Click to toggle the list of open groupchats":["Cliquer pour ouvrir la liste des salons ouverts"],"Open Groupchats":["Ouvrir les salons"],"Are you sure you want to leave the groupchat %1$s?":["Voulez-vous vraiment quitter le salon « %1$s » ?"],"Sorry, there was an error while trying to add %1$s as a contact.":["Désolé, il y a eu une erreur lors de la tentative d’ajout de %1$s comme contact."],"This client does not allow presence subscriptions":["Ce client ne permet pas les mises à jour de disponibilité"],"Click to hide these contacts":["Cliquez pour cacher ces contacts"],"This contact is busy":["Ce contact est occupé"],"This contact is online":["Ce contact est connecté"],"This contact is offline":["Ce contact est déconnecté"],"This contact is unavailable":["Ce contact est indisponible"],"This contact is away for an extended period":["Ce contact est absent"],"This contact is away":["Ce contact est absent"],"Groups":["Groupes"],"My contacts":["Mes contacts"],"Pending contacts":["Contacts en attente"],"Contact requests":["Demandes de contacts"],"Ungrouped":["Sans groupe"],"Contact name":["Nom du contact"],"Add a Contact":["Ajouter un contact"],"XMPP Address":["Adresse XMPP"],"name@example.org":["nom@exemple.org"],"Add":["Ajouter"],"Filter":["Filtrer"],"Filter by contact name":["Filtrer par nom de contact"],"Filter by group name":["Filtrer par nom de groupe"],"Filter by status":["Filtrer par statut"],"Any":["Aucun"],"Unread":["Non lu"],"Chatty":["Bavard"],"Extended Away":["Absence longue durée"],"Click to remove %1$s as a contact":["Cliquez pour retirer le contact %1$s"],"Click to accept the contact request from %1$s":["Cliquez pour accepter la demande d’ajout de contact de %1$s"],"Click to decline the contact request from %1$s":["Cliquez pour décliner la demande d’ajout de contact de %1$s"],"Click to chat with %1$s (JID: %2$s)":["Cliquez pour discuter avec %1$s (JID : %2$s)"],"Are you sure you want to decline this contact request?":["Voulez-vous vraiment rejeter cette demande d’ajout de contact ?"],"Contacts":["Contacts"],"Add a contact":["Ajouter un contact"],"Name":["Nom"],"Groupchat address (JID)":["Adresse du salon (JID) :"],"Description":["Description"],"Topic":["Sujet"],"Topic author":["Auteur du sujet"],"Online users":["Utilisateurs en ligne"],"Features":["Caractéristiques"],"Password protected":["Protégé par mot de passe"],"This groupchat requires a password before entry":["Ce salon nécessite un mot de passe pour y accéder"],"No password required":["Pas de mot de passe nécessaire"],"This groupchat does not require a password upon entry":["Ce salon ne nécessite pas de mot de passe pour y accéder"],"This groupchat is not publicly searchable":["Ce salon ne peut pas être recherché publiquement"],"This groupchat is publicly searchable":["Ce salon peut être recherché publiquement"],"Members only":["Membres uniquement"],"This groupchat is restricted to members only":["Ce salon est restreint aux membres uniquement"],"Anyone can join this groupchat":["N’importe qui peut rejoindre ce salon"],"Persistent":["Persistant"],"This groupchat persists even if it's unoccupied":["Ce salon persiste même s'il est inoccupé"],"This groupchat will disappear once the last person leaves":["Ce salon disparaîtra au départ de la dernière personne"],"Not anonymous":["Non-anonyme"],"All other groupchat participants can see your XMPP username":["Tous les autres occupants de ce salon peuvent voir votre nom d’utilisateur XMPP"],"Only moderators can see your XMPP username":["Seuls les modérateurs peuvent voir votre identifiant XMPP"],"This groupchat is being moderated":["Ce salon est modéré"],"Not moderated":["Non modéré"],"This groupchat is not being moderated":["Ce salon n’est pas modéré"],"Message archiving":["Archivage des messages"],"Messages are archived on the server":["Les messages sont archivés sur le serveur"],"No password":["Pas de mot de passe"],"this groupchat is restricted to members only":["ce salon est restreint aux membres uniquement"],"XMPP Username:":["Nom d’utilisateur XMPP :"],"Password:":["Mot de passe :"],"password":["Mot de passe"],"This is a trusted device":["Ceci est un appareil de confiance"],"To improve performance, we cache your data in this browser. Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. It's important that you explicitly log out, otherwise not all cached data might be deleted.":["Pour améliorer les performances, nous stockons vos données dans ce navigateur. Décochez ce bouton si vous êtes sur un ordinateur public, ou si vous voulez que vos données soient supprimées lorsque vous vous déconnecterez. Il est important que vous vous déconnectiez explicitement, sinon toutes les données stockées ne seront pas forcément supprimées."],"Log in":["Se connecter"],"Click here to log in anonymously":["Cliquez ici pour se connecter anonymement"],"This message has been edited":["Ce message a été édité"],"Edit this message":["Éditer ce message"],"Message versions":["Versions du message"],"Don't have a chat account?":["Vous n’avez pas de compte ?"],"Create an account":["Créer un compte"],"Create your account":["Créer votre compte"],"Please enter the XMPP provider to register with:":["Veuillez saisir le fournisseur XMPP auprès duquel s’inscrire :"],"Already have a chat account?":["Vous avez déjà un compte ?"],"Log in here":["Connectez-vous ici"],"Account Registration:":["Création de compte :"],"Register":["S’inscrire"],"Choose a different provider":["Choisir un autre fournisseur"],"Hold tight, we're fetching the registration form…":["Ne bougez pas, on va chercher le formulaire d’inscription…"],"Download":["Télécharger"],"Download \"%1$s\"":["Télécharger « %1$s »"],"Download video file":["Télécharger le fichier vidéo"],"Download audio file":["Télécharger le fichier audio"]}}} \ No newline at end of file diff --git a/locale/fr/LC_MESSAGES/converse.po b/locale/fr/LC_MESSAGES/converse.po index 783f11752..1e3730801 100644 --- a/locale/fr/LC_MESSAGES/converse.po +++ b/locale/fr/LC_MESSAGES/converse.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: Converse.js 0.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-07-22 11:17+0200\n" -"PO-Revision-Date: 2018-07-22 11:50+0200\n" +"PO-Revision-Date: 2018-08-02 08:49+0000\n" "Last-Translator: Emmanuel Gil Peyrot \n" "Language-Team: French \n" @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 3.1-dev\n" +"X-Generator: Weblate 3.1.1\n" "plural_forms: nplurals=2; plural=(n != 1);\n" "lang: fr\n" "Language-Code: fr\n" @@ -27,9 +27,8 @@ msgstr "" #: dist/converse-no-dependencies.js:40690 #: dist/converse-no-dependencies.js:40775 #: dist/converse-no-dependencies.js:53689 -#, fuzzy msgid "Bookmark this groupchat" -msgstr "Marquer ce salon" +msgstr "Mettre ce salon en marque-page" #: dist/converse-no-dependencies.js:40776 msgid "The name for this bookmark:" @@ -37,64 +36,60 @@ msgstr "Nom de ce marque-page :" #: dist/converse-no-dependencies.js:40777 msgid "Would you like this groupchat to be automatically joined upon startup?" -msgstr "" +msgstr "Voulez-vous que ce salon soit automatiquement rejoint au démarrage ?" #: dist/converse-no-dependencies.js:40778 msgid "What should your nickname for this groupchat be?" -msgstr "" +msgstr "Que devrait être votre pseudo sur ce salon ?" #: dist/converse-no-dependencies.js:40780 #: dist/converse-no-dependencies.js:49483 #: dist/converse-no-dependencies.js:52484 #: dist/converse-no-dependencies.js:52568 msgid "Save" -msgstr "" +msgstr "Sauvegarder" #: dist/converse-no-dependencies.js:40781 #: dist/converse-no-dependencies.js:49484 #: dist/converse-no-dependencies.js:52564 #: dist/converse-no-dependencies.js:58864 msgid "Cancel" -msgstr "" +msgstr "Annuler" #: dist/converse-no-dependencies.js:40854 -#, fuzzy, javascript-format +#, javascript-format msgid "Are you sure you want to remove the bookmark \"%1$s\"?" -msgstr "Voulez-vous vraiment quitter le salon « %1$s » ?" +msgstr "Voulez-vous vraiment supprimer le marque-page « %1$s » ?" #: dist/converse-no-dependencies.js:40970 -#, fuzzy msgid "Sorry, something went wrong while trying to save your bookmark." msgstr "" -"Désolé, quelque chose s’est mal passé pendant la sauvegarde de votre profil." +"Désolé, quelque chose s’est mal passé pendant la sauvegarde de ce marque-" +"page." #: dist/converse-no-dependencies.js:41055 #: dist/converse-no-dependencies.js:53687 msgid "Leave this groupchat" -msgstr "" +msgstr "Quitter ce salon" #: dist/converse-no-dependencies.js:41056 -#, fuzzy msgid "Remove this bookmark" -msgstr "Nom de ce marque-page :" +msgstr "Supprimer ce marque-page" #: dist/converse-no-dependencies.js:41057 #: dist/converse-no-dependencies.js:53688 -#, fuzzy msgid "Unbookmark this groupchat" -msgstr "Marquer ce salon" +msgstr "Retirer ce salon des marque-pages" #: dist/converse-no-dependencies.js:41058 #: dist/converse-no-dependencies.js:48755 #: dist/converse-no-dependencies.js:53690 -#, fuzzy msgid "Show more information on this groupchat" msgstr "Afficher davantage d’informations sur ce salon" #: dist/converse-no-dependencies.js:41061 #: dist/converse-no-dependencies.js:48754 #: dist/converse-no-dependencies.js:53692 -#, fuzzy msgid "Click to open this groupchat" msgstr "Cliquer pour ouvrir ce salon" @@ -115,11 +110,11 @@ msgid "Sorry, could not determine upload URL." msgstr "Désolé, impossible de déterminer l’URL d’envoi de fichier." #: dist/converse-no-dependencies.js:41573 -#, fuzzy, javascript-format +#, javascript-format msgid "" "Sorry, could not succesfully upload your file. Your server’s response: \"%1$s" "\"" -msgstr "Désolé, l’envoi de fichier a échoué." +msgstr "Désolé, l’envoi de fichier a échoué. Votre serveur a répondu : « %1$s »" #: dist/converse-no-dependencies.js:41575 msgid "Sorry, could not succesfully upload your file." @@ -140,7 +135,7 @@ msgstr "" #: dist/converse-no-dependencies.js:41996 msgid "Sorry, an error occurred:" -msgstr "" +msgstr "Désolé, une erreur s’est produite :" #: dist/converse-no-dependencies.js:42538 msgid "Close this chat box" @@ -280,25 +275,25 @@ msgid "Are you sure you want to clear the messages from this conversation?" msgstr "Voulez-vous vraiment effacer les messages de cette conversation ?" #: dist/converse-no-dependencies.js:43413 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has gone offline" -msgstr "s’est déconnecté" +msgstr "%1$s s’est déconnecté" #: dist/converse-no-dependencies.js:43415 #: dist/converse-no-dependencies.js:47662 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has gone away" -msgstr "%1$s a été banni" +msgstr "%1$s n’est plus disponible" #: dist/converse-no-dependencies.js:43417 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is busy" -msgstr "est occupé" +msgstr "%1$s est occupé" #: dist/converse-no-dependencies.js:43419 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is online" -msgstr "est en ligne" +msgstr "%1$s est en ligne" #: dist/converse-no-dependencies.js:44042 msgid "Username" @@ -354,21 +349,21 @@ msgstr "Afficher plus" #: dist/converse-no-dependencies.js:47651 msgid "Typing from another device" -msgstr "" +msgstr "En train d’écrire depuis un autre client" #: dist/converse-no-dependencies.js:47653 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s is typing" -msgstr "%1$s dit" +msgstr "%1$s est en train d’écrire" #: dist/converse-no-dependencies.js:47657 msgid "Stopped typing on the other device" -msgstr "" +msgstr "A arrêté d’écrire sur l’autre client" #: dist/converse-no-dependencies.js:47659 #, javascript-format msgid "%1$s has stopped typing" -msgstr "" +msgstr "%1$s a arrêté d’écrire" #: dist/converse-no-dependencies.js:47905 #: dist/converse-no-dependencies.js:47948 @@ -384,73 +379,59 @@ msgid "Minimized" msgstr "Réduit(s)" #: dist/converse-no-dependencies.js:48597 -#, fuzzy msgid "This groupchat is not anonymous" msgstr "Ce salon n’est pas anonyme" #: dist/converse-no-dependencies.js:48598 -#, fuzzy msgid "This groupchat now shows unavailable members" msgstr "Ce salon affiche maintenant les membres indisponibles" #: dist/converse-no-dependencies.js:48599 -#, fuzzy msgid "This groupchat does not show unavailable members" msgstr "Ce salon n’affiche pas les membres indisponibles" #: dist/converse-no-dependencies.js:48600 -#, fuzzy msgid "The groupchat configuration has changed" msgstr "Les paramètres de ce salon ont été modifiés" #: dist/converse-no-dependencies.js:48601 -#, fuzzy msgid "groupchat logging is now enabled" -msgstr "Le logging du salon est activé" +msgstr "L’enregistrement des logs de ce salon est maintenant activé" #: dist/converse-no-dependencies.js:48602 -#, fuzzy msgid "groupchat logging is now disabled" -msgstr "Le logging du salon est désactivé" +msgstr "L’enregistrement des logs de ce salon est maintenant désactivé" #: dist/converse-no-dependencies.js:48603 -#, fuzzy msgid "This groupchat is now no longer anonymous" msgstr "Ce salon n’est plus anonyme" #: dist/converse-no-dependencies.js:48604 -#, fuzzy msgid "This groupchat is now semi-anonymous" msgstr "Ce salon est maintenant semi-anonyme" #: dist/converse-no-dependencies.js:48605 -#, fuzzy msgid "This groupchat is now fully-anonymous" msgstr "Ce salon est maintenant entièrement anonyme" #: dist/converse-no-dependencies.js:48606 -#, fuzzy msgid "A new groupchat has been created" msgstr "Un nouveau salon a été créé" #: dist/converse-no-dependencies.js:48609 -#, fuzzy msgid "You have been banned from this groupchat" msgstr "Vous avez été banni de ce salon" #: dist/converse-no-dependencies.js:48610 -#, fuzzy msgid "You have been kicked from this groupchat" msgstr "Vous avez été expulsé de ce salon" #: dist/converse-no-dependencies.js:48611 -#, fuzzy msgid "" "You have been removed from this groupchat because of an affiliation change" msgstr "Vous avez été retiré de ce salon du fait d’un changement d’affiliation" #: dist/converse-no-dependencies.js:48612 -#, fuzzy msgid "" "You have been removed from this groupchat because the groupchat has changed " "to members-only and you're not a member" @@ -459,13 +440,12 @@ msgstr "" "membres et vous n’êtes pas membre" #: dist/converse-no-dependencies.js:48613 -#, fuzzy msgid "" "You have been removed from this groupchat because the MUC (Multi-user chat) " "service is being shut down" msgstr "" -"Vous avez été retiré de ce salon parce que le service de chat multi-" -"utilisateur (MUC) est en cours d’extinction" +"Vous avez été retiré de ce salon parce que le service sur lequel il est " +"hébergé est en cours d’extinction" #. XXX: Note the triple underscore function and not double #. * underscore. @@ -517,12 +497,10 @@ msgid "Description:" msgstr "Description :" #: dist/converse-no-dependencies.js:48666 -#, fuzzy msgid "Groupchat Address (JID):" msgstr "Adresse du salon (JID) :" #: dist/converse-no-dependencies.js:48667 -#, fuzzy msgid "Participants:" msgstr "Participants :" @@ -561,9 +539,8 @@ msgid "Open" msgstr "Ouvert" #: dist/converse-no-dependencies.js:48675 -#, fuzzy msgid "Permanent" -msgstr "Salon permanent" +msgstr "Permanent" #: dist/converse-no-dependencies.js:48676 #: dist/converse-no-dependencies.js:57167 @@ -588,7 +565,6 @@ msgid "Unmoderated" msgstr "Non modéré" #: dist/converse-no-dependencies.js:48715 -#, fuzzy msgid "Query for Groupchats" msgstr "Chercher un salon" @@ -597,7 +573,6 @@ msgid "Server address" msgstr "Adresse du serveur" #: dist/converse-no-dependencies.js:48717 -#, fuzzy msgid "Show groupchats" msgstr "Afficher les salons" @@ -606,22 +581,18 @@ msgid "conference.example.org" msgstr "chat.exemple.org" #: dist/converse-no-dependencies.js:48767 -#, fuzzy msgid "No groupchats found" msgstr "Aucun salon trouvé" #: dist/converse-no-dependencies.js:48784 -#, fuzzy msgid "groupchats found:" msgstr "Salons trouvés :" #: dist/converse-no-dependencies.js:48836 -#, fuzzy msgid "Enter a new Groupchat" msgstr "Entrer dans un nouveau salon" #: dist/converse-no-dependencies.js:48837 -#, fuzzy msgid "Groupchat address" msgstr "Adresse du salon" @@ -639,9 +610,9 @@ msgid "Join" msgstr "Rejoindre" #: dist/converse-no-dependencies.js:48884 -#, fuzzy, javascript-format +#, javascript-format msgid "Groupchat info for %1$s" -msgstr "Notification depuis %1$s" +msgstr "Informations sur le salon %1$s" #: dist/converse-no-dependencies.js:48990 msgid "Message" @@ -668,22 +639,18 @@ msgid "%1$s is now a moderator" msgstr "%1$s est désormais un modérateur" #: dist/converse-no-dependencies.js:49056 -#, fuzzy msgid "Close and leave this groupchat" msgstr "Fermer et quitter ce salon" #: dist/converse-no-dependencies.js:49057 -#, fuzzy msgid "Configure this groupchat" msgstr "Configurer ce salon" #: dist/converse-no-dependencies.js:49058 -#, fuzzy msgid "Show more details about this groupchat" msgstr "Afficher davantage d’informations sur ce salon" #: dist/converse-no-dependencies.js:49098 -#, fuzzy msgid "Hide the list of participants" msgstr "Cacher la liste des participants" @@ -710,7 +677,6 @@ msgid "Change user's affiliation to admin" msgstr "Changer le rôle de l’utilisateur en administrateur" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Ban user from groupchat" msgstr "Bannir l’utilisateur du salon" @@ -719,7 +685,6 @@ msgid "Change user role to participant" msgstr "Changer le rôle de l’utilisateur en participant" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Kick user from groupchat" msgstr "Expulser l’utilisateur du salon" @@ -744,7 +709,6 @@ msgid "Grant moderator role to user" msgstr "Changer le rôle de l’utilisateur en modérateur" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Grant ownership of this groupchat" msgstr "Accorder la propriété à ce salon" @@ -753,14 +717,12 @@ msgid "Revoke user's membership" msgstr "Révoquer l’utilisateur des membres" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Set groupchat subject" -msgstr "Indiquer le sujet du salon" +msgstr "Définir le sujet du salon" #: dist/converse-no-dependencies.js:49282 -#, fuzzy msgid "Set groupchat subject (alias for /subject)" -msgstr "Définir le sujet de la salle (alias pour /subject)" +msgstr "Définir le sujet du salon (alias pour /subject)" #: dist/converse-no-dependencies.js:49282 msgid "Allow muted user to post messages" @@ -779,12 +741,10 @@ msgid "Please choose your nickname" msgstr "Veuillez choisir votre alias" #: dist/converse-no-dependencies.js:49640 -#, fuzzy msgid "Enter groupchat" msgstr "Entrer dans le salon" #: dist/converse-no-dependencies.js:49661 -#, fuzzy msgid "This groupchat requires a password" msgstr "Ce salon nécessite un mot de passe" @@ -808,47 +768,45 @@ msgid "The reason given is: \"%1$s\"." msgstr "La raison indiquée est : « %1$s »." #: dist/converse-no-dependencies.js:49828 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has left and re-entered the groupchat" msgstr "%1$s a quitté puis rejoint le salon" #: dist/converse-no-dependencies.js:49834 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has entered the groupchat" msgstr "%1$s a rejoint le salon" #: dist/converse-no-dependencies.js:49836 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has entered the groupchat. \"%2$s\"" msgstr "%1$s a rejoint le salon. « %2$s »" #: dist/converse-no-dependencies.js:49867 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has entered and left the groupchat" msgstr "%1$s a rejoint puis quitté le salon" #: dist/converse-no-dependencies.js:49869 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has entered and left the groupchat. \"%2$s\"" msgstr "%1$s a rejoint puis quitté le salon. « %2$s »" #: dist/converse-no-dependencies.js:49882 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has left the groupchat" msgstr "%1$s a quitté le salon" #: dist/converse-no-dependencies.js:49884 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has left the groupchat. \"%2$s\"" msgstr "%1$s a quitté le salon. « %2$s »" #: dist/converse-no-dependencies.js:49930 -#, fuzzy msgid "You are not on the member list of this groupchat." msgstr "Vous n’êtes pas dans la liste des membres de ce salon." #: dist/converse-no-dependencies.js:49932 -#, fuzzy msgid "You have been banned from this groupchat." msgstr "Vous avez été banni de ce salon." @@ -857,31 +815,27 @@ msgid "No nickname was specified." msgstr "Aucun alias n’a été indiqué." #: dist/converse-no-dependencies.js:49940 -#, fuzzy msgid "You are not allowed to create new groupchats." msgstr "Vous n’êtes pas autorisé à créer des salons." #: dist/converse-no-dependencies.js:49942 -#, fuzzy msgid "Your nickname doesn't conform to this groupchat's policies." -msgstr "Votre alias n’est pas conforme à la politique de ce salon." +msgstr "Votre pseudo n’est pas conforme à la politique de ce salon." #: dist/converse-no-dependencies.js:49946 -#, fuzzy msgid "This groupchat does not (yet) exist." msgstr "Ce salon n’existe pas (pour l’instant)." #: dist/converse-no-dependencies.js:49948 -#, fuzzy msgid "This groupchat has reached its maximum number of participants." msgstr "Ce salon a atteint sa limite maximale d’occupants." #: dist/converse-no-dependencies.js:49950 msgid "Remote server not found" -msgstr "" +msgstr "Serveur distant introuvable" #: dist/converse-no-dependencies.js:49955 -#, fuzzy, javascript-format +#, javascript-format msgid "The explanation given is: \"%1$s\"." msgstr "La raison indiquée est : « %1$s »." @@ -891,17 +845,14 @@ msgid "Topic set by %1$s" msgstr "Le sujet a été défini par %1$s" #: dist/converse-no-dependencies.js:50031 -#, fuzzy msgid "Groupchats" -msgstr "Groupes" +msgstr "Salons" #: dist/converse-no-dependencies.js:50032 -#, fuzzy msgid "Add a new groupchat" msgstr "Ajouter un nouveau salon" #: dist/converse-no-dependencies.js:50033 -#, fuzzy msgid "Query for groupchats" msgstr "Chercher un salon" @@ -915,12 +866,10 @@ msgid "This user is a moderator." msgstr "Cet utilisateur est un modérateur." #: dist/converse-no-dependencies.js:50073 -#, fuzzy msgid "This user can send messages in this groupchat." msgstr "Cet utilisateur peut envoyer des messages dans ce salon." #: dist/converse-no-dependencies.js:50074 -#, fuzzy msgid "This user can NOT send messages in this groupchat." msgstr "Cet utilisateur ne peut PAS envoyer de messages dans ce salon." @@ -946,7 +895,7 @@ msgstr "Administrateur" #: dist/converse-no-dependencies.js:50121 msgid "Participants" -msgstr "" +msgstr "Participants" #: dist/converse-no-dependencies.js:50138 #: dist/converse-no-dependencies.js:50219 @@ -954,31 +903,31 @@ msgid "Invite" msgstr "Inviter" #: dist/converse-no-dependencies.js:50196 -#, fuzzy, javascript-format +#, javascript-format msgid "" "You are about to invite %1$s to the groupchat \"%2$s\". You may optionally " "include a message, explaining the reason for the invitation." msgstr "" "Vous allez inviter %1$s dans le salon %2$s. Vous pouvez facultativement " -"ajouter un message, expliquant la raison de cette invitation." +"ajouter un message expliquant la raison de cette invitation." #: dist/converse-no-dependencies.js:50218 msgid "Please enter a valid XMPP username" msgstr "Veuillez saisir un identifiant utilisateur XMPP valide" #: dist/converse-no-dependencies.js:51591 -#, fuzzy, javascript-format +#, javascript-format msgid "%1$s has invited you to join a groupchat: %2$s" msgstr "%1$s vous invite à rejoindre le salon : %2$s" #: dist/converse-no-dependencies.js:51593 -#, fuzzy, javascript-format +#, javascript-format msgid "" "%1$s has invited you to join a groupchat: %2$s, and left the following " "reason: \"%3$s\"" msgstr "" -"%1$s vous invite à rejoindre le salon : %2$s, avec le message suivant: " -"« %3$s »" +"%1$s vous invite à rejoindre le salon : %2$s, avec le message suivant: « %3$" +"s »" #. workaround for Prosody which doesn't give type "headline" #: dist/converse-no-dependencies.js:51974 @@ -999,9 +948,8 @@ msgid "has gone offline" msgstr "s’est déconnecté" #: dist/converse-no-dependencies.js:52026 -#, fuzzy msgid "has gone away" -msgstr "s’est déconnecté" +msgstr "est absent" #: dist/converse-no-dependencies.js:52028 msgid "is busy" @@ -1018,7 +966,7 @@ msgstr "veut être votre contact" #: dist/converse-no-dependencies.js:52229 #, javascript-format msgid "Log in with %1$s" -msgstr "" +msgstr "Se connecter avec %1$s" #: dist/converse-no-dependencies.js:52476 msgid "Your Profile" @@ -1187,16 +1135,15 @@ msgstr "" "les données que vous avez fournies sont correctes." #: dist/converse-no-dependencies.js:53748 -#, fuzzy msgid "Click to toggle the list of open groupchats" -msgstr "Cliquer pour ouvrir la liste des salons" +msgstr "Cliquer pour ouvrir la liste des salons ouverts" #: dist/converse-no-dependencies.js:53749 msgid "Open Groupchats" -msgstr "" +msgstr "Ouvrir les salons" #: dist/converse-no-dependencies.js:53793 -#, fuzzy, javascript-format +#, javascript-format msgid "Are you sure you want to leave the groupchat %1$s?" msgstr "Voulez-vous vraiment quitter le salon « %1$s » ?" @@ -1345,32 +1292,28 @@ msgid "Add a contact" msgstr "Ajouter un contact" #: dist/converse-no-dependencies.js:57111 -#, fuzzy msgid "Name" -msgstr "Nom complet" +msgstr "Nom" #: dist/converse-no-dependencies.js:57115 -#, fuzzy msgid "Groupchat address (JID)" msgstr "Adresse du salon (JID) :" #: dist/converse-no-dependencies.js:57119 -#, fuzzy msgid "Description" -msgstr "Description :" +msgstr "Description" #: dist/converse-no-dependencies.js:57125 msgid "Topic" -msgstr "" +msgstr "Sujet" #: dist/converse-no-dependencies.js:57129 msgid "Topic author" -msgstr "" +msgstr "Auteur du sujet" #: dist/converse-no-dependencies.js:57135 -#, fuzzy msgid "Online users" -msgstr "En ligne" +msgstr "Utilisateurs en ligne" #: dist/converse-no-dependencies.js:57139 #: dist/converse-no-dependencies.js:57291 @@ -1384,30 +1327,25 @@ msgstr "Protégé par mot de passe" #: dist/converse-no-dependencies.js:57145 #: dist/converse-no-dependencies.js:57297 -#, fuzzy msgid "This groupchat requires a password before entry" msgstr "Ce salon nécessite un mot de passe pour y accéder" #: dist/converse-no-dependencies.js:57151 -#, fuzzy msgid "No password required" -msgstr "Pas de mot de passe" +msgstr "Pas de mot de passe nécessaire" #: dist/converse-no-dependencies.js:57153 #: dist/converse-no-dependencies.js:57305 -#, fuzzy msgid "This groupchat does not require a password upon entry" msgstr "Ce salon ne nécessite pas de mot de passe pour y accéder" #: dist/converse-no-dependencies.js:57161 #: dist/converse-no-dependencies.js:57313 -#, fuzzy msgid "This groupchat is not publicly searchable" msgstr "Ce salon ne peut pas être recherché publiquement" #: dist/converse-no-dependencies.js:57169 #: dist/converse-no-dependencies.js:57321 -#, fuzzy msgid "This groupchat is publicly searchable" msgstr "Ce salon peut être recherché publiquement" @@ -1417,13 +1355,11 @@ msgid "Members only" msgstr "Membres uniquement" #: dist/converse-no-dependencies.js:57177 -#, fuzzy msgid "This groupchat is restricted to members only" msgstr "Ce salon est restreint aux membres uniquement" #: dist/converse-no-dependencies.js:57185 #: dist/converse-no-dependencies.js:57337 -#, fuzzy msgid "Anyone can join this groupchat" msgstr "N’importe qui peut rejoindre ce salon" @@ -1434,25 +1370,21 @@ msgstr "Persistant" #: dist/converse-no-dependencies.js:57193 #: dist/converse-no-dependencies.js:57345 -#, fuzzy msgid "This groupchat persists even if it's unoccupied" msgstr "Ce salon persiste même s'il est inoccupé" #: dist/converse-no-dependencies.js:57201 #: dist/converse-no-dependencies.js:57353 -#, fuzzy msgid "This groupchat will disappear once the last person leaves" msgstr "Ce salon disparaîtra au départ de la dernière personne" #: dist/converse-no-dependencies.js:57207 #: dist/converse-no-dependencies.js:57363 -#, fuzzy msgid "Not anonymous" msgstr "Non-anonyme" #: dist/converse-no-dependencies.js:57209 #: dist/converse-no-dependencies.js:57361 -#, fuzzy msgid "All other groupchat participants can see your XMPP username" msgstr "" "Tous les autres occupants de ce salon peuvent voir votre nom d’utilisateur " @@ -1465,19 +1397,16 @@ msgstr "Seuls les modérateurs peuvent voir votre identifiant XMPP" #: dist/converse-no-dependencies.js:57225 #: dist/converse-no-dependencies.js:57377 -#, fuzzy msgid "This groupchat is being moderated" msgstr "Ce salon est modéré" #: dist/converse-no-dependencies.js:57231 #: dist/converse-no-dependencies.js:57387 -#, fuzzy msgid "Not moderated" msgstr "Non modéré" #: dist/converse-no-dependencies.js:57233 #: dist/converse-no-dependencies.js:57385 -#, fuzzy msgid "This groupchat is not being moderated" msgstr "Ce salon n’est pas modéré" @@ -1496,9 +1425,8 @@ msgid "No password" msgstr "Pas de mot de passe" #: dist/converse-no-dependencies.js:57329 -#, fuzzy msgid "this groupchat is restricted to members only" -msgstr "Ce salon est restreint aux membres uniquement" +msgstr "ce salon est restreint aux membres uniquement" #: dist/converse-no-dependencies.js:58267 msgid "XMPP Username:" @@ -1514,7 +1442,7 @@ msgstr "Mot de passe" #: dist/converse-no-dependencies.js:58283 msgid "This is a trusted device" -msgstr "" +msgstr "Ceci est un appareil de confiance" #: dist/converse-no-dependencies.js:58285 msgid "" @@ -1523,9 +1451,13 @@ msgid "" "log out. It's important that you explicitly log out, otherwise not all " "cached data might be deleted." msgstr "" +"Pour améliorer les performances, nous stockons vos données dans ce " +"navigateur. Décochez ce bouton si vous êtes sur un ordinateur public, ou si " +"vous voulez que vos données soient supprimées lorsque vous vous " +"déconnecterez. Il est important que vous vous déconnectiez explicitement, " +"sinon toutes les données stockées ne seront pas forcément supprimées." #: dist/converse-no-dependencies.js:58287 -#, fuzzy msgid "Log in" msgstr "Se connecter" @@ -1534,19 +1466,16 @@ msgid "Click here to log in anonymously" msgstr "Cliquez ici pour se connecter anonymement" #: dist/converse-no-dependencies.js:58376 -#, fuzzy msgid "This message has been edited" -msgstr "Ce salon est modéré" +msgstr "Ce message a été édité" #: dist/converse-no-dependencies.js:58402 -#, fuzzy msgid "Edit this message" -msgstr "Cacher le message caché" +msgstr "Éditer ce message" #: dist/converse-no-dependencies.js:58427 -#, fuzzy msgid "Message versions" -msgstr "Archivage des messages" +msgstr "Versions du message" #: dist/converse-no-dependencies.js:58759 msgid "Don't have a chat account?" @@ -1594,9 +1523,9 @@ msgid "Download" msgstr "Télécharger" #: dist/converse-no-dependencies.js:59996 -#, fuzzy, javascript-format +#, javascript-format msgid "Download \"%1$s\"" -msgstr "Télécharger : \"%1$s\"" +msgstr "Télécharger « %1$s »" #: dist/converse-no-dependencies.js:60019 msgid "Download video file" diff --git a/package-lock.json b/package-lock.json index 11607d6d9..ad8da0f47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2410,9 +2410,9 @@ "dev": true }, "bootstrap.native": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/bootstrap.native/-/bootstrap.native-2.0.22.tgz", - "integrity": "sha512-eypi4y1eKJoRt8cTwkZCI3QQ7W04rbv4VU1nBjBshqNXkONI7jO6tG3qZTwq9Zd+gDoeaQASyk6V185y+Y7KHQ==", + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/bootstrap.native/-/bootstrap.native-2.0.23.tgz", + "integrity": "sha512-bcbVgqIjRkyiHd6DN8Y18BYjJTKvvnN3Msb7Yh6K5vGGsjRT3gV0IFKR3rcEYgJ5Kvg1egL9exIfN/Hvwn4wNA==", "dev": true }, "bourbon": { diff --git a/package.json b/package.json index 87ea2c9f4..1780f9ae3 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "backbone.overview": "1.0.2", "backbone.vdomview": "1.0.1", "bootstrap": "^4.0.0", - "bootstrap.native": "^2.0.22", + "bootstrap.native": "^2.0.23", "bourbon": "^4.3.2", "bytebuffer": "^3.5.5", "clean-css-cli": "^4.0.10", diff --git a/requirements.txt b/requirements.txt index ffa242065..04c276a61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -zc.buildout==2.11.4 +zc.buildout==2.12.1 diff --git a/spec/bookmarks.js b/spec/bookmarks.js index a20ea300d..4b5bcf1e8 100644 --- a/spec/bookmarks.js +++ b/spec/bookmarks.js @@ -34,24 +34,24 @@ }); spyOn(_converse.connection, 'getUniqueId').and.callThrough(); - test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC'); - var jid = 'theplay@conference.shakespeare.lit'; - var view = _converse.chatboxviews.get(jid); - spyOn(view, 'renderBookmarkForm').and.callThrough(); - spyOn(view, 'closeForm').and.callThrough(); - - test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.toggle-bookmark')); - }, 300).then(function () { - var $bookmark = $(view.el).find('.toggle-bookmark'); - $bookmark[0].click(); + let view; + test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC') + .then(() => { + var jid = 'theplay@conference.shakespeare.lit'; + view = _converse.chatboxviews.get(jid); + spyOn(view, 'renderBookmarkForm').and.callThrough(); + spyOn(view, 'closeForm').and.callThrough(); + return test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark'))); + }).then(() => { + var bookmark = view.el.querySelector('.toggle-bookmark'); + bookmark.click(); expect(view.renderBookmarkForm).toHaveBeenCalled(); view.el.querySelector('.button-cancel').click(); expect(view.closeForm).toHaveBeenCalled(); - expect($bookmark.hasClass('on-button'), false); + expect(u.hasClass('on-button', bookmark), false); - $bookmark[0].click(); + bookmark.click(); expect(view.renderBookmarkForm).toHaveBeenCalled(); /* Client uploads data: @@ -93,7 +93,7 @@ view.el.querySelector('.btn-primary').click(); expect(view.model.get('bookmarked')).toBeTruthy(); - expect($bookmark.hasClass('on-button'), true); + expect(u.hasClass('on-button', bookmark), true); expect(sent_stanza.toLocaleString()).toBe( ""+ @@ -175,100 +175,96 @@ it("displays that it's bookmarked through its bookmark icon", mock.initConverseWithPromises( null, ['rosterGroupsFetched'], {}, function (done, _converse) { + let view; test_utils.waitUntilDiscoConfirmed( _converse, _converse.bare_jid, [{'category': 'pubsub', 'type': 'pep'}], ['http://jabber.org/protocol/pubsub#publish-options'] - ).then(function () { - test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); - var view = _converse.chatboxviews.get('lounge@localhost'); - - test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.toggle-bookmark')); - }, 300).then(function () { - var bookmark_icon = view.el.querySelector('.toggle-bookmark'); - expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy(); - view.model.set('bookmarked', true); - expect(_.includes(bookmark_icon.classList, 'button-on')).toBeTruthy(); - view.model.set('bookmarked', false); - expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy(); - done(); - }); + ).then(() => test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy')) + .then(() => { + view = _converse.chatboxviews.get('lounge@localhost'); + return test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark'))) + }).then(function () { + var bookmark_icon = view.el.querySelector('.toggle-bookmark'); + expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy(); + view.model.set('bookmarked', true); + expect(_.includes(bookmark_icon.classList, 'button-on')).toBeTruthy(); + view.model.set('bookmarked', false); + expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy(); + done(); }); })); it("can be unbookmarked", mock.initConverseWithPromises( null, ['rosterGroupsFetched'], {}, function (done, _converse) { + let sent_stanza, IQ_id, view, sendIQ; + test_utils.waitUntilDiscoConfirmed( _converse, _converse.bare_jid, [{'category': 'pubsub', 'type': 'pep'}], ['http://jabber.org/protocol/pubsub#publish-options'] - ).then(function () { - var sent_stanza, IQ_id; - var sendIQ = _converse.connection.sendIQ; - - test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC'); + ).then(() => { + sendIQ = _converse.connection.sendIQ; + return test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC'); + }).then(() => { var jid = 'theplay@conference.shakespeare.lit'; - var view = _converse.chatboxviews.get(jid); + view = _converse.chatboxviews.get(jid); + return test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark'))); + }).then(function () { + spyOn(view, 'toggleBookmark').and.callThrough(); + spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough(); + view.delegateEvents(); - test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.toggle-bookmark')); - }, 300).then(function () { - spyOn(view, 'toggleBookmark').and.callThrough(); - spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough(); - view.delegateEvents(); - - _converse.bookmarks.create({ - 'jid': view.model.get('jid'), - 'autojoin': false, - 'name': 'The Play', - 'nick': ' Othello' - }); - expect(_converse.bookmarks.length).toBe(1); - expect(view.model.get('bookmarked')).toBeTruthy(); - var $bookmark_icon = $(view.el.querySelector('.toggle-bookmark')); - expect($bookmark_icon.hasClass('button-on')).toBeTruthy(); - - spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { - sent_stanza = iq; - IQ_id = sendIQ.bind(this)(iq, callback, errback); - }); - spyOn(_converse.connection, 'getUniqueId').and.callThrough(); - $bookmark_icon[0].click(); - expect(view.toggleBookmark).toHaveBeenCalled(); - expect($bookmark_icon.hasClass('button-on')).toBeFalsy(); - expect(_converse.bookmarks.length).toBe(0); - - // Check that an IQ stanza is sent out, containing no - // conferences to bookmark (since we removed the one and - // only bookmark). - expect(sent_stanza.toLocaleString()).toBe( - ""+ - ""+ - ""+ - ""+ - ""+ - ""+ - ""+ - ""+ - ""+ - ""+ - "http://jabber.org/protocol/pubsub#publish-options"+ - ""+ - ""+ - "true"+ - ""+ - ""+ - "whitelist"+ - ""+ - ""+ - ""+ - ""+ - "" - ); - done(); + _converse.bookmarks.create({ + 'jid': view.model.get('jid'), + 'autojoin': false, + 'name': 'The Play', + 'nick': ' Othello' }); + expect(_converse.bookmarks.length).toBe(1); + expect(view.model.get('bookmarked')).toBeTruthy(); + var bookmark_icon = view.el.querySelector('.toggle-bookmark'); + expect(u.hasClass('button-on', bookmark_icon)).toBeTruthy(); + + spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { + sent_stanza = iq; + IQ_id = sendIQ.bind(this)(iq, callback, errback); + }); + spyOn(_converse.connection, 'getUniqueId').and.callThrough(); + bookmark_icon.click(); + expect(view.toggleBookmark).toHaveBeenCalled(); + expect(u.hasClass('button-on', bookmark_icon)).toBeFalsy(); + expect(_converse.bookmarks.length).toBe(0); + + // Check that an IQ stanza is sent out, containing no + // conferences to bookmark (since we removed the one and + // only bookmark). + expect(sent_stanza.toLocaleString()).toBe( + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + "http://jabber.org/protocol/pubsub#publish-options"+ + ""+ + ""+ + "true"+ + ""+ + ""+ + "whitelist"+ + ""+ + ""+ + ""+ + ""+ + "" + ); + done(); }); })); }); @@ -587,9 +583,8 @@ 'name': 'The Play', 'nick': '' }); - test_utils.waitUntil(function () { - return $('#chatrooms .bookmarks.rooms-list .room-item:visible').length; - }, 300).then(function () { + test_utils.waitUntil(() => $('#chatrooms .bookmarks.rooms-list .room-item:visible').length + ).then(function () { expect($('#chatrooms .bookmarks.rooms-list').hasClass('collapsed')).toBeFalsy(); expect($('#chatrooms .bookmarks.rooms-list .room-item:visible').length).toBe(1); expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED); @@ -614,6 +609,7 @@ { hide_open_bookmarks: true }, function (done, _converse) { + const jid = 'room@conference.example.org'; test_utils.waitUntilDiscoConfirmed( _converse, _converse.bare_jid, [{'category': 'pubsub', 'type': 'pep'}], @@ -627,14 +623,12 @@ _converse.emit('bookmarksInitialized'); // Check that it's there - var jid = 'room@conference.example.org'; _converse.bookmarks.create({ 'jid': jid, 'autojoin': false, 'name': 'The Play', 'nick': ' Othello' }); - expect(_converse.bookmarks.length).toBe(1); var room_els = _converse.bookmarksview.el.querySelectorAll(".open-room"); expect(room_els.length).toBe(1); @@ -642,9 +636,11 @@ // Check that it disappears once the room is opened var bookmark = _converse.bookmarksview.el.querySelector(".open-room"); bookmark.click(); + return test_utils.waitUntil(() => _converse.chatboxviews.get(jid)); + }).then(() => { expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeTruthy(); // Check that it reappears once the room is closed - var view = _converse.chatboxviews.get(jid); + const view = _converse.chatboxviews.get(jid); view.close(); expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeFalsy(); done(); diff --git a/spec/chatbox.js b/spec/chatbox.js index 2ad792de4..60b5e8007 100644 --- a/spec/chatbox.js +++ b/spec/chatbox.js @@ -7,11 +7,12 @@ ], factory); } (this, function ($, jasmine, mock, test_utils) { "use strict"; - var _ = converse.env._; - var $iq = converse.env.$iq; - var $msg = converse.env.$msg; - var Strophe = converse.env.Strophe; - var u = converse.env.utils; + const _ = converse.env._; + const $iq = converse.env.$iq; + const $msg = converse.env.$msg; + const Strophe = converse.env.Strophe; + const u = converse.env.utils; + const sizzle = converse.env.sizzle; return describe("Chatboxes", function () { @@ -19,32 +20,35 @@ it("has a /help command to show the available commands", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { - test_utils.createContacts(_converse, 'current'); + test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - test_utils.sendMessage(view, '/help'); + test_utils.waitUntil(() => _converse.chatboxes.length == 2).then(() => { + var view = _converse.chatboxviews.get(contact_jid); + test_utils.sendMessage(view, '/help'); - const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info:not(.chat-date)'), 0); - expect(info_messages.length).toBe(3); - expect(info_messages.pop().textContent).toBe('/help: Show this menu'); - expect(info_messages.pop().textContent).toBe('/me: Write in the third person'); - expect(info_messages.pop().textContent).toBe('/clear: Remove messages'); + const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info:not(.chat-date)'), 0); + expect(info_messages.length).toBe(3); + expect(info_messages.pop().textContent).toBe('/help: Show this menu'); + expect(info_messages.pop().textContent).toBe('/me: Write in the third person'); + expect(info_messages.pop().textContent).toBe('/clear: Remove messages'); - var msg = $msg({ - from: contact_jid, - to: _converse.connection.jid, - type: 'chat', - id: (new Date()).getTime() - }).c('body').t('hello world').tree(); - _converse.chatboxes.onMessage(msg); - expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1); - done(); + var msg = $msg({ + from: contact_jid, + to: _converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('body').t('hello world').tree(); + _converse.chatboxes.onMessage(msg); + expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1); + done(); + }); })); @@ -108,82 +112,97 @@ }); })); - it("is created when you click on a roster item", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + it("is created when you click on a roster item", mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - var i, $el, jid, chatboxview; + let jid, online_contacts; // openControlBox was called earlier, so the controlbox is // visible, but no other chat boxes have been created. expect(_converse.chatboxes.length).toEqual(1); spyOn(_converse.chatboxviews, 'trimChats'); - expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open + expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group li').length; - }, 700).then(function () { - var online_contacts = $(_converse.rosterview.el).find('.roster-group .current-xmpp-contact a.open-chat'); + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700).then(function () { + online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat'); expect(online_contacts.length).toBe(15); - for (i=0; i _converse.chatboxes.length == 2); + }).then(() => { + const chatboxview = _converse.chatboxviews.get(jid); + expect(_converse.chatboxviews.trimChats).toHaveBeenCalled(); + // Check that new chat boxes are created to the left of the + // controlbox (but to the right of all existing chat boxes) + expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(2); + expect(document.querySelectorAll("#conversejs .chatbox")[1].id).toBe(chatboxview.model.get('box_id')); + online_contacts[1].click(); + return test_utils.waitUntil(() => _converse.chatboxes.length == 3); + }).then(() => { + const el = online_contacts[1]; + const new_jid = el.textContent.trim().replace(/ /g,'.').toLowerCase() + '@localhost'; + const chatboxview = _converse.chatboxviews.get(jid); + const new_chatboxview = _converse.chatboxviews.get(new_jid); + expect(_converse.chatboxviews.trimChats).toHaveBeenCalled(); + // Check that new chat boxes are created to the left of the + // controlbox (but to the right of all existing chat boxes) + expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(3); + expect(document.querySelectorAll("#conversejs .chatbox")[2].id).toBe(chatboxview.model.get('box_id')); + expect(document.querySelectorAll("#conversejs .chatbox")[1].id).toBe(new_chatboxview.model.get('box_id')); done(); }); })); it("can be trimmed to conserve space", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises(null, ['rosterGroupsFetched'], {}, function (done, _converse) { - test_utils.createContacts(_converse, 'current'); - test_utils.openControlBox(); - - var i, $el, jid, chatbox, chatboxview, trimmedview; - // openControlBox was called earlier, so the controlbox is - // visible, but no other chat boxes have been created. - var trimmed_chatboxes = _converse.minimized_chats; - expect(_converse.chatboxes.length).toEqual(1); spyOn(_converse.chatboxviews, 'trimChats'); + + const trimmed_chatboxes = _converse.minimized_chats; spyOn(trimmed_chatboxes, 'addChat').and.callThrough(); spyOn(trimmed_chatboxes, 'removeChat').and.callThrough(); - expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open + + test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + + test_utils.openControlBox(); + + let online_contacts; + var i, jid, chatbox, chatboxview, trimmedview; + // openControlBox was called earlier, so the controlbox is + // visible, but no other chat boxes have been created. + expect(_converse.chatboxes.length).toEqual(1); + expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attached. - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group li').length; - }, 700).then(function () { + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length) + .then(() => { // Test that they can be maximized again - var online_contacts = $(_converse.rosterview.el).find('.roster-group .current-xmpp-contact a.open-chat'); + online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat'); expect(online_contacts.length).toBe(15); for (i=0; i _converse.chatboxes.length == 16) + }).then(() => { + expect(_converse.chatboxviews.trimChats.calls.count()).toBe(16); + for (i=0; i 1; - }, 500); + return test_utils.waitUntil(() => _converse.chatboxviews.keys().length); }).then(function () { var key = _converse.chatboxviews.keys()[1]; trimmedview = trimmed_chatboxes.get(key); @@ -195,10 +214,9 @@ expect(trimmedview.restore).toHaveBeenCalled(); expect(chatbox.maximize).toHaveBeenCalled(); - expect(_converse.chatboxviews.trimChats).toHaveBeenCalled(); + expect(_converse.chatboxviews.trimChats.calls.count()).toBe(17); done(); }); - done(); })); it("can be opened in minimized mode initially", @@ -224,29 +242,26 @@ it("is focused if its already open and you click on its corresponding roster item", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises(null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); - _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced. + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; - var $el, jid, chatbox; + const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; + let el, jid; // openControlBox was called earlier, so the controlbox is // visible, but no other chat boxes have been created. expect(_converse.chatboxes.length).toEqual(1); - chatbox = test_utils.openChatBoxFor(_converse, contact_jid); - $el = $(_converse.rosterview.el).find('a.open-chat:contains("'+chatbox.getDisplayName()+'")'); - jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost'; - - spyOn(_converse, 'emit'); - $el[0].click(); - test_utils.waitUntil(function () { - return _converse.emit.calls.count(); - }, 300).then(function () { + test_utils.openChatBoxFor(_converse, contact_jid) + .then((view) => { + el = sizzle('a.open-chat:contains("'+view.model.getDisplayName()+'")', _converse.rosterview.el).pop(); + jid = el.textContent.replace(/ /g,'.').toLowerCase() + '@localhost'; + spyOn(_converse, 'emit'); + el.click(); + return test_utils.waitUntil(() => _converse.emit.calls.count(), 500); + }).then(() => { expect(_converse.chatboxes.length).toEqual(2); expect(_converse.emit).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object)); done(); @@ -255,10 +270,11 @@ it("can be saved to, and retrieved from, browserStorage", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched',], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); spyOn(_converse, 'emit'); @@ -266,41 +282,46 @@ test_utils.openControlBox(); test_utils.openChatBoxes(_converse, 6); - expect(_converse.chatboxviews.trimChats).toHaveBeenCalled(); - // We instantiate a new ChatBoxes collection, which by default - // will be empty. - var newchatboxes = new _converse.ChatBoxes(); - expect(newchatboxes.length).toEqual(0); - // The chatboxes will then be fetched from browserStorage inside the - // onConnected method - newchatboxes.onConnected(); - expect(newchatboxes.length).toEqual(7); - // Check that the chatboxes items retrieved from browserStorage - // have the same attributes values as the original ones. - var attrs = ['id', 'box_id', 'visible']; - var new_attrs, old_attrs; - for (var i=0; i _converse.chatboxes.length == 7).then(() => { + expect(_converse.chatboxviews.trimChats).toHaveBeenCalled(); + // We instantiate a new ChatBoxes collection, which by default + // will be empty. + const newchatboxes = new _converse.ChatBoxes(); + expect(newchatboxes.length).toEqual(0); + // The chatboxes will then be fetched from browserStorage inside the + // onConnected method + newchatboxes.onConnected(); + expect(newchatboxes.length).toEqual(7); + // Check that the chatboxes items retrieved from browserStorage + // have the same attributes values as the original ones. + const attrs = ['id', 'box_id', 'visible']; + let new_attrs, old_attrs; + for (var i=0; i _converse.rosterview.el.querySelectorAll('.roster-group').length) + .then(() => test_utils.openChatBoxFor(_converse, contact_jid)) + .then(() => { + var controlview = _converse.chatboxviews.get('controlbox'), // The controlbox is currently open + chatview = _converse.chatboxviews.get(contact_jid); + spyOn(chatview, 'close').and.callThrough(); spyOn(controlview, 'close').and.callThrough(); spyOn(_converse, 'emit'); @@ -325,20 +346,22 @@ it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { - var chatview; + let chatview; test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + test_utils.openControlBox(); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 300) - .then(function () { - var chatbox = test_utils.openChatBoxes(_converse, 1)[0], - trimmed_chatboxes = _converse.minimized_chats, + + const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length) + .then(() => test_utils.openChatBoxFor(_converse, contact_jid)) + .then(() => { + var trimmed_chatboxes = _converse.minimized_chats, trimmedview; - chatview = _converse.chatboxviews.get(chatbox.get('jid')); + chatview = _converse.chatboxviews.get(contact_jid); spyOn(chatview, 'minimize').and.callThrough(); spyOn(_converse, 'emit'); // We need to rebind all events otherwise our spy won't be called @@ -359,9 +382,7 @@ expect(trimmedview.restore).toHaveBeenCalled(); expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object)); - return test_utils.waitUntil(function () { - return $(chatview.el).find('.chat-body').is(':visible'); - }, 500); + return test_utils.waitUntil(() => u.isVisible(chatview.el.querySelector('.chat-body')), 500); }).then(function () { expect($(chatview.el).find('.toggle-chatbox-button').hasClass('fa-minus')).toBeTruthy(); expect($(chatview.el).find('.toggle-chatbox-button').hasClass('fa-plus')).toBeFalsy(); @@ -372,14 +393,14 @@ it("will be removed from browserStorage when closed", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 300).then(function () { + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length) + .then(() => { spyOn(_converse, 'emit'); spyOn(_converse.chatboxviews, 'trimChats'); _converse.chatboxes.browserStorage._clear(); @@ -390,6 +411,8 @@ expect(_converse.chatboxes.length).toEqual(1); expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']); test_utils.openChatBoxes(_converse, 6); + return test_utils.waitUntil(() => _converse.chatboxes.length == 7) + }).then(() => { expect(_converse.chatboxviews.trimChats).toHaveBeenCalled(); expect(_converse.chatboxes.length).toEqual(7); expect(_converse.emit).toHaveBeenCalledWith('chatBoxOpened', jasmine.any(Object)); @@ -415,77 +438,70 @@ it("can be found on each chat box", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var chatbox = _converse.chatboxes.get(contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - expect(chatbox).toBeDefined(); - expect(view).toBeDefined(); - var toolbar = view.el.querySelector('ul.chat-toolbar'); - expect(_.isElement(toolbar)).toBe(true); - expect(toolbar.querySelectorAll(':scope > li').length).toBe(1); - done(); + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + var chatbox = _converse.chatboxes.get(contact_jid); + var view = _converse.chatboxviews.get(contact_jid); + expect(chatbox).toBeDefined(); + expect(view).toBeDefined(); + var toolbar = view.el.querySelector('ul.chat-toolbar'); + expect(_.isElement(toolbar)).toBe(true); + expect(toolbar.querySelectorAll(':scope > li').length).toBe(1); + done(); + }); })); it("contains a button for inserting emojis", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - var toolbar = view.el.querySelector('ul.chat-toolbar'); - expect(toolbar.querySelectorAll('li.toggle-smiley').length).toBe(1); - // Register spies - spyOn(view, 'toggleEmojiMenu').and.callThrough(); - spyOn(view, 'insertEmoji').and.callThrough(); + let timeout = false, view, toolbar; + const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + view = _converse.chatboxviews.get(contact_jid); + toolbar = view.el.querySelector('ul.chat-toolbar'); + expect(toolbar.querySelectorAll('li.toggle-smiley').length).toBe(1); + // Register spies + spyOn(view, 'toggleEmojiMenu').and.callThrough(); + spyOn(view, 'insertEmoji').and.callThrough(); - view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called - toolbar.querySelector('li.toggle-smiley').click(); + view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called + toolbar.querySelector('li.toggle-smiley').click(); - var timeout = false; - - test_utils.waitUntil(function () { - return u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container')); - }, 500).then(function () { + return test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container'), 500)); + }).then(() => { var picker = view.el.querySelector('.toggle-smiley .emoji-picker-container'); var items = picker.querySelectorAll('.emoji-picker li'); items[0].click() expect(view.insertEmoji).toHaveBeenCalled(); - setTimeout(function () { timeout = true; }, 100); - return test_utils.waitUntil(function () { - return timeout; - }, 500); - }).then(function () { + return test_utils.waitUntil(() => timeout, 500); + }).then(() => { timeout = false; toolbar.querySelector('li.toggle-smiley').click(); // Close the panel again - return test_utils.waitUntil(function () { - return !view.el.querySelector('.toggle-smiley .toolbar-menu').offsetHeight; - }, 500); - }).then(function () { + return test_utils.waitUntil(() => !view.el.querySelector('.toggle-smiley .toolbar-menu').offsetHeight, 500); + }).then(() => { setTimeout(function () { timeout = true; }, 100); - return test_utils.waitUntil(function () { - return timeout; - }, 500); - }).then(function () { + return test_utils.waitUntil(() => timeout, 500); + }).then(() => { toolbar.querySelector('li.toggle-smiley').click(); expect(view.toggleEmojiMenu).toHaveBeenCalled(); - return test_utils.waitUntil(function () { - var $picker = $(view.el).find('.toggle-smiley .emoji-picker-container'); - return u.isVisible($picker[0]); - }, 500); - }).then(function () { + return test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container')), 500); + }).then(() => { var nodes = view.el.querySelectorAll('.toggle-smiley ul li'); nodes[nodes.length-1].click(); expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':grinning: '); @@ -496,34 +512,38 @@ it("can contain a button for starting a call", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - var view; - var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; + let view, toolbar, call_button; + const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; spyOn(_converse, 'emit'); // First check that the button doesn't show if it's not enabled // via "visible_toolbar_buttons" _converse.visible_toolbar_buttons.call = false; - test_utils.openChatBoxFor(_converse, contact_jid); - view = _converse.chatboxviews.get(contact_jid); - var toolbar = view.el.querySelector('ul.chat-toolbar'); - var call_button = toolbar.querySelector('.toggle-call'); - expect(_.isNull(call_button)).toBeTruthy(); - view.close(); - // Now check that it's shown if enabled and that it emits - // callButtonClicked - _converse.visible_toolbar_buttons.call = true; // enable the button - test_utils.openChatBoxFor(_converse, contact_jid); - view = _converse.chatboxviews.get(contact_jid); - toolbar = view.el.querySelector('ul.chat-toolbar'); - call_button = toolbar.querySelector('.toggle-call'); - call_button.click(); - expect(_converse.emit).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object)); - done(); + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + view = _converse.chatboxviews.get(contact_jid); + toolbar = view.el.querySelector('ul.chat-toolbar'); + call_button = toolbar.querySelector('.toggle-call'); + expect(_.isNull(call_button)).toBeTruthy(); + view.close(); + // Now check that it's shown if enabled and that it emits + // callButtonClicked + _converse.visible_toolbar_buttons.call = true; // enable the button + return test_utils.openChatBoxFor(_converse, contact_jid); + }).then(() => { + view = _converse.chatboxviews.get(contact_jid); + toolbar = view.el.querySelector('ul.chat-toolbar'); + call_button = toolbar.querySelector('.toggle-call'); + call_button.click(); + expect(_converse.emit).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object)); + done(); + }); })); }); @@ -555,17 +575,19 @@ it("is sent when the user opens a chat box", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openControlBox(); test_utils.waitUntil(function () { return $(_converse.rosterview.el).find('.roster-group').length; }, 300).then(function () { spyOn(_converse.connection, 'send'); - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); + return test_utils.openChatBoxFor(_converse, contact_jid); + }).then(() => { var view = _converse.chatboxviews.get(contact_jid); expect(view.model.get('chat_state')).toBe('active'); expect(_converse.connection.send).toHaveBeenCalled(); @@ -580,26 +602,24 @@ })); it("is sent when the user maximizes a minimized a chat box", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 500).then(function () { - test_utils.openChatBoxFor(_converse, contact_jid); + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length) + .then(() => test_utils.openChatBoxFor(_converse, contact_jid)) + .then(() => { var view = _converse.chatboxviews.get(contact_jid); view.model.minimize(); expect(view.model.get('chat_state')).toBe('inactive'); spyOn(_converse.connection, 'send'); view.model.maximize(); - return test_utils.waitUntil(function () { - return view.model.get('chat_state') === 'active'; - }, 700); - }).then(function () { + return test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 700); + }).then(() => { expect(_converse.connection.send).toHaveBeenCalled(); var calls = _.filter(_converse.connection.send.calls.all(), function (call) { return call.args[0] instanceof Strophe.Builder; @@ -620,16 +640,17 @@ it("is sent as soon as the user starts typing a message which is not a command", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 300).then(function () { - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length) + .then(() => test_utils.openChatBoxFor(_converse, contact_jid)) + .then(() => { var view = _converse.chatboxviews.get(contact_jid); expect(view.model.get('chat_state')).toBe('active'); spyOn(_converse.connection, 'send'); @@ -707,21 +728,20 @@ it("can be a composing carbon message that this user sent from a different client", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { - var contact, sent_stanza, IQ_id, stanza; + let contact, sent_stanza, IQ_id, stanza, recipient_jid; test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) + .then(() => test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'))) .then(function () { - return test_utils.waitUntil(function () { - return _converse.xmppstatus.vcard.get('fullname'); - }, 300); - }).then(function () { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); // Send a message from a different resource spyOn(_converse, 'log'); - var recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, recipient_jid); + recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost'; + return test_utils.openChatBoxFor(_converse, recipient_jid); + }).then(() => { var msg = $msg({ 'from': _converse.bare_jid, 'id': (new Date()).getTime(), @@ -758,19 +778,19 @@ it("is sent if the user has stopped typing since 30 seconds", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { - var view, contact_jid; + let view; test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openControlBox(); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group li').length; - }, 700).then(function () { + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700) + .then(() => { _converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test - - contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); + return test_utils.openChatBoxFor(_converse, contact_jid); + }).then(() => { view = _converse.chatboxviews.get(contact_jid); spyOn(_converse.connection, 'send'); spyOn(view, 'setChatState').and.callThrough(); @@ -783,10 +803,8 @@ expect(_converse.connection.send).toHaveBeenCalled(); var $stanza = $(_converse.connection.send.calls.argsFor(0)[0].tree()); expect($stanza.children().get(0).tagName).toBe('composing'); - return test_utils.waitUntil(function () { - return view.model.get('chat_state') === 'paused'; - }, 500); - }).then(function () { + return test_utils.waitUntil(() => view.model.get('chat_state') === 'paused', 500); + }).then(() => { expect(_converse.connection.send).toHaveBeenCalled(); var calls = _.filter(_converse.connection.send.calls.all(), function (call) { return call.args[0] instanceof Strophe.Builder; @@ -815,7 +833,7 @@ }); expect(view.model.get('chat_state')).toBe('composing'); done(); - }); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)) })); it("will be shown if received", @@ -855,21 +873,20 @@ it("can be a paused carbon message that this user sent from a different client", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { - var contact, sent_stanza, IQ_id, stanza; + let contact, sent_stanza, IQ_id, stanza, recipient_jid; test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']) - .then(function () { - return test_utils.waitUntil(function () { - return _converse.xmppstatus.vcard.get('fullname'); - }, 300); - }).then(function () { + .then(() => test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'))) + .then(() => { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); // Send a message from a different resource spyOn(_converse, 'log'); - var recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, recipient_jid); + recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost'; + return test_utils.openChatBoxFor(_converse, recipient_jid); + }).then(() => { var msg = $msg({ 'from': _converse.bare_jid, 'id': (new Date()).getTime(), @@ -905,99 +922,101 @@ describe("An inactive notifciation", function () { it("is sent if the user has stopped typing since 2 minutes", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { + + // Make the timeouts shorter so that we can test + _converse.TIMEOUTS.PAUSED = 200; + _converse.TIMEOUTS.INACTIVE = 200; - var view, contact_jid; test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + + let view; + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openControlBox(); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 500).then(function () { - // Make the timeouts shorter so that we can test - _converse.TIMEOUTS.PAUSED = 200; - _converse.TIMEOUTS.INACTIVE = 200; - contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500) + .then(() => test_utils.openChatBoxFor(_converse, contact_jid)) + .then(() => { view = _converse.chatboxviews.get(contact_jid); return test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 500); - }).then(function () { + }).then(() => { console.log('chat_state set to active'); - view = _converse.chatboxviews.get(contact_jid); expect(view.model.get('chat_state')).toBe('active'); view.keyPressed({ target: view.el.querySelector('textarea.chat-textarea'), keyCode: 1 }); return test_utils.waitUntil(() => view.model.get('chat_state') === 'composing', 500); - }).then(function () { + }).then(() => { console.log('chat_state set to composing'); view = _converse.chatboxviews.get(contact_jid); expect(view.model.get('chat_state')).toBe('composing'); spyOn(_converse.connection, 'send'); return test_utils.waitUntil(() => view.model.get('chat_state') === 'paused', 500); - }).then(function () { - console.log('chat_state set to paused'); + }).then(() => { return test_utils.waitUntil(() => view.model.get('chat_state') === 'inactive', 500); - }).then(function () { + }).then(() => { console.log('chat_state set to inactive'); expect(_converse.connection.send).toHaveBeenCalled(); var calls = _.filter(_converse.connection.send.calls.all(), function (call) { return call.args[0] instanceof Strophe.Builder; }); expect(calls.length).toBe(2); - var $stanza = $(calls[0].args[0].tree()); - expect($stanza.attr('to')).toBe(contact_jid); - expect($stanza.children().length).toBe(3); - expect($stanza.children().get(0).tagName).toBe('paused'); - expect($stanza.children().get(1).tagName).toBe('no-store'); - expect($stanza.children().get(2).tagName).toBe('no-permanent-store'); + var stanza = calls[0].args[0].tree(); + expect(stanza.getAttribute('to')).toBe(contact_jid); + expect(stanza.children.length).toBe(3); + expect(stanza.children[0].tagName).toBe('paused'); + expect(stanza.children[1].tagName).toBe('no-store'); + expect(stanza.children[2].tagName).toBe('no-permanent-store'); - $stanza = $(_converse.connection.send.calls.mostRecent().args[0].tree()); - expect($stanza.attr('to')).toBe(contact_jid); - expect($stanza.children().length).toBe(3); - expect($stanza.children().get(0).tagName).toBe('inactive'); - expect($stanza.children().get(1).tagName).toBe('no-store'); - expect($stanza.children().get(2).tagName).toBe('no-permanent-store'); + stanza = _converse.connection.send.calls.mostRecent().args[0].tree(); + expect(stanza.getAttribute('to')).toBe(contact_jid); + expect(stanza.children.length).toBe(3); + expect(stanza.children[0].tagName).toBe('inactive'); + expect(stanza.children[1].tagName).toBe('no-store'); + expect(stanza.children[2].tagName).toBe('no-permanent-store'); + done(); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)) - .then(done); })); it("is sent when the user a minimizes a chat box", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - spyOn(_converse.connection, 'send'); - view.minimize(); - expect(view.model.get('chat_state')).toBe('inactive'); - expect(_converse.connection.send).toHaveBeenCalled(); - var $stanza = $(_converse.connection.send.calls.argsFor(0)[0].tree()); - expect($stanza.attr('to')).toBe(contact_jid); - expect($stanza.children().get(0).tagName).toBe('inactive'); - done(); + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + var view = _converse.chatboxviews.get(contact_jid); + spyOn(_converse.connection, 'send'); + view.minimize(); + expect(view.model.get('chat_state')).toBe('inactive'); + expect(_converse.connection.send).toHaveBeenCalled(); + var $stanza = $(_converse.connection.send.calls.argsFor(0)[0].tree()); + expect($stanza.attr('to')).toBe(contact_jid); + expect($stanza.children().get(0).tagName).toBe('inactive'); + done(); + }); })); it("is sent if the user closes a chat box", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openControlBox(); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 300).then(function () { - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length) + .then(() => test_utils.openChatBoxFor(_converse, contact_jid)) + .then((view) => { expect(view.model.get('chat_state')).toBe('active'); spyOn(_converse.connection, 'send'); view.close(); @@ -1015,40 +1034,43 @@ it("will clear any other chat status notifications", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); + const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions spyOn(_converse, 'emit'); - var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); - var view = _converse.chatboxviews.get(sender_jid); - expect(view.el.querySelectorAll('.chat-event').length).toBe(0); - // Insert message, to also check that - // text messages are inserted correctly with - // temporary chat events in the chat contents. - var msg = $msg({ - 'to': _converse.bare_jid, - 'xmlns': 'jabber:client', - 'from': sender_jid, - 'type': 'chat'}) - .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up() - .tree(); - _converse.chatboxes.onMessage(msg); - expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(1); - msg = $msg({ - from: sender_jid, - to: _converse.connection.jid, - type: 'chat', - id: (new Date()).getTime() - }).c('body').c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - _converse.chatboxes.onMessage(msg); - expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); - expect($(view.el).find('.chat-state-notification').length).toBe(0); - done(); + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + var view = _converse.chatboxviews.get(sender_jid); + expect(view.el.querySelectorAll('.chat-event').length).toBe(0); + // Insert message, to also check that + // text messages are inserted correctly with + // temporary chat events in the chat contents. + var msg = $msg({ + 'to': _converse.bare_jid, + 'xmlns': 'jabber:client', + 'from': sender_jid, + 'type': 'chat'}) + .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up() + .tree(); + _converse.chatboxes.onMessage(msg); + expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(1); + msg = $msg({ + from: sender_jid, + to: _converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('body').c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree(); + _converse.chatboxes.onMessage(msg); + expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object)); + expect($(view.el).find('.chat-state-notification').length).toBe(0); + done(); + }); })); }); @@ -1090,39 +1112,42 @@ it("'/clear' can be used to clear messages in a conversation", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; spyOn(_converse, 'emit'); - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - var message = 'This message is another sent from this chatbox'; - // Lets make sure there is at least one message already - // (e.g for when this test is run on its own). - test_utils.sendMessage(view, message); - expect(view.model.messages.length > 0).toBeTruthy(); - expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy(); - expect(_converse.emit).toHaveBeenCalledWith('messageSend', message); + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + var view = _converse.chatboxviews.get(contact_jid); + var message = 'This message is another sent from this chatbox'; + // Lets make sure there is at least one message already + // (e.g for when this test is run on its own). + test_utils.sendMessage(view, message); + expect(view.model.messages.length > 0).toBeTruthy(); + expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy(); + expect(_converse.emit).toHaveBeenCalledWith('messageSend', message); - message = '/clear'; - spyOn(view, 'onMessageSubmitted').and.callThrough(); - spyOn(view, 'clearMessages').and.callThrough(); - spyOn(window, 'confirm').and.callFake(function () { - return true; + message = '/clear'; + spyOn(view, 'onMessageSubmitted').and.callThrough(); + spyOn(view, 'clearMessages').and.callThrough(); + spyOn(window, 'confirm').and.callFake(function () { + return true; + }); + test_utils.sendMessage(view, message); + expect(view.onMessageSubmitted).toHaveBeenCalled(); + expect(view.clearMessages).toHaveBeenCalled(); + expect(window.confirm).toHaveBeenCalled(); + expect(view.model.messages.length, 0); // The messages must be removed from the chatbox + expect(view.model.messages.browserStorage.records.length, 0); // And also from browserStorage + expect(_converse.emit.calls.count(), 1); + expect(_converse.emit.calls.mostRecent().args, ['messageSend', message]); + done(); }); - test_utils.sendMessage(view, message); - expect(view.onMessageSubmitted).toHaveBeenCalled(); - expect(view.clearMessages).toHaveBeenCalled(); - expect(window.confirm).toHaveBeenCalled(); - expect(view.model.messages.length, 0); // The messages must be removed from the chatbox - expect(view.model.messages.browserStorage.records.length, 0); // And also from browserStorage - expect(_converse.emit.calls.count(), 1); - expect(_converse.emit.calls.mostRecent().args, ['messageSend', message]); - done(); })); }); @@ -1254,133 +1279,133 @@ it("is incremented when the message is received and ChatBoxView is scrolled up", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost', - msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread'); + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost', + msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread'); - test_utils.openChatBoxFor(_converse, sender_jid); - var chatbox = _converse.chatboxes.get(sender_jid); - chatbox.save('scrolled', true); - - _converse.chatboxes.onMessage(msg); - expect(chatbox.get('num_unread')).toBe(1); - done(); + test_utils.openChatBoxFor(_converse, sender_jid) + .then((view) => { + view.model.save('scrolled', true); + _converse.chatboxes.onMessage(msg); + expect(view.model.get('num_unread')).toBe(1); + done(); + }); })); it("is not incremented when the message is received and ChatBoxView is scrolled down", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost', - msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be read'); + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost', + msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be read'); - test_utils.openChatBoxFor(_converse, sender_jid); - var chatbox = _converse.chatboxes.get(sender_jid); - - _converse.chatboxes.onMessage(msg); - expect(chatbox.get('num_unread')).toBe(0); - done(); + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + var chatbox = _converse.chatboxes.get(sender_jid); + _converse.chatboxes.onMessage(msg); + expect(chatbox.get('num_unread')).toBe(0); + done(); + }); })); it("is incremeted when message is received, chatbox is scrolled down and the window is not focused", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises(null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; var msgFactory = function () { return test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread'); }; - - test_utils.openChatBoxFor(_converse, sender_jid); - var chatbox = _converse.chatboxes.get(sender_jid); - - _converse.windowState = 'hidden'; - _converse.chatboxes.onMessage(msgFactory()); - - expect(chatbox.get('num_unread')).toBe(1); - done(); + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + var chatbox = _converse.chatboxes.get(sender_jid); + _converse.windowState = 'hidden'; + _converse.chatboxes.onMessage(msgFactory()); + expect(chatbox.get('num_unread')).toBe(1); + done(); + }); })); it("is incremeted when message is received, chatbox is scrolled up and the window is not focused", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; var msgFactory = function () { return test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread'); }; - - test_utils.openChatBoxFor(_converse, sender_jid); - var chatbox = _converse.chatboxes.get(sender_jid); - chatbox.save('scrolled', true); - - _converse.windowState = 'hidden'; - _converse.chatboxes.onMessage(msgFactory()); - - expect(chatbox.get('num_unread')).toBe(1); - done(); + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + var chatbox = _converse.chatboxes.get(sender_jid); + chatbox.save('scrolled', true); + _converse.windowState = 'hidden'; + _converse.chatboxes.onMessage(msgFactory()); + expect(chatbox.get('num_unread')).toBe(1); + done(); + }); })); it("is cleared when ChatBoxView was scrolled down and the window become focused", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; var msgFactory = function () { return test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread'); }; - - test_utils.openChatBoxFor(_converse, sender_jid); - var chatbox = _converse.chatboxes.get(sender_jid); - - _converse.windowState = 'hidden'; - _converse.chatboxes.onMessage(msgFactory()); - expect(chatbox.get('num_unread')).toBe(1); - - _converse.saveWindowState(null, 'focus'); - expect(chatbox.get('num_unread')).toBe(0); - done(); + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + const chatbox = _converse.chatboxes.get(sender_jid); + _converse.windowState = 'hidden'; + _converse.chatboxes.onMessage(msgFactory()); + expect(chatbox.get('num_unread')).toBe(1); + _converse.saveWindowState(null, 'focus'); + expect(chatbox.get('num_unread')).toBe(0); + done(); + }); })); it("is not cleared when ChatBoxView was scrolled up and the windows become focused", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current'); - + _converse.emit('rosterContactsFetched'); var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; var msgFactory = function () { return test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread'); }; - - test_utils.openChatBoxFor(_converse, sender_jid); - var chatbox = _converse.chatboxes.get(sender_jid); - chatbox.save('scrolled', true); - - _converse.windowState = 'hidden'; - _converse.chatboxes.onMessage(msgFactory()); - expect(chatbox.get('num_unread')).toBe(1); - - _converse.saveWindowState(null, 'focus'); - expect(chatbox.get('num_unread')).toBe(1); - done(); + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + var chatbox = _converse.chatboxes.get(sender_jid); + chatbox.save('scrolled', true); + _converse.windowState = 'hidden'; + _converse.chatboxes.onMessage(msgFactory()); + expect(chatbox.get('num_unread')).toBe(1); + _converse.saveWindowState(null, 'focus'); + expect(chatbox.get('num_unread')).toBe(1); + done(); + }); })); }); @@ -1388,48 +1413,48 @@ it("is updated when message is received and chatbox is scrolled up", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 500) - .then(function () { - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); + _converse.emit('rosterContactsFetched'); + + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500) + .then(() => test_utils.openChatBoxFor(_converse, sender_jid)) + .then(() => { var chatbox = _converse.chatboxes.get(sender_jid); chatbox.save('scrolled', true); var msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread'); _converse.chatboxes.onMessage(msg); - var msgIndicatorSelector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator', - $msgIndicator = $($(_converse.rosterview.el).find(msgIndicatorSelector)); + var selector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator', + indicator_el = sizzle(selector, _converse.rosterview.el).pop(); - expect($msgIndicator.text()).toBe('1'); + expect(indicator_el.textContent).toBe('1'); msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too'); _converse.chatboxes.onMessage(msg); - $msgIndicator = $($(_converse.rosterview.el).find(msgIndicatorSelector)); - expect($msgIndicator.text()).toBe('2'); + indicator_el = sizzle(selector, _converse.rosterview.el).pop(); + expect(indicator_el.textContent).toBe('2'); done(); }); })); it("is updated when message is received and chatbox is minimized", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 500) - .then(function () { - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); + _converse.emit('rosterContactsFetched'); + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500) + .then(() => test_utils.openChatBoxFor(_converse, sender_jid)) + .then(() => { var chatbox = _converse.chatboxes.get(sender_jid); var chatboxview = _converse.chatboxviews.get(sender_jid); chatboxview.minimize(); @@ -1437,32 +1462,32 @@ var msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread'); _converse.chatboxes.onMessage(msg); - var msgIndicatorSelector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator', - $msgIndicator = $($(_converse.rosterview.el).find(msgIndicatorSelector)); + var selector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator', + indicator_el = sizzle(selector, _converse.rosterview.el).pop(); - expect($msgIndicator.text()).toBe('1'); + expect(indicator_el.textContent).toBe('1'); msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too'); _converse.chatboxes.onMessage(msg); - $msgIndicator = $($(_converse.rosterview.el).find(msgIndicatorSelector)); - expect($msgIndicator.text()).toBe('2'); + indicator_el = sizzle(selector, _converse.rosterview.el).pop(); + expect(indicator_el.textContent).toBe('2'); done(); }); })); it("is cleared when chatbox is maximzied after receiving messages in minimized mode", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 500) - .then(function () { - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); + _converse.emit('rosterContactsFetched'); + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500) + .then(() => test_utils.openChatBoxFor(_converse, sender_jid)) + .then(() => { var chatbox = _converse.chatboxes.get(sender_jid); var chatboxview = _converse.chatboxviews.get(sender_jid); var msgsIndicatorSelector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator'; @@ -1470,7 +1495,6 @@ var msgFactory = function () { return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read'); }; - chatboxview.minimize(); _converse.chatboxes.onMessage(msgFactory()); @@ -1487,16 +1511,16 @@ it("is cleared when unread messages are viewed which were received in scrolled-up chatbox", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 500) - .then(function () { - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); + _converse.emit('rosterContactsFetched'); + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500) + .then(() => test_utils.openChatBoxFor(_converse, sender_jid)) + .then(() => { var chatbox = _converse.chatboxes.get(sender_jid); var chatboxview = _converse.chatboxviews.get(sender_jid); var msgFactory = function () { @@ -1519,16 +1543,16 @@ it("is not cleared after user clicks on roster view when chatbox is already opened and scrolled up", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 500) - .then(function () { - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); + _converse.emit('rosterContactsFetched'); + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500) + .then(() => test_utils.openChatBoxFor(_converse, sender_jid)) + .then(() => { var chatbox = _converse.chatboxes.get(sender_jid); var chatboxview = _converse.chatboxviews.get(sender_jid); var msgFactory = function () { @@ -1552,89 +1576,100 @@ describe("A Minimized ChatBoxView's Unread Message Count", function () { it("is displayed when scrolled up chatbox is minimized after receiving unread messages", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); - var msgFactory = function () { - return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read'); - }; - var selectUnreadMsgCount = function () { - var minimizedChatBoxView = _converse.minimized_chats.get(sender_jid); - return $(minimizedChatBoxView.el).find('.message-count'); - }; + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + const msgFactory = function () { + return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read'); + }; + const selectUnreadMsgCount = function () { + const minimizedChatBoxView = _converse.minimized_chats.get(sender_jid); + return $(minimizedChatBoxView.el).find('.message-count'); + }; - var chatbox = _converse.chatboxes.get(sender_jid); - chatbox.save('scrolled', true); - _converse.chatboxes.onMessage(msgFactory()); + const chatbox = _converse.chatboxes.get(sender_jid); + chatbox.save('scrolled', true); + _converse.chatboxes.onMessage(msgFactory()); - var chatboxview = _converse.chatboxviews.get(sender_jid); - chatboxview.minimize(); + const chatboxview = _converse.chatboxviews.get(sender_jid); + chatboxview.minimize(); - var $unreadMsgCount = selectUnreadMsgCount(); - expect(u.isVisible($unreadMsgCount[0])).toBeTruthy(); - expect($unreadMsgCount.html()).toBe('1'); - done(); + const $unreadMsgCount = selectUnreadMsgCount(); + expect(u.isVisible($unreadMsgCount[0])).toBeTruthy(); + expect($unreadMsgCount.html()).toBe('1'); + done(); + }); })); it("is incremented when message is received and windows is not focused", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); - var msgFactory = function () { - return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read'); - }; - var selectUnreadMsgCount = function () { - var minimizedChatBoxView = _converse.minimized_chats.get(sender_jid); - return $(minimizedChatBoxView.el).find('.message-count'); - }; + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + const msgFactory = function () { + return test_utils.createChatMessage(_converse, sender_jid, + 'This message will be received as unread, but eventually will be read'); + }; + const selectUnreadMsgCount = function () { + const minimizedChatBoxView = _converse.minimized_chats.get(sender_jid); + return $(minimizedChatBoxView.el).find('.message-count'); + }; - var chatboxview = _converse.chatboxviews.get(sender_jid); - chatboxview.minimize(); + const chatboxview = _converse.chatboxviews.get(sender_jid); + chatboxview.minimize(); - _converse.chatboxes.onMessage(msgFactory()); + _converse.chatboxes.onMessage(msgFactory()); - var $unreadMsgCount = selectUnreadMsgCount(); - expect(u.isVisible($unreadMsgCount[0])).toBeTruthy(); - expect($unreadMsgCount.html()).toBe('1'); - done(); + const $unreadMsgCount = selectUnreadMsgCount(); + expect(u.isVisible($unreadMsgCount[0])).toBeTruthy(); + expect($unreadMsgCount.html()).toBe('1'); + done(); + }); })); it("will render Openstreetmap-URL from geo-URI", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { - test_utils.createContacts(_converse, 'current'); - var base_url = document.URL.split(window.location.pathname)[0]; - var message = "geo:37.786971,-122.399677"; - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); + test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + + let view; + const base_url = document.URL.split(window.location.pathname)[0], + message = "geo:37.786971,-122.399677", + contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + view = _converse.chatboxviews.get(contact_jid); spyOn(view.model, 'sendMessage').and.callThrough(); test_utils.sendMessage(view, message); - test_utils.waitUntil(function () { - return $(view.el).find('.chat-content').find('.chat-msg').length; - }, 1000).then(function () { - expect(view.model.sendMessage).toHaveBeenCalled(); - var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); - expect(msg.html()).toEqual( - 'https://www.openstreetmap.org/?mlat=37.7869'+ - '71&mlon=-122.399677#map=18/37.786971/-122.399677'); - done(); - }); - })); + return test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg').length, 1000); + }).then(() => { + expect(view.model.sendMessage).toHaveBeenCalled(); + var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); + expect(msg.html()).toEqual( + 'https://www.openstreetmap.org/?mlat=37.7869'+ + '71&mlon=-122.399677#map=18/37.786971/-122.399677'); + done(); + }); + })); }); }); })); diff --git a/spec/chatroom.js b/spec/chatroom.js index fcf429a97..e0746de03 100644 --- a/spec/chatroom.js +++ b/spec/chatroom.js @@ -1,15 +1,16 @@ (function (root, factory) { define(["jquery", "jasmine", "mock", "test-utils" ], factory); } (this, function ($, jasmine, mock, test_utils) { - var _ = converse.env._; - var $pres = converse.env.$pres; - var $iq = converse.env.$iq; - var $msg = converse.env.$msg; - var Strophe = converse.env.Strophe; - var Promise = converse.env.Promise; - var moment = converse.env.moment; - var sizzle = converse.env.sizzle; - var u = converse.env.utils; + const _ = converse.env._, + $pres = converse.env.$pres, + $iq = converse.env.$iq, + $msg = converse.env.$msg, + Strophe = converse.env.Strophe, + Promise = converse.env.Promise, + moment = converse.env.moment, + sizzle = converse.env.sizzle, + Backbone = converse.env.Backbone, + u = converse.env.utils; return describe("ChatRooms", function () { describe("The \"rooms\" API", function () { @@ -106,9 +107,9 @@ })); it("has a method 'open' which opens (optionally configures) and returns a wrapped chat box", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { // Mock 'getRoomFeatures', otherwise the room won't be // displayed as it waits first for the features to be returned @@ -119,22 +120,24 @@ return deferred.promise(); }); + const sent_IQ_els = []; + let jid = 'lounge@localhost'; + let chatroomview, sent_IQ, IQ_id; test_utils.openControlBox(); test_utils.createContacts(_converse, 'current'); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group .group-toggle').length; - }, 300).then(function () { - var jid = 'lounge@localhost'; - var room = _converse.api.rooms.open(jid); + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length) + .then(() => _converse.api.rooms.open(jid)) + .then((room) => { // Test on groupchat that's not yet open - expect(room instanceof Object).toBeTruthy(); - var chatroomview = _converse.chatboxviews.get(jid); + expect(room instanceof Backbone.Model).toBeTruthy(); + chatroomview = _converse.chatboxviews.get(jid); expect(chatroomview.is_chatroom).toBeTruthy(); expect(u.isVisible(chatroomview.el)).toBeTruthy(); // Test again, now that the room exists. - room = _converse.api.rooms.open(jid); - expect(room instanceof Object).toBeTruthy(); + return _converse.api.rooms.open(jid); + }).then((room) => { + expect(room instanceof Backbone.Model).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid); expect(chatroomview.is_chatroom).toBeTruthy(); expect(u.isVisible(chatroomview.el)).toBeTruthy(); @@ -142,26 +145,28 @@ // Test with mixed case in JID jid = 'Leisure@localhost'; - room = _converse.api.rooms.open(jid); - expect(room instanceof Object).toBeTruthy(); + return _converse.api.rooms.open(jid); + }).then((room) => { + expect(room instanceof Backbone.Model).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid.toLowerCase()); expect(u.isVisible(chatroomview.el)).toBeTruthy(); jid = 'leisure@localhost'; - room = _converse.api.rooms.open(jid); - expect(room instanceof Object).toBeTruthy(); + return _converse.api.rooms.open(jid); + }).then((room) => { + expect(room instanceof Backbone.Model).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid.toLowerCase()); expect(u.isVisible(chatroomview.el)).toBeTruthy(); jid = 'leiSure@localhost'; - room = _converse.api.rooms.open(jid); - expect(room instanceof Object).toBeTruthy(); + return _converse.api.rooms.open(jid); + }).then((room) => { + expect(room instanceof Backbone.Model).toBeTruthy(); chatroomview = _converse.chatboxviews.get(jid.toLowerCase()); expect(u.isVisible(chatroomview.el)).toBeTruthy(); chatroomview.close(); _converse.muc_instant_rooms = false; - var sent_IQ, IQ_id, sent_IQ_els = []; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { sent_IQ = iq; @@ -169,7 +174,7 @@ IQ_id = sendIQ.bind(this)(iq, callback, errback); }); // Test with configuration - _converse.api.rooms.open('room@conference.example.org', { + return _converse.api.rooms.open('room@conference.example.org', { 'nick': 'some1', 'auto_configure': true, 'roomconfig': { @@ -181,6 +186,7 @@ 'whois': 'anyone' } }); + }).then((room) => { chatroomview = _converse.chatboxviews.get('room@conference.example.org'); // We pretend this is a new room, so no disco info is returned. @@ -252,25 +258,21 @@ spyOn(chatroomview.model, 'sendConfiguration').and.callThrough(); _converse.connection._dataRecv(test_utils.createRequest(node.firstElementChild)); - - - return test_utils.waitUntil(function () { - return chatroomview.model.sendConfiguration.calls.count() === 1; - }, 300).then(function () { - var sent_stanza = sent_IQ_els.pop(); - while (sent_stanza.getAttribute('type') !== 'set') { - sent_stanza = sent_IQ_els.pop(); - } - expect(sizzle('field[var="muc#roomconfig_roomname"] value', sent_stanza).pop().textContent).toBe('Room'); - expect(sizzle('field[var="muc#roomconfig_roomdesc"] value', sent_stanza).pop().textContent).toBe('Welcome to this groupchat'); - expect(sizzle('field[var="muc#roomconfig_persistentroom"] value', sent_stanza).pop().textContent).toBe('1'); - expect(sizzle('field[var="muc#roomconfig_publicroom"] value ', sent_stanza).pop().textContent).toBe('1'); - expect(sizzle('field[var="muc#roomconfig_changesubject"] value', sent_stanza).pop().textContent).toBe('0'); - expect(sizzle('field[var="muc#roomconfig_whois"] value ', sent_stanza).pop().textContent).toBe('anyone'); - expect(sizzle('field[var="muc#roomconfig_membersonly"] value', sent_stanza).pop().textContent).toBe('1'); - expect(sizzle('field[var="muc#roomconfig_historylength"] value', sent_stanza).pop().textContent).toBe('20'); - done(); - }); + return test_utils.waitUntil(() => chatroomview.model.sendConfiguration.calls.count() === 1); + }).then(() => { + var sent_stanza = sent_IQ_els.pop(); + while (sent_stanza.getAttribute('type') !== 'set') { + sent_stanza = sent_IQ_els.pop(); + } + expect(sizzle('field[var="muc#roomconfig_roomname"] value', sent_stanza).pop().textContent).toBe('Room'); + expect(sizzle('field[var="muc#roomconfig_roomdesc"] value', sent_stanza).pop().textContent).toBe('Welcome to this groupchat'); + expect(sizzle('field[var="muc#roomconfig_persistentroom"] value', sent_stanza).pop().textContent).toBe('1'); + expect(sizzle('field[var="muc#roomconfig_publicroom"] value ', sent_stanza).pop().textContent).toBe('1'); + expect(sizzle('field[var="muc#roomconfig_changesubject"] value', sent_stanza).pop().textContent).toBe('0'); + expect(sizzle('field[var="muc#roomconfig_whois"] value ', sent_stanza).pop().textContent).toBe('anyone'); + expect(sizzle('field[var="muc#roomconfig_membersonly"] value', sent_stanza).pop().textContent).toBe('1'); + expect(sizzle('field[var="muc#roomconfig_historylength"] value', sent_stanza).pop().textContent).toBe('20'); + done(); }); })); }); @@ -278,13 +280,13 @@ describe("An instant groupchat", function () { it("will be created when muc_instant_rooms is set to true", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { - var IQ_stanzas = _converse.connection.IQ_stanzas; - var sent_IQ, IQ_id; - var sendIQ = _converse.connection.sendIQ; + const IQ_stanzas = _converse.connection.IQ_stanzas; + const sendIQ = _converse.connection.sendIQ; + let sent_IQ, IQ_id, view; spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { if (iq.nodeTree.getAttribute('to') === 'lounge@localhost') { sent_IQ = iq; @@ -293,44 +295,41 @@ sendIQ.bind(this)(iq, callback, errback); } }); - test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); + test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy') + .then(() => { + // We pretend this is a new room, so no disco info is returned. + // + /* + * + * + * + * + * + * + * + */ + var features_stanza = $iq({ + 'from': 'lounge@localhost', + 'id': IQ_id, + 'to': 'dummy@localhost/desktop', + 'type': 'error' + }).c('error', {'type': 'cancel'}) + .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); - // We pretend this is a new room, so no disco info is returned. - // - /* - * - * - * - * - * - * - * - */ - var features_stanza = $iq({ - 'from': 'lounge@localhost', - 'id': IQ_id, - 'to': 'dummy@localhost/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); - _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); + view = _converse.chatboxviews.get('lounge@localhost'); + spyOn(view, 'join').and.callThrough(); + spyOn(view, 'submitNickname').and.callThrough(); - var view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'join').and.callThrough(); - spyOn(view, 'submitNickname').and.callThrough(); - - /* - * - * - */ - test_utils.waitUntil(function () { - return _.filter(IQ_stanzas, function (iq) { - return iq.nodeTree.querySelector('query[node="x-roomuser-item"]'); - }).length > 0; - }, 300).then(function () { + /* + * + * + */ + return test_utils.waitUntil(() => _.filter(IQ_stanzas, (iq) => iq.nodeTree.querySelector('query[node="x-roomuser-item"]')).length); + }).then(() => { const iq = _.filter(IQ_stanzas, function (iq) { return iq.nodeTree.querySelector(`query[node="x-roomuser-item"]`); }).pop(); @@ -410,200 +409,202 @@ describe("A Groupchat", function () { it("shows join/leave messages when users enter or exit a groupchat", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { - test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1'); - var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - var $chat_content = $(view.el).find('.chat-content'); + test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1') + .then(() => { + var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + var $chat_content = $(view.el).find('.chat-content'); - /* We don't show join/leave messages for existing occupants. We - * know about them because we receive their presences before we - * receive our own. - */ - var presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/oldguy' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'oldguy@localhost/_converse.js-290929789', - 'role': 'participant' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(0); + /* We don't show join/leave messages for existing occupants. We + * know about them because we receive their presences before we + * receive our own. + */ + var presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/oldguy' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'oldguy@localhost/_converse.js-290929789', + 'role': 'participant' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(0); - /* - * - * - * - * - * - */ - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/some1' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'owner', - 'jid': 'dummy@localhost/_converse.js-29092160', - 'role': 'moderator' - }).up() - .c('status', {code: '110'}); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the groupchat"); + /* + * + * + * + * + * + */ + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/some1' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'owner', + 'jid': 'dummy@localhost/_converse.js-29092160', + 'role': 'moderator' + }).up() + .c('status', {code: '110'}); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the groupchat"); - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/newguy' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'newguy@localhost/_converse.js-290929789', - 'role': 'participant' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(2); - expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the groupchat"); - - // Add another entrant, otherwise the above message will be - // collapsed if "newguy" leaves immediately again - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/newgirl' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'newgirl@localhost/_converse.js-213098781', - 'role': 'participant' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3); - expect($chat_content.find('div.chat-info:last').html()).toBe("newgirl has entered the groupchat"); - - // Don't show duplicate join messages - presence = $pres({ - to: 'dummy@localhost/_converse.js-290918392', - from: 'coven@chat.shakespeare.lit/newguy' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'newguy@localhost/_converse.js-290929789', - 'role': 'participant' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3); - - /* - * Disconnected: Replaced by new connection - * - * - * - * - */ - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - type: 'unavailable', - from: 'coven@chat.shakespeare.lit/newguy' - }) - .c('status', 'Disconnected: Replaced by new connection').up() - .c('x', {xmlns: Strophe.NS.MUC_USER}) + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/newguy' + }) + .c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'newguy@localhost/_converse.js-290929789', - 'role': 'none' + 'role': 'participant' }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content.find('div.chat-info').length).toBe(4); - expect($chat_content.find('div.chat-info:last').html()).toBe( - 'newguy has left the groupchat. '+ - '"Disconnected: Replaced by new connection"'); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(2); + expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the groupchat"); - // When the user immediately joins again, we collapse the - // multiple join/leave messages. - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/newguy' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'newguy@localhost/_converse.js-290929789', - 'role': 'participant' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content.find('div.chat-info').length).toBe(4); - var $msg_el = $chat_content.find('div.chat-info:last'); - expect($msg_el.html()).toBe("newguy has left and re-entered the groupchat"); - expect($msg_el.data('leavejoin')).toBe('"newguy"'); + // Add another entrant, otherwise the above message will be + // collapsed if "newguy" leaves immediately again + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/newgirl' + }) + .c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'newgirl@localhost/_converse.js-213098781', + 'role': 'participant' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3); + expect($chat_content.find('div.chat-info:last').html()).toBe("newgirl has entered the groupchat"); - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - type: 'unavailable', - from: 'coven@chat.shakespeare.lit/newguy' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) + // Don't show duplicate join messages + presence = $pres({ + to: 'dummy@localhost/_converse.js-290918392', + from: 'coven@chat.shakespeare.lit/newguy' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) .c('item', { 'affiliation': 'none', 'jid': 'newguy@localhost/_converse.js-290929789', + 'role': 'participant' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3); + + /* + * Disconnected: Replaced by new connection + * + * + * + * + */ + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + type: 'unavailable', + from: 'coven@chat.shakespeare.lit/newguy' + }) + .c('status', 'Disconnected: Replaced by new connection').up() + .c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'newguy@localhost/_converse.js-290929789', + 'role': 'none' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content.find('div.chat-info').length).toBe(4); + expect($chat_content.find('div.chat-info:last').html()).toBe( + 'newguy has left the groupchat. '+ + '"Disconnected: Replaced by new connection"'); + + // When the user immediately joins again, we collapse the + // multiple join/leave messages. + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/newguy' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'newguy@localhost/_converse.js-290929789', + 'role': 'participant' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content.find('div.chat-info').length).toBe(4); + var $msg_el = $chat_content.find('div.chat-info:last'); + expect($msg_el.html()).toBe("newguy has left and re-entered the groupchat"); + expect($msg_el.data('leavejoin')).toBe('"newguy"'); + + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + type: 'unavailable', + from: 'coven@chat.shakespeare.lit/newguy' + }) + .c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'newguy@localhost/_converse.js-290929789', + 'role': 'none' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content.find('div.chat-info').length).toBe(4); + $msg_el = $chat_content.find('div.chat-info:last'); + expect($msg_el.html()).toBe('newguy has left the groupchat'); + expect($msg_el.data('leave')).toBe('"newguy"'); + + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/nomorenicks' + }) + .c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'nomorenicks@localhost/_converse.js-290929789', + 'role': 'participant' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(5); + expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the groupchat"); + + presence = $pres({ + to: 'dummy@localhost/_converse.js-290918392', + type: 'unavailable', + from: 'coven@chat.shakespeare.lit/nomorenicks' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'nomorenicks@localhost/_converse.js-290929789', 'role': 'none' }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content.find('div.chat-info').length).toBe(4); - $msg_el = $chat_content.find('div.chat-info:last'); - expect($msg_el.html()).toBe('newguy has left the groupchat'); - expect($msg_el.data('leave')).toBe('"newguy"'); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(5); + expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered and left the groupchat"); - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/nomorenicks' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'nomorenicks@localhost/_converse.js-290929789', - 'role': 'participant' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(5); - expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the groupchat"); - - presence = $pres({ - to: 'dummy@localhost/_converse.js-290918392', - type: 'unavailable', - from: 'coven@chat.shakespeare.lit/nomorenicks' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'nomorenicks@localhost/_converse.js-290929789', - 'role': 'none' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(5); - expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered and left the groupchat"); - - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/nomorenicks' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'nomorenicks@localhost/_converse.js-290929789', - 'role': 'participant' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(5); - expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the groupchat"); - done(); + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/nomorenicks' + }) + .c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'nomorenicks@localhost/_converse.js-290929789', + 'role': 'participant' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(5); + expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the groupchat"); + done(); + }); })); it("shows a new day indicator if a join/leave message is received on a new day", @@ -773,73 +774,50 @@ })); it("shows its description in the chat heading", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { - var sent_IQ, IQ_id; - var sendIQ = _converse.connection.sendIQ; + let sent_IQ, IQ_id, view; + const sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { sent_IQ = iq; IQ_id = sendIQ.bind(this)(iq, callback, errback); }); - _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); - var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - - var features_stanza = $iq({ - from: 'coven@chat.shakespeare.lit', - 'id': IQ_id, - 'to': 'dummy@localhost/desktop', - 'type': 'result' - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { - 'category': 'conference', - 'name': 'A Dark Cave', - 'type': 'text' - }).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() - .c('feature', {'var': 'muc_passwordprotected'}).up() - .c('feature', {'var': 'muc_hidden'}).up() - .c('feature', {'var': 'muc_temporary'}).up() - .c('feature', {'var': 'muc_open'}).up() - .c('feature', {'var': 'muc_unmoderated'}).up() - .c('feature', {'var': 'muc_nonanonymous'}).up() - .c('feature', {'var': 'urn:xmpp:mam:0'}).up() - .c('x', { 'xmlns':'jabber:x:data', 'type':'result'}) - .c('field', {'var':'FORM_TYPE', 'type':'hidden'}) - .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up() - .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'}) - .c('value').t('This is the description').up().up() - .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of participants'}) - .c('value').t(0); - _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); - test_utils.waitUntil(() => _.get(view.el.querySelector('.chatroom-description'), 'textContent')) - .then(function () { - expect($(view.el.querySelector('.chatroom-description')).text()).toBe('This is the description'); - done(); - }); - })); - - it("will specially mark messages in which you are mentioned", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { - - test_utils.createContacts(_converse, 'current'); - test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { - var view = _converse.chatboxviews.get('lounge@localhost'); - if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); } - var message = 'dummy: Your attention is required'; - var nick = mock.chatroom_names[0], - msg = $msg({ - from: 'lounge@localhost/'+nick, - id: (new Date()).getTime(), - to: 'dummy@localhost', - type: 'groupchat' - }).c('body').t(message).tree(); - view.model.onMessage(msg); - expect($(view.el).find('.chat-msg').hasClass('mentioned')).toBeTruthy(); + _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}) + .then(() => { + view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + const features_stanza = $iq({ + from: 'coven@chat.shakespeare.lit', + 'id': IQ_id, + 'to': 'dummy@localhost/desktop', + 'type': 'result' + }) + .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) + .c('identity', { + 'category': 'conference', + 'name': 'A Dark Cave', + 'type': 'text' + }).up() + .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() + .c('feature', {'var': 'muc_passwordprotected'}).up() + .c('feature', {'var': 'muc_hidden'}).up() + .c('feature', {'var': 'muc_temporary'}).up() + .c('feature', {'var': 'muc_open'}).up() + .c('feature', {'var': 'muc_unmoderated'}).up() + .c('feature', {'var': 'muc_nonanonymous'}).up() + .c('feature', {'var': 'urn:xmpp:mam:0'}).up() + .c('x', { 'xmlns':'jabber:x:data', 'type':'result'}) + .c('field', {'var':'FORM_TYPE', 'type':'hidden'}) + .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up() + .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'}) + .c('value').t('This is the description').up().up() + .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of participants'}) + .c('value').t(0); + _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); + return test_utils.waitUntil(() => _.get(view.el.querySelector('.chatroom-description'), 'textContent')) + }).then(function () { + expect(view.el.querySelector('.chatroom-description').textContent).toBe('This is the description'); done(); }); })); @@ -885,9 +863,9 @@ })); it("can be configured if you're its owner", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { var view; var sent_IQ, IQ_id; @@ -897,48 +875,46 @@ IQ_id = sendIQ.bind(this)(iq, callback, errback); }); - _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); - view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}) + .then(() => { + view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - spyOn(view.model, 'saveAffiliationAndRole').and.callThrough(); + spyOn(view.model, 'saveAffiliationAndRole').and.callThrough(); - // We pretend this is a new room, so no disco info is returned. - var features_stanza = $iq({ - from: 'coven@chat.shakespeare.lit', - 'id': IQ_id, - 'to': 'dummy@localhost/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); - _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); + // We pretend this is a new room, so no disco info is returned. + var features_stanza = $iq({ + from: 'coven@chat.shakespeare.lit', + 'id': IQ_id, + 'to': 'dummy@localhost/desktop', + 'type': 'error' + }).c('error', {'type': 'cancel'}) + .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); - /* - * - * - * - * - * - */ - var presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/some1' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'owner', - 'jid': 'dummy@localhost/_converse.js-29092160', - 'role': 'moderator' - }).up() - .c('status', {code: '110'}); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(view.model.saveAffiliationAndRole).toHaveBeenCalled(); - expect($(view.el.querySelector('.toggle-chatbox-button')).is(':visible')).toBeTruthy(); - - test_utils.waitUntil(function () { - return !_.isNull(view.el.querySelector('.configure-chatroom-button')); - }, 300).then(function () { + /* + * + * + * + * + * + */ + var presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/some1' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'owner', + 'jid': 'dummy@localhost/_converse.js-29092160', + 'role': 'moderator' + }).up() + .c('status', {code: '110'}); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect(view.model.saveAffiliationAndRole).toHaveBeenCalled(); + expect($(view.el.querySelector('.toggle-chatbox-button')).is(':visible')).toBeTruthy(); + return test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.configure-chatroom-button'))) + }).then(() => { expect($(view.el.querySelector('.configure-chatroom-button')).is(':visible')).toBeTruthy(); - view.el.querySelector('.configure-chatroom-button').click(); /* Check that an IQ is sent out, asking for the @@ -1074,42 +1050,40 @@ .c('value').t('cauldronburn'); _converse.connection._dataRecv(test_utils.createRequest(config_stanza)); - test_utils.waitUntil(function () { - return $(view.el.querySelector('form.chatroom-form')).length; - }, 300).then(function () { - expect($(view.el.querySelector('form.chatroom-form')).length).toBe(1); - expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2); - var $membersonly = $(view.el.querySelector('input[name="muc#roomconfig_membersonly"]')); - expect($membersonly.length).toBe(1); - expect($membersonly.attr('type')).toBe('checkbox'); - $membersonly.prop('checked', true); + return test_utils.waitUntil(() => view.el.querySelectorAll('form.chatroom-form').length) + }).then(() => { + expect($(view.el.querySelector('form.chatroom-form')).length).toBe(1); + expect(view.el.querySelectorAll('form.chatroom-form fieldset').length).toBe(2); + var $membersonly = $(view.el.querySelector('input[name="muc#roomconfig_membersonly"]')); + expect($membersonly.length).toBe(1); + expect($membersonly.attr('type')).toBe('checkbox'); + $membersonly.prop('checked', true); - var $moderated = $(view.el.querySelector('input[name="muc#roomconfig_moderatedroom"]')); - expect($moderated.length).toBe(1); - expect($moderated.attr('type')).toBe('checkbox'); - $moderated.prop('checked', true); + var $moderated = $(view.el.querySelector('input[name="muc#roomconfig_moderatedroom"]')); + expect($moderated.length).toBe(1); + expect($moderated.attr('type')).toBe('checkbox'); + $moderated.prop('checked', true); - var $password = $(view.el.querySelector('input[name="muc#roomconfig_roomsecret"]')); - expect($password.length).toBe(1); - expect($password.attr('type')).toBe('password'); + var $password = $(view.el.querySelector('input[name="muc#roomconfig_roomsecret"]')); + expect($password.length).toBe(1); + expect($password.attr('type')).toBe('password'); - var $allowpm = $(view.el.querySelector('select[name="muc#roomconfig_allowpm"]')); - expect($allowpm.length).toBe(1); - $allowpm.val('moderators'); + var $allowpm = $(view.el.querySelector('select[name="muc#roomconfig_allowpm"]')); + expect($allowpm.length).toBe(1); + $allowpm.val('moderators'); - var $presencebroadcast = $(view.el.querySelector('select[name="muc#roomconfig_presencebroadcast"]')); - expect($presencebroadcast.length).toBe(1); - $presencebroadcast.val(['moderator']); + var $presencebroadcast = $(view.el.querySelector('select[name="muc#roomconfig_presencebroadcast"]')); + expect($presencebroadcast.length).toBe(1); + $presencebroadcast.val(['moderator']); - view.el.querySelector('input[type="submit"]').click(); + view.el.querySelector('input[type="submit"]').click(); - var $sent_stanza = $(sent_IQ.toLocaleString()); - expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1'); - expect($sent_stanza.find('field[var="muc#roomconfig_moderatedroom"] value').text()).toBe('1'); - expect($sent_stanza.find('field[var="muc#roomconfig_allowpm"] value').text()).toBe('moderators'); - expect($sent_stanza.find('field[var="muc#roomconfig_presencebroadcast"] value').text()).toBe('moderator'); - done(); - }); + var $sent_stanza = $(sent_IQ.toLocaleString()); + expect($sent_stanza.find('field[var="muc#roomconfig_membersonly"] value').text()).toBe('1'); + expect($sent_stanza.find('field[var="muc#roomconfig_moderatedroom"] value').text()).toBe('1'); + expect($sent_stanza.find('field[var="muc#roomconfig_allowpm"] value').text()).toBe('moderators'); + expect($sent_stanza.find('field[var="muc#roomconfig_presencebroadcast"] value').text()).toBe('moderator'); + done(); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); })); @@ -1317,13 +1291,13 @@ })); it("will use the user's reserved nickname, if it exists", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { - var IQ_stanzas = _converse.connection.IQ_stanzas; - var sent_IQ, IQ_id; - var sendIQ = _converse.connection.sendIQ; + let sent_IQ, IQ_id, view; + const IQ_stanzas = _converse.connection.IQ_stanzas; + const sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { if (iq.nodeTree.getAttribute('to') === 'lounge@localhost') { sent_IQ = iq; @@ -1333,35 +1307,31 @@ } }); - test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); + test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy') + .then(() => { + // We pretend this is a new room, so no disco info is returned. + var features_stanza = $iq({ + from: 'lounge@localhost', + 'id': IQ_id, + 'to': 'dummy@localhost/desktop', + 'type': 'error' + }).c('error', {'type': 'cancel'}) + .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); - // We pretend this is a new room, so no disco info is returned. - var features_stanza = $iq({ - from: 'lounge@localhost', - 'id': IQ_id, - 'to': 'dummy@localhost/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); - _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); + view = _converse.chatboxviews.get('lounge@localhost'); + spyOn(view, 'join').and.callThrough(); - var view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'join').and.callThrough(); - - /* - * - * - */ - - test_utils.waitUntil(function () { - return _.filter(IQ_stanzas, function (iq) { - return iq.nodeTree.querySelector('query[node="x-roomuser-item"]'); - }).length > 0; - }, 300).then(function () { + /* + * + * + */ + return test_utils.waitUntil(() => _.filter(IQ_stanzas, (iq) => iq.nodeTree.querySelector('query[node="x-roomuser-item"]')).length) + }).then(() => { const iq = _.filter(IQ_stanzas, function (iq) { return iq.nodeTree.querySelector(`query[node="x-roomuser-item"]`); }).pop(); @@ -1420,33 +1390,35 @@ })); it("allows the user to invite their roster contacts to enter the groupchat", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { - test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); test_utils.createContacts(_converse, 'current'); // We need roster contacts, so that we have someone to invite // Since we don't actually fetch roster contacts, we need to // cheat here and emit the event. _converse.emit('rosterContactsFetched'); - spyOn(_converse, 'emit'); - spyOn(window, 'prompt').and.callFake(function () { - return "Please join!"; - }); - var view = _converse.chatboxviews.get('lounge@localhost'); + let view; + test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy') + .then(() => { - // XXX: cheating a lttle bit, normally this'll be set after - // receiving the features for the groupchat. - view.model.set('open', 'true'); + spyOn(_converse, 'emit'); + spyOn(window, 'prompt').and.callFake(function () { + return "Please join!"; + }); + view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view.model, 'directInvite').and.callThrough(); - var $input; - $(view.el).find('.chat-area').remove(); + // XXX: cheating a lttle bit, normally this'll be set after + // receiving the features for the groupchat. + view.model.set('open', 'true'); - test_utils.waitUntil(function () { - return $(view.el).find('input.invited-contact').length; - }, 300).then(function () { + spyOn(view.model, 'directInvite').and.callThrough(); + var $input; + $(view.el).find('.chat-area').remove(); + + return test_utils.waitUntil(() => view.el.querySelectorAll('input.invited-contact').length) + }).then(function () { var $input = $(view.el).find('input.invited-contact'); expect($input.attr('placeholder')).toBe('Invite'); $input.val("Felix"); @@ -1781,9 +1753,9 @@ })); it("queries for the groupchat information before attempting to join the user", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { var sent_IQ, IQ_id; var sendIQ = _converse.connection.sendIQ; @@ -1792,57 +1764,58 @@ IQ_id = sendIQ.bind(this)(iq, callback, errback); }); - _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); - - // Check that the groupchat queried for the feautures. - expect(sent_IQ.toLocaleString()).toBe( - ""+ - ""+ - ""); - - var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - spyOn(view.model, 'parseRoomFeatures').and.callThrough(); - /* - * - * - * - * - * - * - * - * - * - * - * - */ - var features_stanza = $iq({ - from: 'coven@chat.shakespeare.lit', - 'id': IQ_id, - 'to': 'dummy@localhost/desktop', - 'type': 'result' - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { - 'category': 'conference', - 'name': 'A Dark Cave', - 'type': 'text' - }).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() - .c('feature', {'var': 'muc_passwordprotected'}).up() - .c('feature', {'var': 'muc_hidden'}).up() - .c('feature', {'var': 'muc_temporary'}).up() - .c('feature', {'var': 'muc_open'}).up() - .c('feature', {'var': 'muc_unmoderated'}).up() - .c('feature', {'var': 'muc_nonanonymous'}); - _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); - test_utils.waitUntil(() => view.model.parseRoomFeatures.calls.count(), 300) + let view; + _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}) .then(() => { + // Check that the groupchat queried for the feautures. + expect(sent_IQ.toLocaleString()).toBe( + ""+ + ""+ + ""); + + view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + spyOn(view.model, 'parseRoomFeatures').and.callThrough(); + /* + * + * + * + * + * + * + * + * + * + * + * + */ + const features_stanza = $iq({ + from: 'coven@chat.shakespeare.lit', + 'id': IQ_id, + 'to': 'dummy@localhost/desktop', + 'type': 'result' + }) + .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) + .c('identity', { + 'category': 'conference', + 'name': 'A Dark Cave', + 'type': 'text' + }).up() + .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() + .c('feature', {'var': 'muc_passwordprotected'}).up() + .c('feature', {'var': 'muc_hidden'}).up() + .c('feature', {'var': 'muc_temporary'}).up() + .c('feature', {'var': 'muc_open'}).up() + .c('feature', {'var': 'muc_unmoderated'}).up() + .c('feature', {'var': 'muc_nonanonymous'}); + _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); + return test_utils.waitUntil(() => view.model.parseRoomFeatures.calls.count(), 300) + }).then(() => { expect(view.model.get('features_fetched')).toBeTruthy(); expect(view.model.get('passwordprotected')).toBe(true); expect(view.model.get('hidden')).toBe(true); @@ -2013,82 +1986,89 @@ })); it("can be saved to, and retrieved from, browserStorage", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { - test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); - // We instantiate a new ChatBoxes collection, which by default - // will be empty. - test_utils.openControlBox(); - var newchatboxes = new _converse.ChatBoxes(); - expect(newchatboxes.length).toEqual(0); - // The chatboxes will then be fetched from browserStorage inside the - // onConnected method - newchatboxes.onConnected(); - expect(newchatboxes.length).toEqual(2); - // Check that the chatrooms retrieved from browserStorage - // have the same attributes values as the original ones. - var attrs = ['id', 'box_id', 'visible']; - var new_attrs, old_attrs; - for (var i=0; i { + // We instantiate a new ChatBoxes collection, which by default + // will be empty. + test_utils.openControlBox(); + var newchatboxes = new _converse.ChatBoxes(); + expect(newchatboxes.length).toEqual(0); + // The chatboxes will then be fetched from browserStorage inside the + // onConnected method + newchatboxes.onConnected(); + expect(newchatboxes.length).toEqual(2); + // Check that the chatrooms retrieved from browserStorage + // have the same attributes values as the original ones. + var attrs = ['id', 'box_id', 'visible']; + var new_attrs, old_attrs; + for (var i=0; i { + const view = _converse.chatboxviews.get('lounge@localhost'), + trimmed_chatboxes = _converse.minimized_chats; - spyOn(view, 'minimize').and.callThrough(); - spyOn(view, 'maximize').and.callThrough(); - spyOn(_converse, 'emit'); - view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called - view.el.querySelector('.toggle-chatbox-button').click(); + spyOn(view, 'minimize').and.callThrough(); + spyOn(view, 'maximize').and.callThrough(); + spyOn(_converse, 'emit'); + view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called + view.el.querySelector('.toggle-chatbox-button').click(); - expect(view.minimize).toHaveBeenCalled(); - expect(_converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object)); - expect(u.isVisible(view.el)).toBeFalsy(); - expect(view.model.get('minimized')).toBeTruthy(); - expect(view.minimize).toHaveBeenCalled(); - var trimmedview = trimmed_chatboxes.get(view.model.get('id')); - trimmedview.el.querySelector("a.restore-chat").click(); - expect(view.maximize).toHaveBeenCalled(); - expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object)); - expect(view.model.get('minimized')).toBeFalsy(); - expect(_converse.emit.calls.count(), 3); - done(); + expect(view.minimize).toHaveBeenCalled(); + expect(_converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object)); + expect(u.isVisible(view.el)).toBeFalsy(); + expect(view.model.get('minimized')).toBeTruthy(); + expect(view.minimize).toHaveBeenCalled(); + var trimmedview = trimmed_chatboxes.get(view.model.get('id')); + trimmedview.el.querySelector("a.restore-chat").click(); + expect(view.maximize).toHaveBeenCalled(); + expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object)); + expect(view.model.get('minimized')).toBeFalsy(); + expect(_converse.emit.calls.count(), 3); + done(); + + }); })); it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { - test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); - var view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'close').and.callThrough(); - spyOn(_converse, 'emit'); - spyOn(view.model, 'leave'); - view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called - view.el.querySelector('.close-chatbox-button').click(); - expect(view.close).toHaveBeenCalled(); - expect(view.model.leave).toHaveBeenCalled(); - expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object)); - done(); + test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy') + .then(() => { + const view = _converse.chatboxviews.get('lounge@localhost'); + spyOn(view, 'close').and.callThrough(); + spyOn(_converse, 'emit'); + spyOn(view.model, 'leave'); + view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called + view.el.querySelector('.close-chatbox-button').click(); + expect(view.close).toHaveBeenCalled(); + expect(view.model.leave).toHaveBeenCalled(); + expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object)); + done(); + }); })); }); @@ -2952,44 +2932,44 @@ describe("Someone being invited to a groupchat", function () { it("will first be added to the member list if the groupchat is members only", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { var sent_IQs = [], IQ_ids = []; - var invitee_jid, sent_stanza, sent_id; + let invitee_jid, sent_stanza, sent_id, view; var sendIQ = _converse.connection.sendIQ; spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { sent_IQs.push(iq); IQ_ids.push(sendIQ.bind(this)(iq, callback, errback)); }); - _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'dummy'}); - - var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - spyOn(view.model, 'parseRoomFeatures').and.callThrough(); - - // State that the chat is members-only via the features IQ - var features_stanza = $iq({ - from: 'coven@chat.shakespeare.lit', - 'id': IQ_ids.pop(), - 'to': 'dummy@localhost/desktop', - 'type': 'result' - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { - 'category': 'conference', - 'name': 'A Dark Cave', - 'type': 'text' - }).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() - .c('feature', {'var': 'muc_hidden'}).up() - .c('feature', {'var': 'muc_temporary'}).up() - .c('feature', {'var': 'muc_membersonly'}).up(); - _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); - - test_utils.waitUntil(() => view.model.parseRoomFeatures.calls.count(), 300) + _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'dummy'}) .then(() => { + view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + spyOn(view.model, 'parseRoomFeatures').and.callThrough(); + + // State that the chat is members-only via the features IQ + var features_stanza = $iq({ + from: 'coven@chat.shakespeare.lit', + 'id': IQ_ids.pop(), + 'to': 'dummy@localhost/desktop', + 'type': 'result' + }) + .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) + .c('identity', { + 'category': 'conference', + 'name': 'A Dark Cave', + 'type': 'text' + }).up() + .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() + .c('feature', {'var': 'muc_hidden'}).up() + .c('feature', {'var': 'muc_temporary'}).up() + .c('feature', {'var': 'muc_membersonly'}).up(); + _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); + + return test_utils.waitUntil(() => view.model.parseRoomFeatures.calls.count(), 300); + }).then(() => { expect(view.model.get('membersonly')).toBeTruthy(); test_utils.createContacts(_converse, 'current'); @@ -3173,11 +3153,13 @@ describe("The \"Groupchats\" section", function () { it("contains a link to a modal through which a new chatroom can be created", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.openControlBox(); + _converse.emit('rosterContactsFetched'); + var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; roomspanel.el.querySelector('.show-add-muc-modal').click(); test_utils.closeControlBox(_converse); @@ -3193,16 +3175,17 @@ modal.el.querySelector('input[name="chatroom"]').value = 'lounce@muc.localhost'; modal.el.querySelector('form input[type="submit"]').click(); + return test_utils.waitUntil(() => _converse.chatboxes.length); + }).then(() => { expect($('.chatroom:visible').length).toBe(1); // There should now be an open chatroom done(); }).catch(_.partial(console.error, _)); })); it("contains a link to a modal which can list groupchats publically available on the server", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { - + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { var sendIQ = _converse.connection.sendIQ; var sent_stanza, IQ_id; @@ -3216,9 +3199,8 @@ roomspanel.el.querySelector('.show-list-muc-modal').click(); test_utils.closeControlBox(_converse); const modal = roomspanel.list_rooms_modal; - test_utils.waitUntil(function () { - return u.isVisible(modal.el); - }, 1000).then(function () { + test_utils.waitUntil(() => u.isVisible(modal.el), 1000) + .then(() => { spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(function () { var deferred = new $.Deferred(); deferred.resolve(); @@ -3231,7 +3213,8 @@ const input = modal.el.querySelector('input[name="server"]').value = 'chat.shakespear.lit'; modal.el.querySelector('input[type="submit"]').click(); - + return test_utils.waitUntil(() => _converse.chatboxes.length); + }).then(() => { expect(sent_stanza.toLocaleString()).toBe( ""+ ""+ @@ -3260,6 +3243,8 @@ expect(rooms[4].textContent.trim()).toBe("Macbeth's Castle"); rooms[4].querySelector('.open-room').click(); + return test_utils.waitUntil(() => _converse.chatboxes.length > 1); + }).then(() => { expect($('.chatroom:visible').length).toBe(1); // There should now be an open chatroom var view = _converse.chatboxviews.get('inverness@chat.shakespeare.lit'); expect(view.el.querySelector('.chat-head-chatroom').textContent.trim()).toBe("Macbeth's Castle"); @@ -3508,143 +3493,145 @@ describe("A paused notification", function () { it("will be shown if received", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { - test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1'); - var room_jid = 'coven@chat.shakespeare.lit'; - var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - var $chat_content = $(view.el).find('.chat-content'); + test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1') + .then(() => { + var room_jid = 'coven@chat.shakespeare.lit'; + var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + var $chat_content = $(view.el).find('.chat-content'); - /* - * - * - * - * - * - */ - var presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/some1' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'owner', - 'jid': 'dummy@localhost/_converse.js-29092160', - 'role': 'moderator' - }).up() - .c('status', {code: '110'}); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the groupchat"); + /* + * + * + * + * + * + */ + var presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/some1' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'owner', + 'jid': 'dummy@localhost/_converse.js-29092160', + 'role': 'moderator' + }).up() + .c('status', {code: '110'}); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the groupchat"); - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/newguy' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'newguy@localhost/_converse.js-290929789', - 'role': 'participant' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(2); - expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the groupchat"); + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/newguy' + }) + .c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'newguy@localhost/_converse.js-290929789', + 'role': 'participant' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(2); + expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the groupchat"); - presence = $pres({ - to: 'dummy@localhost/_converse.js-29092160', - from: 'coven@chat.shakespeare.lit/nomorenicks' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'nomorenicks@localhost/_converse.js-290929789', - 'role': 'participant' - }); - _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3); - expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the groupchat"); + presence = $pres({ + to: 'dummy@localhost/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/nomorenicks' + }) + .c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'nomorenicks@localhost/_converse.js-290929789', + 'role': 'participant' + }); + _converse.connection._dataRecv(test_utils.createRequest(presence)); + expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3); + expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the groupchat"); - // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions + // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions - // state - var msg = $msg({ - from: room_jid+'/newguy', - id: (new Date()).getTime(), - to: 'dummy@localhost', - type: 'groupchat' - }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - view.model.onMessage(msg); + // state + var msg = $msg({ + from: room_jid+'/newguy', + id: (new Date()).getTime(), + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); + view.model.onMessage(msg); - // Check that the notification appears inside the chatbox in the DOM - var events = view.el.querySelectorAll('.chat-event'); - expect(events.length).toBe(3); - expect(events[0].textContent).toEqual('some1 has entered the groupchat'); - expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); + // Check that the notification appears inside the chatbox in the DOM + var events = view.el.querySelectorAll('.chat-event'); + expect(events.length).toBe(3); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); + expect(events[1].textContent).toEqual('newguy has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); - var notifications = view.el.querySelectorAll('.chat-state-notification'); - expect(notifications.length).toBe(1); - expect(notifications[0].textContent).toEqual('newguy is typing'); + var notifications = view.el.querySelectorAll('.chat-state-notification'); + expect(notifications.length).toBe(1); + expect(notifications[0].textContent).toEqual('newguy is typing'); - // Check that it doesn't appear twice - msg = $msg({ - from: room_jid+'/newguy', - id: (new Date()).getTime(), - to: 'dummy@localhost', - type: 'groupchat' - }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - view.model.onMessage(msg); + // Check that it doesn't appear twice + msg = $msg({ + from: room_jid+'/newguy', + id: (new Date()).getTime(), + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); + view.model.onMessage(msg); - events = view.el.querySelectorAll('.chat-event'); - expect(events.length).toBe(3); - expect(events[0].textContent).toEqual('some1 has entered the groupchat'); - expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); + events = view.el.querySelectorAll('.chat-event'); + expect(events.length).toBe(3); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); + expect(events[1].textContent).toEqual('newguy has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); - notifications = view.el.querySelectorAll('.chat-state-notification'); - expect(notifications.length).toBe(1); - expect(notifications[0].textContent).toEqual('newguy is typing'); + notifications = view.el.querySelectorAll('.chat-state-notification'); + expect(notifications.length).toBe(1); + expect(notifications[0].textContent).toEqual('newguy is typing'); - // state for a different occupant - msg = $msg({ - from: room_jid+'/nomorenicks', - id: (new Date()).getTime(), - to: 'dummy@localhost', - type: 'groupchat' - }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - view.model.onMessage(msg); - events = view.el.querySelectorAll('.chat-event'); - expect(events.length).toBe(3); - expect(events[0].textContent).toEqual('some1 has entered the groupchat'); - expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); + // state for a different occupant + msg = $msg({ + from: room_jid+'/nomorenicks', + id: (new Date()).getTime(), + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); + view.model.onMessage(msg); + events = view.el.querySelectorAll('.chat-event'); + expect(events.length).toBe(3); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); + expect(events[1].textContent).toEqual('newguy has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); - notifications = view.el.querySelectorAll('.chat-state-notification'); - expect(notifications.length).toBe(2); - expect(notifications[0].textContent).toEqual('newguy is typing'); - expect(notifications[1].textContent).toEqual('nomorenicks is typing'); + notifications = view.el.querySelectorAll('.chat-state-notification'); + expect(notifications.length).toBe(2); + expect(notifications[0].textContent).toEqual('newguy is typing'); + expect(notifications[1].textContent).toEqual('nomorenicks is typing'); - // state from occupant who typed first - msg = $msg({ - from: room_jid+'/newguy', - id: (new Date()).getTime(), - to: 'dummy@localhost', - type: 'groupchat' - }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - view.model.onMessage(msg); - events = view.el.querySelectorAll('.chat-event'); - expect(events.length).toBe(3); - expect(events[0].textContent).toEqual('some1 has entered the groupchat'); - expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); + // state from occupant who typed first + msg = $msg({ + from: room_jid+'/newguy', + id: (new Date()).getTime(), + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree(); + view.model.onMessage(msg); + events = view.el.querySelectorAll('.chat-event'); + expect(events.length).toBe(3); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); + expect(events[1].textContent).toEqual('newguy has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); - notifications = view.el.querySelectorAll('.chat-state-notification'); - expect(notifications.length).toBe(2); - expect(notifications[0].textContent).toEqual('nomorenicks is typing'); - expect(notifications[1].textContent).toEqual('newguy has stopped typing'); - done(); + notifications = view.el.querySelectorAll('.chat-state-notification'); + expect(notifications.length).toBe(2); + expect(notifications[0].textContent).toEqual('nomorenicks is typing'); + expect(notifications[1].textContent).toEqual('newguy has stopped typing'); + done(); + }); })); }); }); diff --git a/spec/controlbox.js b/spec/controlbox.js index 08d4b66b2..8d8caee2c 100644 --- a/spec/controlbox.js +++ b/spec/controlbox.js @@ -66,47 +66,50 @@ it("shows the number of unread mentions received", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'all').openControlBox(); + _converse.emit('rosterContactsFetched'); - var contacts_panel = _converse.chatboxviews.get('controlbox').contactspanel; + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openChatBoxFor(_converse, sender_jid); - var chatview = _converse.chatboxviews.get(sender_jid); - chatview.model.set({'minimized': true}); + return test_utils.waitUntil(() => _converse.chatboxes.length).then(() => { - expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy(); - expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy(); + const chatview = _converse.chatboxviews.get(sender_jid); + chatview.model.set({'minimized': true}); - var msg = $msg({ - from: sender_jid, - to: _converse.connection.jid, - type: 'chat', - id: (new Date()).getTime() - }).c('body').t('hello').up() - .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); - _converse.chatboxes.onMessage(msg); - expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1'); - expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1'); + expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy(); + expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy(); - msg = $msg({ - from: sender_jid, - to: _converse.connection.jid, - type: 'chat', - id: (new Date()).getTime() - }).c('body').t('hello again').up() - .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); - _converse.chatboxes.onMessage(msg); - expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('2'); - expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('2'); + var msg = $msg({ + from: sender_jid, + to: _converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('body').t('hello').up() + .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); + _converse.chatboxes.onMessage(msg); + expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1'); + expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1'); - chatview.model.set({'minimized': false}); - expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy(); - expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy(); - done(); + msg = $msg({ + from: sender_jid, + to: _converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('body').t('hello again').up() + .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); + _converse.chatboxes.onMessage(msg); + expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('2'); + expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('2'); + + chatview.model.set({'minimized': false}); + expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy(); + expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy(); + done(); + }); })); }); diff --git a/spec/converse.js b/spec/converse.js index 29af74f01..df411b3ad 100644 --- a/spec/converse.js +++ b/spec/converse.js @@ -1,12 +1,12 @@ (function (root, factory) { define([ - "jquery", "jasmine", "mock", "test-utils"], factory); -} (this, function ($, jasmine, mock, test_utils) { - var b64_sha1 = converse.env.b64_sha1; - var _ = converse.env._; +} (this, function (jasmine, mock, test_utils) { + const b64_sha1 = converse.env.b64_sha1, + _ = converse.env._, + u = converse.env.utils; describe("Converse", function() { @@ -274,59 +274,72 @@ describe("The \"chats\" API", function() { - it("has a method 'get' which returns the chatbox model", mock.initConverseWithPromises( - null, ['rosterInitialized'], {}, function (done, _converse) { + it("has a method 'get' which returns the promise that resolves to a chat model", mock.initConverseWithPromises( + null, ['rosterInitialized', 'chatBoxesInitialized'], {}, function (done, _converse) { test_utils.openControlBox(); - test_utils.createContacts(_converse, 'current'); + test_utils.createContacts(_converse, 'current', 2); + _converse.emit('rosterContactsFetched'); + // Test on chat that doesn't exist. expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy(); - var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; + // Test on chat that's not open - var box = _converse.api.chats.get(jid); + let box = _converse.api.chats.get(jid); expect(typeof box === 'undefined').toBeTruthy(); - var chatboxview = _converse.chatboxviews.get(jid); - // Test for single JID + expect(_converse.chatboxes.length).toBe(1); + + // Test for one JID test_utils.openChatBoxFor(_converse, jid); - box = _converse.api.chats.get(jid); + test_utils.waitUntil(() => _converse.chatboxes.length == 1).then(() => { + box = _converse.api.chats.get(jid); + expect(box instanceof Object).toBeTruthy(); + expect(box.get('box_id')).toBe(b64_sha1(jid)); + + const chatboxview = _converse.chatboxviews.get(jid); + expect(u.isVisible(chatboxview.el)).toBeTruthy(); + // Test for multiple JIDs + test_utils.openChatBoxFor(_converse, jid2); + return test_utils.waitUntil(() => _converse.chatboxes.length == 2); + }).then(() => { + const list = _converse.api.chats.get([jid, jid2]); + expect(_.isArray(list)).toBeTruthy(); + expect(list[0].get('box_id')).toBe(b64_sha1(jid)); + expect(list[1].get('box_id')).toBe(b64_sha1(jid2)); + done(); + }).catch(_.partial(console.error, _)); + })); + + it("has a method 'open' which opens and returns a promise that resolves to a chat model", mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesInitialized'], {}, function (done, _converse) { + + test_utils.openControlBox(); + test_utils.createContacts(_converse, 'current', 2); + _converse.emit('rosterContactsFetched'); + + const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; + // Test on chat that doesn't exist. + expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy(); + + return _converse.api.chats.open(jid).then((box) => { expect(box instanceof Object).toBeTruthy(); expect(box.get('box_id')).toBe(b64_sha1(jid)); - chatboxview = _converse.chatboxviews.get(jid); - expect($(chatboxview.el).is(':visible')).toBeTruthy(); + expect( + _.keys(box), + ['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set'] + ); + const chatboxview = _converse.chatboxviews.get(jid); + expect(u.isVisible(chatboxview.el)).toBeTruthy(); // Test for multiple JIDs - var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, jid2); - var list = _converse.api.chats.get([jid, jid2]); + return _converse.api.chats.open([jid, jid2]); + }).then((list) => { expect(_.isArray(list)).toBeTruthy(); expect(list[0].get('box_id')).toBe(b64_sha1(jid)); expect(list[1].get('box_id')).toBe(b64_sha1(jid2)); done(); - })); - - it("has a method 'open' which opens and returns the chatbox model", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, function (done, _converse) { - - test_utils.openControlBox(); - test_utils.createContacts(_converse, 'current'); - var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - var chatboxview; - // Test on chat that doesn't exist. - expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy(); - var box = _converse.api.chats.open(jid); - expect(box instanceof Object).toBeTruthy(); - expect(box.get('box_id')).toBe(b64_sha1(jid)); - expect( - _.keys(box), - ['close', 'focus', 'get', 'is_chatroom', 'maximize', 'minimize', 'open', 'set'] - ); - chatboxview = _converse.chatboxviews.get(jid); - expect($(chatboxview.el).is(':visible')).toBeTruthy(); - // Test for multiple JIDs - var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; - var list = _converse.api.chats.open([jid, jid2]); - expect(_.isArray(list)).toBeTruthy(); - expect(list[0].get('box_id')).toBe(b64_sha1(jid)); - expect(list[1].get('box_id')).toBe(b64_sha1(jid2)); - done(); + }); })); }); diff --git a/spec/http-file-upload.js b/spec/http-file-upload.js index 077be88b5..b68f8e1a0 100644 --- a/spec/http-file-upload.js +++ b/spec/http-file-upload.js @@ -225,25 +225,28 @@ [{'category': 'server', 'type':'IM'}], ['http://jabber.org/protocol/disco#items'], [], 'info').then(function () { - test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.localhost'], 'items').then(function () { - test_utils.waitUntilDiscoConfirmed(_converse, 'upload.localhost', [], [Strophe.NS.HTTPUPLOAD], []).then(function () { - test_utils.createContacts(_converse, 'current'); - var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - test_utils.waitUntil(function () { - return view.el.querySelector('.upload-file'); - }, 150).then(function () { - expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null); - done(); - }); - }); + let contact_jid, view; + + test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.localhost'], 'items') + .then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.localhost', [], [Strophe.NS.HTTPUPLOAD], [])) + .then(() => { + test_utils.createContacts(_converse, 'current', 3); + _converse.emit('rosterContactsFetched'); + + contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; + return test_utils.openChatBoxFor(_converse, contact_jid); + }).then(() => { + view = _converse.chatboxviews.get(contact_jid); + test_utils.waitUntil(() => view.el.querySelector('.upload-file')); + }).then(() => { + expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null); + done(); }); }); })); it("appears in MUC chats", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.waitUntilDiscoConfirmed( @@ -251,19 +254,15 @@ [{'category': 'server', 'type':'IM'}], ['http://jabber.org/protocol/disco#items'], [], 'info').then(function () { - test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.localhost'], 'items').then(function () { - test_utils.waitUntilDiscoConfirmed(_converse, 'upload.localhost', [], [Strophe.NS.HTTPUPLOAD], []).then(function () { - test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { - var view = _converse.chatboxviews.get('lounge@localhost'); - test_utils.waitUntil(function () { - return view.el.querySelector('.upload-file'); - }).then(function () { - expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null); - done(); - }); - }); - }); - }); + test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.localhost'], 'items') + .then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.localhost', [], [Strophe.NS.HTTPUPLOAD], [])) + .then(() => test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy')) + .then(() => test_utils.waitUntil(() => _converse.chatboxviews.get('lounge@localhost').el.querySelector('.upload-file'))) + .then(() => { + const view = _converse.chatboxviews.get('lounge@localhost'); + expect(view.el.querySelector('.chat-toolbar .upload-file')).not.toBe(null); + done(); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); }); })); @@ -277,101 +276,104 @@ var send_backup = XMLHttpRequest.prototype.send; var IQ_stanzas = _converse.connection.IQ_stanzas; + let contact_jid; + + test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items') + .then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], [])) + .then(() => { + test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; + return test_utils.openChatBoxFor(_converse, contact_jid); + }).then(() => { + var view = _converse.chatboxviews.get(contact_jid); + var file = { + 'type': 'image/jpeg', + 'size': '23456' , + 'lastModifiedDate': "", + 'name': "my-juliet.jpg" + }; + view.model.sendFiles([file]); + return test_utils.waitUntil(function () { + return _.filter(IQ_stanzas, function (iq) { + return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request'); + }).length > 0; + }).then(function () { + var iq = IQ_stanzas.pop(); + expect(iq.toLocaleString()).toBe( + ""+ + ""+ + ""); + + var base_url = document.URL.split(window.location.pathname)[0]; + var message = base_url+"/logo/conversejs-filled.svg"; + + var stanza = Strophe.xmlHtmlNode( + ""+ + ""+ + " "+ + "
Basic Base64String==
"+ + "
foo=bar; user=romeo
"+ + "
"+ + " "+ + "
"+ + "
").firstElementChild; + + spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { + const message = view.model.messages.at(0); + expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); + message.set('progress', 0.5); + expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5'); + message.set('progress', 1); + expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1'); + message.save({ + 'upload': _converse.SUCCESS, + 'oob_url': message.get('get'), + 'message': message.get('get') + }); + }); + var sent_stanza; + spyOn(_converse.connection, 'send').and.callFake(function (stanza) { + sent_stanza = stanza; + }); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); - test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items').then(function () { - test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []).then(function () { - test_utils.createContacts(_converse, 'current'); - var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - var file = { - 'type': 'image/jpeg', - 'size': '23456' , - 'lastModifiedDate': "", - 'name': "my-juliet.jpg" - }; - view.model.sendFiles([file]); return test_utils.waitUntil(function () { - return _.filter(IQ_stanzas, function (iq) { - return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request'); - }).length > 0; - }).then(function () { - var iq = IQ_stanzas.pop(); - expect(iq.toLocaleString()).toBe( - ""+ - ""+ - ""); - - var base_url = document.URL.split(window.location.pathname)[0]; - var message = base_url+"/logo/conversejs-filled.svg"; - - var stanza = Strophe.xmlHtmlNode( - ""+ - ""+ - " "+ - "
Basic Base64String==
"+ - "
foo=bar; user=romeo
"+ - "
"+ - " "+ - "
"+ - "
").firstElementChild; - - spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { - const message = view.model.messages.at(0); - expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); - message.set('progress', 0.5); - expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5'); - message.set('progress', 1); - expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1'); - message.save({ - 'upload': _converse.SUCCESS, - 'oob_url': message.get('get'), - 'message': message.get('get') - }); - }); - var sent_stanza; - spyOn(_converse.connection, 'send').and.callFake(function (stanza) { - sent_stanza = stanza; - }); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - + return sent_stanza; + }, 1000).then(function () { + expect(sent_stanza.toLocaleString()).toBe( + ""+ + ""+message+""+ + ""+ + ""+ + ""+message+""+ + ""+ + ""); return test_utils.waitUntil(function () { - return sent_stanza; - }, 1000).then(function () { - expect(sent_stanza.toLocaleString()).toBe( - ""+ - ""+message+""+ - ""+ - ""+ - ""+message+""+ - ""+ - ""); - return test_utils.waitUntil(function () { - return view.el.querySelector('.chat-image'); - }, 1000); - }).then(function () { - // Check that the image renders - expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual( - '\n'+ - ''+ - ''+ - ''); - XMLHttpRequest.prototype.send = send_backup; - done(); - }); + return view.el.querySelector('.chat-image'); + }, 1000); + }).then(function () { + // Check that the image renders + expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual( + `\n`+ + ``+ + ``+ + ``); + XMLHttpRequest.prototype.send = send_backup; + done(); }); }); }); @@ -473,9 +475,10 @@ }).then(function () { // Check that the image renders expect(view.el.querySelector('.chat-msg .chat-msg__media').innerHTML.trim()).toEqual( - '\n'+ - ''+ - '') + `\n`+ + ``+ + ``+ + ``); XMLHttpRequest.prototype.send = send_backup; done(); }); @@ -486,56 +489,53 @@ }); })); - it("shows and error message if the file is too large", mock.initConverseWithAsync(function (done, _converse) { - var IQ_stanzas = _converse.connection.IQ_stanzas; - var IQ_ids = _converse.connection.IQ_ids; - var send_backup = XMLHttpRequest.prototype.send; + it("shows an error message if the file is too large", mock.initConverseWithAsync(function (done, _converse) { + const IQ_stanzas = _converse.connection.IQ_stanzas; + const IQ_ids = _converse.connection.IQ_ids; + const send_backup = XMLHttpRequest.prototype.send; + let view, contact_jid; - test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []).then(function () { - test_utils.waitUntil(function () { + test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], []) + .then(() => test_utils.waitUntil(() => _.filter( + IQ_stanzas, (iq) => iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]')).length + )).then(() => { + var stanza = _.find(IQ_stanzas, function (iq) { + return iq.nodeTree.querySelector( + 'iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]'); + }); + var info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)]; + + stanza = $iq({ + 'type': 'result', + 'from': 'localhost', + 'to': 'dummy@localhost/resource', + 'id': info_IQ_id + }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}) + .c('identity', { + 'category': 'server', + 'type': 'im'}).up() + .c('feature', { + 'var': 'http://jabber.org/protocol/disco#info'}).up() + .c('feature', { + 'var': 'http://jabber.org/protocol/disco#items'}); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + return _converse.api.disco.entities.get(); + }).then(function (entities) { + expect(entities.length).toBe(2); + expect(_.includes(entities.pluck('jid'), 'localhost')).toBe(true); + expect(_.includes(entities.pluck('jid'), 'dummy@localhost')).toBe(true); + + expect(entities.get(_converse.domain).features.length).toBe(2); + expect(entities.get(_converse.domain).identities.length).toBe(1); + + return test_utils.waitUntil(function () { + // Converse.js sees that the entity has a disco#items feature, + // so it will make a query for it. return _.filter(IQ_stanzas, function (iq) { - return iq.nodeTree.querySelector( - 'iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]'); + return iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#items"]'); }).length > 0; - }, 300).then(function () { - var stanza = _.find(IQ_stanzas, function (iq) { - return iq.nodeTree.querySelector( - 'iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]'); - }); - var info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)]; - - stanza = $iq({ - 'type': 'result', - 'from': 'localhost', - 'to': 'dummy@localhost/resource', - 'id': info_IQ_id - }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { - 'category': 'server', - 'type': 'im'}).up() - .c('feature', { - 'var': 'http://jabber.org/protocol/disco#info'}).up() - .c('feature', { - 'var': 'http://jabber.org/protocol/disco#items'}); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - - _converse.api.disco.entities.get().then(function(entities) { - expect(entities.length).toBe(2); - expect(_.includes(entities.pluck('jid'), 'localhost')).toBe(true); - expect(_.includes(entities.pluck('jid'), 'dummy@localhost')).toBe(true); - - expect(entities.get(_converse.domain).features.length).toBe(2); - expect(entities.get(_converse.domain).identities.length).toBe(1); - - return test_utils.waitUntil(function () { - // Converse.js sees that the entity has a disco#items feature, - // so it will make a query for it. - return _.filter(IQ_stanzas, function (iq) { - return iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#items"]'); - }).length > 0; - }, 300); - }); - }).then(function () { + }, 300); + }).then(function () { var stanza = _.find(IQ_stanzas, function (iq) { return iq.nodeTree.querySelector('iq[to="localhost"] query[xmlns="http://jabber.org/protocol/disco#items"]'); }); @@ -549,77 +549,77 @@ .c('item', { 'jid': 'upload.localhost', 'name': 'HTTP File Upload'}); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - _converse.api.disco.entities.get().then(function (entities) { - expect(entities.length).toBe(2); - expect(entities.get('localhost').items.length).toBe(1); - return test_utils.waitUntil(function () { - // Converse.js sees that the entity has a disco#info feature, - // so it will make a query for it. - return _.filter(IQ_stanzas, function (iq) { - return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]'); - }).length > 0; - }, 300); - }); - }).then(function () { - var stanza = _.find(IQ_stanzas, function (iq) { - return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]'); - }); - var IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)]; - expect(stanza.toLocaleString()).toBe( - ""+ - ""+ - ""); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); - // Upload service responds and reports a maximum file size of 5MiB - stanza = $iq({'type': 'result', 'to': 'dummy@localhost/resource', 'id': IQ_id, 'from': 'upload.localhost'}) - .c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', {'category':'store', 'type':'file', 'name':'HTTP File Upload'}).up() - .c('feature', {'var':'urn:xmpp:http:upload:0'}).up() - .c('x', {'type':'result', 'xmlns':'jabber:x:data'}) - .c('field', {'var':'FORM_TYPE', 'type':'hidden'}) - .c('value').t('urn:xmpp:http:upload:0').up().up() - .c('field', {'var':'max-file-size'}) - .c('value').t('5242880'); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); + _converse.api.disco.entities.get().then(function (entities) { + expect(entities.length).toBe(2); + expect(entities.get('localhost').items.length).toBe(1); + return test_utils.waitUntil(function () { + // Converse.js sees that the entity has a disco#info feature, + // so it will make a query for it. + return _.filter(IQ_stanzas, function (iq) { + return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]'); + }).length > 0; + }, 300); + }); + }).then(function () { + var stanza = _.find(IQ_stanzas, function (iq) { + return iq.nodeTree.querySelector('iq[to="upload.localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]'); + }); + var IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)]; + expect(stanza.toLocaleString()).toBe( + ""+ + ""+ + ""); - _converse.api.disco.entities.get().then(function (entities) { - expect(entities.get('localhost').items.get('upload.localhost').identities.where({'category': 'store'}).length).toBe(1); - _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then( - function (result) { - test_utils.createContacts(_converse, 'current'); - var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - var file = { - 'type': 'image/jpeg', - 'size': '5242881', - 'lastModifiedDate': "", - 'name': "my-juliet.jpg" - }; - view.model.sendFiles([file]); - return test_utils.waitUntil(function () { - return view.el.querySelectorAll('.message').length; - }).then(function () { - const messages = view.el.querySelectorAll('.message.chat-error'); - expect(messages.length).toBe(1); - expect(messages[0].textContent).toBe( - 'The size of your file, my-juliet.jpg, exceeds the maximum allowed by your server, which is 5 MB.'); - done(); - }); - } - ); - }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); - }) - }); + // Upload service responds and reports a maximum file size of 5MiB + stanza = $iq({'type': 'result', 'to': 'dummy@localhost/resource', 'id': IQ_id, 'from': 'upload.localhost'}) + .c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}) + .c('identity', {'category':'store', 'type':'file', 'name':'HTTP File Upload'}).up() + .c('feature', {'var':'urn:xmpp:http:upload:0'}).up() + .c('x', {'type':'result', 'xmlns':'jabber:x:data'}) + .c('field', {'var':'FORM_TYPE', 'type':'hidden'}) + .c('value').t('urn:xmpp:http:upload:0').up().up() + .c('field', {'var':'max-file-size'}) + .c('value').t('5242880'); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + return _converse.api.disco.entities.get(); + }).then(function (entities) { + expect(entities.get('localhost').items.get('upload.localhost').identities.where({'category': 'store'}).length).toBe(1); + return _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain); + }).then(function (result) { + test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + + contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; + return test_utils.openChatBoxFor(_converse, contact_jid); + }).then(() => { + view = _converse.chatboxviews.get(contact_jid); + var file = { + 'type': 'image/jpeg', + 'size': '5242881', + 'lastModifiedDate': "", + 'name': "my-juliet.jpg" + }; + view.model.sendFiles([file]); + return test_utils.waitUntil(() => view.el.querySelectorAll('.message').length) + }).then(function () { + const messages = view.el.querySelectorAll('.message.chat-error'); + expect(messages.length).toBe(1); + expect(messages[0].textContent).toBe( + 'The size of your file, my-juliet.jpg, exceeds the maximum allowed by your server, which is 5 MB.'); + done(); + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)) })); }); }); describe("While a file is being uploaded", function () { - it("shows a progress bar", mock.initConverseWithAsync(function (done, _converse) { + it("shows a progress bar", mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { + test_utils.waitUntilDiscoConfirmed( _converse, _converse.domain, [{'category': 'server', 'type':'IM'}], @@ -627,77 +627,74 @@ var send_backup = XMLHttpRequest.prototype.send; var IQ_stanzas = _converse.connection.IQ_stanzas; + let view, contact_jid; - test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items').then(function () { - test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []).then(function () { - test_utils.createContacts(_converse, 'current'); - var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - var file = { - 'type': 'image/jpeg', - 'size': '23456' , - 'lastModifiedDate': "", - 'name': "my-juliet.jpg" - }; - view.model.sendFiles([file]); - return test_utils.waitUntil(function () { - return _.filter(IQ_stanzas, function (iq) { - return iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request'); - }).length > 0; - }).then(function () { - var iq = IQ_stanzas.pop(); - expect(iq.toLocaleString()).toBe( - ""+ - ""+ - ""); + test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items') + .then(() => test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], [])) + .then(() => { + test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); - var base_url = document.URL.split(window.location.pathname)[0]; - var message = base_url+"/logo/conversejs-filled.svg"; + contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; + return test_utils.openChatBoxFor(_converse, contact_jid); + }).then(() => { + view = _converse.chatboxviews.get(contact_jid); + const file = { + 'type': 'image/jpeg', + 'size': '23456' , + 'lastModifiedDate': "", + 'name': "my-juliet.jpg" + }; + view.model.sendFiles([file]); + return test_utils.waitUntil(() => _.filter(IQ_stanzas, (iq) => iq.nodeTree.querySelector('iq[to="upload.montague.tld"] request')).length) + }).then(function () { + const iq = IQ_stanzas.pop(); + expect(iq.toLocaleString()).toBe( + ""+ + ""+ + ""); - var stanza = Strophe.xmlHtmlNode( - ""+ - ""+ - " "+ - "
Basic Base64String==
"+ - "
foo=bar; user=romeo
"+ - "
"+ - " "+ - "
"+ - "
").firstElementChild; - - spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { - const message = view.model.messages.at(0); - expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); - message.set('progress', 0.5); - expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5'); - message.set('progress', 1); - expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1'); - expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB'); - done(); - }); - var sent_stanza; - spyOn(_converse.connection, 'send').and.callFake(function (stanza) { - sent_stanza = stanza; - }); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - }); + const base_url = document.URL.split(window.location.pathname)[0]; + const message = base_url+"/logo/conversejs-filled.svg"; + const stanza = Strophe.xmlHtmlNode( + ""+ + ""+ + " "+ + "
Basic Base64String==
"+ + "
foo=bar; user=romeo
"+ + "
"+ + " "+ + "
"+ + "
").firstElementChild; + spyOn(XMLHttpRequest.prototype, 'send').and.callFake(function () { + const message = view.model.messages.at(0); + expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0'); + message.set('progress', 0.5); + expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('0.5'); + message.set('progress', 1); + expect(view.el.querySelector('.chat-content progress').getAttribute('value')).toBe('1'); + expect(view.el.querySelector('.chat-content .chat-msg__text').textContent).toBe('Uploading file: my-juliet.jpg, 22.91 KB'); + done(); }); + var sent_stanza; + spyOn(_converse.connection, 'send').and.callFake(function (stanza) { + sent_stanza = stanza; + }); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); }); }); })); }); - }); }); })); diff --git a/spec/messages.js b/spec/messages.js index 2ba1bbcc6..1c351218d 100644 --- a/spec/messages.js +++ b/spec/messages.js @@ -21,247 +21,251 @@ it("can be sent as a correction by clicking the pencil icon", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + const view = _converse.chatboxviews.get(contact_jid); + const textarea = view.el.querySelector('textarea.chat-textarea'); - const view = _converse.chatboxviews.get(contact_jid); - const textarea = view.el.querySelector('textarea.chat-textarea'); + textarea.value = 'But soft, what light through yonder airlock breaks?'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 // Enter + }); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(view.el.querySelector('.chat-msg__text').textContent) + .toBe('But soft, what light through yonder airlock breaks?'); + expect(textarea.value).toBe(''); - textarea.value = 'But soft, what light through yonder airlock breaks?'; - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 // Enter + const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'}); + + expect(view.el.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(1); + let action = view.el.querySelector('.chat-msg .chat-msg__action'); + expect(action.getAttribute('title')).toBe('Edit this message'); + + action.style.opacity = 1; + action.click(); + + expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?'); + expect(view.model.messages.at(0).get('correcting')).toBe(true); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); + + spyOn(_converse.connection, 'send'); + textarea.value = 'But soft, what light through yonder window breaks?'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 // Enter + }); + expect(_converse.connection.send).toHaveBeenCalled(); + + const msg = _converse.connection.send.calls.all()[0].args[0]; + expect(msg.toLocaleString()) + .toBe(``+ + `But soft, what light through yonder window breaks?`+ + ``+ + ``+ + ``); + expect(view.model.messages.models.length).toBe(1); + const corrected_message = view.model.messages.at(0); + expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid')); + expect(corrected_message.get('correcting')).toBe(false); + expect(corrected_message.get('older_versions').length).toBe(1); + expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?'); + + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false); + + // Test that clicking the pencil icon a second time cancels editing. + action = view.el.querySelector('.chat-msg .chat-msg__action'); + action.style.opacity = 1; + action.click(); + + expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); + expect(view.model.messages.at(0).get('correcting')).toBe(true); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); + + action = view.el.querySelector('.chat-msg .chat-msg__action'); + action.style.opacity = 1; + action.click(); + expect(textarea.value).toBe(''); + expect(view.model.messages.at(0).get('correcting')).toBe(false); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false); + + // Test that messages from other users don't have the pencil icon + _converse.chatboxes.onMessage( + $msg({ + 'from': contact_jid, + 'to': _converse.connection.jid, + 'type': 'chat', + 'id': (new Date()).getTime() + }).c('body').t('Hello').up() + .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree() + ); + expect(view.el.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(1); + done(); }); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(view.el.querySelector('.chat-msg__text').textContent) - .toBe('But soft, what light through yonder airlock breaks?'); - expect(textarea.value).toBe(''); - - const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'}); - - expect(view.el.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(1); - let action = view.el.querySelector('.chat-msg .chat-msg__action'); - expect(action.getAttribute('title')).toBe('Edit this message'); - - action.style.opacity = 1; - action.click(); - - expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?'); - expect(view.model.messages.at(0).get('correcting')).toBe(true); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); - - spyOn(_converse.connection, 'send'); - textarea.value = 'But soft, what light through yonder window breaks?'; - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 // Enter - }); - expect(_converse.connection.send).toHaveBeenCalled(); - - const msg = _converse.connection.send.calls.all()[0].args[0]; - expect(msg.toLocaleString()) - .toBe(``+ - `But soft, what light through yonder window breaks?`+ - ``+ - ``+ - ``); - expect(view.model.messages.models.length).toBe(1); - const corrected_message = view.model.messages.at(0); - expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid')); - expect(corrected_message.get('correcting')).toBe(false); - expect(corrected_message.get('older_versions').length).toBe(1); - expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?'); - - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false); - - // Test that clicking the pencil icon a second time cancels editing. - action = view.el.querySelector('.chat-msg .chat-msg__action'); - action.style.opacity = 1; - action.click(); - - expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); - expect(view.model.messages.at(0).get('correcting')).toBe(true); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); - - action = view.el.querySelector('.chat-msg .chat-msg__action'); - action.style.opacity = 1; - action.click(); - expect(textarea.value).toBe(''); - expect(view.model.messages.at(0).get('correcting')).toBe(false); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false); - - // Test that messages from other users don't have the pencil icon - _converse.chatboxes.onMessage( - $msg({ - 'from': contact_jid, - 'to': _converse.connection.jid, - 'type': 'chat', - 'id': (new Date()).getTime() - }).c('body').t('Hello').up() - .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree() - ); - expect(view.el.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(1); - done(); })); it("can be sent as a correction by using the up arrow", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + const view = _converse.chatboxviews.get(contact_jid); + const textarea = view.el.querySelector('textarea.chat-textarea'); + expect(textarea.value).toBe(''); + view.keyPressed({ + target: textarea, + keyCode: 38 // Up arrow + }); + expect(textarea.value).toBe(''); - const view = _converse.chatboxviews.get(contact_jid); - const textarea = view.el.querySelector('textarea.chat-textarea'); - expect(textarea.value).toBe(''); - view.keyPressed({ - target: textarea, - keyCode: 38 // Up arrow + textarea.value = 'But soft, what light through yonder airlock breaks?'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 // Enter + }); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(view.el.querySelector('.chat-msg__text').textContent) + .toBe('But soft, what light through yonder airlock breaks?'); + + const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'}); + expect(textarea.value).toBe(''); + view.keyPressed({ + target: textarea, + keyCode: 38 // Up arrow + }); + expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?'); + expect(view.model.messages.at(0).get('correcting')).toBe(true); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); + + spyOn(_converse.connection, 'send'); + textarea.value = 'But soft, what light through yonder window breaks?'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 // Enter + }); + expect(_converse.connection.send).toHaveBeenCalled(); + + const msg = _converse.connection.send.calls.all()[0].args[0]; + expect(msg.toLocaleString()) + .toBe(``+ + `But soft, what light through yonder window breaks?`+ + ``+ + ``+ + ``); + expect(view.model.messages.models.length).toBe(1); + const corrected_message = view.model.messages.at(0); + expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid')); + expect(corrected_message.get('correcting')).toBe(false); + expect(corrected_message.get('older_versions').length).toBe(1); + expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?'); + + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false); + + // Test that pressing the down arrow cancels message correction + expect(textarea.value).toBe(''); + view.keyPressed({ + target: textarea, + keyCode: 38 // Up arrow + }); + expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); + expect(view.model.messages.at(0).get('correcting')).toBe(true); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); + expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); + view.keyPressed({ + target: textarea, + keyCode: 40 // Down arrow + }); + expect(textarea.value).toBe(''); + expect(view.model.messages.at(0).get('correcting')).toBe(false); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false); + + textarea.value = 'It is the east, and Juliet is the one.'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 // Enter + }); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); + + textarea.value = 'Arise, fair sun, and kill the envious moon'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 // Enter + }); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(3); + + view.keyPressed({ + target: textarea, + keyCode: 38 // Up arrow + }); + expect(textarea.value).toBe('Arise, fair sun, and kill the envious moon'); + expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); + expect(view.model.messages.at(1).get('correcting')).toBeFalsy(); + expect(view.model.messages.at(2).get('correcting')).toBe(true); + + textarea.selectionEnd = 0; // Happens by pressing up, + // but for some reason not in tests, so we set it manually. + view.keyPressed({ + target: textarea, + keyCode: 38 // Up arrow + }); + expect(textarea.value).toBe('It is the east, and Juliet is the one.'); + expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); + expect(view.model.messages.at(1).get('correcting')).toBe(true); + expect(view.model.messages.at(2).get('correcting')).toBeFalsy(); + + textarea.value = 'It is the east, and Juliet is the sun.'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 // Enter + }); + expect(textarea.value).toBe(''); + const messages = view.el.querySelectorAll('.chat-msg'); + expect(messages.length).toBe(3); + expect(messages[0].querySelector('.chat-msg__text').textContent) + .toBe('But soft, what light through yonder window breaks?'); + expect(messages[1].querySelector('.chat-msg__text').textContent) + .toBe('It is the east, and Juliet is the sun.'); + expect(messages[2].querySelector('.chat-msg__text').textContent) + .toBe('Arise, fair sun, and kill the envious moon'); + + expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); + expect(view.model.messages.at(1).get('correcting')).toBeFalsy(); + expect(view.model.messages.at(2).get('correcting')).toBeFalsy(); + done(); }); - expect(textarea.value).toBe(''); - - textarea.value = 'But soft, what light through yonder airlock breaks?'; - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 // Enter - }); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(view.el.querySelector('.chat-msg__text').textContent) - .toBe('But soft, what light through yonder airlock breaks?'); - - const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'}); - expect(textarea.value).toBe(''); - view.keyPressed({ - target: textarea, - keyCode: 38 // Up arrow - }); - expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?'); - expect(view.model.messages.at(0).get('correcting')).toBe(true); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); - - spyOn(_converse.connection, 'send'); - textarea.value = 'But soft, what light through yonder window breaks?'; - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 // Enter - }); - expect(_converse.connection.send).toHaveBeenCalled(); - - const msg = _converse.connection.send.calls.all()[0].args[0]; - expect(msg.toLocaleString()) - .toBe(``+ - `But soft, what light through yonder window breaks?`+ - ``+ - ``+ - ``); - expect(view.model.messages.models.length).toBe(1); - const corrected_message = view.model.messages.at(0); - expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid')); - expect(corrected_message.get('correcting')).toBe(false); - expect(corrected_message.get('older_versions').length).toBe(1); - expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?'); - - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false); - - // Test that pressing the down arrow cancels message correction - expect(textarea.value).toBe(''); - view.keyPressed({ - target: textarea, - keyCode: 38 // Up arrow - }); - expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); - expect(view.model.messages.at(0).get('correcting')).toBe(true); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); - expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); - view.keyPressed({ - target: textarea, - keyCode: 40 // Down arrow - }); - expect(textarea.value).toBe(''); - expect(view.model.messages.at(0).get('correcting')).toBe(false); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false); - - textarea.value = 'It is the east, and Juliet is the one.'; - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 // Enter - }); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); - - textarea.value = 'Arise, fair sun, and kill the envious moon'; - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 // Enter - }); - expect(view.el.querySelectorAll('.chat-msg').length).toBe(3); - - view.keyPressed({ - target: textarea, - keyCode: 38 // Up arrow - }); - expect(textarea.value).toBe('Arise, fair sun, and kill the envious moon'); - expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); - expect(view.model.messages.at(1).get('correcting')).toBeFalsy(); - expect(view.model.messages.at(2).get('correcting')).toBe(true); - - textarea.selectionEnd = 0; // Happens by pressing up, - // but for some reason not in tests, so we set it manually. - view.keyPressed({ - target: textarea, - keyCode: 38 // Up arrow - }); - expect(textarea.value).toBe('It is the east, and Juliet is the one.'); - expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); - expect(view.model.messages.at(1).get('correcting')).toBe(true); - expect(view.model.messages.at(2).get('correcting')).toBeFalsy(); - - textarea.value = 'It is the east, and Juliet is the sun.'; - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 // Enter - }); - expect(textarea.value).toBe(''); - const messages = view.el.querySelectorAll('.chat-msg'); - expect(messages.length).toBe(3); - expect(messages[0].querySelector('.chat-msg__text').textContent) - .toBe('But soft, what light through yonder window breaks?'); - expect(messages[1].querySelector('.chat-msg__text').textContent) - .toBe('It is the east, and Juliet is the sun.'); - expect(messages[2].querySelector('.chat-msg__text').textContent) - .toBe('Arise, fair sun, and kill the envious moon'); - - expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); - expect(view.model.messages.at(1).get('correcting')).toBeFalsy(); - expect(view.model.messages.at(2).get('correcting')).toBeFalsy(); - done(); })); @@ -321,65 +325,67 @@ it("can be replaced with a correction", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); const message = 'This is a received message'; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); - - const msg_id = u.getUniqueId(); - _converse.chatboxes.onMessage($msg({ - 'from': sender_jid, - 'to': _converse.connection.jid, - 'type': 'chat', - 'id': msg_id, - }).c('body').t('But soft, what light through yonder airlock breaks?').tree()); - - var chatboxview = _converse.chatboxviews.get(sender_jid); - expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(chatboxview.el.querySelector('.chat-msg__text').textContent) - .toBe('But soft, what light through yonder airlock breaks?'); - - _converse.chatboxes.onMessage($msg({ - 'from': sender_jid, - 'to': _converse.connection.jid, - 'type': 'chat', - 'id': u.getUniqueId(), - }).c('body').t('But soft, what light through yonder chimney breaks?').up() - .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); - - test_utils.waitUntil(() => chatboxview.el.querySelector('.chat-msg__text').textContent === - 'But soft, what light through yonder chimney breaks?').then(() => { + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + const msg_id = u.getUniqueId(); + _converse.chatboxes.onMessage($msg({ + 'from': sender_jid, + 'to': _converse.connection.jid, + 'type': 'chat', + 'id': msg_id, + }).c('body').t('But soft, what light through yonder airlock breaks?').tree()); + var chatboxview = _converse.chatboxviews.get(sender_jid); expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(chatboxview.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1); + expect(chatboxview.el.querySelector('.chat-msg__text').textContent) + .toBe('But soft, what light through yonder airlock breaks?'); _converse.chatboxes.onMessage($msg({ 'from': sender_jid, 'to': _converse.connection.jid, 'type': 'chat', 'id': u.getUniqueId(), - }).c('body').t('But soft, what light through yonder window breaks?').up() + }).c('body').t('But soft, what light through yonder chimney breaks?').up() .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); - return test_utils.waitUntil(() => chatboxview.el.querySelector('.chat-msg__text').textContent === - 'But soft, what light through yonder window breaks?'); - }).then(() => { - expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1); - expect(chatboxview.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1); - chatboxview.el.querySelector('.chat-msg__content .fa-edit').click(); - const modal = chatboxview.model.messages.at(0).message_versions_modal; - return test_utils.waitUntil(() => u.isVisible(modal.el), 1000); - }).then(() => { - const modal = chatboxview.model.messages.at(0).message_versions_modal; - const older_msgs = modal.el.querySelectorAll('.older-msg'); - expect(older_msgs.length).toBe(2); - expect(older_msgs[0].textContent).toBe('But soft, what light through yonder airlock breaks?'); - expect(older_msgs[1].textContent).toBe('But soft, what light through yonder chimney breaks?'); - done(); + test_utils.waitUntil(() => chatboxview.el.querySelector('.chat-msg__text').textContent === + 'But soft, what light through yonder chimney breaks?').then(() => { + + expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(chatboxview.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1); + + _converse.chatboxes.onMessage($msg({ + 'from': sender_jid, + 'to': _converse.connection.jid, + 'type': 'chat', + 'id': u.getUniqueId(), + }).c('body').t('But soft, what light through yonder window breaks?').up() + .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); + + return test_utils.waitUntil(() => chatboxview.el.querySelector('.chat-msg__text').textContent === + 'But soft, what light through yonder window breaks?'); + }).then(() => { + expect(chatboxview.el.querySelectorAll('.chat-msg').length).toBe(1); + expect(chatboxview.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1); + chatboxview.el.querySelector('.chat-msg__content .fa-edit').click(); + const modal = chatboxview.model.messages.at(0).message_versions_modal; + return test_utils.waitUntil(() => u.isVisible(modal.el), 1000); + }).then(() => { + const modal = chatboxview.model.messages.at(0).message_versions_modal; + const older_msgs = modal.el.querySelectorAll('.older-msg'); + expect(older_msgs.length).toBe(2); + expect(older_msgs[0].textContent).toBe('But soft, what light through yonder airlock breaks?'); + expect(older_msgs[1].textContent).toBe('But soft, what light through yonder chimney breaks?'); + done(); + }); }); })); @@ -500,10 +506,11 @@ it("will have the error message displayed after itself", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); // TODO: what could still be done for error @@ -513,154 +520,152 @@ // that. /* - * yo - * - * - */ + * to="kirk@enterprise.com.com" + * type="chat" + * id="82bc02ce-9651-4336-baf0-fa04762ed8d2" + * xmlns="jabber:client"> + * yo + * + * + */ var sender_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost'; var fullname = _converse.xmppstatus.get('fullname'); fullname = _.isEmpty(fullname)? _converse.bare_jid: fullname; - _converse.api.chats.open(sender_jid); - var msg_text = 'This message will not be sent, due to an error'; - var view = _converse.chatboxviews.get(sender_jid); - var message = view.model.messages.create({ - 'msgid': '82bc02ce-9651-4336-baf0-fa04762ed8d2', - 'fullname': fullname, - 'sender': 'me', - 'time': moment().format(), - 'message': msg_text + _converse.api.chats.open(sender_jid) + .then(() => { + var msg_text = 'This message will not be sent, due to an error'; + var view = _converse.chatboxviews.get(sender_jid); + var message = view.model.messages.create({ + 'msgid': '82bc02ce-9651-4336-baf0-fa04762ed8d2', + 'fullname': fullname, + 'sender': 'me', + 'time': moment().format(), + 'message': msg_text + }); + view.model.sendMessage(message); + var $chat_content = $(view.el).find('.chat-content'); + var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text(); + expect(msg_txt).toEqual(msg_text); + + // We send another message, for which an error will + // not be received, to test that errors appear + // after the relevant message. + msg_text = 'This message will be sent, and also receive an error'; + message = view.model.messages.create({ + 'msgid': '6fcdeee3-000f-4ce8-a17e-9ce28f0ae104', + 'fullname': fullname, + 'sender': 'me', + 'time': moment().format(), + 'message': msg_text + }); + view.model.sendMessage(message); + msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text(); + expect(msg_txt).toEqual(msg_text); + + /* + * + * + * Server-to-server connection failed: Connecting failed: connection timeout + * + * + */ + var error_txt = 'Server-to-server connection failed: Connecting failed: connection timeout'; + var stanza = $msg({ + 'to': _converse.connection.jid, + 'type':'error', + 'id':'82bc02ce-9651-4336-baf0-fa04762ed8d2', + 'from': sender_jid + }) + .c('error', {'type': 'cancel'}) + .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up() + .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }) + .t('Server-to-server connection failed: Connecting failed: connection timeout'); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + expect($chat_content.find('.chat-error').text()).toEqual(error_txt); + + stanza = $msg({ + 'to': _converse.connection.jid, + 'type':'error', + 'id':'some-other-unused-id', + 'from': sender_jid + }) + .c('error', {'type': 'cancel'}) + .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up() + .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }) + .t('Server-to-server connection failed: Connecting failed: connection timeout'); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + expect($chat_content.find('.chat-error').length).toEqual(2); + + // If the last message is already an error message, + // then we don't render it another time. + stanza = $msg({ + 'to': _converse.connection.jid, + 'type':'error', + 'id':'another-unused-id', + 'from': sender_jid + }) + .c('error', {'type': 'cancel'}) + .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up() + .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }) + .t('Server-to-server connection failed: Connecting failed: connection timeout'); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + expect($chat_content.find('.chat-error').length).toEqual(2); + + // A different error message will however render + stanza = $msg({ + 'to': _converse.connection.jid, + 'type':'error', + 'id':'another-id', + 'from': sender_jid + }) + .c('error', {'type': 'cancel'}) + .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up() + .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }) + .t('Something else went wrong as well'); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + expect($chat_content.find('.chat-error').length).toEqual(3); + done(); }); - view.model.sendMessage(message); - var $chat_content = $(view.el).find('.chat-content'); - var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text(); - expect(msg_txt).toEqual(msg_text); - - // We send another message, for which an error will - // not be received, to test that errors appear - // after the relevant message. - msg_text = 'This message will be sent, and also receive an error'; - message = view.model.messages.create({ - 'msgid': '6fcdeee3-000f-4ce8-a17e-9ce28f0ae104', - 'fullname': fullname, - 'sender': 'me', - 'time': moment().format(), - 'message': msg_text - }); - view.model.sendMessage(message); - msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text(); - expect(msg_txt).toEqual(msg_text); - - /* - * - * - * Server-to-server connection failed: Connecting failed: connection timeout - * - * - */ - var error_txt = 'Server-to-server connection failed: Connecting failed: connection timeout'; - var stanza = $msg({ - 'to': _converse.connection.jid, - 'type':'error', - 'id':'82bc02ce-9651-4336-baf0-fa04762ed8d2', - 'from': sender_jid - }) - .c('error', {'type': 'cancel'}) - .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up() - .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }) - .t('Server-to-server connection failed: Connecting failed: connection timeout'); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - expect($chat_content.find('.chat-error').text()).toEqual(error_txt); - - stanza = $msg({ - 'to': _converse.connection.jid, - 'type':'error', - 'id':'some-other-unused-id', - 'from': sender_jid - }) - .c('error', {'type': 'cancel'}) - .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up() - .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }) - .t('Server-to-server connection failed: Connecting failed: connection timeout'); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - expect($chat_content.find('.chat-error').length).toEqual(2); - - // If the last message is already an error message, - // then we don't render it another time. - stanza = $msg({ - 'to': _converse.connection.jid, - 'type':'error', - 'id':'another-unused-id', - 'from': sender_jid - }) - .c('error', {'type': 'cancel'}) - .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up() - .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }) - .t('Server-to-server connection failed: Connecting failed: connection timeout'); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - expect($chat_content.find('.chat-error').length).toEqual(2); - - // A different error message will however render - stanza = $msg({ - 'to': _converse.connection.jid, - 'type':'error', - 'id':'another-id', - 'from': sender_jid - }) - .c('error', {'type': 'cancel'}) - .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up() - .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }) - .t('Something else went wrong as well'); - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - expect($chat_content.find('.chat-error').length).toEqual(3); - done(); })); }); it("will cause the chat area to be scrolled down only if it was at the bottom originally", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, sender_jid); + let chatboxview; + const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + const message = 'This message is received while the chat area is scrolled up'; + test_utils.openChatBoxFor(_converse, sender_jid) + .then(() => { + chatboxview = _converse.chatboxviews.get(sender_jid); + spyOn(chatboxview, 'onScrolledDown').and.callThrough(); - var chatboxview = _converse.chatboxviews.get(sender_jid); - spyOn(chatboxview, 'onScrolledDown').and.callThrough(); - - // Create enough messages so that there's a scrollbar. - var message = 'This message is received while the chat area is scrolled up'; - for (var i=0; i<20; i++) { - _converse.chatboxes.onMessage($msg({ - from: sender_jid, - to: _converse.connection.jid, - type: 'chat', - id: (new Date()).getTime() - }).c('body').t('Message: '+i).up() - .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); - } - return test_utils.waitUntil(function () { - return chatboxview.content.scrollTop; - }, 1000).then(function () { - return test_utils.waitUntil(function () { - return !chatboxview.model.get('auto_scrolled'); - }, 500); - }).then(function () { + // Create enough messages so that there's a scrollbar. + for (var i=0; i<20; i++) { + _converse.chatboxes.onMessage($msg({ + from: sender_jid, + to: _converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('body').t('Message: '+i).up() + .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); + } + return test_utils.waitUntil(() => chatboxview.content.scrollTop, 1000) + .then(() => test_utils.waitUntil(() => !chatboxview.model.get('auto_scrolled'), 500)) + }).then(() => { chatboxview.content.scrollTop = 0; - return test_utils.waitUntil(function () { - return chatboxview.model.get('scrolled'); - }, 900); - }).then(function () { + return test_utils.waitUntil(() => chatboxview.model.get('scrolled'), 900); + }).then(() => { _converse.chatboxes.onMessage($msg({ from: sender_jid, to: _converse.connection.jid, @@ -670,21 +675,17 @@ .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); // Now check that the message appears inside the chatbox in the DOM - var $chat_content = $(chatboxview.el).find('.chat-content'); - var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text(); + const $chat_content = $(chatboxview.el).find('.chat-content'); + const msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text(); expect(msg_txt).toEqual(message); - return test_utils.waitUntil(function () { - return u.isVisible(chatboxview.el.querySelector('.new-msgs-indicator')); - }, 500); - }).then(function () { + return test_utils.waitUntil(() => u.isVisible(chatboxview.el.querySelector('.new-msgs-indicator')), 900); + }).then(() => { expect(chatboxview.model.get('scrolled')).toBe(true); expect(chatboxview.content.scrollTop).toBe(0); expect(u.isVisible(chatboxview.el.querySelector('.new-msgs-indicator'))).toBeTruthy(); // Scroll down again chatboxview.content.scrollTop = chatboxview.content.scrollHeight; - return test_utils.waitUntil(function () { - return !u.isVisible(chatboxview.el.querySelector('.new-msgs-indicator')); - }, 700); + return test_utils.waitUntil(() => !u.isVisible(chatboxview.el.querySelector('.new-msgs-indicator')), 900); }).then(done); })); @@ -1109,23 +1110,22 @@ it("received for a minimized chat box will increment a counter on its header", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { if (_converse.view_mode === 'fullscreen') { return done(); } test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + const contact_name = mock.cur_names[0]; + const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openControlBox(); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 300) - .then(function () { - var contact_name = mock.cur_names[0]; - var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost'; + spyOn(_converse, 'emit').and.callThrough(); - spyOn(_converse, 'emit').and.callThrough(); - test_utils.openChatBoxFor(_converse, contact_jid); + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length) + .then(() => test_utils.openChatBoxFor(_converse, contact_jid)) + .then(() => { var chatview = _converse.chatboxviews.get(contact_jid); expect(u.isVisible(chatview.el)).toBeTruthy(); expect(chatview.model.get('minimized')).toBeFalsy(); @@ -1171,19 +1171,18 @@ it("will indicate when it has a time difference of more than a day between it and its predecessor", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - test_utils.waitUntil(function () { - return $(_converse.rosterview.el).find('.roster-group').length; - }, 300) - .then(function () { - spyOn(_converse, 'emit'); - var contact_name = mock.cur_names[1]; - var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); + spyOn(_converse, 'emit'); + const contact_name = mock.cur_names[1]; + const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length) + .then(() => test_utils.openChatBoxFor(_converse, contact_jid)) + .then(() => { test_utils.clearChatBoxMessages(_converse, contact_jid); var one_day_ago = moment(); one_day_ago.subtract('days', 1); @@ -1269,117 +1268,132 @@ })); it("can be sent from a chatbox, and will appear inside it", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); spyOn(_converse, 'emit'); - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - expect(_converse.emit).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object)); - var view = _converse.chatboxviews.get(contact_jid); - var message = 'This message is sent from this chatbox'; - spyOn(view.model, 'sendMessage').and.callThrough(); - test_utils.sendMessage(view, message); - expect(view.model.sendMessage).toHaveBeenCalled(); - expect(view.model.messages.length, 2); - expect(_converse.emit.calls.mostRecent().args, ['messageSend', message]); - expect($(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text').text()).toEqual(message); - done(); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + expect(_converse.emit).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object)); + const view = _converse.chatboxviews.get(contact_jid); + const message = 'This message is sent from this chatbox'; + spyOn(view.model, 'sendMessage').and.callThrough(); + test_utils.sendMessage(view, message); + expect(view.model.sendMessage).toHaveBeenCalled(); + expect(view.model.messages.length, 2); + expect(_converse.emit.calls.mostRecent().args, ['messageSend', message]); + expect($(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text').text()).toEqual(message); + done(); + }); })); it("is sanitized to prevent Javascript injection attacks", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - var message = '

This message contains some markup

'; - spyOn(view.model, 'sendMessage').and.callThrough(); - test_utils.sendMessage(view, message); - expect(view.model.sendMessage).toHaveBeenCalled(); - var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); - expect(msg.text()).toEqual(message); - expect(msg.html()).toEqual('<p>This message contains <em>some</em> <b>markup</b></p>'); - done(); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + const view = _converse.chatboxviews.get(contact_jid); + const message = '

This message contains some markup

'; + spyOn(view.model, 'sendMessage').and.callThrough(); + test_utils.sendMessage(view, message); + expect(view.model.sendMessage).toHaveBeenCalled(); + const msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); + expect(msg.text()).toEqual(message); + expect(msg.html()).toEqual('<p>This message contains <em>some</em> <b>markup</b></p>'); + done(); + }); })); it("can contain hyperlinks, which will be clickable", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - var message = 'This message contains a hyperlink: www.opkode.com'; - spyOn(view.model, 'sendMessage').and.callThrough(); - test_utils.sendMessage(view, message); - expect(view.model.sendMessage).toHaveBeenCalled(); - var msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); - expect(msg.text()).toEqual(message); - expect(msg.html()).toEqual('This message contains a hyperlink: www.opkode.com'); - done(); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + const view = _converse.chatboxviews.get(contact_jid); + const message = 'This message contains a hyperlink: www.opkode.com'; + spyOn(view.model, 'sendMessage').and.callThrough(); + test_utils.sendMessage(view, message); + expect(view.model.sendMessage).toHaveBeenCalled(); + const msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); + expect(msg.text()).toEqual(message); + expect(msg.html()).toEqual('This message contains a hyperlink: www.opkode.com'); + done(); + }); })); it("will have properly escaped URLs", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); test_utils.openControlBox(); - var message, msg; - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); - message = "http://www.opkode.com/'onmouseover='alert(1)'whatever"; - test_utils.sendMessage(view, message); - expect(view.model.sendMessage).toHaveBeenCalled(); - msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); - expect(msg.text()).toEqual(message); - expect(msg.html()).toEqual('http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever'); - message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever'; - test_utils.sendMessage(view, message); + let message, msg; + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + var view = _converse.chatboxviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); + message = "http://www.opkode.com/'onmouseover='alert(1)'whatever"; + test_utils.sendMessage(view, message); + expect(view.model.sendMessage).toHaveBeenCalled(); + msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); + expect(msg.text()).toEqual(message); + expect(msg.html()).toEqual('http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever'); + message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever'; + test_utils.sendMessage(view, message); - expect(view.model.sendMessage).toHaveBeenCalled(); - msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); - expect(msg.text()).toEqual(message); - expect(msg.html()).toEqual('http://www.opkode.com/"onmouseover="alert(1)"whatever'); + expect(view.model.sendMessage).toHaveBeenCalled(); + msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); + expect(msg.text()).toEqual(message); + expect(msg.html()).toEqual('http://www.opkode.com/"onmouseover="alert(1)"whatever'); - message = "https://en.wikipedia.org/wiki/Ender's_Game"; - test_utils.sendMessage(view, message); + message = "https://en.wikipedia.org/wiki/Ender's_Game"; + test_utils.sendMessage(view, message); - expect(view.model.sendMessage).toHaveBeenCalled(); - msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); - expect(msg.text()).toEqual(message); - expect(msg.html()).toEqual(''+message+''); + expect(view.model.sendMessage).toHaveBeenCalled(); + msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); + expect(msg.text()).toEqual(message); + expect(msg.html()).toEqual(''+message+''); - message = "https://en.wikipedia.org/wiki/Ender's_Game"; - test_utils.sendMessage(view, message); + message = "https://en.wikipedia.org/wiki/Ender's_Game"; + test_utils.sendMessage(view, message); - expect(view.model.sendMessage).toHaveBeenCalled(); - msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); - expect(msg.text()).toEqual(message); - expect(msg.html()).toEqual(''+message+''); - done(); + expect(view.model.sendMessage).toHaveBeenCalled(); + msg = $(view.el).find('.chat-content').find('.chat-msg').last().find('.chat-msg__text'); + expect(msg.text()).toEqual(message); + expect(msg.html()).toEqual(''+message+''); + done(); + }); })); - it("will render newlines", mock.initConverseWithPromises(null, ['rosterGroupsFetched'], {}, function (done, _converse) { + it("will render newlines", + mock.initConverseWithPromises(null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { + test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openChatBoxFor(_converse, contact_jid); @@ -1416,20 +1430,23 @@ })); it("will render images from their URLs", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); const base_url = document.URL.split(window.location.pathname)[0]; let message = base_url+"/logo/conversejs-filled.svg"; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - const view = _converse.chatboxviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); - test_utils.sendMessage(view, message); - - test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000).then(() => { + let view; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + view = _converse.chatboxviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); + test_utils.sendMessage(view, message); + return test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000) + }).then(() => { expect(view.model.sendMessage).toHaveBeenCalled(); const msg = $(view.el).find('.chat-content .chat-msg').last().find('.chat-msg__text'); expect(msg.html().trim()).toEqual( @@ -1461,30 +1478,33 @@ })); it("will render the message time as configured", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], + {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); _converse.time_format = 'hh:mm'; - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var view = _converse.chatboxviews.get(contact_jid); - var message = 'This message is sent from this chatbox'; - test_utils.sendMessage(view, message); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + const view = _converse.chatboxviews.get(contact_jid); + const message = 'This message is sent from this chatbox'; + test_utils.sendMessage(view, message); - var chatbox = _converse.chatboxes.get(contact_jid); - expect(chatbox.messages.models.length, 1); - var msg_object = chatbox.messages.models[0]; + const chatbox = _converse.chatboxes.get(contact_jid); + expect(chatbox.messages.models.length, 1); + const msg_object = chatbox.messages.models[0]; - var msg_author = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__author'); - expect(msg_author.textContent.trim()).toBe('Max Mustermann'); + const msg_author = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__author'); + expect(msg_author.textContent.trim()).toBe('Max Mustermann'); - var msg_time = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__time'); - var time = moment(msg_object.get('time')).format(_converse.time_format); - expect(msg_time.textContent).toBe(time); - done(); + const msg_time = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__time'); + const time = moment(msg_object.get('time')).format(_converse.time_format); + expect(msg_time.textContent).toBe(time); + done(); + }); })); it("will be correctly identified and rendered as a followup message", @@ -1655,29 +1675,33 @@ })); - describe("which contains a OOB URL", function () { + describe("which contains an OOB URL", function () { it("will render audio from oob mp3 URLs", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - const view = _converse.chatboxviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); + let view; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + view = _converse.chatboxviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); - const stanza = Strophe.xmlHtmlNode( - ""+ - " Have you heard this funny audio?"+ - " http://localhost/audio.mp3"+ - "").firstChild; - _converse.connection._dataRecv(test_utils.createRequest(stanza)); + const stanza = Strophe.xmlHtmlNode( + ""+ + " Have you heard this funny audio?"+ + " http://localhost/audio.mp3"+ + "").firstChild + _converse.connection._dataRecv(test_utils.createRequest(stanza)); - test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg audio').length, 1000).then(function () { + return test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg audio').length, 1000); + }).then(() => { let msg = view.el.querySelector('.chat-msg .chat-msg__text'); expect(msg.outerHTML).toEqual('
Have you heard this funny audio?
'); let media = view.el.querySelector('.chat-msg .chat-msg__media'); @@ -1708,76 +1732,83 @@ })); it("will render video from oob mp4 URLs", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - const view = _converse.chatboxviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + const view = _converse.chatboxviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); - const stanza = Strophe.xmlHtmlNode( - ""+ - " Have you seen this funny video?"+ - " http://localhost/video.mp4"+ - "").firstChild; - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - - test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg video').length, 2000).then(function () { - let msg = view.el.querySelector('.chat-msg .chat-msg__text'); - expect(msg.outerHTML).toEqual('
Have you seen this funny video?
'); - let media = view.el.querySelector('.chat-msg .chat-msg__media'); - expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual( - ''+ - ''+ - 'Download video file'); - - // If the and contents is the same, don't duplicate. const stanza = Strophe.xmlHtmlNode( ""+ - " http://localhost/video.mp4"+ + " Have you seen this funny video?"+ " http://localhost/video.mp4"+ "").firstChild; _converse.connection._dataRecv(test_utils.createRequest(stanza)); - msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text'); - expect(msg.innerHTML).toEqual(''); // Emtpy - media = view.el.querySelector('.chat-msg:last-child .chat-msg__media'); - expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual( - ''+ - ''+ - 'Download video file'); - done(); + test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg video').length, 2000).then(function () { + let msg = view.el.querySelector('.chat-msg .chat-msg__text'); + expect(msg.outerHTML).toEqual('
Have you seen this funny video?
'); + let media = view.el.querySelector('.chat-msg .chat-msg__media'); + expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual( + ''+ + ''+ + 'Download video file'); + + // If the and contents is the same, don't duplicate. + const stanza = Strophe.xmlHtmlNode( + ""+ + " http://localhost/video.mp4"+ + " http://localhost/video.mp4"+ + "").firstChild; + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + + msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text'); + expect(msg.innerHTML).toEqual(''); // Emtpy + media = view.el.querySelector('.chat-msg:last-child .chat-msg__media'); + expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual( + ''+ + ''+ + 'Download video file'); + done(); + }); }); })); it("will render download links for files from oob URLs", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - const view = _converse.chatboxviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); + let view; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + view = _converse.chatboxviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); - const stanza = Strophe.xmlHtmlNode( - ""+ - " Have you downloaded this funny file?"+ - " http://localhost/funny.pdf"+ - "").firstChild; - _converse.connection._dataRecv(test_utils.createRequest(stanza)); + const stanza = Strophe.xmlHtmlNode( + ""+ + " Have you downloaded this funny file?"+ + " http://localhost/funny.pdf"+ + "").firstChild; + _converse.connection._dataRecv(test_utils.createRequest(stanza)); - test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg a').length, 1000).then(function () { + test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg a').length, 1000); + }).then(function () { const msg = view.el.querySelector('.chat-msg .chat-msg__text'); expect(msg.outerHTML).toEqual('
Have you downloaded this funny file?
'); const media = view.el.querySelector('.chat-msg .chat-msg__media'); @@ -1789,36 +1820,39 @@ })); it("will render images from oob URLs", - mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, - function (done, _converse) { + mock.initConverseWithPromises( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - const view = _converse.chatboxviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); - const base_url = document.URL.split(window.location.pathname)[0]; - const url = base_url+"/logo/conversejs-filled.svg"; + let view; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + view = _converse.chatboxviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); + const base_url = document.URL.split(window.location.pathname)[0]; + const url = base_url+"/logo/conversejs-filled.svg"; - const stanza = Strophe.xmlHtmlNode( - ""+ - " Have you seen this funny image?"+ - " "+url+""+ - "").firstChild; - _converse.connection._dataRecv(test_utils.createRequest(stanza)); - - test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg img').length, 2000).then(function () { + const stanza = Strophe.xmlHtmlNode( + ""+ + " Have you seen this funny image?"+ + " "+url+""+ + "").firstChild; + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + return test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg img').length, 2000); + }).then(function () { const msg = view.el.querySelector('.chat-msg .chat-msg__text'); expect(msg.outerHTML).toEqual('
Have you seen this funny image?
'); const media = view.el.querySelector('.chat-msg .chat-msg__media'); expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual( - ''+ - ''+ - ''+ - ''); + ``+ + ``+ + ``+ + ``); done(); }).catch(_.partial(console.error, _)); })); @@ -1828,6 +1862,50 @@ describe("A Groupchat Message", function () { + it("is specially marked when you are mentioned in it", + mock.initConverseWithPromises( + null, ['rosterGroupsFetched'], {}, + function (done, _converse) { + + test_utils.createContacts(_converse, 'current'); + test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { + const view = _converse.chatboxviews.get('lounge@localhost'); + if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); } + const message = 'dummy: Your attention is required'; + const nick = mock.chatroom_names[0], + msg = $msg({ + from: 'lounge@localhost/'+nick, + id: (new Date()).getTime(), + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').t(message).tree(); + view.model.onMessage(msg); + expect($(view.el).find('.chat-msg').hasClass('mentioned')).toBeTruthy(); + done(); + }); + })); + + + it("keeps track whether you are the sender or not", + mock.initConverseWithPromises( + null, ['rosterGroupsFetched'], {}, + function (done, _converse) { + + test_utils.createContacts(_converse, 'current'); + test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { + const view = _converse.chatboxviews.get('lounge@localhost'); + const msg = $msg({ + from: 'lounge@localhost/dummy', + id: (new Date()).getTime(), + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').t('I wrote this message!').tree(); + view.model.onMessage(msg); + expect(view.model.messages.last().get('sender')).toBe('me'); + done(); + }); + })); + it("can be replaced with a correction", mock.initConverseWithPromises( null, ['rosterGroupsFetched'], {}, diff --git a/spec/minchats.js b/spec/minchats.js index 9de323052..2fa79237e 100644 --- a/spec/minchats.js +++ b/spec/minchats.js @@ -1,8 +1,9 @@ (function (root, factory) { define(["jquery", "jasmine", "mock", "test-utils"], factory); } (this, function ($, jasmine, mock, test_utils) { - var _ = converse.env._; - var $msg = converse.env.$msg; + const _ = converse.env._; + const $msg = converse.env.$msg; + const u = converse.env.utils; describe("The Minimized Chats Widget", function () { @@ -12,32 +13,37 @@ function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + test_utils.openControlBox(); _converse.minimized_chats.toggleview.model.browserStorage._clear(); _converse.minimized_chats.initToggle(); - var contact_jid, chatview; - contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - chatview = _converse.chatboxviews.get(contact_jid); - expect(chatview.model.get('minimized')).toBeFalsy(); - expect($(_converse.minimized_chats.el).is(':visible')).toBeFalsy(); - chatview.el.querySelector('.toggle-chatbox-button').click(); - expect(chatview.model.get('minimized')).toBeTruthy(); - expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy(); - expect(_converse.minimized_chats.keys().length).toBe(1); - expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid); + let contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + let chatview; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + chatview = _converse.chatboxviews.get(contact_jid); + expect(chatview.model.get('minimized')).toBeFalsy(); + expect($(_converse.minimized_chats.el).is(':visible')).toBeFalsy(); + chatview.el.querySelector('.toggle-chatbox-button').click(); + expect(chatview.model.get('minimized')).toBeTruthy(); + expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy(); + expect(_converse.minimized_chats.keys().length).toBe(1); + expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid); - contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - chatview = _converse.chatboxviews.get(contact_jid); - expect(chatview.model.get('minimized')).toBeFalsy(); - chatview.el.querySelector('.toggle-chatbox-button').click(); - expect(chatview.model.get('minimized')).toBeTruthy(); - expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy(); - expect(_converse.minimized_chats.keys().length).toBe(2); - expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy(); - done(); + contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; + return test_utils.openChatBoxFor(_converse, contact_jid); + }).then(() => { + chatview = _converse.chatboxviews.get(contact_jid); + expect(chatview.model.get('minimized')).toBeFalsy(); + chatview.el.querySelector('.toggle-chatbox-button').click(); + expect(chatview.model.get('minimized')).toBeTruthy(); + expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy(); + expect(_converse.minimized_chats.keys().length).toBe(2); + expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy(); + done(); + }); })); it("can be toggled to hide or show minimized chats", @@ -46,25 +52,26 @@ function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + test_utils.openControlBox(); _converse.minimized_chats.toggleview.model.browserStorage._clear(); _converse.minimized_chats.initToggle(); - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - var chatview = _converse.chatboxviews.get(contact_jid); - expect($(_converse.minimized_chats.el).is(':visible')).toBeFalsy(); - chatview.model.set({'minimized': true}); - expect($(_converse.minimized_chats.el).is(':visible')).toBeTruthy(); - expect(_converse.minimized_chats.keys().length).toBe(1); - expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid); - expect($(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')).is(':visible')).toBeTruthy(); - expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeFalsy(); - _converse.minimized_chats.el.querySelector('#toggle-minimized-chats').click(); - - return test_utils.waitUntil(function () { - return $(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')).is(':visible'); - }, 500).then(function () { + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + const chatview = _converse.chatboxviews.get(contact_jid); + expect(u.isVisible(_converse.minimized_chats.el)).toBeFalsy(); + chatview.model.set({'minimized': true}); + expect(u.isVisible(_converse.minimized_chats.el)).toBeTruthy(); + expect(_converse.minimized_chats.keys().length).toBe(1); + expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid); + expect(u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout'))).toBeTruthy(); + expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeFalsy(); + _converse.minimized_chats.el.querySelector('#toggle-minimized-chats').click(); + return test_utils.waitUntil(() => u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout'))); + }).then(() => { expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy(); done(); }); @@ -72,70 +79,79 @@ it("shows the number messages received to minimized chats", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); + _converse.emit('rosterContactsFetched'); + test_utils.openControlBox(); _converse.minimized_chats.toggleview.model.browserStorage._clear(); _converse.minimized_chats.initToggle(); var i, contact_jid, chatview, msg; _converse.minimized_chats.toggleview.model.set({'collapsed': true}); - expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).is(':visible')).toBeFalsy(); + + const unread_el = _converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'); + expect(_.isNull(unread_el)).toBe(true); + for (i=0; i<3; i++) { contact_jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openChatBoxFor(_converse, contact_jid); - chatview = _converse.chatboxviews.get(contact_jid); - chatview.model.set({'minimized': true}); - msg = $msg({ + } + return test_utils.waitUntil(() => _converse.chatboxes.length == 4).then(() => { + for (i=0; i<3; i++) { + chatview = _converse.chatboxviews.get(contact_jid); + chatview.model.set({'minimized': true}); + msg = $msg({ + from: contact_jid, + to: _converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('body').t('This message is sent to a minimized chatbox').up() + .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); + _converse.chatboxes.onMessage(msg); + expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).is(':visible')).toBeTruthy(); + expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i+1).toString()); + } + // Chat state notifications don't increment the unread messages counter + // state + _converse.chatboxes.onMessage($msg({ from: contact_jid, to: _converse.connection.jid, type: 'chat', id: (new Date()).getTime() - }).c('body').t('This message is sent to a minimized chatbox').up() - .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); - _converse.chatboxes.onMessage(msg); - expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).is(':visible')).toBeTruthy(); - expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i+1).toString()); - } - // Chat state notifications don't increment the unread messages counter - // state - _converse.chatboxes.onMessage($msg({ - from: contact_jid, - to: _converse.connection.jid, - type: 'chat', - id: (new Date()).getTime() - }).c('composing', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); - expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString()); + }).c('composing', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); + expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString()); - // state - _converse.chatboxes.onMessage($msg({ - from: contact_jid, - to: _converse.connection.jid, - type: 'chat', - id: (new Date()).getTime() - }).c('paused', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); - expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString()); + // state + _converse.chatboxes.onMessage($msg({ + from: contact_jid, + to: _converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('paused', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); + expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString()); - // state - _converse.chatboxes.onMessage($msg({ - from: contact_jid, - to: _converse.connection.jid, - type: 'chat', - id: (new Date()).getTime() - }).c('gone', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); - expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString()); + // state + _converse.chatboxes.onMessage($msg({ + from: contact_jid, + to: _converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('gone', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); + expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString()); - // state - _converse.chatboxes.onMessage($msg({ - from: contact_jid, - to: _converse.connection.jid, - type: 'chat', - id: (new Date()).getTime() - }).c('inactive', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); - expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString()); - done(); + // state + _converse.chatboxes.onMessage($msg({ + from: contact_jid, + to: _converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('inactive', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); + expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString()); + done(); + }); })); it("shows the number messages received to minimized groupchats", diff --git a/spec/roomslist.js b/spec/roomslist.js index c1761846f..932b122f7 100644 --- a/spec/roomslist.js +++ b/spec/roomslist.js @@ -12,44 +12,46 @@ describe("A list of open rooms", function () { it("is shown in the \"Rooms\" panel", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], + null, ['rosterGroupsFetched', 'chatBoxesFetched'], { allow_bookmarks: false // Makes testing easier, otherwise we // have to mock stanza traffic. }, function (done, _converse) { test_utils.openControlBox(); - var controlbox = _converse.chatboxviews.get('controlbox'); - - var list = controlbox.el.querySelector('div.rooms-list-container'); + const controlbox = _converse.chatboxviews.get('controlbox'); + let list = controlbox.el.querySelector('div.rooms-list-container'); expect(_.includes(list.classList, 'hidden')).toBeTruthy(); - test_utils.openChatRoom(_converse, 'room', 'conference.shakespeare.lit', 'JC'); + let room_els; - expect(_.isUndefined(_converse.rooms_list_view)).toBeFalsy(); - var room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(1); - expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit'); + test_utils.openChatRoom(_converse, 'room', 'conference.shakespeare.lit', 'JC') + .then(() => { + expect(_.isUndefined(_converse.rooms_list_view)).toBeFalsy(); + room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(1); + expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit'); + return test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); + }).then(() => { + room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(2); - test_utils.openChatRoom(_converse, 'lounge', 'localhost', 'dummy'); - room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(2); + var view = _converse.chatboxviews.get('room@conference.shakespeare.lit'); + view.close(); + room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(1); + expect(room_els[0].innerText).toBe('lounge@localhost'); + list = controlbox.el.querySelector('div.rooms-list-container'); + expect(_.includes(list.classList, 'hidden')).toBeFalsy(); - var view = _converse.chatboxviews.get('room@conference.shakespeare.lit'); - view.close(); - room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(1); - expect(room_els[0].innerText).toBe('lounge@localhost'); - list = controlbox.el.querySelector('div.rooms-list-container'); - expect(_.includes(list.classList, 'hidden')).toBeFalsy(); + view = _converse.chatboxviews.get('lounge@localhost'); + view.close(); + room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(0); - view = _converse.chatboxviews.get('lounge@localhost'); - view.close(); - room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(0); - - list = controlbox.el.querySelector('div.rooms-list-container'); - expect(_.includes(list.classList, 'hidden')).toBeTruthy(); - done(); + list = controlbox.el.querySelector('div.rooms-list-container'); + expect(_.includes(list.classList, 'hidden')).toBeTruthy(); + done(); + }); } )); }); @@ -57,79 +59,81 @@ describe("A groupchat shown in the groupchats list", function () { it("is highlighted if its currently open", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], + null, ['rosterGroupsFetched', 'chatBoxesFetched'], { whitelisted_plugins: ['converse-roomslist'], allow_bookmarks: false // Makes testing easier, otherwise we // have to mock stanza traffic. }, function (done, _converse) { - spyOn(_converse, 'isSingleton').and.callFake(function () { - return true; - }); + spyOn(_converse, 'isSingleton').and.callFake(() => true); + let room_els, item; test_utils.openControlBox(); - _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); - let room_els = _converse.rooms_list_view.el.querySelectorAll(".available-chatroom"); - expect(room_els.length).toBe(1); + _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}) + .then(() => { + room_els = _converse.rooms_list_view.el.querySelectorAll(".available-chatroom"); + expect(room_els.length).toBe(1); - let item = room_els[0]; - expect(u.hasClass('open', item)).toBe(true); - expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit'); + item = room_els[0]; + expect(u.hasClass('open', item)).toBe(true); + expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit'); + return _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'}); + }).then(() => { + room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(2); - _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'}); - room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(2); - - room_els = _converse.rooms_list_view.el.querySelectorAll(".available-chatroom.open"); - expect(room_els.length).toBe(1); - item = room_els[0]; - expect(item.textContent.trim()).toBe('balcony@chat.shakespeare.lit'); - done(); + room_els = _converse.rooms_list_view.el.querySelectorAll(".available-chatroom.open"); + expect(room_els.length).toBe(1); + item = room_els[0]; + expect(item.textContent.trim()).toBe('balcony@chat.shakespeare.lit'); + done(); + }); })); it("has an info icon which opens a details modal when clicked", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], + null, ['rosterGroupsFetched', 'chatBoxesFetched'], { whitelisted_plugins: ['converse-roomslist'], allow_bookmarks: false // Makes testing easier, otherwise we // have to mock stanza traffic. }, function (done, _converse) { + let view; test_utils.openControlBox(); - _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); - const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - const last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree; - const IQ_id = last_stanza.getAttribute('id'); - const features_stanza = $iq({ - 'from': 'coven@chat.shakespeare.lit', - 'id': IQ_id, - 'to': 'dummy@localhost/desktop', - 'type': 'result' - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { - 'category': 'conference', - 'name': 'A Dark Cave', - 'type': 'text' - }).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() - .c('feature', {'var': 'muc_passwordprotected'}).up() - .c('feature', {'var': 'muc_hidden'}).up() - .c('feature', {'var': 'muc_temporary'}).up() - .c('feature', {'var': 'muc_open'}).up() - .c('feature', {'var': 'muc_unmoderated'}).up() - .c('feature', {'var': 'muc_nonanonymous'}).up() - .c('feature', {'var': 'urn:xmpp:mam:0'}).up() - .c('x', { 'xmlns':'jabber:x:data', 'type':'result'}) - .c('field', {'var':'FORM_TYPE', 'type':'hidden'}) - .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up() - .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'}) - .c('value').t('This is the description').up().up() - .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'}) - .c('value').t(0); - _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); - - test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING) - .then(function () { + _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}) + .then(() => { + view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + const last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree; + const IQ_id = last_stanza.getAttribute('id'); + const features_stanza = $iq({ + 'from': 'coven@chat.shakespeare.lit', + 'id': IQ_id, + 'to': 'dummy@localhost/desktop', + 'type': 'result' + }) + .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) + .c('identity', { + 'category': 'conference', + 'name': 'A Dark Cave', + 'type': 'text' + }).up() + .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() + .c('feature', {'var': 'muc_passwordprotected'}).up() + .c('feature', {'var': 'muc_hidden'}).up() + .c('feature', {'var': 'muc_temporary'}).up() + .c('feature', {'var': 'muc_open'}).up() + .c('feature', {'var': 'muc_unmoderated'}).up() + .c('feature', {'var': 'muc_nonanonymous'}).up() + .c('feature', {'var': 'urn:xmpp:mam:0'}).up() + .c('x', { 'xmlns':'jabber:x:data', 'type':'result'}) + .c('field', {'var':'FORM_TYPE', 'type':'hidden'}) + .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up() + .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'}) + .c('value').t('This is the description').up().up() + .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'}) + .c('value').t(0); + _converse.connection._dataRecv(test_utils.createRequest(features_stanza)); + return test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING) + }).then(function () { var presence = $pres({ to: _converse.connection.jid, from: 'coven@chat.shakespeare.lit/some1', @@ -201,23 +205,22 @@ }, function (done, _converse) { - spyOn(window, 'confirm').and.callFake(function () { - return true; + spyOn(window, 'confirm').and.callFake(() => true); + expect(_converse.chatboxes.length).toBe(1); + test_utils.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC') + .then(() => { + expect(_converse.chatboxes.length).toBe(2); + var room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(1); + var close_el = _converse.rooms_list_view.el.querySelector(".close-room"); + close_el.click(); + expect(window.confirm).toHaveBeenCalledWith( + 'Are you sure you want to leave the groupchat lounge@conference.shakespeare.lit?'); + room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); + expect(room_els.length).toBe(0); + expect(_converse.chatboxes.length).toBe(1); + done(); }); - expect(_converse.chatboxes.length).toBe(1); - test_utils.openChatRoom( - _converse, 'lounge', 'conference.shakespeare.lit', 'JC'); - expect(_converse.chatboxes.length).toBe(2); - var room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(1); - var close_el = _converse.rooms_list_view.el.querySelector(".close-room"); - close_el.click(); - expect(window.confirm).toHaveBeenCalledWith( - 'Are you sure you want to leave the groupchat lounge@conference.shakespeare.lit?'); - room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); - expect(room_els.length).toBe(0); - expect(_converse.chatboxes.length).toBe(1); - done(); })); it("shows unread messages directed at the user", mock.initConverseWithAsync( diff --git a/spec/spoilers.js b/spec/spoilers.js index a63789855..1de792aa5 100644 --- a/spec/spoilers.js +++ b/spec/spoilers.js @@ -92,12 +92,14 @@ it("can be sent without a hint", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { - test_utils.createContacts(_converse, 'current'); + test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); + test_utils.openControlBox(); - var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; // XXX: We need to send a presence from the contact, so that we // have a resource, that resource is then queried to see @@ -108,9 +110,9 @@ 'to': 'dummy@localhost' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - test_utils.openChatBoxFor(_converse, contact_jid); - - test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () { + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER])) + .then(() => { var view = _converse.chatboxviews.get(contact_jid); spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(_converse.connection, 'send'); @@ -167,10 +169,12 @@ it("can be sent with a hint", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { - test_utils.createContacts(_converse, 'current'); + test_utils.createContacts(_converse, 'current', 1); + _converse.emit('rosterContactsFetched'); + test_utils.openControlBox(); var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; @@ -183,9 +187,9 @@ 'to': 'dummy@localhost' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - test_utils.openChatBoxFor(_converse, contact_jid); - - test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () { + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER])) + .then(() => { var view = _converse.chatboxviews.get(contact_jid); var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); spoiler_toggle.click(); @@ -206,17 +210,17 @@ expect(view.onMessageSubmitted).toHaveBeenCalled(); /* Test the XML stanza - * - * - * This is the spoiler - * - * This is the hint - * " - */ + * + * + * This is the spoiler + * + * This is the hint + * " + */ var stanza = _converse.connection.send.calls.argsFor(0)[0].tree(); var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]'); diff --git a/spec/user-details-modal.js b/spec/user-details-modal.js index 7b968623f..296755b1b 100644 --- a/spec/user-details-modal.js +++ b/spec/user-details-modal.js @@ -16,22 +16,23 @@ it("can be used to remove a contact", mock.initConverseWithPromises( - null, ['rosterGroupsFetched'], {}, + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) { test_utils.createContacts(_converse, 'current'); _converse.emit('rosterContactsFetched'); + let view, show_modal_button, modal; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; test_utils.openChatBoxFor(_converse, contact_jid); - - const view = _converse.chatboxviews.get(contact_jid); - const show_modal_button = view.el.querySelector('.show-user-details-modal'); - expect(u.isVisible(show_modal_button)).toBeTruthy(); - show_modal_button.click(); - const modal = view.user_details_modal; - test_utils.waitUntil(() => u.isVisible(modal.el), 1000) - .then(function () { + return test_utils.waitUntil(() => _converse.chatboxes.length).then(() => { + view = _converse.chatboxviews.get(contact_jid); + show_modal_button = view.el.querySelector('.show-user-details-modal'); + expect(u.isVisible(show_modal_button)).toBeTruthy(); + show_modal_button.click(); + modal = view.user_details_modal; + return test_utils.waitUntil(() => u.isVisible(modal.el), 1000); + }).then(function () { spyOn(window, 'confirm').and.returnValue(true); spyOn(view.model.contact, 'removeFromRoster').and.callFake(function (callback) { callback(); @@ -57,16 +58,17 @@ test_utils.createContacts(_converse, 'current'); _converse.emit('rosterContactsFetched'); + let view, modal; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; - test_utils.openChatBoxFor(_converse, contact_jid); - - const view = _converse.chatboxviews.get(contact_jid); - const show_modal_button = view.el.querySelector('.show-user-details-modal'); - expect(u.isVisible(show_modal_button)).toBeTruthy(); - show_modal_button.click(); - const modal = view.user_details_modal; - test_utils.waitUntil(() => u.isVisible(modal.el), 2000) - .then(function () { + test_utils.openChatBoxFor(_converse, contact_jid) + .then(() => { + view = _converse.chatboxviews.get(contact_jid); + const show_modal_button = view.el.querySelector('.show-user-details-modal'); + expect(u.isVisible(show_modal_button)).toBeTruthy(); + show_modal_button.click(); + modal = view.user_details_modal; + return test_utils.waitUntil(() => u.isVisible(modal.el), 2000); + }).then(function () { spyOn(window, 'confirm').and.returnValue(true); spyOn(view.model.contact, 'removeFromRoster').and.callFake(function (callback, errback) { errback(); diff --git a/src/converse-chatboxes.js b/src/converse-chatboxes.js index c10137a05..e1bfb3f7f 100644 --- a/src/converse-chatboxes.js +++ b/src/converse-chatboxes.js @@ -69,12 +69,7 @@ Strophe.LogLevel.WARN ); } - Promise.all([ - _converse.api.waitUntil('rosterContactsFetched'), - _converse.api.waitUntil('chatBoxesFetched') - ]).then(() => { - _converse.api.chats.open(jid); - }); + _converse.api.chats.open(jid); } _converse.router.route('converse/chat?jid=:jid', openChat); @@ -485,7 +480,7 @@ if (attrs.type === 'groupchat') { attrs.from = stanza.getAttribute('from'); attrs.nick = Strophe.unescapeNode(Strophe.getResourceFromJid(attrs.from)); - if (attrs.from === this.get('nick')) { + if (Strophe.getResourceFromJid(attrs.from) === this.get('nick')) { attrs.sender = 'me'; } else { attrs.sender = 'them'; @@ -876,6 +871,11 @@ /************************ BEGIN API ************************/ _.extend(_converse.api, { + /** + * The "chats" grouping (used for one-on-one chats) + * + * @namespace + */ 'chats': { 'create' (jids, attrs) { if (_.isUndefined(jids)) { @@ -885,7 +885,6 @@ ); return null; } - if (_.isString(jids)) { if (attrs && !_.get(attrs, 'fullname')) { attrs.fullname = _.get(_converse.api.contacts.get(jids), 'attributes.fullname'); @@ -902,17 +901,80 @@ return _converse.chatboxes.getChatBox(jid, attrs, true).trigger('show'); }); }, + + /** + * Opens a new one-on-one chat. + * + * @function + * + * @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com'] + * @returns {Promise} Promise which resolves with the Backbone.Model representing the chat. + * + * @example + * // To open a single chat, provide the JID of the contact you're chatting with in that chat: + * converse.plugins.add('myplugin', { + * initialize: function() { + * var _converse = this._converse; + * // Note, buddy@example.org must be in your contacts roster! + * _converse.api.chats.open('buddy@example.com').then((chat) => { + * // Now you can do something with the chat model + * }); + * } + * }); + * + * @example + * // To open an array of chats, provide an array of JIDs: + * converse.plugins.add('myplugin', { + * initialize: function () { + * var _converse = this._converse; + * // Note, these users must first be in your contacts roster! + * _converse.api.chats.open(['buddy1@example.com', 'buddy2@example.com']).then((chats) => { + * // Now you can do something with the chat models + * }); + * } + * }); + * + */ 'open' (jids, attrs) { - if (_.isUndefined(jids)) { - _converse.log("chats.open: You need to provide at least one JID", Strophe.LogLevel.ERROR); - return null; - } else if (_.isString(jids)) { - const chatbox = _converse.api.chats.create(jids, attrs); - chatbox.trigger('show'); - return chatbox; - } - return _.map(jids, (jid) => _converse.api.chats.create(jid, attrs).trigger('show')); + return new Promise((resolve, reject) => { + Promise.all([ + _converse.api.waitUntil('rosterContactsFetched'), + _converse.api.waitUntil('chatBoxesFetched') + ]).then(() => { + if (_.isUndefined(jids)) { + const err_msg = "chats.open: You need to provide at least one JID"; + _converse.log(err_msg, Strophe.LogLevel.ERROR); + reject(new Error(err_msg)); + } else if (_.isString(jids)) { + resolve(_converse.api.chats.create(jids, attrs).trigger('show')); + } else { + resolve(_.map(jids, (jid) => _converse.api.chats.create(jid, attrs).trigger('show'))); + } + }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); + }); }, + + /** + * Returns a chat model. The chat should already be open. + * + * @function + * + * @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com'] + * @returns {Backbone.Model} + * + * @example + * // To return a single chat, provide the JID of the contact you're chatting with in that chat: + * const model = _converse.api.chats.get('buddy@example.com'); + * + * @example + * // To return an array of chats, provide an array of JIDs: + * const models = _converse.api.chats.get(['buddy1@example.com', 'buddy2@example.com']); + * + * @example + * // To return all open chats, call the method without any parameters:: + * const models = _converse.api.chats.get(); + * + */ 'get' (jids) { if (_.isUndefined(jids)) { const result = []; diff --git a/src/converse-chatview.js b/src/converse-chatview.js index 48d2052b7..9def0876d 100644 --- a/src/converse-chatview.js +++ b/src/converse-chatview.js @@ -888,6 +888,9 @@ const textarea = this.el.querySelector('.chat-textarea'), message = textarea.value; + if (!message.replace(/\s/g, '').length) { + return; + } let spoiler_hint; if (this.model.get('composing_spoiler')) { const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); @@ -901,10 +904,8 @@ event.initEvent('input', true, true); textarea.dispatchEvent(event); - if (message !== '') { - this.onMessageSubmitted(message, spoiler_hint); - _converse.emit('messageSend', message); - } + this.onMessageSubmitted(message, spoiler_hint); + _converse.emit('messageSend', message); this.setChatState(_converse.ACTIVE); }, diff --git a/src/converse-core.js b/src/converse-core.js index 8031a7b1e..bcdea0f41 100644 --- a/src/converse-core.js +++ b/src/converse-core.js @@ -99,6 +99,11 @@ 'converse-vcard' ]; + // Setting wait to 59 instead of 60 to avoid timing conflicts with the + // webserver, which is often also set to 60 and might therefore sometimes + // return a 504 error page instead of passing through to the BOSH proxy. + const BOSH_WAIT = 59; + // Make converse pluggable pluggable.enable(_converse, '_converse', 'pluggable'); @@ -1026,7 +1031,7 @@ if (!this.connection.reconnecting) { this.connection.reset(); } - this.connection.connect(this.jid.toLowerCase(), null, this.onConnectStatusChanged); + this.connection.connect(this.jid.toLowerCase(), null, this.onConnectStatusChanged, BOSH_WAIT); } else if (this.authentication === _converse.LOGIN) { const password = _.isNil(credentials) ? (_converse.connection.pass || this.password) : credentials.password; if (!password) { @@ -1047,7 +1052,7 @@ if (!this.connection.reconnecting) { this.connection.reset(); } - this.connection.connect(this.jid, password, this.onConnectStatusChanged); + this.connection.connect(this.jid, password, this.onConnectStatusChanged, BOSH_WAIT); } }; diff --git a/src/converse-disco.js b/src/converse-disco.js index 612b68310..2a6c83db2 100644 --- a/src/converse-disco.js +++ b/src/converse-disco.js @@ -296,6 +296,7 @@ if (from !== null) { iqresult.attrs({'to': from}); } + iqresult.c('query', attrs); _.each(plugin._identities, (identity) => { const attrs = { 'category': identity.category, diff --git a/src/converse-embedded.js b/src/converse-embedded.js index f7528e88b..de56b56eb 100644 --- a/src/converse-embedded.js +++ b/src/converse-embedded.js @@ -30,11 +30,10 @@ if (!_.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) { throw new Error("converse-embedded: auto_join_rooms must be an Array"); } - if (_converse.auto_join_rooms.length !== 1 && _converse.auto_join_private_chats.length !== 1) { + if (_converse.auto_join_rooms.length > 1 && _converse.auto_join_private_chats.length > 1) { throw new Error("converse-embedded: It doesn't make "+ - "sense to have the auto_join_rooms setting to zero or "+ - "more then one, since only one chat room can be open "+ - "at any time."); + "sense to have the auto_join_rooms setting more then one, "+ + "since only one chat room can be open at any time."); } } }); diff --git a/src/converse-muc-views.js b/src/converse-muc-views.js index 6118b09e9..384bb217b 100644 --- a/src/converse-muc-views.js +++ b/src/converse-muc-views.js @@ -550,16 +550,7 @@ this.model.occupants.on('add', this.showJoinNotification, this); this.model.occupants.on('remove', this.showLeaveNotification, this); - this.model.occupants.on('change:show', (occupant) => { - if (!occupant.isMember() || _.includes(occupant.get('states'), '303')) { - return; - } - if (occupant.get('show') === 'offline') { - this.showLeaveNotification(occupant); - } else if (occupant.get('show') === 'online') { - this.showJoinNotification(occupant); - } - }); + this.model.occupants.on('change:show', this.showJoinOrLeaveNotification, this); this.createEmojiPicker(); this.createOccupantsView(); @@ -570,8 +561,7 @@ const handler = () => { if (!u.isPersistableModel(this.model)) { // Happens during tests, nothing to do if this - // is a hanging chatbox (i.e. not in the - // collection anymore). + // is a hanging chatbox (i.e. not in the collection anymore). return; } this.populateAndJoin(); @@ -1383,6 +1373,17 @@ } }, + showJoinOrLeaveNotification (occupant) { + if (!occupant.isMember() || _.includes(occupant.get('states'), '303')) { + return; + } + if (occupant.get('show') === 'offline') { + this.showLeaveNotification(occupant); + } else if (occupant.get('show') === 'online') { + this.showJoinNotification(occupant); + } + }, + showJoinNotification (occupant) { if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { return; @@ -1429,11 +1430,11 @@ showLeaveNotification (occupant) { const nick = occupant.get('nick'), stat = occupant.get('status'), - last_el = this.content.lastElementChild, - last_msg_date = last_el.getAttribute('data-isodate'); + last_el = this.content.lastElementChild; - if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && - moment(last_msg_date).isSame(new Date(), "day") && + if (last_el && + _.includes(_.get(last_el, 'classList', []), 'chat-info') && + moment(last_el.getAttribute('data-isodate')).isSame(new Date(), "day") && _.get(last_el, 'dataset', {}).join === `"${nick}"`) { let message; @@ -1462,7 +1463,8 @@ 'extra_classes': 'chat-event', 'data': `data-leave="${nick}"` } - if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && + if (last_el && + _.includes(_.get(last_el, 'classList', []), 'chat-info') && _.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) { last_el.outerHTML = tpl_info(data); diff --git a/src/converse-muc.js b/src/converse-muc.js index a334901e7..4ec312a2e 100644 --- a/src/converse-muc.js +++ b/src/converse-muc.js @@ -1254,14 +1254,23 @@ } return _.map(jids, _.partial(createChatRoom, _, attrs)); }, + 'open' (jids, attrs) { - if (_.isUndefined(jids)) { - throw new TypeError('rooms.open: You need to provide at least one JID'); - } else if (_.isString(jids)) { - return _converse.api.rooms.create(jids, attrs).trigger('show'); - } - return _.map(jids, (jid) => _converse.api.rooms.create(jid, attrs).trigger('show')); + return new Promise((resolve, reject) => { + _converse.api.waitUntil('chatBoxesFetched').then(() => { + if (_.isUndefined(jids)) { + const err_msg = 'rooms.open: You need to provide at least one JID'; + _converse.log(err_msg, Strophe.LogLevel.ERROR); + reject(new TypeError(err_msg)); + } else if (_.isString(jids)) { + resolve(_converse.api.rooms.create(jids, attrs).trigger('show')); + } else { + resolve(_.map(jids, (jid) => _converse.api.rooms.create(jid, attrs).trigger('show'))); + } + }); + }); }, + 'get' (jids, attrs, create) { if (_.isString(attrs)) { attrs = {'nick': attrs}; diff --git a/src/i18n.js b/src/i18n.js index b570537a5..07c2bb353 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -148,8 +148,13 @@ ); xhr.onload = function () { if (xhr.status >= 200 && xhr.status < 400) { - jed_instance = new Jed(window.JSON.parse(xhr.responseText)); - resolve(); + try { + const data = window.JSON.parse(xhr.responseText); + jed_instance = new Jed(data); + resolve(); + } catch (e) { + xhr.onerror(e); + } } else { xhr.onerror(); } diff --git a/tests/utils.js b/tests/utils.js index ca0f0c1e1..115ab8e23 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -98,8 +98,9 @@ return views; }; - utils.openChatBoxFor = function (converse, jid) { - return converse.roster.get(jid).trigger("open"); + utils.openChatBoxFor = function (_converse, jid) { + _converse.roster.get(jid).trigger("open"); + return utils.waitUntil(() => _converse.chatboxviews.get(jid)); }; utils.openChatRoomViaModal = function (_converse, jid, nick) { @@ -121,64 +122,64 @@ }; utils.openChatRoom = function (_converse, room, server, nick) { - _converse.api.rooms.open(`${room}@${server}`); + return _converse.api.rooms.open(`${room}@${server}`); }; utils.openAndEnterChatRoom = function (_converse, room, server, nick) { let last_stanza; - return new Promise(function (resolve, reject) { - _converse.api.rooms.open(`${room}@${server}`); - const view = _converse.chatboxviews.get((room+'@'+server).toLowerCase()); - // We pretend this is a new room, so no disco info is returned. - let last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree; - const IQ_id = last_stanza.getAttribute('id'); - const features_stanza = $iq({ - 'from': room+'@'+server, - 'id': IQ_id, - 'to': nick+'@'+server, - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); - _converse.connection._dataRecv(utils.createRequest(features_stanza)); - - utils.waitUntil(() => { - return _.filter( - _converse.connection.IQ_stanzas, (node) => { - const query = node.nodeTree.querySelector('query'); - if (query && query.getAttribute('node') === 'x-roomuser-item') { - last_stanza = node.nodeTree; - return true; - } - }).length - }).then(function () { - // The XMPP server returns the reserved nick for this user. + return new Promise((resolve, reject) => { + return _converse.api.rooms.open(`${room}@${server}`).then(() => { + const view = _converse.chatboxviews.get((room+'@'+server).toLowerCase()); + // We pretend this is a new room, so no disco info is returned. + let last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree; const IQ_id = last_stanza.getAttribute('id'); - const stanza = $iq({ - 'type': 'result', - 'id': IQ_id, - 'from': view.model.get('jid'), - 'to': _converse.connection.jid - }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'}) - .c('identity', {'category': 'conference', 'name': nick, 'type': 'text'}); - _converse.connection._dataRecv(utils.createRequest(stanza)); - // The user has just entered the room (because join was called) - // and receives their own presence from the server. - // See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres - var presence = $pres({ - to: _converse.connection.jid, - from: room+'@'+server+'/'+nick, - id: 'DC352437-C019-40EC-B590-AF29E879AF97' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: 'member', - jid: _converse.bare_jid, - role: 'participant' - }).up() - .c('status').attrs({code:'110'}); - _converse.connection._dataRecv(utils.createRequest(presence)); - resolve(); + const features_stanza = $iq({ + 'from': room+'@'+server, + 'id': IQ_id, + 'to': nick+'@'+server, + 'type': 'error' + }).c('error', {'type': 'cancel'}) + .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + _converse.connection._dataRecv(utils.createRequest(features_stanza)); + utils.waitUntil(() => { + return _.filter( + _converse.connection.IQ_stanzas, (node) => { + const query = node.nodeTree.querySelector('query'); + if (query && query.getAttribute('node') === 'x-roomuser-item') { + last_stanza = node.nodeTree; + return true; + } + }).length + }).then(function () { + // The XMPP server returns the reserved nick for this user. + const IQ_id = last_stanza.getAttribute('id'); + const stanza = $iq({ + 'type': 'result', + 'id': IQ_id, + 'from': view.model.get('jid'), + 'to': _converse.connection.jid + }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'}) + .c('identity', {'category': 'conference', 'name': nick, 'type': 'text'}); + _converse.connection._dataRecv(utils.createRequest(stanza)); + // The user has just entered the room (because join was called) + // and receives their own presence from the server. + // See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres + var presence = $pres({ + to: _converse.connection.jid, + from: room+'@'+server+'/'+nick, + id: 'DC352437-C019-40EC-B590-AF29E879AF97' + }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) + .c('item').attrs({ + affiliation: 'member', + jid: _converse.bare_jid, + role: 'participant' + }).up() + .c('status').attrs({code:'110'}); + _converse.connection._dataRecv(utils.createRequest(presence)); + resolve(); + }).catch(_.partial(console.error, _)); }).catch(_.partial(console.error, _)); }).catch(_.partial(console.error, _)); };