Use Karma as test runner

This commit is contained in:
JC Brand 2020-04-22 13:11:48 +02:00
parent 7e23adf26f
commit 4b270359f6
47 changed files with 21654 additions and 21267 deletions

View File

@ -1,4 +1,4 @@
dist: xenial
dist: bionic
language: node_js
cache:
directories:
@ -6,7 +6,11 @@ cache:
addons:
chrome: stable
node_js:
- "10"
- "14"
install: make node_modules
before_script: make serve_bg
script: make check
services:
- xvfb
before_script:
- make serve_bg
- export DISPLAY=:99.0
script: make check ARGS=--single-run

View File

@ -2,6 +2,7 @@
BABEL ?= node_modules/.bin/babel
BOOTSTRAP = ./node_modules/
BUILDDIR = ./docs
KARMA ?= ./node_modules/.bin/karma
CHROMIUM ?= ./node_modules/.bin/run-headless-chromium
CLEANCSS ?= ./node_modules/clean-css-cli/bin/cleancss --skip-rebase
ESLINT ?= ./node_modules/.bin/eslint
@ -197,7 +198,11 @@ eslint: node_modules
.PHONY: check
check: eslint dev
LOG_CR_VERBOSITY=INFO $(CHROMIUM) --disable-gpu --no-sandbox http://localhost:$(HTTPSERVE_PORT)/tests/index.html
$(KARMA) start karma.conf.js $(ARGS)
.PHONY: test
test:
$(KARMA) start karma.conf.js $(ARGS)
########################################################################
## Documentation

107
karma.conf.js Normal file
View File

@ -0,0 +1,107 @@
/* global module */
const path = require('path');
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
frameworks: ['jasmine'],
files: [
{ pattern: 'dist/*.js.map', included: false },
{ pattern: 'dist/*.css.map', included: false },
{ pattern: "dist/emojis.js", served: true },
"dist/converse.js",
"dist/converse.css",
{ pattern: "dist/webfonts/**/*.*", included: false },
{ pattern: "node_modules/sinon/pkg/sinon.js", type: 'module' },
{ pattern: "tests/console-reporter.js", type: 'module' },
{ pattern: "tests/mock.js", type: 'module' },
{ pattern: "spec/spoilers.js", type: 'module' },
{ pattern: "spec/roomslist.js", type: 'module' },
{ pattern: "spec/utils.js", type: 'module' },
{ pattern: "spec/converse.js", type: 'module' },
{ pattern: "spec/bookmarks.js", type: 'module' },
{ pattern: "spec/headline.js", type: 'module' },
{ pattern: "spec/disco.js", type: 'module' },
{ pattern: "spec/protocol.js", type: 'module' },
{ pattern: "spec/presence.js", type: 'module' },
{ pattern: "spec/eventemitter.js", type: 'module' },
{ pattern: "spec/smacks.js", type: 'module' },
{ pattern: "spec/ping.js", type: 'module' },
{ pattern: "spec/push.js", type: 'module' },
{ pattern: "spec/xmppstatus.js", type: 'module' },
{ pattern: "spec/mam.js", type: 'module' },
{ pattern: "spec/omemo.js", type: 'module' },
{ pattern: "spec/controlbox.js", type: 'module' },
{ pattern: "spec/roster.js", type: 'module' },
{ pattern: "spec/chatbox.js", type: 'module' },
{ pattern: "spec/user-details-modal.js", type: 'module' },
{ pattern: "spec/messages.js", type: 'module' },
{ pattern: "spec/muc_messages.js", type: 'module' },
{ pattern: "spec/retractions.js", type: 'module' },
{ pattern: "spec/muc.js", type: 'module' },
{ pattern: "spec/modtools.js", type: 'module' },
{ pattern: "spec/room_registration.js", type: 'module' },
{ pattern: "spec/autocomplete.js", type: 'module' },
{ pattern: "spec/minchats.js", type: 'module' },
{ pattern: "spec/notification.js", type: 'module' },
{ pattern: "spec/login.js", type: 'module' },
{ pattern: "spec/register.js", type: 'module' },
{ pattern: "spec/hats.js", type: 'module' },
{ pattern: "spec/http-file-upload.js", type: 'module' },
{ pattern: "spec/emojis.js", type: 'module' },
{ pattern: "spec/xss.js", type: 'module' },
],
exclude: ['**/*.sw?'],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'kjhtml'],
webpack: {
mode: 'development',
devtool: 'inline-source-map',
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|test)/
}]
},
output: {
path: path.resolve('test'),
filename: '[name].out.js',
chunkFilename: '[id].[chunkHash].js'
}
},
port: 9876,
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
}

905
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -84,8 +84,14 @@
"http-server": "^0.12.1",
"imports-loader": "^0.8.0",
"install": "^0.13.0",
"jasmine-core": "2.99.1",
"jasmine": "^3.5.0",
"jsdoc": "^3.6.4",
"karma": "^5.0.2",
"karma-chrome-launcher": "^3.1.0",
"karma-cli": "^2.0.0",
"karma-jasmine": "^3.1.1",
"karma-jasmine-html-reporter": "^1.5.3",
"karma-webpack": "^4.0.2",
"lerna": "^3.20.2",
"lit-html": "^1.2.1",
"lodash-template-webpack-loader": "jcbrand/lodash-template-webpack-loader",

View File

@ -1,217 +1,215 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const $pres = converse.env.$pres;
const $msg = converse.env.$msg;
const Strophe = converse.env.Strophe;
const u = converse.env.utils;
/*global mock */
describe("The nickname autocomplete feature", function () {
const $pres = converse.env.$pres;
const $msg = converse.env.$msg;
const Strophe = converse.env.Strophe;
const u = converse.env.utils;
it("shows all autocompletion options when the user presses @",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
describe("The nickname autocomplete feature", function () {
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom');
const view = _converse.chatboxviews.get('lounge@montague.lit');
it("shows all autocompletion options when the user presses @",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
// Nicknames from presences
['dick', 'harry'].forEach((nick) => {
_converse.connection._dataRecv(test_utils.createRequest(
$pres({
'to': 'tom@montague.lit/resource',
'from': `lounge@montague.lit/${nick}`
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `${nick}@montague.lit/resource`,
'role': 'participant'
})));
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom');
const view = _converse.chatboxviews.get('lounge@montague.lit');
// Nicknames from presences
['dick', 'harry'].forEach((nick) => {
_converse.connection._dataRecv(mock.createRequest(
$pres({
'to': 'tom@montague.lit/resource',
'from': `lounge@montague.lit/${nick}`
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `${nick}@montague.lit/resource`,
'role': 'participant'
})));
});
// Nicknames from messages
const msg = $msg({
from: 'lounge@montague.lit/jane',
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('Hello world').tree();
await view.model.queueMessage(msg);
// Test that pressing @ brings up all options
const textarea = view.el.querySelector('textarea.chat-textarea');
const at_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 50,
'key': '@'
};
view.onKeyDown(at_event);
textarea.value = '@';
view.onKeyUp(at_event);
await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 4);
expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
done();
}));
it("autocompletes when the user presses tab",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.model.occupants.length).toBe(1);
let presence = $pres({
'to': 'romeo@montague.lit/orchard',
'from': 'lounge@montague.lit/some1'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some1@montague.lit/resource',
'role': 'participant'
});
_converse.connection._dataRecv(mock.createRequest(presence));
expect(view.model.occupants.length).toBe(2);
// Nicknames from messages
const msg = $msg({
from: 'lounge@montague.lit/jane',
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('Hello world').tree();
await view.model.queueMessage(msg);
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = "hello som";
// Test that pressing @ brings up all options
const textarea = view.el.querySelector('textarea.chat-textarea');
const at_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 50,
'key': '@'
};
view.onKeyDown(at_event);
textarea.value = '@';
view.onKeyUp(at_event);
// Press tab
const tab_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 9,
'key': 'Tab'
}
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 4);
expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
expect(view.el.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
done();
}));
it("autocompletes when the user presses tab",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.model.occupants.length).toBe(1);
let presence = $pres({
'to': 'romeo@montague.lit/orchard',
'from': 'lounge@montague.lit/some1'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some1@montague.lit/resource',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(view.model.occupants.length).toBe(2);
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = "hello som";
// Press tab
const tab_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 9,
'key': 'Tab'
}
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
const backspace_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'keyCode': 8
}
for (var i=0; i<3; i++) {
// Press backspace 3 times to remove "som"
view.onKeyDown(backspace_event);
textarea.value = textarea.value.slice(0, textarea.value.length-1)
view.onKeyUp(backspace_event);
}
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === true);
presence = $pres({
'to': 'romeo@montague.lit/orchard',
'from': 'lounge@montague.lit/some2'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some2@montague.lit/resource',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
textarea.value = "hello s s";
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
const up_arrow_event = {
'target': textarea,
'preventDefault': () => (up_arrow_event.defaultPrevented = true),
'stopPropagation': function stopPropagation () {},
'keyCode': 38
}
view.onKeyDown(up_arrow_event);
view.onKeyUp(up_arrow_event);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('some1');
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('some2');
view.onKeyDown({
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 13 // Enter
});
expect(textarea.value).toBe('hello s @some2 ');
// Test that pressing tab twice selects
presence = $pres({
'to': 'romeo@montague.lit/orchard',
'from': 'lounge@montague.lit/z3r0'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'z3r0@montague.lit/resource',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
textarea.value = "hello z";
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => textarea.value === 'hello @z3r0 ');
done();
}));
it("autocompletes when the user presses backspace",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.model.occupants.length).toBe(1);
const presence = $pres({
'to': 'romeo@montague.lit/orchard',
'from': 'lounge@montague.lit/some1'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some1@montague.lit/resource',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(view.model.occupants.length).toBe(2);
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = "hello @some1 ";
// Press backspace
const backspace_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 8,
'key': 'Backspace'
}
const backspace_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'keyCode': 8
}
for (var i=0; i<3; i++) {
// Press backspace 3 times to remove "som"
view.onKeyDown(backspace_event);
textarea.value = "hello @some1"; // Mimic backspace
textarea.value = textarea.value.slice(0, textarea.value.length-1)
view.onKeyUp(backspace_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
done();
}));
});
}
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === true);
presence = $pres({
'to': 'romeo@montague.lit/orchard',
'from': 'lounge@montague.lit/some2'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some2@montague.lit/resource',
'role': 'participant'
});
_converse.connection._dataRecv(mock.createRequest(presence));
textarea.value = "hello s s";
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
const up_arrow_event = {
'target': textarea,
'preventDefault': () => (up_arrow_event.defaultPrevented = true),
'stopPropagation': function stopPropagation () {},
'keyCode': 38
}
view.onKeyDown(up_arrow_event);
view.onKeyUp(up_arrow_event);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(2);
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('some1');
expect(view.el.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('some2');
view.onKeyDown({
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 13 // Enter
});
expect(textarea.value).toBe('hello s @some2 ');
// Test that pressing tab twice selects
presence = $pres({
'to': 'romeo@montague.lit/orchard',
'from': 'lounge@montague.lit/z3r0'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'z3r0@montague.lit/resource',
'role': 'participant'
});
_converse.connection._dataRecv(mock.createRequest(presence));
textarea.value = "hello z";
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
view.onKeyDown(tab_event);
view.onKeyUp(tab_event);
await u.waitUntil(() => textarea.value === 'hello @z3r0 ');
done();
}));
it("autocompletes when the user presses backspace",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.model.occupants.length).toBe(1);
const presence = $pres({
'to': 'romeo@montague.lit/orchard',
'from': 'lounge@montague.lit/some1'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some1@montague.lit/resource',
'role': 'participant'
});
_converse.connection._dataRecv(mock.createRequest(presence));
expect(view.model.occupants.length).toBe(2);
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = "hello @some1 ";
// Press backspace
const backspace_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 8,
'key': 'Backspace'
}
view.onKeyDown(backspace_event);
textarea.value = "hello @some1"; // Mimic backspace
view.onKeyUp(backspace_event);
await u.waitUntil(() => view.el.querySelector('.suggestion-box__results').hidden === false);
expect(view.el.querySelectorAll('.suggestion-box__results li').length).toBe(1);
expect(view.el.querySelector('.suggestion-box__results li').textContent).toBe('some1');
done();
}));
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,389 +1,387 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const _ = converse.env._,
$msg = converse.env.$msg,
u = converse.env.utils,
Strophe = converse.env.Strophe,
sizzle = converse.env.sizzle;
/*global mock */
const _ = converse.env._,
$msg = converse.env.$msg,
u = converse.env.utils,
Strophe = converse.env.Strophe,
sizzle = converse.env.sizzle;
describe("The Controlbox", function () {
describe("The Controlbox", function () {
it("can be opened by clicking a DOM element with class 'toggle-controlbox'",
it("can be opened by clicking a DOM element with class 'toggle-controlbox'",
mock.initConverse(
['rosterGroupsFetched'], {},
function (done, _converse) {
// This spec will only pass if the controlbox is not currently
// open yet.
let el = document.querySelector("div#controlbox");
expect(_.isElement(el)).toBe(true);
expect(u.isVisible(el)).toBe(false);
spyOn(_converse.controlboxtoggle, 'onClick').and.callThrough();
spyOn(_converse.controlboxtoggle, 'showControlBox').and.callThrough();
spyOn(_converse.api, "trigger").and.callThrough();
// Redelegate so that the spies are now registered as the event handlers (specifically for 'onClick')
_converse.controlboxtoggle.delegateEvents();
document.querySelector('.toggle-controlbox').click();
expect(_converse.controlboxtoggle.onClick).toHaveBeenCalled();
expect(_converse.controlboxtoggle.showControlBox).toHaveBeenCalled();
expect(_converse.api.trigger).toHaveBeenCalledWith('controlBoxOpened', jasmine.any(Object));
el = document.querySelector("div#controlbox");
expect(u.isVisible(el)).toBe(true);
done();
}));
describe("The \"Contacts\" section", function () {
it("can be used to add contact and it checks for case-sensivity",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.api, "trigger").and.callThrough();
spyOn(_converse.rosterview, 'update').and.callThrough();
await mock.openControlBox(_converse);
// Adding two contacts one with Capital initials and one with small initials of same JID (Case sensitive check)
_converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
subscription: 'none',
ask: 'subscribe',
fullname: mock.pend_names[0]
});
_converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.') + '@montague.lit',
subscription: 'none',
ask: 'subscribe',
fullname: mock.pend_names[0]
});
await u.waitUntil(() => _.filter(_converse.rosterview.el.querySelectorAll('.roster-group li'), u.isVisible).length, 700);
// Checking that only one entry is created because both JID is same (Case sensitive check)
expect(_.filter(_converse.rosterview.el.querySelectorAll('li'), u.isVisible).length).toBe(1);
expect(_converse.rosterview.update).toHaveBeenCalled();
done();
}));
it("shows the number of unread mentions received",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'all');
await mock.openControlBox(_converse);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, sender_jid);
await u.waitUntil(() => _converse.chatboxes.length);
const chatview = _converse.chatboxviews.get(sender_jid);
chatview.model.set({'minimized': true});
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count') === null).toBeTruthy();
expect(_converse.rosterview.el.querySelector('.msgs-indicator') === null).toBeTruthy();
let msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('body').t('hello').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.handleMessageStanza(msg);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll(".msgs-indicator").length);
spyOn(chatview.model, 'incrementUnreadMsgCounter').and.callThrough();
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1');
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1');
msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('body').t('hello again').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.handleMessageStanza(msg);
await u.waitUntil(() => chatview.model.incrementUnreadMsgCounter.calls.count());
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(_converse.chatboxviews.el.querySelector('.restore-chat .message-count')).toBe(null);
await u.waitUntil(() => _converse.rosterview.el.querySelector('.msgs-indicator') === null);
done();
}));
});
describe("The Status Widget", function () {
it("shows the user's chat status, which is online by default",
mock.initConverse(
['rosterGroupsFetched'], {},
function (done, _converse) {
// This spec will only pass if the controlbox is not currently
// open yet.
let el = document.querySelector("div#controlbox");
expect(_.isElement(el)).toBe(true);
expect(u.isVisible(el)).toBe(false);
spyOn(_converse.controlboxtoggle, 'onClick').and.callThrough();
spyOn(_converse.controlboxtoggle, 'showControlBox').and.callThrough();
spyOn(_converse.api, "trigger").and.callThrough();
// Redelegate so that the spies are now registered as the event handlers (specifically for 'onClick')
_converse.controlboxtoggle.delegateEvents();
document.querySelector('.toggle-controlbox').click();
expect(_converse.controlboxtoggle.onClick).toHaveBeenCalled();
expect(_converse.controlboxtoggle.showControlBox).toHaveBeenCalled();
expect(_converse.api.trigger).toHaveBeenCalledWith('controlBoxOpened', jasmine.any(Object));
el = document.querySelector("div#controlbox");
expect(u.isVisible(el)).toBe(true);
mock.openControlBox(_converse);
var view = _converse.xmppstatusview;
expect(u.hasClass('online', view.el.querySelector('.xmpp-status span:first-child'))).toBe(true);
expect(view.el.querySelector('.xmpp-status span.online').textContent.trim()).toBe('I am online');
done();
}));
describe("The \"Contacts\" section", function () {
it("can be used to add contact and it checks for case-sensivity",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.api, "trigger").and.callThrough();
spyOn(_converse.rosterview, 'update').and.callThrough();
await test_utils.openControlBox(_converse);
// Adding two contacts one with Capital initials and one with small initials of same JID (Case sensitive check)
_converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
subscription: 'none',
ask: 'subscribe',
fullname: mock.pend_names[0]
});
_converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.') + '@montague.lit',
subscription: 'none',
ask: 'subscribe',
fullname: mock.pend_names[0]
});
await u.waitUntil(() => _.filter(_converse.rosterview.el.querySelectorAll('.roster-group li'), u.isVisible).length, 700);
// Checking that only one entry is created because both JID is same (Case sensitive check)
expect(_.filter(_converse.rosterview.el.querySelectorAll('li'), u.isVisible).length).toBe(1);
expect(_converse.rosterview.update).toHaveBeenCalled();
done();
}));
it("shows the number of unread mentions received",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'all');
await test_utils.openControlBox(_converse);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, sender_jid);
await u.waitUntil(() => _converse.chatboxes.length);
const chatview = _converse.chatboxviews.get(sender_jid);
chatview.model.set({'minimized': true});
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count') === null).toBeTruthy();
expect(_converse.rosterview.el.querySelector('.msgs-indicator') === null).toBeTruthy();
let msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('body').t('hello').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.handleMessageStanza(msg);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll(".msgs-indicator").length);
spyOn(chatview.model, 'incrementUnreadMsgCounter').and.callThrough();
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1');
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1');
msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('body').t('hello again').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.handleMessageStanza(msg);
await u.waitUntil(() => chatview.model.incrementUnreadMsgCounter.calls.count());
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(_converse.chatboxviews.el.querySelector('.restore-chat .message-count')).toBe(null);
await u.waitUntil(() => _converse.rosterview.el.querySelector('.msgs-indicator') === null);
done();
}));
});
describe("The Status Widget", function () {
it("shows the user's chat status, which is online by default",
mock.initConverse(
['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.openControlBox(_converse);
var view = _converse.xmppstatusview;
expect(u.hasClass('online', view.el.querySelector('.xmpp-status span:first-child'))).toBe(true);
expect(view.el.querySelector('.xmpp-status span.online').textContent.trim()).toBe('I am online');
done();
}));
it("can be used to set the current user's chat status",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
await test_utils.openControlBox(_converse);
var cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click()
var modal = _converse.xmppstatusview.status_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const view = _converse.xmppstatusview;
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
modal.el.querySelector('[type="submit"]').click();
const sent_stanzas = _converse.connection.sent_stanzas;
const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
expect(Strophe.serialize(sent_presence)).toBe(
`<presence xmlns="jabber:client">`+
`<show>dnd</show>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`);
const first_child = view.el.querySelector('.xmpp-status span:first-child');
expect(u.hasClass('online', first_child)).toBe(false);
expect(u.hasClass('dnd', first_child)).toBe(true);
expect(view.el.querySelector('.xmpp-status span:first-child').textContent.trim()).toBe('I am busy');
done();
}));
it("can be used to set a custom status message",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
await test_utils.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click()
const modal = _converse.xmppstatusview.status_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const view = _converse.xmppstatusview;
const msg = 'I am happy';
modal.el.querySelector('input[name="status_message"]').value = msg;
modal.el.querySelector('[type="submit"]').click();
const sent_stanzas = _converse.connection.sent_stanzas;
const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
expect(Strophe.serialize(sent_presence)).toBe(
`<presence xmlns="jabber:client">`+
`<status>I am happy</status>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`);
const first_child = view.el.querySelector('.xmpp-status span:first-child');
expect(u.hasClass('online', first_child)).toBe(true);
expect(view.el.querySelector('.xmpp-status span:first-child').textContent.trim()).toBe(msg);
done();
}));
});
});
describe("The 'Add Contact' widget", function () {
it("opens up an add modal when you click on it",
it("can be used to set the current user's chat status",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'all');
await test_utils.openControlBox(_converse);
await mock.openControlBox(_converse);
var cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click()
var modal = _converse.xmppstatusview.status_modal;
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
expect(modal.el.querySelector('form.add-xmpp-contact')).not.toBe(null);
const input_jid = modal.el.querySelector('input[name="jid"]');
const input_name = modal.el.querySelector('input[name="name"]');
input_jid.value = 'someone@';
const evt = new Event('input');
input_jid.dispatchEvent(evt);
expect(modal.el.querySelector('.suggestion-box li').textContent).toBe('someone@montague.lit');
input_jid.value = 'someone@montague.lit';
input_name.value = 'Someone';
modal.el.querySelector('button[type="submit"]').click();
const sent_IQs = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
expect(Strophe.serialize(sent_stanza)).toEqual(
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit" name="Someone"/></query>`+
`</iq>`);
const view = _converse.xmppstatusview;
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
modal.el.querySelector('[type="submit"]').click();
const sent_stanzas = _converse.connection.sent_stanzas;
const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
expect(Strophe.serialize(sent_presence)).toBe(
`<presence xmlns="jabber:client">`+
`<show>dnd</show>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`);
const first_child = view.el.querySelector('.xmpp-status span:first-child');
expect(u.hasClass('online', first_child)).toBe(false);
expect(u.hasClass('dnd', first_child)).toBe(true);
expect(view.el.querySelector('.xmpp-status span:first-child').textContent.trim()).toBe('I am busy');
done();
}));
it("can be configured to not provide search suggestions",
it("can be used to set a custom status message",
mock.initConverse(
['rosterGroupsFetched'], {'autocomplete_add_contact': false},
['rosterGroupsFetched'], {},
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'all', 0);
test_utils.openControlBox(_converse);
await mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal;
expect(modal.jid_auto_complete).toBe(undefined);
expect(modal.name_auto_complete).toBe(undefined);
cbview.el.querySelector('.change-status').click()
const modal = _converse.xmppstatusview.status_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
expect(modal.el.querySelector('form.add-xmpp-contact')).not.toBe(null);
const input_jid = modal.el.querySelector('input[name="jid"]');
input_jid.value = 'someone@montague.lit';
modal.el.querySelector('button[type="submit"]').click();
const view = _converse.xmppstatusview;
const msg = 'I am happy';
modal.el.querySelector('input[name="status_message"]').value = msg;
modal.el.querySelector('[type="submit"]').click();
const sent_stanzas = _converse.connection.sent_stanzas;
const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
expect(Strophe.serialize(sent_presence)).toBe(
`<presence xmlns="jabber:client">`+
`<status>I am happy</status>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`);
const IQ_stanzas = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(
() => IQ_stanzas.filter(s => sizzle(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`, s).length).pop()
);
expect(Strophe.serialize(sent_stanza)).toEqual(
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit"/></query>`+
`</iq>`
);
done();
}));
it("integrates with xhr_user_search_url to search for contacts",
mock.initConverse(
['rosterGroupsFetched'],
{ 'xhr_user_search_url': 'http://example.org/?' },
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'all', 0);
const xhr = {
'open': function open () {},
'send': function () {
xhr.responseText = JSON.stringify([
{"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
{"jid": "doc@brown.com", "fullname": "Doc Brown"}
]);
xhr.onload();
}
};
const XMLHttpRequestBackup = window.XMLHttpRequest;
window.XMLHttpRequest = jasmine.createSpy('XMLHttpRequest');
XMLHttpRequest.and.callFake(() => xhr);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
// We only have autocomplete for the name input
expect(modal.jid_auto_complete).toBe(undefined);
expect(modal.name_auto_complete instanceof _converse.AutoComplete).toBe(true);
const input_el = modal.el.querySelector('input[name="name"]');
input_el.value = 'marty';
input_el.dispatchEvent(new Event('input'));
await u.waitUntil(() => modal.el.querySelector('.suggestion-box li'), 1000);
expect(modal.el.querySelectorAll('.suggestion-box li').length).toBe(1);
const suggestion = modal.el.querySelector('.suggestion-box li');
expect(suggestion.textContent).toBe('Marty McFly');
// Mock selection
modal.name_auto_complete.select(suggestion);
expect(input_el.value).toBe('Marty McFly');
expect(modal.el.querySelector('input[name="jid"]').value).toBe('marty@mcfly.net');
modal.el.querySelector('button[type="submit"]').click();
const sent_IQs = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
expect(Strophe.serialize(sent_stanza)).toEqual(
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"/></query>`+
`</iq>`);
window.XMLHttpRequest = XMLHttpRequestBackup;
done();
}));
it("can be configured to not provide search suggestions for XHR search results",
mock.initConverse(
['rosterGroupsFetched'],
{ 'autocomplete_add_contact': false,
'xhr_user_search_url': 'http://example.org/?' },
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'all');
await test_utils.openControlBox(_converse);
var modal;
const xhr = {
'open': function open () {},
'send': function () {
const value = modal.el.querySelector('input[name="name"]').value;
if (value === 'existing') {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
xhr.responseText = JSON.stringify([{"jid": contact_jid, "fullname": mock.cur_names[0]}]);
} else if (value === 'romeo') {
xhr.responseText = JSON.stringify([{"jid": "romeo@montague.lit", "fullname": "Romeo Montague"}]);
} else if (value === 'ambiguous') {
xhr.responseText = JSON.stringify([
{"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
{"jid": "doc@brown.com", "fullname": "Doc Brown"}
]);
} else if (value === 'insufficient') {
xhr.responseText = JSON.stringify([]);
} else {
xhr.responseText = JSON.stringify([{"jid": "marty@mcfly.net", "fullname": "Marty McFly"}]);
}
xhr.onload();
}
};
const XMLHttpRequestBackup = window.XMLHttpRequest;
window.XMLHttpRequest = jasmine.createSpy('XMLHttpRequest');
XMLHttpRequest.and.callFake(() => xhr);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
modal = _converse.rosterview.add_contact_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
expect(modal.jid_auto_complete).toBe(undefined);
expect(modal.name_auto_complete).toBe(undefined);
const input_el = modal.el.querySelector('input[name="name"]');
input_el.value = 'ambiguous';
modal.el.querySelector('button[type="submit"]').click();
let feedback_el = modal.el.querySelector('.invalid-feedback');
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
feedback_el.textContent = '';
input_el.value = 'insufficient';
modal.el.querySelector('button[type="submit"]').click();
feedback_el = modal.el.querySelector('.invalid-feedback');
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
feedback_el.textContent = '';
input_el.value = 'existing';
modal.el.querySelector('button[type="submit"]').click();
feedback_el = modal.el.querySelector('.invalid-feedback');
expect(feedback_el.textContent).toBe('This contact has already been added');
input_el.value = 'Marty McFly';
modal.el.querySelector('button[type="submit"]').click();
const sent_IQs = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
expect(Strophe.serialize(sent_stanza)).toEqual(
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"/></query>`+
`</iq>`);
window.XMLHttpRequest = XMLHttpRequestBackup;
const first_child = view.el.querySelector('.xmpp-status span:first-child');
expect(u.hasClass('online', first_child)).toBe(true);
expect(view.el.querySelector('.xmpp-status span:first-child').textContent.trim()).toBe(msg);
done();
}));
});
});
describe("The 'Add Contact' widget", function () {
it("opens up an add modal when you click on it",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'all');
await mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
expect(modal.el.querySelector('form.add-xmpp-contact')).not.toBe(null);
const input_jid = modal.el.querySelector('input[name="jid"]');
const input_name = modal.el.querySelector('input[name="name"]');
input_jid.value = 'someone@';
const evt = new Event('input');
input_jid.dispatchEvent(evt);
expect(modal.el.querySelector('.suggestion-box li').textContent).toBe('someone@montague.lit');
input_jid.value = 'someone@montague.lit';
input_name.value = 'Someone';
modal.el.querySelector('button[type="submit"]').click();
const sent_IQs = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
expect(Strophe.serialize(sent_stanza)).toEqual(
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit" name="Someone"/></query>`+
`</iq>`);
done();
}));
it("can be configured to not provide search suggestions",
mock.initConverse(
['rosterGroupsFetched'], {'autocomplete_add_contact': false},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'all', 0);
mock.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal;
expect(modal.jid_auto_complete).toBe(undefined);
expect(modal.name_auto_complete).toBe(undefined);
await u.waitUntil(() => u.isVisible(modal.el), 1000);
expect(modal.el.querySelector('form.add-xmpp-contact')).not.toBe(null);
const input_jid = modal.el.querySelector('input[name="jid"]');
input_jid.value = 'someone@montague.lit';
modal.el.querySelector('button[type="submit"]').click();
const IQ_stanzas = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(
() => IQ_stanzas.filter(s => sizzle(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`, s).length).pop()
);
expect(Strophe.serialize(sent_stanza)).toEqual(
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"><item jid="someone@montague.lit"/></query>`+
`</iq>`
);
done();
}));
it("integrates with xhr_user_search_url to search for contacts",
mock.initConverse(
['rosterGroupsFetched'],
{ 'xhr_user_search_url': 'http://example.org/?' },
async function (done, _converse) {
await mock.waitForRoster(_converse, 'all', 0);
const xhr = {
'open': function open () {},
'send': function () {
xhr.responseText = JSON.stringify([
{"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
{"jid": "doc@brown.com", "fullname": "Doc Brown"}
]);
xhr.onload();
}
};
const XMLHttpRequestBackup = window.XMLHttpRequest;
window.XMLHttpRequest = jasmine.createSpy('XMLHttpRequest');
XMLHttpRequest.and.callFake(() => xhr);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
// We only have autocomplete for the name input
expect(modal.jid_auto_complete).toBe(undefined);
expect(modal.name_auto_complete instanceof _converse.AutoComplete).toBe(true);
const input_el = modal.el.querySelector('input[name="name"]');
input_el.value = 'marty';
input_el.dispatchEvent(new Event('input'));
await u.waitUntil(() => modal.el.querySelector('.suggestion-box li'), 1000);
expect(modal.el.querySelectorAll('.suggestion-box li').length).toBe(1);
const suggestion = modal.el.querySelector('.suggestion-box li');
expect(suggestion.textContent).toBe('Marty McFly');
// Mock selection
modal.name_auto_complete.select(suggestion);
expect(input_el.value).toBe('Marty McFly');
expect(modal.el.querySelector('input[name="jid"]').value).toBe('marty@mcfly.net');
modal.el.querySelector('button[type="submit"]').click();
const sent_IQs = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
expect(Strophe.serialize(sent_stanza)).toEqual(
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"/></query>`+
`</iq>`);
window.XMLHttpRequest = XMLHttpRequestBackup;
done();
}));
it("can be configured to not provide search suggestions for XHR search results",
mock.initConverse(
['rosterGroupsFetched'],
{ 'autocomplete_add_contact': false,
'xhr_user_search_url': 'http://example.org/?' },
async function (done, _converse) {
await mock.waitForRoster(_converse, 'all');
await mock.openControlBox(_converse);
var modal;
const xhr = {
'open': function open () {},
'send': function () {
const value = modal.el.querySelector('input[name="name"]').value;
if (value === 'existing') {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
xhr.responseText = JSON.stringify([{"jid": contact_jid, "fullname": mock.cur_names[0]}]);
} else if (value === 'romeo') {
xhr.responseText = JSON.stringify([{"jid": "romeo@montague.lit", "fullname": "Romeo Montague"}]);
} else if (value === 'ambiguous') {
xhr.responseText = JSON.stringify([
{"jid": "marty@mcfly.net", "fullname": "Marty McFly"},
{"jid": "doc@brown.com", "fullname": "Doc Brown"}
]);
} else if (value === 'insufficient') {
xhr.responseText = JSON.stringify([]);
} else {
xhr.responseText = JSON.stringify([{"jid": "marty@mcfly.net", "fullname": "Marty McFly"}]);
}
xhr.onload();
}
};
const XMLHttpRequestBackup = window.XMLHttpRequest;
window.XMLHttpRequest = jasmine.createSpy('XMLHttpRequest');
XMLHttpRequest.and.callFake(() => xhr);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
modal = _converse.rosterview.add_contact_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
expect(modal.jid_auto_complete).toBe(undefined);
expect(modal.name_auto_complete).toBe(undefined);
const input_el = modal.el.querySelector('input[name="name"]');
input_el.value = 'ambiguous';
modal.el.querySelector('button[type="submit"]').click();
let feedback_el = modal.el.querySelector('.invalid-feedback');
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
feedback_el.textContent = '';
input_el.value = 'insufficient';
modal.el.querySelector('button[type="submit"]').click();
feedback_el = modal.el.querySelector('.invalid-feedback');
expect(feedback_el.textContent).toBe('Sorry, could not find a contact with that name');
feedback_el.textContent = '';
input_el.value = 'existing';
modal.el.querySelector('button[type="submit"]').click();
feedback_el = modal.el.querySelector('.invalid-feedback');
expect(feedback_el.textContent).toBe('This contact has already been added');
input_el.value = 'Marty McFly';
modal.el.querySelector('button[type="submit"]').click();
const sent_IQs = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[type="set"] query[xmlns="${Strophe.NS.ROSTER}"]`)).pop());
expect(Strophe.serialize(sent_stanza)).toEqual(
`<iq id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"><item jid="marty@mcfly.net" name="Marty McFly"/></query>`+
`</iq>`);
window.XMLHttpRequest = XMLHttpRequestBackup;
done();
}));
});

View File

@ -1,367 +1,365 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const _ = converse.env._,
u = converse.env.utils;
/* global mock */
describe("Converse", function() {
describe("Converse", function() {
describe("Authentication", function () {
describe("Authentication", function () {
it("needs either a bosh_service_url a websocket_url or both", mock.initConverse(async (done, _converse) => {
const url = _converse.bosh_service_url;
const connection = _converse.connection;
delete _converse.bosh_service_url;
delete _converse.connection;
try {
await _converse.initConnection();
} catch (e) {
_converse.bosh_service_url = url;
_converse.connection = connection;
expect(e.message).toBe("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both.");
done();
}
}));
});
describe("A chat state indication", function () {
it("are sent out when the client becomes or stops being idle",
mock.initConverse(['discoInitialized'], {}, (done, _converse) => {
spyOn(_converse, 'sendCSI').and.callThrough();
let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
sent_stanza = stanza;
});
let i = 0;
_converse.idle_seconds = 0; // Usually initialized by registerIntervalHandler
_converse.disco_entities.get(_converse.domain).features['urn:xmpp:csi:0'] = true; // Mock that the server supports CSI
_converse.api.settings.set('csi_waiting_time', 3);
while (i <= _converse.api.settings.get("csi_waiting_time")) {
expect(_converse.sendCSI).not.toHaveBeenCalled();
_converse.onEverySecond();
i++;
}
expect(_converse.sendCSI).toHaveBeenCalledWith('inactive');
expect(sent_stanza.toLocaleString()).toBe('<inactive xmlns="urn:xmpp:csi:0"/>');
_converse.onUserActivity();
expect(_converse.sendCSI).toHaveBeenCalledWith('active');
expect(sent_stanza.toLocaleString()).toBe('<active xmlns="urn:xmpp:csi:0"/>');
it("needs either a bosh_service_url a websocket_url or both", mock.initConverse(async (done, _converse) => {
const url = _converse.bosh_service_url;
const connection = _converse.connection;
delete _converse.bosh_service_url;
delete _converse.connection;
try {
await _converse.initConnection();
} catch (e) {
_converse.bosh_service_url = url;
_converse.connection = connection;
expect(e.message).toBe("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both.");
done();
}));
});
}
}));
});
describe("Automatic status change", function () {
describe("A chat state indication", function () {
it("happens when the client is idle for long enough", mock.initConverse((done, _converse) => {
let i = 0;
// Usually initialized by registerIntervalHandler
_converse.idle_seconds = 0;
_converse.auto_changed_status = false;
_converse.api.settings.set('auto_away', 3);
_converse.api.settings.set('auto_xa', 6);
it("are sent out when the client becomes or stops being idle",
mock.initConverse(['discoInitialized'], {}, (done, _converse) => {
expect(_converse.api.user.status.get()).toBe('online');
while (i <= _converse.api.settings.get("auto_away")) {
_converse.onEverySecond(); i++;
}
expect(_converse.auto_changed_status).toBe(true);
while (i <= _converse.auto_xa) {
expect(_converse.api.user.status.get()).toBe('away');
_converse.onEverySecond();
i++;
}
expect(_converse.api.user.status.get()).toBe('xa');
expect(_converse.auto_changed_status).toBe(true);
_converse.onUserActivity();
expect(_converse.api.user.status.get()).toBe('online');
expect(_converse.auto_changed_status).toBe(false);
// Check that it also works for the chat feature
_converse.api.user.status.set('chat')
i = 0;
while (i <= _converse.api.settings.get("auto_away")) {
_converse.onEverySecond();
i++;
}
expect(_converse.auto_changed_status).toBe(true);
while (i <= _converse.auto_xa) {
expect(_converse.api.user.status.get()).toBe('away');
_converse.onEverySecond();
i++;
}
expect(_converse.api.user.status.get()).toBe('xa');
expect(_converse.auto_changed_status).toBe(true);
_converse.onUserActivity();
expect(_converse.api.user.status.get()).toBe('online');
expect(_converse.auto_changed_status).toBe(false);
// Check that it doesn't work for 'dnd'
_converse.api.user.status.set('dnd');
i = 0;
while (i <= _converse.api.settings.get("auto_away")) {
_converse.onEverySecond();
i++;
}
expect(_converse.api.user.status.get()).toBe('dnd');
expect(_converse.auto_changed_status).toBe(false);
while (i <= _converse.auto_xa) {
expect(_converse.api.user.status.get()).toBe('dnd');
_converse.onEverySecond();
i++;
}
expect(_converse.api.user.status.get()).toBe('dnd');
expect(_converse.auto_changed_status).toBe(false);
_converse.onUserActivity();
expect(_converse.api.user.status.get()).toBe('dnd');
expect(_converse.auto_changed_status).toBe(false);
done();
}));
});
describe("The \"user\" grouping", function () {
describe("The \"status\" API", function () {
it("has a method for getting the user's availability", mock.initConverse((done, _converse) => {
_converse.xmppstatus.set('status', 'online');
expect(_converse.api.user.status.get()).toBe('online');
_converse.xmppstatus.set('status', 'dnd');
expect(_converse.api.user.status.get()).toBe('dnd');
done();
}));
it("has a method for setting the user's availability", mock.initConverse((done, _converse) => {
_converse.api.user.status.set('away');
expect(_converse.xmppstatus.get('status')).toBe('away');
_converse.api.user.status.set('dnd');
expect(_converse.xmppstatus.get('status')).toBe('dnd');
_converse.api.user.status.set('xa');
expect(_converse.xmppstatus.get('status')).toBe('xa');
_converse.api.user.status.set('chat');
expect(_converse.xmppstatus.get('status')).toBe('chat');
expect(_.partial(_converse.api.user.status.set, 'invalid')).toThrow(
new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1')
);
done();
}));
it("allows setting the status message as well", mock.initConverse((done, _converse) => {
_converse.api.user.status.set('away', "I'm in a meeting");
expect(_converse.xmppstatus.get('status')).toBe('away');
expect(_converse.xmppstatus.get('status_message')).toBe("I'm in a meeting");
done();
}));
it("has a method for getting the user's status message", mock.initConverse((done, _converse) => {
_converse.xmppstatus.set('status_message', undefined);
expect(_converse.api.user.status.message.get()).toBe(undefined);
_converse.xmppstatus.set('status_message', "I'm in a meeting");
expect(_converse.api.user.status.message.get()).toBe("I'm in a meeting");
done();
}));
it("has a method for setting the user's status message", mock.initConverse((done, _converse) => {
_converse.xmppstatus.set('status_message', undefined);
_converse.api.user.status.message.set("I'm in a meeting");
expect(_converse.xmppstatus.get('status_message')).toBe("I'm in a meeting");
done();
}));
spyOn(_converse, 'sendCSI').and.callThrough();
let sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
sent_stanza = stanza;
});
});
let i = 0;
_converse.idle_seconds = 0; // Usually initialized by registerIntervalHandler
_converse.disco_entities.get(_converse.domain).features['urn:xmpp:csi:0'] = true; // Mock that the server supports CSI
describe("The \"tokens\" API", function () {
_converse.api.settings.set('csi_waiting_time', 3);
while (i <= _converse.api.settings.get("csi_waiting_time")) {
expect(_converse.sendCSI).not.toHaveBeenCalled();
_converse.onEverySecond();
i++;
}
expect(_converse.sendCSI).toHaveBeenCalledWith('inactive');
expect(sent_stanza.toLocaleString()).toBe('<inactive xmlns="urn:xmpp:csi:0"/>');
_converse.onUserActivity();
expect(_converse.sendCSI).toHaveBeenCalledWith('active');
expect(sent_stanza.toLocaleString()).toBe('<active xmlns="urn:xmpp:csi:0"/>');
done();
}));
});
it("has a method for retrieving the next RID", mock.initConverse((done, _converse) => {
test_utils.createContacts(_converse, 'current');
const old_connection = _converse.connection;
_converse.connection._proto.rid = '1234';
expect(_converse.api.tokens.get('rid')).toBe('1234');
_converse.connection = undefined;
expect(_converse.api.tokens.get('rid')).toBe(null);
// Restore the connection
_converse.connection = old_connection;
describe("Automatic status change", function () {
it("happens when the client is idle for long enough", mock.initConverse((done, _converse) => {
let i = 0;
// Usually initialized by registerIntervalHandler
_converse.idle_seconds = 0;
_converse.auto_changed_status = false;
_converse.api.settings.set('auto_away', 3);
_converse.api.settings.set('auto_xa', 6);
expect(_converse.api.user.status.get()).toBe('online');
while (i <= _converse.api.settings.get("auto_away")) {
_converse.onEverySecond(); i++;
}
expect(_converse.auto_changed_status).toBe(true);
while (i <= _converse.auto_xa) {
expect(_converse.api.user.status.get()).toBe('away');
_converse.onEverySecond();
i++;
}
expect(_converse.api.user.status.get()).toBe('xa');
expect(_converse.auto_changed_status).toBe(true);
_converse.onUserActivity();
expect(_converse.api.user.status.get()).toBe('online');
expect(_converse.auto_changed_status).toBe(false);
// Check that it also works for the chat feature
_converse.api.user.status.set('chat')
i = 0;
while (i <= _converse.api.settings.get("auto_away")) {
_converse.onEverySecond();
i++;
}
expect(_converse.auto_changed_status).toBe(true);
while (i <= _converse.auto_xa) {
expect(_converse.api.user.status.get()).toBe('away');
_converse.onEverySecond();
i++;
}
expect(_converse.api.user.status.get()).toBe('xa');
expect(_converse.auto_changed_status).toBe(true);
_converse.onUserActivity();
expect(_converse.api.user.status.get()).toBe('online');
expect(_converse.auto_changed_status).toBe(false);
// Check that it doesn't work for 'dnd'
_converse.api.user.status.set('dnd');
i = 0;
while (i <= _converse.api.settings.get("auto_away")) {
_converse.onEverySecond();
i++;
}
expect(_converse.api.user.status.get()).toBe('dnd');
expect(_converse.auto_changed_status).toBe(false);
while (i <= _converse.auto_xa) {
expect(_converse.api.user.status.get()).toBe('dnd');
_converse.onEverySecond();
i++;
}
expect(_converse.api.user.status.get()).toBe('dnd');
expect(_converse.auto_changed_status).toBe(false);
_converse.onUserActivity();
expect(_converse.api.user.status.get()).toBe('dnd');
expect(_converse.auto_changed_status).toBe(false);
done();
}));
});
describe("The \"user\" grouping", function () {
describe("The \"status\" API", function () {
it("has a method for getting the user's availability", mock.initConverse((done, _converse) => {
_converse.xmppstatus.set('status', 'online');
expect(_converse.api.user.status.get()).toBe('online');
_converse.xmppstatus.set('status', 'dnd');
expect(_converse.api.user.status.get()).toBe('dnd');
done();
}));
it("has a method for retrieving the SID", mock.initConverse((done, _converse) => {
test_utils.createContacts(_converse, 'current');
const old_connection = _converse.connection;
_converse.connection._proto.sid = '1234';
expect(_converse.api.tokens.get('sid')).toBe('1234');
_converse.connection = undefined;
expect(_converse.api.tokens.get('sid')).toBe(null);
// Restore the connection
_converse.connection = old_connection;
it("has a method for setting the user's availability", mock.initConverse((done, _converse) => {
_converse.api.user.status.set('away');
expect(_converse.xmppstatus.get('status')).toBe('away');
_converse.api.user.status.set('dnd');
expect(_converse.xmppstatus.get('status')).toBe('dnd');
_converse.api.user.status.set('xa');
expect(_converse.xmppstatus.get('status')).toBe('xa');
_converse.api.user.status.set('chat');
expect(_converse.xmppstatus.get('status')).toBe('chat');
expect(() => _converse.api.user.status.set('invalid')).toThrow(
new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1')
);
done();
}));
it("allows setting the status message as well", mock.initConverse((done, _converse) => {
_converse.api.user.status.set('away', "I'm in a meeting");
expect(_converse.xmppstatus.get('status')).toBe('away');
expect(_converse.xmppstatus.get('status_message')).toBe("I'm in a meeting");
done();
}));
it("has a method for getting the user's status message", mock.initConverse((done, _converse) => {
_converse.xmppstatus.set('status_message', undefined);
expect(_converse.api.user.status.message.get()).toBe(undefined);
_converse.xmppstatus.set('status_message', "I'm in a meeting");
expect(_converse.api.user.status.message.get()).toBe("I'm in a meeting");
done();
}));
it("has a method for setting the user's status message", mock.initConverse((done, _converse) => {
_converse.xmppstatus.set('status_message', undefined);
_converse.api.user.status.message.set("I'm in a meeting");
expect(_converse.xmppstatus.get('status_message')).toBe("I'm in a meeting");
done();
}));
});
});
describe("The \"contacts\" API", function () {
describe("The \"tokens\" API", function () {
it("has a method 'get' which returns wrapped contacts",
mock.initConverse([], {}, async function (done, _converse) {
it("has a method for retrieving the next RID", mock.initConverse((done, _converse) => {
mock.createContacts(_converse, 'current');
const old_connection = _converse.connection;
_converse.connection._proto.rid = '1234';
expect(_converse.api.tokens.get('rid')).toBe('1234');
_converse.connection = undefined;
expect(_converse.api.tokens.get('rid')).toBe(null);
// Restore the connection
_converse.connection = old_connection;
done();
}));
await test_utils.waitForRoster(_converse, 'current');
let contact = await _converse.api.contacts.get('non-existing@jabber.org');
expect(contact).toBeFalsy();
// Check when a single jid is given
const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
contact = await _converse.api.contacts.get(jid);
expect(contact.getDisplayName()).toBe(mock.cur_names[0]);
expect(contact.get('jid')).toBe(jid);
// You can retrieve multiple contacts by passing in an array
const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let list = await _converse.api.contacts.get([jid, jid2]);
expect(Array.isArray(list)).toBeTruthy();
expect(list[0].getDisplayName()).toBe(mock.cur_names[0]);
expect(list[1].getDisplayName()).toBe(mock.cur_names[1]);
// Check that all JIDs are returned if you call without any parameters
list = await _converse.api.contacts.get();
expect(list.length).toBe(mock.cur_names.length);
done();
}));
it("has a method for retrieving the SID", mock.initConverse((done, _converse) => {
mock.createContacts(_converse, 'current');
const old_connection = _converse.connection;
_converse.connection._proto.sid = '1234';
expect(_converse.api.tokens.get('sid')).toBe('1234');
_converse.connection = undefined;
expect(_converse.api.tokens.get('sid')).toBe(null);
// Restore the connection
_converse.connection = old_connection;
done();
}));
});
it("has a method 'add' with which contacts can be added",
mock.initConverse(['rosterInitialized'], {}, async (done, _converse) => {
describe("The \"contacts\" API", function () {
await test_utils.waitForRoster(_converse, 'current', 0);
try {
await _converse.api.contacts.add();
throw new Error('Call should have failed');
} catch (e) {
expect(e.message).toBe('contacts.add: invalid jid');
it("has a method 'get' which returns wrapped contacts",
mock.initConverse([], {}, async function (done, _converse) {
}
try {
await _converse.api.contacts.add("invalid jid");
throw new Error('Call should have failed');
} catch (e) {
expect(e.message).toBe('contacts.add: invalid jid');
}
spyOn(_converse.roster, 'addAndSubscribe');
await _converse.api.contacts.add("newcontact@example.org");
expect(_converse.roster.addAndSubscribe).toHaveBeenCalled();
done();
}));
});
await mock.waitForRoster(_converse, 'current');
let contact = await _converse.api.contacts.get('non-existing@jabber.org');
expect(contact).toBeFalsy();
// Check when a single jid is given
const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
contact = await _converse.api.contacts.get(jid);
expect(contact.getDisplayName()).toBe(mock.cur_names[0]);
expect(contact.get('jid')).toBe(jid);
// You can retrieve multiple contacts by passing in an array
const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let list = await _converse.api.contacts.get([jid, jid2]);
expect(Array.isArray(list)).toBeTruthy();
expect(list[0].getDisplayName()).toBe(mock.cur_names[0]);
expect(list[1].getDisplayName()).toBe(mock.cur_names[1]);
// Check that all JIDs are returned if you call without any parameters
list = await _converse.api.contacts.get();
expect(list.length).toBe(mock.cur_names.length);
done();
}));
describe("The \"chats\" API", function() {
it("has a method 'add' with which contacts can be added",
mock.initConverse(['rosterInitialized'], {}, async (done, _converse) => {
it("has a method 'get' which returns the promise that resolves to a chat model", mock.initConverse(
await mock.waitForRoster(_converse, 'current', 0);
try {
await _converse.api.contacts.add();
throw new Error('Call should have failed');
} catch (e) {
expect(e.message).toBe('contacts.add: invalid jid');
}
try {
await _converse.api.contacts.add("invalid jid");
throw new Error('Call should have failed');
} catch (e) {
expect(e.message).toBe('contacts.add: invalid jid');
}
spyOn(_converse.roster, 'addAndSubscribe');
await _converse.api.contacts.add("newcontact@example.org");
expect(_converse.roster.addAndSubscribe).toHaveBeenCalled();
done();
}));
});
describe("The \"chats\" API", function() {
it("has a method 'get' which returns the promise that resolves to a chat model", mock.initConverse(
['rosterInitialized', 'chatBoxesInitialized'], {},
async (done, _converse) => {
await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 2);
const u = converse.env.utils;
// Test on chat that doesn't exist.
let chat = await _converse.api.chats.get('non-existing@jabber.org');
expect(chat).toBeFalsy();
const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current', 2);
// Test on chat that's not open
chat = await _converse.api.chats.get(jid);
expect(chat === null).toBeTruthy();
expect(_converse.chatboxes.length).toBe(1);
// Test on chat that doesn't exist.
let chat = await _converse.api.chats.get('non-existing@jabber.org');
expect(chat).toBeFalsy();
const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
// Test for one JID
chat = await _converse.api.chats.open(jid);
expect(chat instanceof Object).toBeTruthy();
expect(chat.get('box_id')).toBe(`box-${btoa(jid)}`);
// Test on chat that's not open
chat = await _converse.api.chats.get(jid);
expect(chat === null).toBeTruthy();
expect(_converse.chatboxes.length).toBe(1);
const view = _converse.chatboxviews.get(jid);
await u.waitUntil(() => u.isVisible(view.el));
// Test for multiple JIDs
test_utils.openChatBoxFor(_converse, jid2);
await u.waitUntil(() => _converse.chatboxes.length == 3);
const list = await _converse.api.chats.get([jid, jid2]);
expect(Array.isArray(list)).toBeTruthy();
expect(list[0].get('box_id')).toBe(`box-${btoa(jid)}`);
expect(list[1].get('box_id')).toBe(`box-${btoa(jid2)}`);
done();
}));
// Test for one JID
chat = await _converse.api.chats.open(jid);
expect(chat instanceof Object).toBeTruthy();
expect(chat.get('box_id')).toBe(`box-${btoa(jid)}`);
it("has a method 'open' which opens and returns a promise that resolves to a chat model", mock.initConverse(
const view = _converse.chatboxviews.get(jid);
await u.waitUntil(() => u.isVisible(view.el));
// Test for multiple JIDs
mock.openChatBoxFor(_converse, jid2);
await u.waitUntil(() => _converse.chatboxes.length == 3);
const list = await _converse.api.chats.get([jid, jid2]);
expect(Array.isArray(list)).toBeTruthy();
expect(list[0].get('box_id')).toBe(`box-${btoa(jid)}`);
expect(list[1].get('box_id')).toBe(`box-${btoa(jid2)}`);
done();
}));
it("has a method 'open' which opens and returns a promise that resolves to a chat model", mock.initConverse(
['rosterGroupsFetched', 'chatBoxesInitialized'], {},
async (done, _converse) => {
await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 2);
const u = converse.env.utils;
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current', 2);
const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
// Test on chat that doesn't exist.
let chat = await _converse.api.chats.get('non-existing@jabber.org');
expect(chat).toBeFalsy();
const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
// Test on chat that doesn't exist.
let chat = await _converse.api.chats.get('non-existing@jabber.org');
expect(chat).toBeFalsy();
chat = await _converse.api.chats.open(jid);
expect(chat instanceof Object).toBeTruthy();
expect(chat.get('box_id')).toBe(`box-${btoa(jid)}`);
expect(
Object.keys(chat),
['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
);
const view = _converse.chatboxviews.get(jid);
await u.waitUntil(() => u.isVisible(view.el));
// Test for multiple JIDs
const list = await _converse.api.chats.open([jid, jid2]);
expect(Array.isArray(list)).toBeTruthy();
expect(list[0].get('box_id')).toBe(`box-${btoa(jid)}`);
expect(list[1].get('box_id')).toBe(`box-${btoa(jid2)}`);
chat = await _converse.api.chats.open(jid);
expect(chat instanceof Object).toBeTruthy();
expect(chat.get('box_id')).toBe(`box-${btoa(jid)}`);
expect(
Object.keys(chat),
['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
);
const view = _converse.chatboxviews.get(jid);
await u.waitUntil(() => u.isVisible(view.el));
// Test for multiple JIDs
const list = await _converse.api.chats.open([jid, jid2]);
expect(Array.isArray(list)).toBeTruthy();
expect(list[0].get('box_id')).toBe(`box-${btoa(jid)}`);
expect(list[1].get('box_id')).toBe(`box-${btoa(jid2)}`);
done();
}));
});
describe("The \"settings\" API", function() {
it("has methods 'get' and 'set' to set configuration settings",
mock.initConverse(null, {'play_sounds': true}, (done, _converse) => {
expect(Object.keys(_converse.api.settings)).toEqual(["update", "get", "set"]);
expect(_converse.api.settings.get("play_sounds")).toBe(true);
_converse.api.settings.set("play_sounds", false);
expect(_converse.api.settings.get("play_sounds")).toBe(false);
_converse.api.settings.set({"play_sounds": true});
expect(_converse.api.settings.get("play_sounds")).toBe(true);
// Only whitelisted settings allowed.
expect(typeof _converse.api.settings.get("non_existing")).toBe("undefined");
_converse.api.settings.set("non_existing", true);
expect(typeof _converse.api.settings.get("non_existing")).toBe("undefined");
done();
}));
});
describe("The \"plugins\" API", function() {
it("only has a method 'add' for registering plugins", mock.initConverse((done, _converse) => {
expect(Object.keys(converse.plugins)).toEqual(["add"]);
// Cheating a little bit. We clear the plugins to test more easily.
const _old_plugins = _converse.pluggable.plugins;
_converse.pluggable.plugins = [];
converse.plugins.add('plugin1', {});
expect(Object.keys(_converse.pluggable.plugins)).toEqual(['plugin1']);
converse.plugins.add('plugin2', {});
expect(Object.keys(_converse.pluggable.plugins)).toEqual(['plugin1', 'plugin2']);
_converse.pluggable.plugins = _old_plugins;
done();
}));
describe("The \"plugins.add\" method", function() {
it("throws an error when multiple plugins attempt to register with the same name",
mock.initConverse((done, _converse) => { // eslint-disable-line no-unused-vars
converse.plugins.add('myplugin', {});
const error = new TypeError('Error: plugin with name "myplugin" has already been registered!');
expect(() => converse.plugins.add('myplugin', {})).toThrow(error);
done();
}));
});
describe("The \"settings\" API", function() {
it("has methods 'get' and 'set' to set configuration settings",
mock.initConverse(null, {'play_sounds': true}, (done, _converse) => {
expect(_.keys(_converse.api.settings)).toEqual(["update", "get", "set"]);
expect(_converse.api.settings.get("play_sounds")).toBe(true);
_converse.api.settings.set("play_sounds", false);
expect(_converse.api.settings.get("play_sounds")).toBe(false);
_converse.api.settings.set({"play_sounds": true});
expect(_converse.api.settings.get("play_sounds")).toBe(true);
// Only whitelisted settings allowed.
expect(typeof _converse.api.settings.get("non_existing")).toBe("undefined");
_converse.api.settings.set("non_existing", true);
expect(typeof _converse.api.settings.get("non_existing")).toBe("undefined");
done();
}));
});
describe("The \"plugins\" API", function() {
it("only has a method 'add' for registering plugins", mock.initConverse((done, _converse) => {
expect(_.keys(converse.plugins)).toEqual(["add"]);
// Cheating a little bit. We clear the plugins to test more easily.
const _old_plugins = _converse.pluggable.plugins;
_converse.pluggable.plugins = [];
converse.plugins.add('plugin1', {});
expect(_.keys(_converse.pluggable.plugins)).toEqual(['plugin1']);
converse.plugins.add('plugin2', {});
expect(_.keys(_converse.pluggable.plugins)).toEqual(['plugin1', 'plugin2']);
_converse.pluggable.plugins = _old_plugins;
done();
}));
describe("The \"plugins.add\" method", function() {
it("throws an error when multiple plugins attempt to register with the same name",
mock.initConverse((done, _converse) => { // eslint-disable-line no-unused-vars
converse.plugins.add('myplugin', {});
const error = new TypeError('Error: plugin with name "myplugin" has already been registered!');
expect(_.partial(converse.plugins.add, 'myplugin', {})).toThrow(error);
done();
}));
});
});
});
});

View File

@ -1,190 +1,183 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const Strophe = converse.env.Strophe;
const $iq = converse.env.$iq;
const _ = converse.env._;
const u = converse.env.utils;
/*global mock */
describe("Service Discovery", function () {
describe("Service Discovery", function () {
describe("Whenever converse.js queries a server for its features", function () {
describe("Whenever converse.js queries a server for its features", function () {
it("stores the features it receives",
mock.initConverse(
['discoInitialized'], {},
async function (done, _converse) {
it("stores the features it receives",
mock.initConverse(
['discoInitialized'], {},
async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas;
const IQ_ids = _converse.connection.IQ_ids;
await u.waitUntil(function () {
return _.filter(IQ_stanzas, function (iq) {
return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
}).length > 0;
});
/* <iq type='result'
* from='plays.shakespeare.lit'
* to='romeo@montague.net/orchard'
* id='info1'>
* <query xmlns='http://jabber.org/protocol/disco#info'>
* <identity
* category='server'
* type='im'/>
* <identity
* category='conference'
* type='text'
* name='Play-Specific Chatrooms'/>
* <identity
* category='directory'
* type='chatroom'
* name='Play-Specific Chatrooms'/>
* <feature var='http://jabber.org/protocol/disco#info'/>
* <feature var='http://jabber.org/protocol/disco#items'/>
* <feature var='http://jabber.org/protocol/muc'/>
* <feature var='jabber:iq:register'/>
* <feature var='jabber:iq:search'/>
* <feature var='jabber:iq:time'/>
* <feature var='jabber:iq:version'/>
* </query>
* </iq>
*/
let stanza = _.find(IQ_stanzas, function (iq) {
const { u, $iq } = converse.env;
const IQ_stanzas = _converse.connection.IQ_stanzas;
const IQ_ids = _converse.connection.IQ_ids;
await u.waitUntil(function () {
return IQ_stanzas.filter(function (iq) {
return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
}).length > 0;
});
/* <iq type='result'
* from='plays.shakespeare.lit'
* to='romeo@montague.net/orchard'
* id='info1'>
* <query xmlns='http://jabber.org/protocol/disco#info'>
* <identity
* category='server'
* type='im'/>
* <identity
* category='conference'
* type='text'
* name='Play-Specific Chatrooms'/>
* <identity
* category='directory'
* type='chatroom'
* name='Play-Specific Chatrooms'/>
* <feature var='http://jabber.org/protocol/disco#info'/>
* <feature var='http://jabber.org/protocol/disco#items'/>
* <feature var='http://jabber.org/protocol/muc'/>
* <feature var='jabber:iq:register'/>
* <feature var='jabber:iq:search'/>
* <feature var='jabber:iq:time'/>
* <feature var='jabber:iq:version'/>
* </query>
* </iq>
*/
let stanza = IQ_stanzas.find(function (iq) {
return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]');
});
const info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
stanza = $iq({
'type': 'result',
'from': 'montague.lit',
'to': 'romeo@montague.lit/orchard',
'id': info_IQ_id
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
.c('identity', {
'category': 'server',
'type': 'im'}).up()
.c('identity', {
'category': 'conference',
'type': 'text',
'name': 'Play-Specific Chatrooms'}).up()
.c('identity', {
'category': 'directory',
'type': 'chatroom',
'name': 'Play-Specific Chatrooms'}).up()
.c('feature', {
'var': 'http://jabber.org/protocol/disco#info'}).up()
.c('feature', {
'var': 'http://jabber.org/protocol/disco#items'}).up()
.c('feature', {
'var': 'jabber:iq:register'}).up()
.c('feature', {
'var': 'jabber:iq:time'}).up()
.c('feature', {
'var': 'jabber:iq:version'});
_converse.connection._dataRecv(mock.createRequest(stanza));
let entities = await _converse.api.disco.entities.get()
expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
expect(entities.get(_converse.domain).features.length).toBe(5);
expect(entities.get(_converse.domain).identities.length).toBe(3);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:version'}).length).toBe(1);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:time'}).length).toBe(1);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:register'}).length).toBe(1);
expect(entities.get('montague.lit').features.where(
{'var': 'http://jabber.org/protocol/disco#items'}).length).toBe(1);
expect(entities.get('montague.lit').features.where(
{'var': 'http://jabber.org/protocol/disco#info'}).length).toBe(1);
await u.waitUntil(function () {
// Converse.js sees that the entity has a disco#items feature,
// so it will make a query for it.
return IQ_stanzas.filter(iq => iq.querySelector('query[xmlns="http://jabber.org/protocol/disco#items"]')).length > 0;
});
/* <iq type='result'
* from='catalog.shakespeare.lit'
* to='romeo@montague.net/orchard'
* id='items2'>
* <query xmlns='http://jabber.org/protocol/disco#items'>
* <item jid='people.shakespeare.lit'
* name='Directory of Characters'/>
* <item jid='plays.shakespeare.lit'
* name='Play-Specific Chatrooms'/>
* <item jid='mim.shakespeare.lit'
* name='Gateway to Marlowe IM'/>
* <item jid='words.shakespeare.lit'
* name='Shakespearean Lexicon'/>
*
* <item jid='catalog.shakespeare.lit'
* node='books'
* name='Books by and about Shakespeare'/>
* <item jid='catalog.shakespeare.lit'
* node='clothing'
* name='Wear your literary taste with pride'/>
* <item jid='catalog.shakespeare.lit'
* node='music'
* name='Music from the time of Shakespeare'/>
* </query>
* </iq>
*/
stanza = IQ_stanzas.find(function (iq) {
return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]');
});
const items_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
stanza = $iq({
'type': 'result',
'from': 'montague.lit',
'to': 'romeo@montague.lit/orchard',
'id': items_IQ_id
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'})
.c('item', {
'jid': 'people.shakespeare.lit',
'name': 'Directory of Characters'}).up()
.c('item', {
'jid': 'plays.shakespeare.lit',
'name': 'Play-Specific Chatrooms'}).up()
.c('item', {
'jid': 'words.shakespeare.lit',
'name': 'Gateway to Marlowe IM'}).up()
.c('item', {
'jid': 'montague.lit',
'node': 'books',
'name': 'Books by and about Shakespeare'}).up()
.c('item', {
'node': 'montague.lit',
'name': 'Wear your literary taste with pride'}).up()
.c('item', {
'jid': 'montague.lit',
'node': 'music',
'name': 'Music from the time of Shakespeare'
});
const info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
stanza = $iq({
'type': 'result',
'from': 'montague.lit',
'to': 'romeo@montague.lit/orchard',
'id': info_IQ_id
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
.c('identity', {
'category': 'server',
'type': 'im'}).up()
.c('identity', {
'category': 'conference',
'type': 'text',
'name': 'Play-Specific Chatrooms'}).up()
.c('identity', {
'category': 'directory',
'type': 'chatroom',
'name': 'Play-Specific Chatrooms'}).up()
.c('feature', {
'var': 'http://jabber.org/protocol/disco#info'}).up()
.c('feature', {
'var': 'http://jabber.org/protocol/disco#items'}).up()
.c('feature', {
'var': 'jabber:iq:register'}).up()
.c('feature', {
'var': 'jabber:iq:time'}).up()
.c('feature', {
'var': 'jabber:iq:version'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => _converse.disco_entities);
entities = _converse.disco_entities;
expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
expect(entities.get(_converse.domain).items.length).toBe(3);
expect(entities.get(_converse.domain).items.pluck('jid').includes('people.shakespeare.lit')).toBeTruthy();
expect(entities.get(_converse.domain).items.pluck('jid').includes('plays.shakespeare.lit')).toBeTruthy();
expect(entities.get(_converse.domain).items.pluck('jid').includes('words.shakespeare.lit')).toBeTruthy();
expect(entities.get(_converse.domain).identities.where({'category': 'conference'}).length).toBe(1);
expect(entities.get(_converse.domain).identities.where({'category': 'directory'}).length).toBe(1);
done();
}));
});
let entities = await _converse.api.disco.entities.get()
expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
expect(entities.get(_converse.domain).features.length).toBe(5);
expect(entities.get(_converse.domain).identities.length).toBe(3);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:version'}).length).toBe(1);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:time'}).length).toBe(1);
expect(entities.get('montague.lit').features.where({'var': 'jabber:iq:register'}).length).toBe(1);
expect(entities.get('montague.lit').features.where(
{'var': 'http://jabber.org/protocol/disco#items'}).length).toBe(1);
expect(entities.get('montague.lit').features.where(
{'var': 'http://jabber.org/protocol/disco#info'}).length).toBe(1);
describe("Whenever converse.js discovers a new server feature", function () {
it("emits the serviceDiscovered event",
mock.initConverse(
['discoInitialized'], {},
function (done, _converse) {
await u.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.querySelector('query[xmlns="http://jabber.org/protocol/disco#items"]');
}).length > 0;
});
/* <iq type='result'
* from='catalog.shakespeare.lit'
* to='romeo@montague.net/orchard'
* id='items2'>
* <query xmlns='http://jabber.org/protocol/disco#items'>
* <item jid='people.shakespeare.lit'
* name='Directory of Characters'/>
* <item jid='plays.shakespeare.lit'
* name='Play-Specific Chatrooms'/>
* <item jid='mim.shakespeare.lit'
* name='Gateway to Marlowe IM'/>
* <item jid='words.shakespeare.lit'
* name='Shakespearean Lexicon'/>
*
* <item jid='catalog.shakespeare.lit'
* node='books'
* name='Books by and about Shakespeare'/>
* <item jid='catalog.shakespeare.lit'
* node='clothing'
* name='Wear your literary taste with pride'/>
* <item jid='catalog.shakespeare.lit'
* node='music'
* name='Music from the time of Shakespeare'/>
* </query>
* </iq>
*/
stanza = _.find(IQ_stanzas, function (iq) {
return iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]');
});
var items_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)];
stanza = $iq({
'type': 'result',
'from': 'montague.lit',
'to': 'romeo@montague.lit/orchard',
'id': items_IQ_id
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'})
.c('item', {
'jid': 'people.shakespeare.lit',
'name': 'Directory of Characters'}).up()
.c('item', {
'jid': 'plays.shakespeare.lit',
'name': 'Play-Specific Chatrooms'}).up()
.c('item', {
'jid': 'words.shakespeare.lit',
'name': 'Gateway to Marlowe IM'}).up()
.c('item', {
'jid': 'montague.lit',
'node': 'books',
'name': 'Books by and about Shakespeare'}).up()
.c('item', {
'node': 'montague.lit',
'name': 'Wear your literary taste with pride'}).up()
.c('item', {
'jid': 'montague.lit',
'node': 'music',
'name': 'Music from the time of Shakespeare'
});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await u.waitUntil(() => _converse.disco_entities);
entities = _converse.disco_entities;
expect(entities.length).toBe(2); // We have an extra entity, which is the user's JID
expect(entities.get(_converse.domain).items.length).toBe(3);
expect(_.includes(entities.get(_converse.domain).items.pluck('jid'), 'people.shakespeare.lit')).toBeTruthy();
expect(_.includes(entities.get(_converse.domain).items.pluck('jid'), 'plays.shakespeare.lit')).toBeTruthy();
expect(_.includes(entities.get(_converse.domain).items.pluck('jid'), 'words.shakespeare.lit')).toBeTruthy();
expect(entities.get(_converse.domain).identities.where({'category': 'conference'}).length).toBe(1);
expect(entities.get(_converse.domain).identities.where({'category': 'directory'}).length).toBe(1);
done();
}));
});
describe("Whenever converse.js discovers a new server feature", function () {
it("emits the serviceDiscovered event",
mock.initConverse(
['discoInitialized'], {},
function (done, _converse) {
sinon.spy(_converse.api, "trigger");
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
expect(_converse.api.trigger.called).toBe(true);
expect(_converse.api.trigger.args[0][0]).toBe('serviceDiscovered');
expect(_converse.api.trigger.args[0][1].get('var')).toBe(Strophe.NS.MAM);
done();
}));
});
const { Strophe } = converse.env;
sinon.spy(_converse.api, "trigger");
_converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
expect(_converse.api.trigger.called).toBe(true);
expect(_converse.api.trigger.args[0][0]).toBe('serviceDiscovered');
expect(_converse.api.trigger.args[0][1].get('var')).toBe(Strophe.NS.MAM);
done();
}));
});
});

View File

@ -1,238 +1,236 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const { Promise, $msg, $pres, sizzle } = converse.env;
const u = converse.env.utils;
/*global mock */
describe("Emojis", function () {
describe("The emoji picker", function () {
const { Promise, $msg, $pres, sizzle } = converse.env;
const u = converse.env.utils;
it("can be opened by clicking a button in the chat toolbar",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
describe("Emojis", function () {
describe("The emoji picker", function () {
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse);
await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const toolbar = await u.waitUntil(() => view.el.querySelector('ul.chat-toolbar'));
expect(toolbar.querySelectorAll('li.toggle-smiley__container').length).toBe(1);
toolbar.querySelector('a.toggle-smiley').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')), 1000);
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'), 1000);
const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a'), 1000);
item.click()
expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
toolbar.querySelector('a.toggle-smiley').click(); // Close the panel again
done();
}));
it("can be opened by clicking a button in the chat toolbar",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
it("is opened to autocomplete emojis in the textarea",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
await mock.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const toolbar = await u.waitUntil(() => view.el.querySelector('ul.chat-toolbar'));
expect(toolbar.querySelectorAll('li.toggle-smiley__container').length).toBe(1);
toolbar.querySelector('a.toggle-smiley').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')), 1000);
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'), 1000);
const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji a'), 1000);
item.click()
expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
toolbar.querySelector('a.toggle-smiley').click(); // Close the panel again
done();
}));
const muc_jid = 'lounge@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
it("is opened to autocomplete emojis in the textarea",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = ':gri';
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
// Press tab
const tab_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 9,
'key': 'Tab'
}
view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
let picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'));
const input = picker.querySelector('.emoji-search');
expect(input.value).toBe(':gri');
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
expect(visible_emojis.length).toBe(3);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':grin:');
expect(visible_emojis[2].getAttribute('data-emoji')).toBe(':grinning:');
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = ':gri';
// Test that TAB autocompletes the to first match
view.emoji_picker_view.onKeyDown(tab_event);
visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
expect(visible_emojis.length).toBe(1);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:');
expect(input.value).toBe(':grimacing:');
// Press tab
const tab_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'keyCode': 9,
'key': 'Tab'
}
view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
let picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'));
const input = picker.querySelector('.emoji-search');
expect(input.value).toBe(':gri');
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
expect(visible_emojis.length).toBe(3);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':grin:');
expect(visible_emojis[2].getAttribute('data-emoji')).toBe(':grinning:');
// Check that ENTER now inserts the match
const enter_event = Object.assign({}, tab_event, {'keyCode': 13, 'key': 'Enter', 'target': input});
view.emoji_picker_view.onKeyDown(enter_event);
expect(input.value).toBe('');
expect(textarea.value).toBe(':grimacing: ');
// Test that TAB autocompletes the to first match
view.emoji_picker_view.onKeyDown(tab_event);
visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
expect(visible_emojis.length).toBe(1);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':grimacing:');
expect(input.value).toBe(':grimacing:');
// Test that username starting with : doesn't cause issues
const presence = $pres({
'from': `${muc_jid}/:username`,
'id': '27C55F89-1C6A-459A-9EB5-77690145D624',
'to': _converse.jid
})
.c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
.c('item', {
'jid': 'some1@montague.lit',
'affiliation': 'member',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
// Check that ENTER now inserts the match
const enter_event = Object.assign({}, tab_event, {'keyCode': 13, 'key': 'Enter', 'target': input});
view.emoji_picker_view.onKeyDown(enter_event);
expect(input.value).toBe('');
expect(textarea.value).toBe(':grimacing: ');
textarea.value = ':use';
view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'));
expect(input.value).toBe(':use');
visible_emojis = sizzle('.insert-emoji:not(.hidden)', picker);
expect(visible_emojis.length).toBe(0);
done();
}));
// Test that username starting with : doesn't cause issues
const presence = $pres({
'from': `${muc_jid}/:username`,
'id': '27C55F89-1C6A-459A-9EB5-77690145D624',
'to': _converse.jid
})
.c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'})
.c('item', {
'jid': 'some1@montague.lit',
'affiliation': 'member',
'role': 'participant'
});
_converse.connection._dataRecv(mock.createRequest(presence));
textarea.value = ':use';
view.onKeyDown(tab_event);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'));
expect(input.value).toBe(':use');
visible_emojis = sizzle('.insert-emoji:not(.hidden)', picker);
expect(visible_emojis.length).toBe(0);
done();
}));
it("allows you to search for particular emojis",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
it("allows you to search for particular emojis",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const muc_jid = 'lounge@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
const toolbar = view.el.querySelector('ul.chat-toolbar');
expect(toolbar.querySelectorAll('.toggle-smiley__container').length).toBe(1);
toolbar.querySelector('.toggle-smiley').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'));
const input = picker.querySelector('.emoji-search');
expect(sizzle('.insert-emoji:not(.hidden)', picker).length).toBe(1589);
const view = _converse.chatboxviews.get(muc_jid);
const toolbar = view.el.querySelector('ul.chat-toolbar');
expect(toolbar.querySelectorAll('.toggle-smiley__container').length).toBe(1);
toolbar.querySelector('.toggle-smiley').click();
await u.waitUntil(() => u.isVisible(view.el.querySelector('.emoji-picker__lists')));
const picker = await u.waitUntil(() => view.el.querySelector('.emoji-picker__container'));
const input = picker.querySelector('.emoji-search');
expect(sizzle('.insert-emoji:not(.hidden)', picker).length).toBe(1589);
expect(view.emoji_picker_view.model.get('query')).toBeUndefined();
input.value = 'smiley';
const event = {
'target': input,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {}
};
view.emoji_picker_view.onKeyDown(event);
await u.waitUntil(() => view.emoji_picker_view.model.get('query') === 'smiley');
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
expect(visible_emojis.length).toBe(2);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':smiley_cat:');
expect(view.emoji_picker_view.model.get('query')).toBeUndefined();
input.value = 'smiley';
const event = {
'target': input,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {}
};
view.emoji_picker_view.onKeyDown(event);
await u.waitUntil(() => view.emoji_picker_view.model.get('query') === 'smiley');
let visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
expect(visible_emojis.length).toBe(2);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':smiley_cat:');
// Check that pressing enter without an unambiguous match does nothing
const enter_event = Object.assign({}, event, {'keyCode': 13});
view.emoji_picker_view.onKeyDown(enter_event);
expect(input.value).toBe('smiley');
// Check that pressing enter without an unambiguous match does nothing
const enter_event = Object.assign({}, event, {'keyCode': 13});
view.emoji_picker_view.onKeyDown(enter_event);
expect(input.value).toBe('smiley');
// Test that TAB autocompletes the to first match
const tab_event = Object.assign({}, event, {'keyCode': 9, 'key': 'Tab'});
view.emoji_picker_view.onKeyDown(tab_event);
expect(input.value).toBe(':smiley:');
visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
expect(visible_emojis.length).toBe(1);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
// Test that TAB autocompletes the to first match
const tab_event = Object.assign({}, event, {'keyCode': 9, 'key': 'Tab'});
view.emoji_picker_view.onKeyDown(tab_event);
expect(input.value).toBe(':smiley:');
visible_emojis = sizzle('.emojis-lists__container--search .insert-emoji', picker);
expect(visible_emojis.length).toBe(1);
expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
// Check that ENTER now inserts the match
view.emoji_picker_view.onKeyDown(enter_event);
expect(input.value).toBe('');
expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
done();
}));
});
// Check that ENTER now inserts the match
view.emoji_picker_view.onKeyDown(enter_event);
expect(input.value).toBe('');
expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
done();
}));
});
describe("A Chat Message", function () {
it("will display larger if it's only emojis",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {'use_system_emojis': true},
async function (done, _converse) {
describe("A Chat Message", function () {
it("will display larger if it's only emojis",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {'use_system_emojis': true},
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.handleMessageStanza($msg({
'from': sender_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': _converse.connection.getUniqueId()
}).c('body').t('😇').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
await new Promise(resolve => _converse.on('chatBoxViewInitialized', resolve));
const view = _converse.api.chatviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
let message = view.content.querySelector('.chat-msg__text');
expect(u.hasClass('chat-msg__text--larger', message)).toBe(true);
await mock.waitForRoster(_converse, 'current');
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.handleMessageStanza($msg({
'from': sender_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': _converse.connection.getUniqueId()
}).c('body').t('😇').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
await new Promise(resolve => _converse.on('chatBoxViewInitialized', resolve));
const view = _converse.api.chatviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
let message = view.content.querySelector('.chat-msg__text');
expect(u.hasClass('chat-msg__text--larger', message)).toBe(true);
_converse.handleMessageStanza($msg({
'from': sender_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': _converse.connection.getUniqueId()
}).c('body').t('😇 Hello world! 😇 😇').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
await new Promise(resolve => view.once('messageInserted', resolve));
message = view.content.querySelector('.message:last-child .chat-msg__text');
expect(u.hasClass('chat-msg__text--larger', message)).toBe(false);
_converse.handleMessageStanza($msg({
'from': sender_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': _converse.connection.getUniqueId()
}).c('body').t('😇 Hello world! 😇 😇').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
await new Promise(resolve => view.once('messageInserted', resolve));
message = view.content.querySelector('.message:last-child .chat-msg__text');
expect(u.hasClass('chat-msg__text--larger', message)).toBe(false);
// Test that a modified message that no longer contains only
// emojis now renders normally again.
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = ':poop: :innocent:';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
expect(view.content.querySelector('.message:last-child .chat-msg__text').textContent).toBe('💩 😇');
expect(textarea.value).toBe('');
view.onKeyDown({
target: textarea,
keyCode: 38 // Up arrow
});
expect(textarea.value).toBe('💩 😇');
expect(view.model.messages.at(2).get('correcting')).toBe(true);
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg:last-child')), 500);
textarea.value = textarea.value += 'This is no longer an emoji-only message';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.model.messages.models.length).toBe(3);
message = view.content.querySelector('.message:last-child .chat-msg__text');
expect(u.hasClass('chat-msg__text--larger', message)).toBe(false);
// Test that a modified message that no longer contains only
// emojis now renders normally again.
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = ':poop: :innocent:';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
expect(view.content.querySelector('.message:last-child .chat-msg__text').textContent).toBe('💩 😇');
expect(textarea.value).toBe('');
view.onKeyDown({
target: textarea,
keyCode: 38 // Up arrow
});
expect(textarea.value).toBe('💩 😇');
expect(view.model.messages.at(2).get('correcting')).toBe(true);
await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg:last-child')), 500);
textarea.value = textarea.value += 'This is no longer an emoji-only message';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(view.model.messages.models.length).toBe(3);
message = view.content.querySelector('.message:last-child .chat-msg__text');
expect(u.hasClass('chat-msg__text--larger', message)).toBe(false);
textarea.value = ':smile: Hello world!';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await new Promise(resolve => view.once('messageInserted', resolve));
textarea.value = ':smile: Hello world!';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await new Promise(resolve => view.once('messageInserted', resolve));
textarea.value = ':smile: :smiley: :imp:';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await new Promise(resolve => view.once('messageInserted', resolve));
textarea.value = ':smile: :smiley: :imp:';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await new Promise(resolve => view.once('messageInserted', resolve));
message = view.content.querySelector('.message:last-child .chat-msg__text');
expect(u.hasClass('chat-msg__text--larger', message)).toBe(true);
done()
}));
});
message = view.content.querySelector('.message:last-child .chat-msg__text');
expect(u.hasClass('chat-msg__text--larger', message)).toBe(true);
done()
}));
});
});

View File

@ -1,63 +1,61 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
/*global mock */
return describe("The _converse Event Emitter", function() {
describe("The _converse Event Emitter", function() {
it("allows you to subscribe to emitted events", mock.initConverse((done, _converse) => {
this.callback = function () {};
spyOn(this, 'callback');
_converse.on('connected', this.callback);
_converse.api.trigger('connected');
expect(this.callback).toHaveBeenCalled();
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 2);
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 3);
done();
}));
it("allows you to subscribe to emitted events", mock.initConverse((done, _converse) => {
this.callback = function () {};
spyOn(this, 'callback');
_converse.on('connected', this.callback);
_converse.api.trigger('connected');
expect(this.callback).toHaveBeenCalled();
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 2);
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 3);
done();
}));
it("allows you to listen once for an emitted event", mock.initConverse((done, _converse) => {
this.callback = function () {};
spyOn(this, 'callback');
_converse.once('connected', this.callback);
_converse.api.trigger('connected');
expect(this.callback).toHaveBeenCalled();
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 1);
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 1);
done();
}));
it("allows you to listen once for an emitted event", mock.initConverse((done, _converse) => {
this.callback = function () {};
spyOn(this, 'callback');
_converse.once('connected', this.callback);
_converse.api.trigger('connected');
expect(this.callback).toHaveBeenCalled();
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 1);
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 1);
done();
}));
it("allows you to stop listening or subscribing to an event", mock.initConverse((done, _converse) => {
this.callback = function () {};
this.anotherCallback = function () {};
this.neverCalled = function () {};
it("allows you to stop listening or subscribing to an event", mock.initConverse((done, _converse) => {
this.callback = function () {};
this.anotherCallback = function () {};
this.neverCalled = function () {};
spyOn(this, 'callback');
spyOn(this, 'anotherCallback');
spyOn(this, 'neverCalled');
_converse.on('connected', this.callback);
_converse.on('connected', this.anotherCallback);
spyOn(this, 'callback');
spyOn(this, 'anotherCallback');
spyOn(this, 'neverCalled');
_converse.on('connected', this.callback);
_converse.on('connected', this.anotherCallback);
_converse.api.trigger('connected');
expect(this.callback).toHaveBeenCalled();
expect(this.anotherCallback).toHaveBeenCalled();
_converse.api.trigger('connected');
expect(this.callback).toHaveBeenCalled();
expect(this.anotherCallback).toHaveBeenCalled();
_converse.off('connected', this.callback);
_converse.off('connected', this.callback);
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 1);
expect(this.anotherCallback.calls.count(), 2);
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 1);
expect(this.anotherCallback.calls.count(), 2);
_converse.once('connected', this.neverCalled);
_converse.off('connected', this.neverCalled);
_converse.once('connected', this.neverCalled);
_converse.off('connected', this.neverCalled);
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 1);
expect(this.anotherCallback.calls.count(), 3);
expect(this.neverCalled).not.toHaveBeenCalled();
done();
}));
});
_converse.api.trigger('connected');
expect(this.callback.calls.count(), 1);
expect(this.anotherCallback.calls.count(), 3);
expect(this.neverCalled).not.toHaveBeenCalled();
done();
}));
});

View File

@ -1,80 +1,78 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const u = converse.env.utils;
/*global mock */
describe("A XEP-0317 MUC Hat", function () {
const u = converse.env.utils;
it("can be included in a presence stanza",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
describe("A XEP-0317 MUC Hat", function () {
const muc_jid = 'lounge@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
const hat1_id = u.getUniqueId();
const hat2_id = u.getUniqueId();
_converse.connection._dataRecv(test_utils.createRequest(u.toStanza(`
<presence from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="member" role="participant"/>
</x>
<hats xmlns="xmpp:prosody.im/protocol/hats:1">
<hat title="Teacher&apos;s Assistant" id="${hat1_id}"/>
<hat title="Dark Mage" id="${hat2_id}"/>
</hats>
</presence>
`)));
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.trim() ===
"romeo and Terry have entered the groupchat");
it("can be included in a presence stanza",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
let hats = view.model.getOccupant("Terry").get('hats');
expect(hats.length).toBe(2);
expect(hats.map(h => h.title).join(' ')).toBe("Teacher's Assistant Dark Mage");
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
const hat1_id = u.getUniqueId();
const hat2_id = u.getUniqueId();
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
<presence from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="member" role="participant"/>
</x>
<hats xmlns="xmpp:prosody.im/protocol/hats:1">
<hat title="Teacher&apos;s Assistant" id="${hat1_id}"/>
<hat title="Dark Mage" id="${hat2_id}"/>
</hats>
</presence>
`)));
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.trim() ===
"romeo and Terry have entered the groupchat");
_converse.connection._dataRecv(test_utils.createRequest(u.toStanza(`
<message type="groupchat" from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
<body>Hello world</body>
</message>
`)));
let hats = view.model.getOccupant("Terry").get('hats');
expect(hats.length).toBe(2);
expect(hats.map(h => h.title).join(' ')).toBe("Teacher's Assistant Dark Mage");
const msg_el = await u.waitUntil(() => view.el.querySelector('.chat-msg'));
let badges = Array.from(msg_el.querySelectorAll('.badge'));
expect(badges.length).toBe(2);
expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage");
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
<message type="groupchat" from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
<body>Hello world</body>
</message>
`)));
const hat3_id = u.getUniqueId();
_converse.connection._dataRecv(test_utils.createRequest(u.toStanza(`
<presence from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="member" role="participant"/>
</x>
<hats xmlns="xmpp:prosody.im/protocol/hats:1">
<hat title="Teacher&apos;s Assistant" id="${hat1_id}"/>
<hat title="Dark Mage" id="${hat2_id}"/>
<hat title="Mad hatter" id="${hat3_id}"/>
</hats>
</presence>
`)));
const msg_el = await u.waitUntil(() => view.el.querySelector('.chat-msg'));
let badges = Array.from(msg_el.querySelectorAll('.badge'));
expect(badges.length).toBe(2);
expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage");
await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 3);
hats = view.model.getOccupant("Terry").get('hats');
expect(hats.map(h => h.title).join(' ')).toBe("Teacher's Assistant Dark Mage Mad hatter");
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .badge').length === 3);
badges = Array.from(view.el.querySelectorAll('.chat-msg .badge'));
expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage Mad hatter");
const hat3_id = u.getUniqueId();
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
<presence from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="member" role="participant"/>
</x>
<hats xmlns="xmpp:prosody.im/protocol/hats:1">
<hat title="Teacher&apos;s Assistant" id="${hat1_id}"/>
<hat title="Dark Mage" id="${hat2_id}"/>
<hat title="Mad hatter" id="${hat3_id}"/>
</hats>
</presence>
`)));
_converse.connection._dataRecv(test_utils.createRequest(u.toStanza(`
<presence from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="member" role="participant"/>
</x>
</presence>
`)));
await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 0);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .badge').length === 0);
done();
}));
})
});
await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 3);
hats = view.model.getOccupant("Terry").get('hats');
expect(hats.map(h => h.title).join(' ')).toBe("Teacher's Assistant Dark Mage Mad hatter");
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .badge').length === 3);
badges = Array.from(view.el.querySelectorAll('.chat-msg .badge'));
expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage Mad hatter");
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
<presence from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="member" role="participant"/>
</x>
</presence>
`)));
await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 0);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .badge').length === 0);
done();
}));
})

View File

@ -1,177 +1,176 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const $msg = converse.env.$msg,
_ = converse.env._,
u = converse.env.utils;
/*global mock */
describe("A headlines box", function () {
describe("A headlines box", function () {
it("will not open nor display non-headline messages",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) {
it("will not open nor display non-headline messages",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) {
/* XMPP spam message:
*
* <message xmlns="jabber:client"
* to="romeo@montague.lit"
* type="chat"
* from="gapowa20102106@rds-rostov.ru/Adium">
* <nick xmlns="http://jabber.org/protocol/nick">-wwdmz</nick>
* <body>SORRY FOR THIS ADVERT</body
* </message
*/
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'xmlns': 'jabber:client',
'to': 'romeo@montague.lit',
'type': 'chat',
'from': 'gapowa20102106@rds-rostov.ru/Adium',
})
.c('nick', {'xmlns': "http://jabber.org/protocol/nick"}).t("-wwdmz").up()
.c('body').t('SORRY FOR THIS ADVERT');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(false)).toBeTruthy();
expect(_converse.api.headlines.get().length === 0);
u.isHeadlineMessage.restore();
done();
}));
const { u, $msg} = converse.env;
/* XMPP spam message:
*
* <message xmlns="jabber:client"
* to="romeo@montague.lit"
* type="chat"
* from="gapowa20102106@rds-rostov.ru/Adium">
* <nick xmlns="http://jabber.org/protocol/nick">-wwdmz</nick>
* <body>SORRY FOR THIS ADVERT</body
* </message
*/
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'xmlns': 'jabber:client',
'to': 'romeo@montague.lit',
'type': 'chat',
'from': 'gapowa20102106@rds-rostov.ru/Adium',
})
.c('nick', {'xmlns': "http://jabber.org/protocol/nick"}).t("-wwdmz").up()
.c('body').t('SORRY FOR THIS ADVERT');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(false)).toBeTruthy();
expect(_converse.api.headlines.get().length === 0);
u.isHeadlineMessage.restore();
done();
}));
it("will open and display headline messages", mock.initConverse(
['rosterGroupsFetched'], {}, async function (done, _converse) {
/* <message from='notify.example.com'
* to='romeo@im.example.com'
* type='headline'
* xml:lang='en'>
* <subject>SIEVE</subject>
* <body>&lt;juliet@example.com&gt; You got mail.</body>
* <x xmlns='jabber:x:oob'>
* <url>
* imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18
* </url>
* </x>
* </message>
*/
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await u.waitUntil(() => _converse.chatboxviews.keys().includes('notify.example.com'));
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
u.isHeadlineMessage.restore(); // unwraps
const view = _converse.chatboxviews.get('notify.example.com');
expect(view.model.get('show_avatar')).toBeFalsy();
expect(view.el.querySelector('img.avatar')).toBe(null);
done();
}));
it("will show headline messages in the controlbox", mock.initConverse(
it("will open and display headline messages", mock.initConverse(
['rosterGroupsFetched'], {}, async function (done, _converse) {
/* <message from='notify.example.com'
* to='romeo@im.example.com'
* type='headline'
* xml:lang='en'>
* <subject>SIEVE</subject>
* <body>&lt;juliet@example.com&gt; You got mail.</body>
* <x xmlns='jabber:x:oob'>
* <url>
* imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18
* </url>
* </x>
* </message>
*/
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
const { u, $msg} = converse.env;
/* <message from='notify.example.com'
* to='romeo@im.example.com'
* type='headline'
* xml:lang='en'>
* <subject>SIEVE</subject>
* <body>&lt;juliet@example.com&gt; You got mail.</body>
* <x xmlns='jabber:x:oob'>
* <url>
* imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18
* </url>
* </x>
* </message>
*/
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
const view = _converse.chatboxviews.get('controlbox');
await u.waitUntil(() => view.el.querySelectorAll(".open-headline").length);
expect(view.el.querySelectorAll('.open-headline').length).toBe(1);
expect(view.el.querySelector('.open-headline').text).toBe('notify.example.com');
done();
}));
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => _converse.chatboxviews.keys().includes('notify.example.com'));
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
u.isHeadlineMessage.restore(); // unwraps
const view = _converse.chatboxviews.get('notify.example.com');
expect(view.model.get('show_avatar')).toBeFalsy();
expect(view.el.querySelector('img.avatar')).toBe(null);
done();
}));
it("will remove headline messages from the controlbox if closed", mock.initConverse(
['rosterGroupsFetched'], {}, async function (done, _converse) {
it("will show headline messages in the controlbox", mock.initConverse(
['rosterGroupsFetched'], {}, async function (done, _converse) {
await test_utils.openControlBox(_converse);
/* <message from='notify.example.com'
* to='romeo@im.example.com'
* type='headline'
* xml:lang='en'>
* <subject>SIEVE</subject>
* <body>&lt;juliet@example.com&gt; You got mail.</body>
* <x xmlns='jabber:x:oob'>
* <url>
* imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18
* </url>
* </x>
* </message>
*/
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
const { u, $msg} = converse.env;
/* <message from='notify.example.com'
* to='romeo@im.example.com'
* type='headline'
* xml:lang='en'>
* <subject>SIEVE</subject>
* <body>&lt;juliet@example.com&gt; You got mail.</body>
* <x xmlns='jabber:x:oob'>
* <url>
* imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18
* </url>
* </x>
* </message>
*/
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
const cbview = _converse.chatboxviews.get('controlbox');
await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length);
const hlview = _converse.chatboxviews.get('notify.example.com');
await u.isVisible(hlview.el);
const close_el = await u.waitUntil(() => hlview.el.querySelector('.close-chatbox-button'));
close_el.click();
await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length === 0);
expect(cbview.el.querySelectorAll('.open-headline').length).toBe(0);
done();
}));
_converse.connection._dataRecv(mock.createRequest(stanza));
const view = _converse.chatboxviews.get('controlbox');
await u.waitUntil(() => view.el.querySelectorAll(".open-headline").length);
expect(view.el.querySelectorAll('.open-headline').length).toBe(1);
expect(view.el.querySelector('.open-headline').text).toBe('notify.example.com');
done();
}));
it("will not show a headline messages from a full JID if allow_non_roster_messaging is false",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) {
it("will remove headline messages from the controlbox if closed", mock.initConverse(
['rosterGroupsFetched'], {}, async function (done, _converse) {
_converse.allow_non_roster_messaging = false;
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'type': 'headline',
'from': 'andre5114@jabber.snc.ru/Spark',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('nick').t('gpocy').up()
.c('body').t('Здравствуйте друзья');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_.without('controlbox', _converse.chatboxviews.keys()).length).toBe(0);
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
u.isHeadlineMessage.restore(); // unwraps
done();
}));
});
const { u, $msg} = converse.env;
await mock.openControlBox(_converse);
/* <message from='notify.example.com'
* to='romeo@im.example.com'
* type='headline'
* xml:lang='en'>
* <subject>SIEVE</subject>
* <body>&lt;juliet@example.com&gt; You got mail.</body>
* <x xmlns='jabber:x:oob'>
* <url>
* imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18
* </url>
* </x>
* </message>
*/
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(mock.createRequest(stanza));
const cbview = _converse.chatboxviews.get('controlbox');
await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length);
const hlview = _converse.chatboxviews.get('notify.example.com');
await u.isVisible(hlview.el);
const close_el = await u.waitUntil(() => hlview.el.querySelector('.close-chatbox-button'));
close_el.click();
await u.waitUntil(() => cbview.el.querySelectorAll(".open-headline").length === 0);
expect(cbview.el.querySelectorAll('.open-headline').length).toBe(0);
done();
}));
it("will not show a headline messages from a full JID if allow_non_roster_messaging is false",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) {
const { u, $msg, _ } = converse.env;
_converse.allow_non_roster_messaging = false;
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'type': 'headline',
'from': 'andre5114@jabber.snc.ru/Spark',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('nick').t('gpocy').up()
.c('body').t('Здравствуйте друзья');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(_.without('controlbox', _converse.chatboxviews.keys()).length).toBe(0);
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
u.isHeadlineMessage.restore(); // unwraps
done();
}));
});

File diff suppressed because it is too large Load Diff

View File

@ -1,79 +1,77 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const u = converse.env.utils;
/*global mock */
describe("The Login Form", function () {
const u = converse.env.utils;
it("contains a checkbox to indicate whether the computer is trusted or not",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
allow_registration: false },
async function (done, _converse) {
describe("The Login Form", function () {
test_utils.openControlBox(_converse);
const cbview = await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
it("contains a checkbox to indicate whether the computer is trusted or not",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
allow_registration: false },
async function (done, _converse) {
mock.openControlBox(_converse);
const cbview = await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1);
const checkbox = checkboxes[0];
const label = cbview.el.querySelector(`label[for="${checkbox.getAttribute('id')}"]`);
expect(label.textContent).toBe('This is a trusted device');
expect(checkbox.checked).toBe(true);
cbview.el.querySelector('input[name="jid"]').value = 'romeo@montague.lit';
cbview.el.querySelector('input[name="password"]').value = 'secret';
spyOn(cbview.loginpanel, 'connect');
cbview.delegateEvents();
expect(_converse.config.get('storage')).toBe('persistent');
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('persistent');
expect(cbview.loginpanel.connect).toHaveBeenCalled();
checkbox.click();
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('session');
done();
}));
it("checkbox can be set to false by default",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
trusted: false,
allow_registration: false },
function (done, _converse) {
u.waitUntil(() => _converse.chatboxviews.get('controlbox'))
.then(() => {
var cbview = _converse.chatboxviews.get('controlbox');
mock.openControlBox(_converse);
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1);
const checkbox = checkboxes[0];
const label = cbview.el.querySelector(`label[for="${checkbox.getAttribute('id')}"]`);
expect(label.textContent).toBe('This is a trusted device');
expect(checkbox.checked).toBe(true);
expect(checkbox.checked).toBe(false);
cbview.el.querySelector('input[name="jid"]').value = 'romeo@montague.lit';
cbview.el.querySelector('input[name="password"]').value = 'secret';
spyOn(cbview.loginpanel, 'connect');
cbview.delegateEvents();
expect(_converse.config.get('storage')).toBe('persistent');
expect(_converse.config.get('storage')).toBe('session');
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('persistent');
expect(_converse.config.get('storage')).toBe('session');
expect(cbview.loginpanel.connect).toHaveBeenCalled();
checkbox.click();
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('session');
expect(_converse.config.get('storage')).toBe('persistent');
done();
}));
it("checkbox can be set to false by default",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
trusted: false,
allow_registration: false },
function (done, _converse) {
u.waitUntil(() => _converse.chatboxviews.get('controlbox'))
.then(() => {
var cbview = _converse.chatboxviews.get('controlbox');
test_utils.openControlBox(_converse);
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1);
const checkbox = checkboxes[0];
const label = cbview.el.querySelector(`label[for="${checkbox.getAttribute('id')}"]`);
expect(label.textContent).toBe('This is a trusted device');
expect(checkbox.checked).toBe(false);
cbview.el.querySelector('input[name="jid"]').value = 'romeo@montague.lit';
cbview.el.querySelector('input[name="password"]').value = 'secret';
spyOn(cbview.loginpanel, 'connect');
expect(_converse.config.get('storage')).toBe('session');
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('session');
expect(cbview.loginpanel.connect).toHaveBeenCalled();
checkbox.click();
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('persistent');
done();
});
}));
});
});
}));
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,167 +1,165 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const _ = converse.env._;
const $msg = converse.env.$msg;
const u = converse.env.utils;
/*global mock */
describe("The Minimized Chats Widget", function () {
const _ = converse.env._;
const $msg = converse.env.$msg;
const u = converse.env.utils;
it("shows chats that have been minimized",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
describe("The Minimized Chats Widget", function () {
await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse);
_converse.minimized_chats.initToggle();
it("shows chats that have been minimized",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
let contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid)
let chatview = _converse.chatboxviews.get(contact_jid);
expect(chatview.model.get('minimized')).toBeFalsy();
expect(u.isVisible(_converse.minimized_chats.el)).toBe(false);
chatview.el.querySelector('.toggle-chatbox-button').click();
expect(chatview.model.get('minimized')).toBeTruthy();
expect(u.isVisible(_converse.minimized_chats.el)).toBe(true);
expect(_converse.minimized_chats.keys().length).toBe(1);
expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
_converse.minimized_chats.initToggle();
contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await 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(u.isVisible(_converse.minimized_chats.el)).toBe(true);
expect(_converse.minimized_chats.keys().length).toBe(2);
expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
done();
}));
let contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
let chatview = _converse.chatboxviews.get(contact_jid);
expect(chatview.model.get('minimized')).toBeFalsy();
expect(u.isVisible(_converse.minimized_chats.el)).toBe(false);
chatview.el.querySelector('.toggle-chatbox-button').click();
expect(chatview.model.get('minimized')).toBeTruthy();
expect(u.isVisible(_converse.minimized_chats.el)).toBe(true);
expect(_converse.minimized_chats.keys().length).toBe(1);
expect(_converse.minimized_chats.keys()[0]).toBe(contact_jid);
it("can be toggled to hide or show minimized chats",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
contact_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.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(u.isVisible(_converse.minimized_chats.el)).toBe(true);
expect(_converse.minimized_chats.keys().length).toBe(2);
expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
done();
}));
await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse);
_converse.minimized_chats.initToggle();
it("can be toggled to hide or show minimized chats",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid);
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();
await u.waitUntil(() => u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')));
expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
done();
}));
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
_converse.minimized_chats.initToggle();
it("shows the number messages received to minimized chats",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
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();
await u.waitUntil(() => u.isVisible(_converse.minimized_chats.el.querySelector('.minimized-chats-flyout')));
expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
done();
}));
await test_utils.waitForRoster(_converse, 'current', 4);
await test_utils.openControlBox(_converse);
_converse.minimized_chats.initToggle();
it("shows the number messages received to minimized chats",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
var i, contact_jid, chatview, msg;
_converse.minimized_chats.toggleview.model.set({'collapsed': true});
await mock.waitForRoster(_converse, 'current', 4);
await mock.openControlBox(_converse);
_converse.minimized_chats.initToggle();
const unread_el = _converse.minimized_chats.toggleview.el.querySelector('.unread-message-count');
expect(unread_el === null).toBe(true);
var i, contact_jid, chatview, msg;
_converse.minimized_chats.toggleview.model.set({'collapsed': true});
for (i=0; i<3; i++) {
contact_jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
test_utils.openChatBoxFor(_converse, contact_jid);
}
await u.waitUntil(() => _converse.chatboxes.length == 4);
const unread_el = _converse.minimized_chats.toggleview.el.querySelector('.unread-message-count');
expect(unread_el === null).toBe(true);
chatview = _converse.chatboxviews.get(contact_jid);
chatview.model.set({'minimized': true});
for (i=0; i<3; i++) {
msg = $msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('body').t('This message is sent to a minimized chatbox').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.handleMessageStanza(msg);
}
await u.waitUntil(() => chatview.model.messages.length === 3, 500);
for (i=0; i<3; i++) {
contact_jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
mock.openChatBoxFor(_converse, contact_jid);
}
await u.waitUntil(() => _converse.chatboxes.length == 4);
expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((3).toString());
// Chat state notifications don't increment the unread messages counter
// <composing> state
_converse.handleMessageStanza($msg({
chatview = _converse.chatboxviews.get(contact_jid);
chatview.model.set({'minimized': true});
for (i=0; i<3; i++) {
msg = $msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('composing', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
}).c('body').t('This message is sent to a minimized chatbox').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.handleMessageStanza(msg);
}
await u.waitUntil(() => chatview.model.messages.length === 3, 500);
// <paused> state
_converse.handleMessageStanza($msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('paused', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((3).toString());
// Chat state notifications don't increment the unread messages counter
// <composing> state
_converse.handleMessageStanza($msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('composing', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
// <gone> state
_converse.handleMessageStanza($msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('gone', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
// <paused> state
_converse.handleMessageStanza($msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('paused', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
// <inactive> state
_converse.handleMessageStanza($msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('inactive', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
done();
}));
// <gone> state
_converse.handleMessageStanza($msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('gone', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
it("shows the number messages received to minimized groupchats",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
// <inactive> state
_converse.handleMessageStanza($msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('inactive', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
done();
}));
const muc_jid = 'kitchen@conference.shakespeare.lit';
await test_utils.openAndEnterChatRoom(_converse, 'kitchen@conference.shakespeare.lit', 'fires');
const view = _converse.chatboxviews.get(muc_jid);
view.model.set({'minimized': true});
const message = 'fires: Your attention is required';
const nick = mock.chatroom_names[0];
const msg = $msg({
from: muc_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(message).tree();
view.model.queueMessage(msg);
await u.waitUntil(() => view.model.messages.length);
expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe('1');
done();
}));
});
it("shows the number messages received to minimized groupchats",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
const muc_jid = 'kitchen@conference.shakespeare.lit';
await mock.openAndEnterChatRoom(_converse, 'kitchen@conference.shakespeare.lit', 'fires');
const view = _converse.chatboxviews.get(muc_jid);
view.model.set({'minimized': true});
const message = 'fires: Your attention is required';
const nick = mock.chatroom_names[0];
const msg = $msg({
from: muc_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(message).tree();
view.model.queueMessage(msg);
await u.waitUntil(() => view.model.messages.length);
expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe('1');
done();
}));
});

View File

@ -1,370 +1,368 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const _ = converse.env._;
const $iq = converse.env.$iq;
const $pres = converse.env.$pres;
const sizzle = converse.env.sizzle;
const Strophe = converse.env.Strophe;
const u = converse.env.utils;
/*global mock */
describe("The groupchat moderator tool", function () {
const _ = converse.env._;
const $iq = converse.env.$iq;
const $pres = converse.env.$pres;
const sizzle = converse.env.sizzle;
const Strophe = converse.env.Strophe;
const u = converse.env.utils;
it("allows you to set affiliations and roles",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
describe("The groupchat moderator tool", function () {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
let members = [
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
{'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'admin'},
{'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'owner'},
{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'},
];
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 5), 1000);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
let tab = modal.el.querySelector('#affiliations-tab');
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
tab.click();
let select = modal.el.querySelector('.select-affiliation');
expect(select.value).toBe('owner');
select.value = 'admin';
let button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
let user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(1);
expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: wiccarocks@shakespeare.lit');
expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: wiccan');
expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: admin');
_converse.connection.IQ_stanzas = [];
select.value = 'owner';
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(2);
expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: romeo@montague.lit');
expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: romeo');
expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
expect(user_els[1].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: crone1@shakespeare.lit');
expect(user_els[1].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: thirdwitch');
expect(user_els[1].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
const toggle = user_els[1].querySelector('.list-group-item:nth-child(3n) .toggle-form');
const form = user_els[1].querySelector('.list-group-item:nth-child(3n) .affiliation-form');
expect(u.hasClass('hidden', form)).toBeTruthy();
toggle.click();
expect(u.hasClass('hidden', form)).toBeFalsy();
select = form.querySelector('.select-affiliation');
expect(select.value).toBe('owner');
select.value = 'admin';
const input = form.querySelector('input[name="reason"]');
input.value = "You're an admin now";
const submit = form.querySelector('.btn-primary');
submit.click();
spyOn(_converse.ChatRoomOccupants.prototype, 'fetchMembers').and.callThrough();
const sent_IQ = _converse.connection.IQ_stanzas.pop();
expect(Strophe.serialize(sent_IQ)).toBe(
`<iq id="${sent_IQ.getAttribute('id')}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/muc#admin">`+
`<item affiliation="admin" jid="crone1@shakespeare.lit">`+
`<reason>You&apos;re an admin now</reason>`+
`</item>`+
`</query>`+
`</iq>`);
_converse.connection.IQ_stanzas = [];
const stanza = $iq({
'type': 'result',
'id': sent_IQ.getAttribute('id'),
'from': view.model.get('jid'),
'to': _converse.connection.jid
});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await u.waitUntil(() => view.model.occupants.fetchMembers.calls.count());
members = [
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
{'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'admin'},
{'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'admin'},
{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'},
];
await test_utils.returnMemberLists(_converse, muc_jid, members);
await u.waitUntil(() => view.model.occupants.pluck('affiliation').filter(o => o === 'owner').length === 1);
const alert = modal.el.querySelector('.alert-primary');
expect(alert.textContent.trim()).toBe('Affiliation changed');
user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(1);
expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: romeo@montague.lit');
expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: romeo');
expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
tab = modal.el.querySelector('#roles-tab');
tab.click();
select = modal.el.querySelector('.select-role');
expect(u.isVisible(select)).toBe(true);
expect(select.value).toBe('moderator');
button = modal.el.querySelector('.btn-primary[name="users_with_role"]');
button.click();
const roles_panel = modal.el.querySelector('#roles-tabpanel');
await u.waitUntil(() => roles_panel.querySelectorAll('.list-group--users > li').length === 1);
select.value = 'participant';
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
user_els = roles_panel.querySelectorAll('.list-group--users > li')
expect(user_els.length).toBe(1);
expect(user_els[0].textContent.trim()).toBe('No users with that role found.');
done();
}));
it("allows you to filter affiliation search results",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
const members = [
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
{'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'member'},
{'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'member'},
{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'member'},
{'jid': 'juliet@capulet.lit', 'nick': 'juliet', 'affiliation': 'member'},
];
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 6), 1000);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
const select = modal.el.querySelector('.select-affiliation');
expect(select.value).toBe('owner');
select.value = 'member';
const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
const user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(6);
const nicks = Array.from(modal.el.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
expect(nicks.join(' ')).toBe('gower juliet romeo thirdwitch wiccan witch');
const filter = modal.el.querySelector('[name="filter"]');
expect(filter).not.toBe(null);
filter.value = 'romeo';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
filter.value = 'r';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 3));
filter.value = 'gower';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
done();
}));
it("allows you to filter role search results",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', []);
const view = _converse.chatboxviews.get(muc_jid);
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/nomorenicks`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `nomorenicks@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/newb`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `newb@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/some1`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `some1@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/oldhag`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `oldhag@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/crone`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `crone@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(test_utils.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/tux`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `tux@montague.lit`,
'role': 'participant'
})
));
await u.waitUntil(() => (view.model.occupants.length === 7), 1000);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const tab = modal.el.querySelector('#roles-tab');
tab.click();
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
const select = modal.el.querySelector('.select-role');
expect(select.value).toBe('moderator');
select.value = 'participant';
const button = modal.el.querySelector('.btn-primary[name="users_with_role"]');
button.click();
await u.waitUntil(() => !modal.loading_users_with_role);
const user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(6);
const nicks = Array.from(modal.el.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
expect(nicks.join(' ')).toBe('crone newb nomorenicks oldhag some1 tux');
const filter = modal.el.querySelector('[name="filter"]');
expect(filter).not.toBe(null);
filter.value = 'tux';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
filter.value = 'r';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 2));
filter.value = 'crone';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
done();
}));
it("shows an error message if a particular affiliation list may not be retrieved",
it("allows you to set affiliations and roles",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
const members = [
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
{'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'admin'},
{'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'owner'},
{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'},
];
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 5));
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
let members = [
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
{'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'admin'},
{'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'owner'},
{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'},
];
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 5), 1000);
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const tab = modal.el.querySelector('#affiliations-tab');
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
const IQ_stanzas = _converse.connection.IQ_stanzas;
tab.click();
const select = modal.el.querySelector('.select-affiliation');
select.value = 'outcast';
const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
button.click();
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const iq_query = await u.waitUntil(() => _.filter(
IQ_stanzas,
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="outcast"]`, s).length
).pop());
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
let tab = modal.el.querySelector('#affiliations-tab');
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
tab.click();
let select = modal.el.querySelector('.select-affiliation');
expect(select.value).toBe('owner');
select.value = 'admin';
let button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
let user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(1);
expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: wiccarocks@shakespeare.lit');
expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: wiccan');
expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: admin');
const error = u.toStanza(
`<iq from="${muc_jid}"
id="${iq_query.getAttribute('id')}"
type="error"
to="${_converse.jid}">
_converse.connection.IQ_stanzas = [];
select.value = 'owner';
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(2);
expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: romeo@montague.lit');
expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: romeo');
expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
<error type="auth">
<forbidden xmlns="${Strophe.NS.STANZAS}"/>
</error>
</iq>`);
_converse.connection._dataRecv(test_utils.createRequest(error));
await u.waitUntil(() => !modal.loading_users_with_affiliation);
expect(user_els[1].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: crone1@shakespeare.lit');
expect(user_els[1].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: thirdwitch');
expect(user_els[1].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
const user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(1);
expect(user_els[0].textContent.trim()).toBe('Error: not allowed to fetch outcast list for MUC lounge@montague.lit');
done();
}));
});
const toggle = user_els[1].querySelector('.list-group-item:nth-child(3n) .toggle-form');
const form = user_els[1].querySelector('.list-group-item:nth-child(3n) .affiliation-form');
expect(u.hasClass('hidden', form)).toBeTruthy();
toggle.click();
expect(u.hasClass('hidden', form)).toBeFalsy();
select = form.querySelector('.select-affiliation');
expect(select.value).toBe('owner');
select.value = 'admin';
const input = form.querySelector('input[name="reason"]');
input.value = "You're an admin now";
const submit = form.querySelector('.btn-primary');
submit.click();
spyOn(_converse.ChatRoomOccupants.prototype, 'fetchMembers').and.callThrough();
const sent_IQ = _converse.connection.IQ_stanzas.pop();
expect(Strophe.serialize(sent_IQ)).toBe(
`<iq id="${sent_IQ.getAttribute('id')}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/muc#admin">`+
`<item affiliation="admin" jid="crone1@shakespeare.lit">`+
`<reason>You&apos;re an admin now</reason>`+
`</item>`+
`</query>`+
`</iq>`);
_converse.connection.IQ_stanzas = [];
const stanza = $iq({
'type': 'result',
'id': sent_IQ.getAttribute('id'),
'from': view.model.get('jid'),
'to': _converse.connection.jid
});
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.model.occupants.fetchMembers.calls.count());
members = [
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
{'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'admin'},
{'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'admin'},
{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'},
];
await mock.returnMemberLists(_converse, muc_jid, members);
await u.waitUntil(() => view.model.occupants.pluck('affiliation').filter(o => o === 'owner').length === 1);
const alert = modal.el.querySelector('.alert-primary');
expect(alert.textContent.trim()).toBe('Affiliation changed');
user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(1);
expect(user_els[0].querySelector('.list-group-item.active').textContent.trim()).toBe('JID: romeo@montague.lit');
expect(user_els[0].querySelector('.list-group-item:nth-child(2n)').textContent.trim()).toBe('Nickname: romeo');
expect(user_els[0].querySelector('.list-group-item:nth-child(3n) div').textContent.trim()).toBe('Affiliation: owner');
tab = modal.el.querySelector('#roles-tab');
tab.click();
select = modal.el.querySelector('.select-role');
expect(u.isVisible(select)).toBe(true);
expect(select.value).toBe('moderator');
button = modal.el.querySelector('.btn-primary[name="users_with_role"]');
button.click();
const roles_panel = modal.el.querySelector('#roles-tabpanel');
await u.waitUntil(() => roles_panel.querySelectorAll('.list-group--users > li').length === 1);
select.value = 'participant';
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
user_els = roles_panel.querySelectorAll('.list-group--users > li')
expect(user_els.length).toBe(1);
expect(user_els[0].textContent.trim()).toBe('No users with that role found.');
done();
}));
it("allows you to filter affiliation search results",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
const members = [
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
{'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'member'},
{'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'member'},
{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'member'},
{'jid': 'juliet@capulet.lit', 'nick': 'juliet', 'affiliation': 'member'},
];
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 6), 1000);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
const select = modal.el.querySelector('.select-affiliation');
expect(select.value).toBe('owner');
select.value = 'member';
const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
button.click();
await u.waitUntil(() => !modal.loading_users_with_affiliation);
const user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(6);
const nicks = Array.from(modal.el.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
expect(nicks.join(' ')).toBe('gower juliet romeo thirdwitch wiccan witch');
const filter = modal.el.querySelector('[name="filter"]');
expect(filter).not.toBe(null);
filter.value = 'romeo';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
filter.value = 'r';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 3));
filter.value = 'gower';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
done();
}));
it("allows you to filter role search results",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', []);
const view = _converse.chatboxviews.get(muc_jid);
_converse.connection._dataRecv(mock.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/nomorenicks`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `nomorenicks@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(mock.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/newb`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `newb@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(mock.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/some1`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `some1@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(mock.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/oldhag`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `oldhag@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(mock.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/crone`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `crone@montague.lit`,
'role': 'participant'
})
));
_converse.connection._dataRecv(mock.createRequest(
$pres({to: _converse.jid, from: `${muc_jid}/tux`})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': `tux@montague.lit`,
'role': 'participant'
})
));
await u.waitUntil(() => (view.model.occupants.length === 7), 1000);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const tab = modal.el.querySelector('#roles-tab');
tab.click();
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
const select = modal.el.querySelector('.select-role');
expect(select.value).toBe('moderator');
select.value = 'participant';
const button = modal.el.querySelector('.btn-primary[name="users_with_role"]');
button.click();
await u.waitUntil(() => !modal.loading_users_with_role);
const user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(6);
const nicks = Array.from(modal.el.querySelectorAll('.list-group--users > li')).map(el => el.getAttribute('data-nick'));
expect(nicks.join(' ')).toBe('crone newb nomorenicks oldhag some1 tux');
const filter = modal.el.querySelector('[name="filter"]');
expect(filter).not.toBe(null);
filter.value = 'tux';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
filter.value = 'r';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 2));
filter.value = 'crone';
u.triggerEvent(filter, "keyup", "KeyboardEvent");
await u.waitUntil(() => ( modal.el.querySelectorAll('.list-group--users > li').length === 1));
done();
}));
it("shows an error message if a particular affiliation list may not be retrieved",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
spyOn(_converse.ChatRoomView.prototype, 'showModeratorToolsModal').and.callThrough();
const muc_jid = 'lounge@montague.lit';
const members = [
{'jid': 'hag66@shakespeare.lit', 'nick': 'witch', 'affiliation': 'member'},
{'jid': 'gower@shakespeare.lit', 'nick': 'gower', 'affiliation': 'member'},
{'jid': 'wiccarocks@shakespeare.lit', 'nick': 'wiccan', 'affiliation': 'admin'},
{'jid': 'crone1@shakespeare.lit', 'nick': 'thirdwitch', 'affiliation': 'owner'},
{'jid': 'romeo@montague.lit', 'nick': 'romeo', 'affiliation': 'owner'},
];
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', [], members);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.occupants.length === 5));
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/modtools';
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, 'keyCode': 13 };
view.onKeyDown(enter);
await u.waitUntil(() => view.showModeratorToolsModal.calls.count());
const modal = view.modtools_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const tab = modal.el.querySelector('#affiliations-tab');
// Clear so that we don't match older stanzas
_converse.connection.IQ_stanzas = [];
const IQ_stanzas = _converse.connection.IQ_stanzas;
tab.click();
const select = modal.el.querySelector('.select-affiliation');
select.value = 'outcast';
const button = modal.el.querySelector('.btn-primary[name="users_with_affiliation"]');
button.click();
const iq_query = await u.waitUntil(() => _.filter(
IQ_stanzas,
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="outcast"]`, s).length
).pop());
const error = u.toStanza(
`<iq from="${muc_jid}"
id="${iq_query.getAttribute('id')}"
type="error"
to="${_converse.jid}">
<error type="auth">
<forbidden xmlns="${Strophe.NS.STANZAS}"/>
</error>
</iq>`);
_converse.connection._dataRecv(mock.createRequest(error));
await u.waitUntil(() => !modal.loading_users_with_affiliation);
const user_els = modal.el.querySelectorAll('.list-group--users > li');
expect(user_els.length).toBe(1);
expect(user_els[0].textContent.trim()).toBe('Error: not allowed to fetch outcast list for MUC lounge@montague.lit');
done();
}));
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,209 +1,207 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const _ = converse.env._;
const $msg = converse.env.$msg;
const u = converse.env.utils;
/*global mock */
describe("Notifications", function () {
// Implement the protocol defined in https://xmpp.org/extensions/xep-0313.html#config
const _ = converse.env._;
const $msg = converse.env.$msg;
const u = converse.env.utils;
describe("When show_desktop_notifications is set to true", function () {
describe("And the desktop is not focused", function () {
describe("an HTML5 Notification", function () {
describe("Notifications", function () {
// Implement the protocol defined in https://xmpp.org/extensions/xep-0313.html#config
it("is shown when a new private message is received",
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
describe("When show_desktop_notifications is set to true", function () {
describe("And the desktop is not focused", function () {
describe("an HTML5 Notification", function () {
await test_utils.waitForRoster(_converse, 'current');
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
const message = 'This message will show a desktop notification';
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
await _converse.handleMessageStanza(msg); // This will emit 'message'
await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
expect(_converse.showMessageNotification).toHaveBeenCalled();
done();
}));
it("is shown when you are mentioned in a groupchat",
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
await test_utils.waitForRoster(_converse, 'current');
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.api.chatviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea();
}
let no_notification = false;
if (typeof window.Notification === 'undefined') {
no_notification = true;
window.Notification = function () {
return {
'close': function () {}
};
};
}
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
const message = 'romeo: This message will show a desktop notification';
const nick = mock.chatroom_names[0],
msg = $msg({
from: 'lounge@montague.lit/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(message).tree();
_converse.connection._dataRecv(test_utils.createRequest(msg));
await new Promise(resolve => view.once('messageInserted', resolve));
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
expect(_converse.showMessageNotification).toHaveBeenCalled();
if (no_notification) {
delete window.Notification;
}
done();
}));
it("is shown for headline messages",
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await u.waitUntil(() => _converse.chatboxviews.keys().length);
const view = _converse.chatboxviews.get('notify.example.com');
await new Promise(resolve => view.once('messageInserted', resolve));
expect(
_.includes(_converse.chatboxviews.keys(),
'notify.example.com')
).toBeTruthy();
expect(_converse.showMessageNotification).toHaveBeenCalled();
done();
}));
it("is not shown for full JID headline messages if allow_non_roster_messaging is false", mock.initConverse((done, _converse) => {
_converse.allow_non_roster_messaging = false;
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
const stanza = $msg({
'type': 'headline',
'from': 'someone@notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(
_.includes(_converse.chatboxviews.keys(),
'someone@notify.example.com')
).toBeFalsy();
expect(_converse.showMessageNotification).not.toHaveBeenCalled();
done();
}));
it("is shown when a user changes their chat state (if show_chat_state_notifications is true)",
mock.initConverse(['rosterGroupsFetched'], {show_chat_state_notifications: true},
async (done, _converse) => {
await test_utils.waitForRoster(_converse, 'current', 3);
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'showChatStateNotification');
const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'busy'); // This will emit 'contactStatusChanged'
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
expect(_converse.showChatStateNotification).toHaveBeenCalled();
done()
}));
});
});
describe("When a new contact request is received", function () {
it("an HTML5 Notification is received", mock.initConverse((done, _converse) => {
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'showContactRequestNotification');
_converse.api.trigger('contactRequest', {'fullname': 'Peter Parker', 'jid': 'peter@parker.com'});
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
expect(_converse.showContactRequestNotification).toHaveBeenCalled();
done();
}));
});
});
describe("When play_sounds is set to true", function () {
describe("A notification sound", function () {
it("is played when the current user is mentioned in a groupchat",
it("is shown when a new private message is received",
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
test_utils.createContacts(_converse, 'current');
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
_converse.play_sounds = true;
spyOn(_converse, 'playSoundNotification');
const view = _converse.chatboxviews.get('lounge@montague.lit');
await mock.waitForRoster(_converse, 'current');
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
const message = 'This message will show a desktop notification';
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
await _converse.handleMessageStanza(msg); // This will emit 'message'
await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
expect(_converse.showMessageNotification).toHaveBeenCalled();
done();
}));
it("is shown when you are mentioned in a groupchat",
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
await mock.waitForRoster(_converse, 'current');
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.api.chatviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea();
}
let text = 'This message will play a sound because it mentions romeo';
let message = $msg({
from: 'lounge@montague.lit/otheruser',
id: '1',
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
await u.waitUntil(() => _converse.playSoundNotification.calls.count());
expect(_converse.playSoundNotification).toHaveBeenCalled();
let no_notification = false;
if (typeof window.Notification === 'undefined') {
no_notification = true;
window.Notification = function () {
return {
'close': function () {}
};
};
}
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
text = "This message won't play a sound";
message = $msg({
from: 'lounge@montague.lit/otheruser',
id: '2',
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false;
const message = 'romeo: This message will show a desktop notification';
const nick = mock.chatroom_names[0],
msg = $msg({
from: 'lounge@montague.lit/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(message).tree();
_converse.connection._dataRecv(mock.createRequest(msg));
await new Promise(resolve => view.once('messageInserted', resolve));
text = "This message won't play a sound because it is sent by romeo";
message = $msg({
from: 'lounge@montague.lit/romeo',
id: '3',
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false;
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
expect(_converse.showMessageNotification).toHaveBeenCalled();
if (no_notification) {
delete window.Notification;
}
done();
}));
it("is shown for headline messages",
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => _converse.chatboxviews.keys().length);
const view = _converse.chatboxviews.get('notify.example.com');
await new Promise(resolve => view.once('messageInserted', resolve));
expect(
_.includes(_converse.chatboxviews.keys(),
'notify.example.com')
).toBeTruthy();
expect(_converse.showMessageNotification).toHaveBeenCalled();
done();
}));
it("is not shown for full JID headline messages if allow_non_roster_messaging is false", mock.initConverse((done, _converse) => {
_converse.allow_non_roster_messaging = false;
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
const stanza = $msg({
'type': 'headline',
'from': 'someone@notify.example.com',
'to': 'romeo@montague.lit',
'xml:lang': 'en'
})
.c('subject').t('SIEVE').up()
.c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(
_.includes(_converse.chatboxviews.keys(),
'someone@notify.example.com')
).toBeFalsy();
expect(_converse.showMessageNotification).not.toHaveBeenCalled();
done();
}));
it("is shown when a user changes their chat state (if show_chat_state_notifications is true)",
mock.initConverse(['rosterGroupsFetched'], {show_chat_state_notifications: true},
async (done, _converse) => {
await mock.waitForRoster(_converse, 'current', 3);
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'showChatStateNotification');
const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'busy'); // This will emit 'contactStatusChanged'
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
expect(_converse.showChatStateNotification).toHaveBeenCalled();
done()
}));
});
});
describe("When a new contact request is received", function () {
it("an HTML5 Notification is received", mock.initConverse((done, _converse) => {
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'showContactRequestNotification');
_converse.api.trigger('contactRequest', {'fullname': 'Peter Parker', 'jid': 'peter@parker.com'});
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
expect(_converse.showContactRequestNotification).toHaveBeenCalled();
done();
}));
});
});
describe("When play_sounds is set to true", function () {
describe("A notification sound", function () {
it("is played when the current user is mentioned in a groupchat",
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
mock.createContacts(_converse, 'current');
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
_converse.play_sounds = true;
spyOn(_converse, 'playSoundNotification');
const view = _converse.chatboxviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea();
}
let text = 'This message will play a sound because it mentions romeo';
let message = $msg({
from: 'lounge@montague.lit/otheruser',
id: '1',
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
await u.waitUntil(() => _converse.playSoundNotification.calls.count());
expect(_converse.playSoundNotification).toHaveBeenCalled();
text = "This message won't play a sound";
message = $msg({
from: 'lounge@montague.lit/otheruser',
id: '2',
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false;
text = "This message won't play a sound because it is sent by romeo";
message = $msg({
from: 'lounge@montague.lit/romeo',
id: '3',
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(text);
await view.model.queueMessage(message.nodeTree);
expect(_converse.playSoundNotification, 1);
_converse.play_sounds = false;
done();
}));
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,34 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const Strophe = converse.env.Strophe;
const u = converse.env.utils;
/*global mock */
const Strophe = converse.env.Strophe;
const u = converse.env.utils;
describe("XMPP Ping", function () {
describe("XMPP Ping", function () {
describe("An IQ stanza", function () {
describe("An IQ stanza", function () {
it("is returned when converse.js gets pinged", mock.initConverse((done, _converse) => {
const ping = u.toStanza(`
<iq from="${_converse.domain}"
to="${_converse.jid}" id="s2c1" type="get">
<ping xmlns="urn:xmpp:ping"/>
</iq>`);
_converse.connection._dataRecv(test_utils.createRequest(ping));
const sent_stanza = _converse.connection.IQ_stanzas.pop();
expect(Strophe.serialize(sent_stanza)).toBe(
`<iq id="s2c1" to="${_converse.domain}" type="result" xmlns="jabber:client"/>`);
done();
}));
it("is returned when converse.js gets pinged", mock.initConverse((done, _converse) => {
const ping = u.toStanza(`
<iq from="${_converse.domain}"
to="${_converse.jid}" id="s2c1" type="get">
<ping xmlns="urn:xmpp:ping"/>
</iq>`);
_converse.connection._dataRecv(mock.createRequest(ping));
const sent_stanza = _converse.connection.IQ_stanzas.pop();
expect(Strophe.serialize(sent_stanza)).toBe(
`<iq id="s2c1" to="${_converse.domain}" type="result" xmlns="jabber:client"/>`);
done();
}));
it("is sent out when converse.js pings a server", mock.initConverse((done, _converse) => {
_converse.api.ping();
const sent_stanza = _converse.connection.IQ_stanzas.pop();
expect(Strophe.serialize(sent_stanza)).toBe(
`<iq id="${sent_stanza.getAttribute('id')}" to="montague.lit" type="get" xmlns="jabber:client">`+
`<ping xmlns="urn:xmpp:ping"/>`+
`</iq>`);
done();
}));
});
it("is sent out when converse.js pings a server", mock.initConverse((done, _converse) => {
_converse.api.ping();
const sent_stanza = _converse.connection.IQ_stanzas.pop();
expect(Strophe.serialize(sent_stanza)).toBe(
`<iq id="${sent_stanza.getAttribute('id')}" to="montague.lit" type="get" xmlns="jabber:client">`+
`<ping xmlns="urn:xmpp:ping"/>`+
`</iq>`);
done();
}));
});
});

View File

@ -1,290 +1,287 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const Strophe = converse.env.Strophe;
const u = converse.env.utils;
// See: https://xmpp.org/rfcs/rfc3921.html
/*global mock */
// See: https://xmpp.org/rfcs/rfc3921.html
describe("A sent presence stanza", function () {
describe("A sent presence stanza", function () {
it("includes a entity capabilities node",
mock.initConverse(
['rosterGroupsFetched'], {},
(done, _converse) => {
it("includes a entity capabilities node",
mock.initConverse(
['rosterGroupsFetched'], {},
(done, _converse) => {
_converse.api.disco.own.identities.clear();
_converse.api.disco.own.features.clear();
_converse.api.disco.own.identities.clear();
_converse.api.disco.own.features.clear();
_converse.api.disco.own.identities.add("client", "pc", "Exodus 0.9.1");
_converse.api.disco.own.features.add("http://jabber.org/protocol/caps");
_converse.api.disco.own.features.add("http://jabber.org/protocol/disco#info");
_converse.api.disco.own.features.add("http://jabber.org/protocol/disco#items");
_converse.api.disco.own.features.add("http://jabber.org/protocol/muc");
_converse.api.disco.own.identities.add("client", "pc", "Exodus 0.9.1");
_converse.api.disco.own.features.add("http://jabber.org/protocol/caps");
_converse.api.disco.own.features.add("http://jabber.org/protocol/disco#info");
_converse.api.disco.own.features.add("http://jabber.org/protocol/disco#items");
_converse.api.disco.own.features.add("http://jabber.org/protocol/muc");
const presence = _converse.xmppstatus.constructPresence();
expect(presence.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="QgayPKawpkPSDYmwT/WM94uAlu0=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`)
done();
}));
const presence = _converse.xmppstatus.constructPresence();
expect(presence.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="QgayPKawpkPSDYmwT/WM94uAlu0=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`)
done();
}));
it("has a given priority", mock.initConverse((done, _converse) => {
let pres = _converse.xmppstatus.constructPresence('online', null, 'Hello world');
expect(pres.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+
`<status>Hello world</status>`+
it("has a given priority", mock.initConverse((done, _converse) => {
let pres = _converse.xmppstatus.constructPresence('online', null, 'Hello world');
expect(pres.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+
`<status>Hello world</status>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`
);
_converse.priority = 2;
pres = _converse.xmppstatus.constructPresence('away', null, 'Going jogging');
expect(pres.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+
`<show>away</show>`+
`<status>Going jogging</status>`+
`<priority>2</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`
);
delete _converse.priority;
pres = _converse.xmppstatus.constructPresence('dnd', null, 'Doing taxes');
expect(pres.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+
`<show>dnd</show>`+
`<status>Doing taxes</status>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`
);
done();
}));
it("includes the saved status message",
mock.initConverse(
['rosterGroupsFetched'], {},
async (done, _converse) => {
const { u, Strophe } = converse.env;
mock.openControlBox(_converse);
spyOn(_converse.connection, 'send').and.callThrough();
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click()
const modal = _converse.xmppstatusview.status_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const msg = 'My custom status';
modal.el.querySelector('input[name="status_message"]').value = msg;
modal.el.querySelector('[type="submit"]').click();
const sent_stanzas = _converse.connection.sent_stanzas;
let sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
expect(Strophe.serialize(sent_presence))
.toBe(`<presence xmlns="jabber:client">`+
`<status>My custom status</status>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`
);
_converse.priority = 2;
pres = _converse.xmppstatus.constructPresence('away', null, 'Going jogging');
expect(pres.toLocaleString()).toBe(
`<presence xmlns="jabber:client">`+
`<show>away</show>`+
`<status>Going jogging</status>`+
`<priority>2</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`
);
`</presence>`)
delete _converse.priority;
pres = _converse.xmppstatus.constructPresence('dnd', null, 'Doing taxes');
expect(pres.toLocaleString()).toBe(
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "true");
await u.waitUntil(() => !u.isVisible(modal.el));
cbview.el.querySelector('.change-status').click()
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000);
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
modal.el.querySelector('[type="submit"]').click();
await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).length === 2);
sent_presence = sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop();
expect(Strophe.serialize(sent_presence))
.toBe(
`<presence xmlns="jabber:client">`+
`<show>dnd</show>`+
`<status>Doing taxes</status>`+
`<status>My custom status</status>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`
);
done();
}));
it("includes the saved status message",
mock.initConverse(
['rosterGroupsFetched'], {},
async (done, _converse) => {
test_utils.openControlBox(_converse);
spyOn(_converse.connection, 'send').and.callThrough();
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click()
const modal = _converse.xmppstatusview.status_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const msg = 'My custom status';
modal.el.querySelector('input[name="status_message"]').value = msg;
modal.el.querySelector('[type="submit"]').click();
const sent_stanzas = _converse.connection.sent_stanzas;
let sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop());
expect(Strophe.serialize(sent_presence))
.toBe(`<presence xmlns="jabber:client">`+
`<status>My custom status</status>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`)
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "true");
await u.waitUntil(() => !u.isVisible(modal.el));
cbview.el.querySelector('.change-status').click()
await u.waitUntil(() => modal.el.getAttribute('aria-hidden') === "false", 1000);
modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd"
modal.el.querySelector('[type="submit"]').click();
await u.waitUntil(() => sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).length === 2);
sent_presence = sent_stanzas.filter(s => Strophe.serialize(s).match('presence')).pop();
expect(Strophe.serialize(sent_presence))
.toBe(
`<presence xmlns="jabber:client">`+
`<show>dnd</show>`+
`<status>My custom status</status>`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`)
done();
}));
});
describe("A received presence stanza", function () {
it("has its priority taken into account",
mock.initConverse(
['rosterGroupsFetched'], {},
async (done, _converse) => {
test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[8].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = await _converse.api.contacts.get(contact_jid);
let stanza = u.toStanza(`
<presence xmlns="jabber:client"
to="romeo@montague.lit/converse.js-21770972"
from="${contact_jid}/priority-1-resource">
<priority>1</priority>
<c xmlns="http://jabber.org/protocol/caps" hash="sha-1" ext="voice-v1 camera-v1 video-v1"
ver="AcN1/PEN8nq7AHD+9jpxMV4U6YM=" node="http://pidgin.im/"/>
<x xmlns="vcard-temp:x:update">
<photo>ce51d94f7f22b87a21274abb93710b9eb7cc1c65</photo>
</x>
<delay xmlns="urn:xmpp:delay" stamp="2017-02-15T20:26:05Z" from="${contact_jid}/priority-1-resource"/>
</presence>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(contact.presence.get('show')).toBe('online');
expect(contact.presence.resources.length).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' from="'+contact_jid+'/priority-0-resource">'+
' <status/>'+
' <priority>0</priority>'+
' <show>xa</show>'+
' <c xmlns="http://jabber.org/protocol/caps" ver="GyIX/Kpa4ScVmsZCxRBboJlLAYU=" hash="sha-1"'+
' node="http://www.igniterealtime.org/projects/smack/"/>'+
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T17:02:24Z" from="'+contact_jid+'/priority-0-resource"/>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(contact.presence.get('show')).toBe('online');
expect(contact.presence.resources.length).toBe(2);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' from="'+contact_jid+'/priority-2-resource">'+
' <priority>2</priority>'+
' <show>dnd</show>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(contact.presence.get('show')).toBe('dnd');
expect(contact.presence.resources.length).toBe(3);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('priority-2-resource').get('priority')).toBe(2);
expect(contact.presence.resources.get('priority-2-resource').get('show')).toBe('dnd');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' from="'+contact_jid+'/priority-3-resource">'+
' <priority>3</priority>'+
' <show>away</show>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('away');
expect(contact.presence.resources.length).toBe(4);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('priority-2-resource').get('priority')).toBe(2);
expect(contact.presence.resources.get('priority-2-resource').get('show')).toBe('dnd');
expect(contact.presence.resources.get('priority-3-resource').get('priority')).toBe(3);
expect(contact.presence.resources.get('priority-3-resource').get('show')).toBe('away');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' from="'+contact_jid+'/older-priority-1-resource">'+
' <priority>1</priority>'+
' <show>dnd</show>'+
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T15:02:24Z" from="'+contact_jid+'/older-priority-1-resource"/>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('away');
expect(contact.presence.resources.length).toBe(5);
expect(contact.presence.resources.get('older-priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('older-priority-1-resource').get('show')).toBe('dnd');
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('priority-2-resource').get('priority')).toBe(2);
expect(contact.presence.resources.get('priority-2-resource').get('show')).toBe('dnd');
expect(contact.presence.resources.get('priority-3-resource').get('priority')).toBe(3);
expect(contact.presence.resources.get('priority-3-resource').get('show')).toBe('away');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-3-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('dnd');
expect(contact.presence.resources.length).toBe(4);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('priority-2-resource').get('priority')).toBe(2);
expect(contact.presence.resources.get('priority-2-resource').get('show')).toBe('dnd');
expect(contact.presence.resources.get('older-priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('older-priority-1-resource').get('show')).toBe('dnd');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-2-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('online');
expect(contact.presence.resources.length).toBe(3);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('older-priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('older-priority-1-resource').get('show')).toBe('dnd');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-1-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('dnd');
expect(contact.presence.resources.length).toBe(2);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('older-priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('older-priority-1-resource').get('show')).toBe('dnd');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/older-priority-1-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('xa');
expect(contact.presence.resources.length).toBe(1);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-0-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('offline');
expect(contact.presence.resources.length).toBe(0);
done();
}));
});
`</presence>`)
done();
}));
});
describe("A received presence stanza", function () {
it("has its priority taken into account",
mock.initConverse(
['rosterGroupsFetched'], {},
async (done, _converse) => {
const u = converse.env.utils;
mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[8].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = await _converse.api.contacts.get(contact_jid);
let stanza = u.toStanza(`
<presence xmlns="jabber:client"
to="romeo@montague.lit/converse.js-21770972"
from="${contact_jid}/priority-1-resource">
<priority>1</priority>
<c xmlns="http://jabber.org/protocol/caps" hash="sha-1" ext="voice-v1 camera-v1 video-v1"
ver="AcN1/PEN8nq7AHD+9jpxMV4U6YM=" node="http://pidgin.im/"/>
<x xmlns="vcard-temp:x:update">
<photo>ce51d94f7f22b87a21274abb93710b9eb7cc1c65</photo>
</x>
<delay xmlns="urn:xmpp:delay" stamp="2017-02-15T20:26:05Z" from="${contact_jid}/priority-1-resource"/>
</presence>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(contact.presence.get('show')).toBe('online');
expect(contact.presence.resources.length).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' from="'+contact_jid+'/priority-0-resource">'+
' <status/>'+
' <priority>0</priority>'+
' <show>xa</show>'+
' <c xmlns="http://jabber.org/protocol/caps" ver="GyIX/Kpa4ScVmsZCxRBboJlLAYU=" hash="sha-1"'+
' node="http://www.igniterealtime.org/projects/smack/"/>'+
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T17:02:24Z" from="'+contact_jid+'/priority-0-resource"/>'+
'</presence>');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(contact.presence.get('show')).toBe('online');
expect(contact.presence.resources.length).toBe(2);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' from="'+contact_jid+'/priority-2-resource">'+
' <priority>2</priority>'+
' <show>dnd</show>'+
'</presence>');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(contact.presence.get('show')).toBe('dnd');
expect(contact.presence.resources.length).toBe(3);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('priority-2-resource').get('priority')).toBe(2);
expect(contact.presence.resources.get('priority-2-resource').get('show')).toBe('dnd');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' from="'+contact_jid+'/priority-3-resource">'+
' <priority>3</priority>'+
' <show>away</show>'+
'</presence>');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('away');
expect(contact.presence.resources.length).toBe(4);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('priority-2-resource').get('priority')).toBe(2);
expect(contact.presence.resources.get('priority-2-resource').get('show')).toBe('dnd');
expect(contact.presence.resources.get('priority-3-resource').get('priority')).toBe(3);
expect(contact.presence.resources.get('priority-3-resource').get('show')).toBe('away');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' from="'+contact_jid+'/older-priority-1-resource">'+
' <priority>1</priority>'+
' <show>dnd</show>'+
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T15:02:24Z" from="'+contact_jid+'/older-priority-1-resource"/>'+
'</presence>');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('away');
expect(contact.presence.resources.length).toBe(5);
expect(contact.presence.resources.get('older-priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('older-priority-1-resource').get('show')).toBe('dnd');
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('priority-2-resource').get('priority')).toBe(2);
expect(contact.presence.resources.get('priority-2-resource').get('show')).toBe('dnd');
expect(contact.presence.resources.get('priority-3-resource').get('priority')).toBe(3);
expect(contact.presence.resources.get('priority-3-resource').get('show')).toBe('away');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-3-resource">'+
'</presence>');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('dnd');
expect(contact.presence.resources.length).toBe(4);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('priority-2-resource').get('priority')).toBe(2);
expect(contact.presence.resources.get('priority-2-resource').get('show')).toBe('dnd');
expect(contact.presence.resources.get('older-priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('older-priority-1-resource').get('show')).toBe('dnd');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-2-resource">'+
'</presence>');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('online');
expect(contact.presence.resources.length).toBe(3);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('priority-1-resource').get('show')).toBe('online');
expect(contact.presence.resources.get('older-priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('older-priority-1-resource').get('show')).toBe('dnd');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-1-resource">'+
'</presence>');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('dnd');
expect(contact.presence.resources.length).toBe(2);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
expect(contact.presence.resources.get('older-priority-1-resource').get('priority')).toBe(1);
expect(contact.presence.resources.get('older-priority-1-resource').get('show')).toBe('dnd');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/older-priority-1-resource">'+
'</presence>');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('xa');
expect(contact.presence.resources.length).toBe(1);
expect(contact.presence.resources.get('priority-0-resource').get('priority')).toBe(0);
expect(contact.presence.resources.get('priority-0-resource').get('show')).toBe('xa');
stanza = u.toStanza(
'<presence xmlns="jabber:client"'+
' to="romeo@montague.lit/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-0-resource">'+
'</presence>');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(_converse.roster.get(contact_jid).presence.get('show')).toBe('offline');
expect(contact.presence.resources.length).toBe(0);
done();
}));
});

View File

@ -1,131 +0,0 @@
(function (root, factory) {
define(["jasmine", "mock", "test-utils"], factory);
} (this, function (jasmine, mock, test_utils) {
var _ = converse.env._;
var $iq = converse.env.$iq;
var $pres = converse.env.$pres;
var u = converse.env.utils;
describe("Profiling", function() {
it("shows users currently present in the groupchat",
mock.initConverse(
['rosterGroupsFetched'], {'muc_show_join_leave': false},
async function (done, _converse) {
test_utils.openControlBox(_converse);
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
_.rangeRight(3000, 0).forEach(i => {
const name = `User ${i.toString().padStart(5, '0')}`;
const presence = $pres({
'to': 'romeo@montague.lit/orchard',
'from': 'lounge@montague.lit/'+name
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'none',
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
// expect(occupants.querySelectorAll('li').length).toBe(1+i);
// const model = view.model.occupants.where({'nick': name})[0];
// const index = view.model.occupants.indexOf(model);
// expect(occupants.querySelectorAll('li .occupant-nick')[index].textContent.trim()).toBe(name);
});
done();
}));
xit("adds hundreds of contacts to the roster",
mock.initConverse(
['rosterGroupsFetched'], {},
function (done, _converse) {
_converse.roster_groups = false;
test_utils.openControlBox(_converse);
expect(_converse.roster.pluck('jid').length).toBe(0);
var stanza = $iq({
to: _converse.connection.jid,
type: 'result',
id: 'roster_1'
}).c('query', {
xmlns: 'jabber:iq:roster'
});
_.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) {
var i;
for (i=0; i<50; i++) {
stanza = stanza.c('item', {
jid: Math.random().toString().replace('0.', '')+'@example.net',
subscription:'both'
}).c('group').t(group).up().up();
}
});
_converse.roster.onReceivedFromServer(stanza.tree());
return u.waitUntil(function () {
var $group = _converse.rosterview.$el.find('.roster-group')
return $group.length && u.isVisible($group[0]);
}).then(function () {
var count = 0;
_converse.roster.each(function (contact) {
if (count < 10) {
contact.set('chat_status', 'online');
count += 1;
}
});
return u.waitUntil(function () {
return _converse.rosterview.$el.find('li.online').length
})
}).then(done);
}));
xit("adds hundreds of contacts to the roster, with roster groups",
mock.initConverse(
['rosterGroupsFetched'], {},
function (done, _converse) {
// _converse.show_only_online_users = true;
_converse.roster_groups = true;
test_utils.openControlBox(_converse);
expect(_converse.roster.pluck('jid').length).toBe(0);
var stanza = $iq({
to: _converse.connection.jid,
type: 'result',
id: 'roster_1'
}).c('query', {
xmlns: 'jabber:iq:roster'
});
_.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) {
var i;
for (i=0; i<100; i++) {
stanza = stanza.c('item', {
jid: Math.random().toString().replace('0.', '')+'@example.net',
subscription:'both'
}).c('group').t(group).up().up();
}
});
_converse.roster.onReceivedFromServer(stanza.tree());
return u.waitUntil(function () {
var $group = _converse.rosterview.$el.find('.roster-group')
return $group.length && u.isVisible($group[0]);
}).then(function () {
_.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) {
var count = 0;
_converse.roster.each(function (contact) {
if (_.includes(contact.get('groups'), group)) {
if (count < 10) {
contact.set('chat_status', 'online');
count += 1;
}
}
});
});
return u.waitUntil(function () {
return _converse.rosterview.$el.find('li.online').length
})
}).then(done);
}));
});
}));

File diff suppressed because it is too large Load Diff

View File

@ -1,191 +1,189 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const $iq = converse.env.$iq;
const Strophe = converse.env.Strophe;
const _ = converse.env._;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
/*global mock */
describe("XEP-0357 Push Notifications", function () {
const $iq = converse.env.$iq;
const Strophe = converse.env.Strophe;
const _ = converse.env._;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
it("can be enabled",
mock.initConverse(
['rosterGroupsFetched'], {
'push_app_servers': [{
'jid': 'push-5@client.example',
'node': 'yxs32uqsflafdk3iuqo'
}]
}, async function (done, _converse) {
describe("XEP-0357 Push Notifications", function () {
const IQ_stanzas = _converse.connection.IQ_stanzas;
expect(_converse.session.get('push_enabled')).toBeFalsy();
it("can be enabled",
mock.initConverse(
['rosterGroupsFetched'], {
'push_app_servers': [{
'jid': 'push-5@client.example',
'node': 'yxs32uqsflafdk3iuqo'
}]
}, async function (done, _converse) {
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.push_app_servers[0].jid,
[{'category': 'pubsub', 'type':'push'}],
['urn:xmpp:push:0'], [], 'info');
await test_utils.waitUntilDiscoConfirmed(
_converse,
_converse.bare_jid,
[{'category': 'account', 'type':'registered'}],
['urn:xmpp:push:0'], [], 'info');
const stanza = await u.waitUntil(() =>
_.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
);
expect(Strophe.serialize(stanza)).toEqual(
`<iq id="${stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
'<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>'+
'</iq>'
)
_converse.connection._dataRecv(test_utils.createRequest($iq({
'to': _converse.connection.jid,
'type': 'result',
'id': stanza.getAttribute('id')
})));
await u.waitUntil(() => _converse.session.get('push_enabled'));
done();
}));
const IQ_stanzas = _converse.connection.IQ_stanzas;
expect(_converse.session.get('push_enabled')).toBeFalsy();
it("can be enabled for a MUC domain",
mock.initConverse(
['rosterGroupsFetched'], {
'enable_muc_push': true,
'push_app_servers': [{
'jid': 'push-5@client.example',
'node': 'yxs32uqsflafdk3iuqo'
}]
}, async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas;
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.push_app_servers[0].jid,
[{'category': 'pubsub', 'type':'push'}],
['urn:xmpp:push:0'], [], 'info');
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid, [],
['urn:xmpp:push:0']);
let iq = await u.waitUntil(() => _.filter(
IQ_stanzas,
iq => sizzle(`iq[type="set"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
).pop());
expect(Strophe.serialize(iq)).toBe(
`<iq id="${iq.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>`+
`</iq>`
);
const result = u.toStanza(`<iq type="result" id="${iq.getAttribute('id')}" to="romeo@montague.lit" />`);
_converse.connection._dataRecv(test_utils.createRequest(result));
await u.waitUntil(() => _converse.session.get('push_enabled'));
expect(_converse.session.get('push_enabled').length).toBe(1);
expect(_.includes(_converse.session.get('push_enabled'), 'romeo@montague.lit')).toBe(true);
test_utils.openAndEnterChatRoom(_converse, 'coven@chat.shakespeare.lit', 'oldhag');
await test_utils.waitUntilDiscoConfirmed(
_converse, 'chat.shakespeare.lit',
[{'category': 'account', 'type':'registered'}],
['urn:xmpp:push:0'], [], 'info');
iq = await u.waitUntil(() => _.filter(
IQ_stanzas,
iq => sizzle(`iq[type="set"][to="chat.shakespeare.lit"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
).pop());
expect(Strophe.serialize(iq)).toEqual(
`<iq id="${iq.getAttribute('id')}" to="chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
'<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>'+
'</iq>'
);
_converse.connection._dataRecv(test_utils.createRequest($iq({
'to': _converse.connection.jid,
'type': 'result',
'id': iq.getAttribute('id')
})));
await u.waitUntil(() => _.includes(_converse.session.get('push_enabled'), 'chat.shakespeare.lit'));
done();
}));
it("can be disabled",
mock.initConverse(
['rosterGroupsFetched'], {
'push_app_servers': [{
'jid': 'push-5@client.example',
'node': 'yxs32uqsflafdk3iuqo',
'disable': true
}]
}, async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas;
expect(_converse.session.get('push_enabled')).toBeFalsy();
await test_utils.waitUntilDiscoConfirmed(
await mock.waitUntilDiscoConfirmed(
_converse, _converse.push_app_servers[0].jid,
[{'category': 'pubsub', 'type':'push'}],
['urn:xmpp:push:0'], [], 'info');
await mock.waitUntilDiscoConfirmed(
_converse,
_converse.bare_jid,
[{'category': 'account', 'type':'registered'}],
['urn:xmpp:push:0'], [], 'info');
const stanza = await u.waitUntil(
() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] disable[xmlns="urn:xmpp:push:0"]')).pop()
);
expect(Strophe.serialize(stanza)).toEqual(
`<iq id="${stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
'<disable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>'+
'</iq>'
);
_converse.connection._dataRecv(test_utils.createRequest($iq({
'to': _converse.connection.jid,
'type': 'result',
'id': stanza.getAttribute('id')
})));
await u.waitUntil(() => _converse.session.get('push_enabled'))
done();
}));
const stanza = await u.waitUntil(() =>
_.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
);
expect(Strophe.serialize(stanza)).toEqual(
`<iq id="${stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
'<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>'+
'</iq>'
)
_converse.connection._dataRecv(mock.createRequest($iq({
'to': _converse.connection.jid,
'type': 'result',
'id': stanza.getAttribute('id')
})));
await u.waitUntil(() => _converse.session.get('push_enabled'));
done();
}));
it("can be enabled for a MUC domain",
mock.initConverse(
['rosterGroupsFetched'], {
'enable_muc_push': true,
'push_app_servers': [{
'jid': 'push-5@client.example',
'node': 'yxs32uqsflafdk3iuqo'
}]
}, async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas;
await mock.waitUntilDiscoConfirmed(
_converse, _converse.push_app_servers[0].jid,
[{'category': 'pubsub', 'type':'push'}],
['urn:xmpp:push:0'], [], 'info');
await mock.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid, [],
['urn:xmpp:push:0']);
let iq = await u.waitUntil(() => _.filter(
IQ_stanzas,
iq => sizzle(`iq[type="set"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
).pop());
expect(Strophe.serialize(iq)).toBe(
`<iq id="${iq.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>`+
`</iq>`
);
const result = u.toStanza(`<iq type="result" id="${iq.getAttribute('id')}" to="romeo@montague.lit" />`);
_converse.connection._dataRecv(mock.createRequest(result));
await u.waitUntil(() => _converse.session.get('push_enabled'));
expect(_converse.session.get('push_enabled').length).toBe(1);
expect(_.includes(_converse.session.get('push_enabled'), 'romeo@montague.lit')).toBe(true);
mock.openAndEnterChatRoom(_converse, 'coven@chat.shakespeare.lit', 'oldhag');
await mock.waitUntilDiscoConfirmed(
_converse, 'chat.shakespeare.lit',
[{'category': 'account', 'type':'registered'}],
['urn:xmpp:push:0'], [], 'info');
iq = await u.waitUntil(() => _.filter(
IQ_stanzas,
iq => sizzle(`iq[type="set"][to="chat.shakespeare.lit"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
).pop());
expect(Strophe.serialize(iq)).toEqual(
`<iq id="${iq.getAttribute('id')}" to="chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
'<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>'+
'</iq>'
);
_converse.connection._dataRecv(mock.createRequest($iq({
'to': _converse.connection.jid,
'type': 'result',
'id': iq.getAttribute('id')
})));
await u.waitUntil(() => _.includes(_converse.session.get('push_enabled'), 'chat.shakespeare.lit'));
done();
}));
it("can be disabled",
mock.initConverse(
['rosterGroupsFetched'], {
'push_app_servers': [{
'jid': 'push-5@client.example',
'node': 'yxs32uqsflafdk3iuqo',
'disable': true
}]
}, async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas;
expect(_converse.session.get('push_enabled')).toBeFalsy();
await mock.waitUntilDiscoConfirmed(
_converse,
_converse.bare_jid,
[{'category': 'account', 'type':'registered'}],
['urn:xmpp:push:0'], [], 'info');
const stanza = await u.waitUntil(
() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] disable[xmlns="urn:xmpp:push:0"]')).pop()
);
expect(Strophe.serialize(stanza)).toEqual(
`<iq id="${stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
'<disable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>'+
'</iq>'
);
_converse.connection._dataRecv(mock.createRequest($iq({
'to': _converse.connection.jid,
'type': 'result',
'id': stanza.getAttribute('id')
})));
await u.waitUntil(() => _converse.session.get('push_enabled'))
done();
}));
it("can require a secret token to be included",
mock.initConverse(
['rosterGroupsFetched'], {
'push_app_servers': [{
'jid': 'push-5@client.example',
'node': 'yxs32uqsflafdk3iuqo',
'secret': 'eruio234vzxc2kla-91'
}]
}, async function (done, _converse) {
it("can require a secret token to be included",
mock.initConverse(
['rosterGroupsFetched'], {
'push_app_servers': [{
'jid': 'push-5@client.example',
'node': 'yxs32uqsflafdk3iuqo',
'secret': 'eruio234vzxc2kla-91'
}]
}, async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas;
expect(_converse.session.get('push_enabled')).toBeFalsy();
const IQ_stanzas = _converse.connection.IQ_stanzas;
expect(_converse.session.get('push_enabled')).toBeFalsy();
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.push_app_servers[0].jid,
[{'category': 'pubsub', 'type':'push'}],
await mock.waitUntilDiscoConfirmed(
_converse, _converse.push_app_servers[0].jid,
[{'category': 'pubsub', 'type':'push'}],
['urn:xmpp:push:0'], [], 'info');
await mock.waitUntilDiscoConfirmed(
_converse,
_converse.bare_jid,
[{'category': 'account', 'type':'registered'}],
['urn:xmpp:push:0'], [], 'info');
await test_utils.waitUntilDiscoConfirmed(
_converse,
_converse.bare_jid,
[{'category': 'account', 'type':'registered'}],
['urn:xmpp:push:0'], [], 'info');
const stanza = await u.waitUntil(
() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
);
expect(Strophe.serialize(stanza)).toEqual(
`<iq id="${stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
'<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0">'+
'<x type="submit" xmlns="jabber:x:data">'+
'<field var="FORM_TYPE"><value>http://jabber.org/protocol/pubsub#publish-options</value></field>'+
'<field var="secret"><value>eruio234vzxc2kla-91</value></field>'+
'</x>'+
'</enable>'+
'</iq>'
)
_converse.connection._dataRecv(test_utils.createRequest($iq({
'to': _converse.connection.jid,
'type': 'result',
'id': stanza.getAttribute('id')
})));
await u.waitUntil(() => _converse.session.get('push_enabled'))
done();
}));
});
const stanza = await u.waitUntil(
() => _.filter(IQ_stanzas, iq => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
);
expect(Strophe.serialize(stanza)).toEqual(
`<iq id="${stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
'<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0">'+
'<x type="submit" xmlns="jabber:x:data">'+
'<field var="FORM_TYPE"><value>http://jabber.org/protocol/pubsub#publish-options</value></field>'+
'<field var="secret"><value>eruio234vzxc2kla-91</value></field>'+
'</x>'+
'</enable>'+
'</iq>'
)
_converse.connection._dataRecv(mock.createRequest($iq({
'to': _converse.connection.jid,
'type': 'result',
'id': stanza.getAttribute('id')
})));
await u.waitUntil(() => _converse.session.get('push_enabled'))
done();
}));
});

View File

@ -1,365 +1,363 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const Strophe = converse.env.Strophe;
const $iq = converse.env.$iq;
const { _, sizzle} = converse.env;
const u = converse.env.utils;
/*global mock */
describe("The Registration Panel", function () {
const Strophe = converse.env.Strophe;
const $iq = converse.env.$iq;
const { _, sizzle} = converse.env;
const u = converse.env.utils;
it("is not available unless allow_registration=true",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
allow_registration: false },
async function (done, _converse) {
describe("The Registration Panel", function () {
await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
const cbview = _converse.api.controlbox.get();
expect(cbview.el.querySelectorAll('a.register-account').length).toBe(0);
done();
}));
it("is not available unless allow_registration=true",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
allow_registration: false },
async function (done, _converse) {
it("can be opened by clicking on the registration tab",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
allow_registration: true },
async function (done, _converse) {
await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
const cbview = _converse.api.controlbox.get();
expect(cbview.el.querySelectorAll('a.register-account').length).toBe(0);
done();
}));
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
it("can be opened by clicking on the registration tab",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
allow_registration: true },
async function (done, _converse) {
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'), 300);
const cbview = _converse.chatboxviews.get('controlbox');
const panels = cbview.el.querySelector('.controlbox-panes');
const login = panels.firstElementChild;
const registration = panels.childNodes[1];
const register_link = cbview.el.querySelector('a.register-account');
expect(register_link.textContent).toBe("Create an account");
register_link.click();
await u.waitUntil(() => u.isVisible(registration));
expect(u.isVisible(login)).toBe(false);
done();
}));
it("allows the user to choose an XMPP provider's domain",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
spyOn(Strophe.Connection.prototype, 'connect');
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const toggle = document.querySelector(".toggle-controlbox");
toggle.click();
}
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'), 300);
const cbview = _converse.chatboxviews.get('controlbox');
const panels = cbview.el.querySelector('.controlbox-panes');
const login = panels.firstElementChild;
const registration = panels.childNodes[1];
const register_link = cbview.el.querySelector('a.register-account');
expect(register_link.textContent).toBe("Create an account");
register_link.click();
const cbview = _converse.api.controlbox.get();
await u.waitUntil(() => u.isVisible(cbview.el));
const registerview = cbview.registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
await u.waitUntil(() => u.isVisible(registration));
expect(u.isVisible(login)).toBe(false);
done();
}));
// Open the register panel
cbview.el.querySelector('.toggle-register-login').click();
it("allows the user to choose an XMPP provider's domain",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
// Check the form layout
const form = cbview.el.querySelector('#converse-register');
expect(form.querySelectorAll('input').length).toEqual(2);
expect(form.querySelectorAll('input')[0].getAttribute('name')).toEqual('domain');
expect(sizzle('input:last', form).pop().getAttribute('type')).toEqual('submit');
// Check that the input[type=domain] input is required
const submit_button = form.querySelector('input[type=submit]');
submit_button.click();
expect(registerview.onProviderChosen).not.toHaveBeenCalled();
spyOn(Strophe.Connection.prototype, 'connect');
// Check that the form is accepted if input[type=domain] has a value
form.querySelector('input[name=domain]').value = 'conversejs.org';
submit_button.click();
expect(registerview.onProviderChosen).toHaveBeenCalled();
await u.waitUntil(() => _converse.connection.connect.calls.count());
done();
}));
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const toggle = document.querySelector(".toggle-controlbox");
toggle.click();
it("will render a registration form as received from the XMPP provider",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
const cbview = _converse.api.controlbox.get();
await u.waitUntil(() => u.isVisible(cbview.el));
const registerview = cbview.registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
spyOn(Strophe.Connection.prototype, 'connect');
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
// Open the register panel
cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
// Check the form layout
const form = cbview.el.querySelector('#converse-register');
expect(form.querySelectorAll('input').length).toEqual(2);
expect(form.querySelectorAll('input')[0].getAttribute('name')).toEqual('domain');
expect(sizzle('input:last', form).pop().getAttribute('type')).toEqual('submit');
// Check that the input[type=domain] input is required
const submit_button = form.querySelector('input[type=submit]');
submit_button.click();
expect(registerview.onProviderChosen).not.toHaveBeenCalled();
expect(registerview._registering).toBeFalsy();
expect(_converse.api.connection.connected()).toBeFalsy();
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
expect(registerview.onProviderChosen).toHaveBeenCalled();
expect(registerview._registering).toBeTruthy();
await u.waitUntil(() => _converse.connection.connect.calls.count());
// Check that the form is accepted if input[type=domain] has a value
form.querySelector('input[name=domain]').value = 'conversejs.org';
submit_button.click();
expect(registerview.onProviderChosen).toHaveBeenCalled();
await u.waitUntil(() => _converse.connection.connect.calls.count());
done();
}));
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
'xmlns': "jabber:client"
})
.c('register', {xmlns: "http://jabber.org/features/iq-register"}).up()
.c('mechanisms', {xmlns: "urn:ietf:params:xml:ns:xmpp-sasl"});
_converse.connection._connect_cb(test_utils.createRequest(stanza));
it("will render a registration form as received from the XMPP provider",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
expect(registerview.getRegistrationFields).toHaveBeenCalled();
spyOn(Strophe.Connection.prototype, 'connect');
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
stanza = $iq({
'type': 'result',
'id': 'reg1'
}).c('query', {'xmlns': 'jabber:iq:register'})
.c('instructions')
.t('Please choose a username, password and provide your email address').up()
.c('username').up()
.c('password').up()
.c('email');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(registerview.onRegistrationFields).toHaveBeenCalled();
expect(registerview.renderRegistrationForm).toHaveBeenCalled();
expect(registerview.el.querySelectorAll('input').length).toBe(5);
expect(registerview.el.querySelectorAll('input[type=submit]').length).toBe(1);
expect(registerview.el.querySelectorAll('input[type=button]').length).toBe(1);
done();
}));
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
it("will set form_type to legacy and submit it as legacy",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
expect(registerview._registering).toBeFalsy();
expect(_converse.api.connection.connected()).toBeFalsy();
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
expect(registerview.onProviderChosen).toHaveBeenCalled();
expect(registerview._registering).toBeTruthy();
await u.waitUntil(() => _converse.connection.connect.calls.count());
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
'xmlns': "jabber:client"
})
.c('register', {xmlns: "http://jabber.org/features/iq-register"}).up()
.c('mechanisms', {xmlns: "urn:ietf:params:xml:ns:xmpp-sasl"});
_converse.connection._connect_cb(mock.createRequest(stanza));
expect(registerview.getRegistrationFields).toHaveBeenCalled();
stanza = $iq({
'type': 'result',
'id': 'reg1'
}).c('query', {'xmlns': 'jabber:iq:register'})
.c('instructions')
.t('Please choose a username, password and provide your email address').up()
.c('username').up()
.c('password').up()
.c('email');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(registerview.onRegistrationFields).toHaveBeenCalled();
expect(registerview.renderRegistrationForm).toHaveBeenCalled();
expect(registerview.el.querySelectorAll('input').length).toBe(5);
expect(registerview.el.querySelectorAll('input[type=submit]').length).toBe(1);
expect(registerview.el.querySelectorAll('input[type=button]').length).toBe(1);
done();
}));
it("will set form_type to legacy and submit it as legacy",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
toggle.click();
}
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
const registerview = cbview.registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
const registerview = cbview.registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
'xmlns': "jabber:client"
})
.c('register', {xmlns: "http://jabber.org/features/iq-register"}).up()
.c('mechanisms', {xmlns: "urn:ietf:params:xml:ns:xmpp-sasl"});
_converse.connection._connect_cb(test_utils.createRequest(stanza));
stanza = $iq({
'type': 'result',
'id': 'reg1'
}).c('query', {'xmlns': 'jabber:iq:register'})
.c('instructions')
.t('Please choose a username, password and provide your email address').up()
.c('username').up()
.c('password').up()
.c('email');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(registerview.form_type).toBe('legacy');
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
'xmlns': "jabber:client"
})
.c('register', {xmlns: "http://jabber.org/features/iq-register"}).up()
.c('mechanisms', {xmlns: "urn:ietf:params:xml:ns:xmpp-sasl"});
_converse.connection._connect_cb(mock.createRequest(stanza));
stanza = $iq({
'type': 'result',
'id': 'reg1'
}).c('query', {'xmlns': 'jabber:iq:register'})
.c('instructions')
.t('Please choose a username, password and provide your email address').up()
.c('username').up()
.c('password').up()
.c('email');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(registerview.form_type).toBe('legacy');
registerview.el.querySelector('input[name=username]').value = 'testusername';
registerview.el.querySelector('input[name=password]').value = 'testpassword';
registerview.el.querySelector('input[name=email]').value = 'test@email.local';
registerview.el.querySelector('input[name=username]').value = 'testusername';
registerview.el.querySelector('input[name=password]').value = 'testpassword';
registerview.el.querySelector('input[name=email]').value = 'test@email.local';
spyOn(_converse.connection, 'send');
registerview.el.querySelector('input[type=submit]').click();
spyOn(_converse.connection, 'send');
registerview.el.querySelector('input[type=submit]').click();
expect(_converse.connection.send).toHaveBeenCalled();
stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
expect(stanza.querySelector('query').childNodes.length).toBe(3);
expect(stanza.querySelector('query').firstElementChild.tagName).toBe('username');
expect(_converse.connection.send).toHaveBeenCalled();
stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
expect(stanza.querySelector('query').childNodes.length).toBe(3);
expect(stanza.querySelector('query').firstElementChild.tagName).toBe('username');
delete _converse.connection;
done();
}));
delete _converse.connection;
done();
}));
it("will set form_type to xform and submit it as xform",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
it("will set form_type to xform and submit it as xform",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
toggle.click();
}
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
'xmlns': "jabber:client"
})
.c('register', {xmlns: "http://jabber.org/features/iq-register"}).up()
.c('mechanisms', {xmlns: "urn:ietf:params:xml:ns:xmpp-sasl"});
_converse.connection._connect_cb(test_utils.createRequest(stanza));
stanza = $iq({
'type': 'result',
'id': 'reg1'
}).c('query', {'xmlns': 'jabber:iq:register'})
.c('instructions')
.t('Using xform data').up()
.c('x', { 'xmlns': 'jabber:x:data', 'type': 'form' })
.c('instructions').t('xform instructions').up()
.c('field', {'type': 'text-single', 'var': 'username'}).c('required').up().up()
.c('field', {'type': 'text-private', 'var': 'password'}).c('required').up().up()
.c('field', {'type': 'text-single', 'var': 'email'}).c('required').up().up();
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(registerview.form_type).toBe('xform');
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
'xmlns': "jabber:client"
})
.c('register', {xmlns: "http://jabber.org/features/iq-register"}).up()
.c('mechanisms', {xmlns: "urn:ietf:params:xml:ns:xmpp-sasl"});
_converse.connection._connect_cb(mock.createRequest(stanza));
stanza = $iq({
'type': 'result',
'id': 'reg1'
}).c('query', {'xmlns': 'jabber:iq:register'})
.c('instructions')
.t('Using xform data').up()
.c('x', { 'xmlns': 'jabber:x:data', 'type': 'form' })
.c('instructions').t('xform instructions').up()
.c('field', {'type': 'text-single', 'var': 'username'}).c('required').up().up()
.c('field', {'type': 'text-private', 'var': 'password'}).c('required').up().up()
.c('field', {'type': 'text-single', 'var': 'email'}).c('required').up().up();
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(registerview.form_type).toBe('xform');
registerview.el.querySelector('input[name=username]').value = 'testusername';
registerview.el.querySelector('input[name=password]').value = 'testpassword';
registerview.el.querySelector('input[name=email]').value = 'test@email.local';
registerview.el.querySelector('input[name=username]').value = 'testusername';
registerview.el.querySelector('input[name=password]').value = 'testpassword';
registerview.el.querySelector('input[name=email]').value = 'test@email.local';
spyOn(_converse.connection, 'send');
spyOn(_converse.connection, 'send');
registerview.el.querySelector('input[type=submit]').click();
registerview.el.querySelector('input[type=submit]').click();
expect(_converse.connection.send).toHaveBeenCalled();
stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
expect(Strophe.serialize(stanza).toLocaleString().trim().replace(/(\n|\s{2,})/g, '')).toEqual(
'<iq id="'+stanza.getAttribute('id')+'" type="set" xmlns="jabber:client">'+
'<query xmlns="jabber:iq:register">'+
'<x type="submit" xmlns="jabber:x:data">'+
'<field var="username">'+
'<value>testusername</value>'+
'</field>'+
'<field var="password">'+
'<value>testpassword</value>'+
'</field>'+
'<field var="email">'+
'<value>test@email.local</value>'+
'</field>'+
'</x>'+
'</query>'+
'</iq>'
);
expect(_converse.connection.send).toHaveBeenCalled();
stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
expect(Strophe.serialize(stanza).toLocaleString().trim().replace(/(\n|\s{2,})/g, '')).toEqual(
'<iq id="'+stanza.getAttribute('id')+'" type="set" xmlns="jabber:client">'+
'<query xmlns="jabber:iq:register">'+
'<x type="submit" xmlns="jabber:x:data">'+
'<field var="username">'+
'<value>testusername</value>'+
'</field>'+
'<field var="password">'+
'<value>testpassword</value>'+
'</field>'+
'<field var="email">'+
'<value>test@email.local</value>'+
'</field>'+
'</x>'+
'</query>'+
'</iq>'
);
delete _converse.connection;
done();
}));
delete _converse.connection;
done();
}));
it("renders the account registration form",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
view_mode: 'fullscreen',
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
it("renders the account registration form",
mock.initConverse(
['chatBoxesInitialized'],
{ auto_login: false,
view_mode: 'fullscreen',
discover_connection_methods: false,
allow_registration: true },
async function (done, _converse) {
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
toggle.click();
}
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
spyOn(registerview, 'getRegistrationFields').and.callThrough();
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click();
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
'xmlns': "jabber:client"
})
.c('register', {xmlns: "http://jabber.org/features/iq-register"}).up()
.c('mechanisms', {xmlns: "urn:ietf:params:xml:ns:xmpp-sasl"});
_converse.connection._connect_cb(test_utils.createRequest(stanza));
let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams",
'xmlns': "jabber:client"
})
.c('register', {xmlns: "http://jabber.org/features/iq-register"}).up()
.c('mechanisms', {xmlns: "urn:ietf:params:xml:ns:xmpp-sasl"});
_converse.connection._connect_cb(mock.createRequest(stanza));
stanza = u.toStanza(`
<iq xmlns="jabber:client" type="result" from="conversations.im" id="ad1e0d50-5adb-4397-a997-5feab56fe418:sendIQ" xml:lang="en">
<query xmlns="jabber:iq:register">
<x xmlns="jabber:x:data" type="form">
<instructions>Choose a username and password to register with this server</instructions>
<field var="FORM_TYPE" type="hidden"><value>urn:xmpp:captcha</value></field>
<field var="username" type="text-single" label="User"><required/></field>
<field var="password" type="text-private" label="Password"><required/></field>
<field var="from" type="hidden"><value>conversations.im</value></field>
<field var="challenge" type="hidden"><value>15376320046808160053</value></field>
<field var="sid" type="hidden"><value>ad1e0d50-5adb-4397-a997-5feab56fe418:sendIQ</value></field>
<field var="ocr" type="text-single" label="Enter the text you see">
<media xmlns="urn:xmpp:media-element">
<uri type="image/png">cid:sha1+2df8c1b366f1e90ce60354f97d1fe75237290b8a@bob.xmpp.org</uri>
</media>
<required/>
</field>
</x>
<data xmlns="urn:xmpp:bob" cid="sha1+2df8c1b366f1e90ce60354f97d1fe75237290b8a@bob.xmpp.org"
type="image/png"
max-age="0">iVBORw0KGgoAAAANSUhEUgAAALQAAAA8BAMAAAA9AI20AAAAMFBMVEX///8AAADf39+fn59fX19/f3+/v78fHx8/Pz9PT08bGxsvLy9jY2NTU1MXFxcnJyc84bkWAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAERUlEQVRYhe1WTXMaRxDdDxY4JWpYvDinpVyxdATLin0MiRLlCHEi+7hYUcVHTSI7urhK6yr5//gn5N/4Z7inX89+CQkTcFUO6gOwS8/r7tdvesbzvoT5ROR5JJ9bB97xAK22XWAY1WznlnUr7QaAzSOsWufXQ6wH/FmO60b4D936LJr8TWRwW4SNgOsodZr8m4vZUoRt2xZ3xHXgna1FCE5+f5aWwPU//bXgg8eHjyqPp4aXJeOlwLUIt0O39zOvPWW3WfHmCCkli816FxlK0rnFGKZ484dN+eIXsw1R+G+JfjwgOpMnm+r5SxA63gS2Q8MchO1RLN8jSn4W4F5OPed2evhTthKLG3bsfjLL874XGBpWHLrU0953i/ev7JsfViHbhsWSQTunJDOppeAe0hVGokJUHBOphmjrbBlgabviJKXbIP0B//gKSBHZh2rvJnQp3wsapMFz+VsTPNhPr0Hn9N57YOjywaxFSU6S79fUF39KBDgnt6yjZOeSffk+4IXDZovbQl9E96m34EzQKMepQcbzijAGiBmDsO+LaqzqG3m3kEf+DQ2mY+vdk5c2n2Iaj5QGi6n59FHDmcuP4t8MGlRaF39P6ENyIaB2EXdpjLnQq9IgdVxfax3ilBc10u4gowX9K6BaKiZNmCC7CF/WpkJvWxN00OjuoqGYLqAnpILLE68Ymrt9M0S9hcznUJ8RykdlLalUfFaDjvA8pT2kxmsl5fuMaM6mSWUpUhDoudSucdhiZFDwphEHwsMwhEpH0jsm+/UBK2wCzFIiitalN7YjWkyIBgTNPgpDXX4rjk4UH+yPPgfK4HNZQCP/KZ0fGnrnKl8+pXl3X7FwZuwNUdwDGO+BjPUn6XaKtbkm+MJ6vtaXSnIz6wBT/m+VvZNIhz7ayabQLSeRQDmYkjt0KlmHDa555v9DzFxx+CCvCG4K3dbx6mTYtfPs1Dgdh0i3W+cl4lnnhblMKKBBA23X1Ezc3E5ZoPS5KHjPiU1rKTviYe1fTsa6e3UwXGWI4ykB8uiGqkmA6Cbf3K4JTH3LOBlbX+yPWll57LKVeH8CTEvyVPV2TXL8kPnPqtA51CaFYxOH2rJoZunSnvsSj48WiaDccl6KEgiMSarITsa+rWWBnqFloYlT1qWW2GKw9nPSbEvoVHFst967XgNQjxdA66Q6VFEUh488xfaSo7cHB52XYzA4eRlVteeT8ostWfuPea0oF6MwzlwgZE9gQI+uUV0gzK+WlpUrNI8juhhX/OyNwZnRrsDfxOqS1aDR+gC6NUPvJpvQeVZ9eiNr9aDUuddY3bLnA4tH4r/49UboznH1ia8PV/uP3WUB3dxtzj1uxfDZgbEbZx17Itwrf0Jyc8N4en+5dhivtKeYjGJ8yXgUzKvSU/uWJZmsuAYtseDku+K3zMHi4lC1h0suPmtZaEp2tm3hEV2lXwb6zu7szv6f9glF5rPGT5xR7AAAAABJRU5ErkJggg==</data>
<instructions>You need a client that supports x:data and CAPTCHA to register</instructions>
</query>
</iq>`);
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(registerview.form_type).toBe('xform');
expect(registerview.el.querySelectorAll('#converse-register input[required="required"]').length).toBe(3);
// Hide the controlbox so that we can see whether the test
// passed or failed
u.addClass('hidden', _converse.chatboxviews.get('controlbox').el);
delete _converse.connection;
done();
}));
});
stanza = u.toStanza(`
<iq xmlns="jabber:client" type="result" from="conversations.im" id="ad1e0d50-5adb-4397-a997-5feab56fe418:sendIQ" xml:lang="en">
<query xmlns="jabber:iq:register">
<x xmlns="jabber:x:data" type="form">
<instructions>Choose a username and password to register with this server</instructions>
<field var="FORM_TYPE" type="hidden"><value>urn:xmpp:captcha</value></field>
<field var="username" type="text-single" label="User"><required/></field>
<field var="password" type="text-private" label="Password"><required/></field>
<field var="from" type="hidden"><value>conversations.im</value></field>
<field var="challenge" type="hidden"><value>15376320046808160053</value></field>
<field var="sid" type="hidden"><value>ad1e0d50-5adb-4397-a997-5feab56fe418:sendIQ</value></field>
<field var="ocr" type="text-single" label="Enter the text you see">
<media xmlns="urn:xmpp:media-element">
<uri type="image/png">cid:sha1+2df8c1b366f1e90ce60354f97d1fe75237290b8a@bob.xmpp.org</uri>
</media>
<required/>
</field>
</x>
<data xmlns="urn:xmpp:bob" cid="sha1+2df8c1b366f1e90ce60354f97d1fe75237290b8a@bob.xmpp.org"
type="image/png"
max-age="0">iVBORw0KGgoAAAANSUhEUgAAALQAAAA8BAMAAAA9AI20AAAAMFBMVEX///8AAADf39+fn59fX19/f3+/v78fHx8/Pz9PT08bGxsvLy9jY2NTU1MXFxcnJyc84bkWAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAERUlEQVRYhe1WTXMaRxDdDxY4JWpYvDinpVyxdATLin0MiRLlCHEi+7hYUcVHTSI7urhK6yr5//gn5N/4Z7inX89+CQkTcFUO6gOwS8/r7tdvesbzvoT5ROR5JJ9bB97xAK22XWAY1WznlnUr7QaAzSOsWufXQ6wH/FmO60b4D936LJr8TWRwW4SNgOsodZr8m4vZUoRt2xZ3xHXgna1FCE5+f5aWwPU//bXgg8eHjyqPp4aXJeOlwLUIt0O39zOvPWW3WfHmCCkli816FxlK0rnFGKZ484dN+eIXsw1R+G+JfjwgOpMnm+r5SxA63gS2Q8MchO1RLN8jSn4W4F5OPed2evhTthKLG3bsfjLL874XGBpWHLrU0953i/ev7JsfViHbhsWSQTunJDOppeAe0hVGokJUHBOphmjrbBlgabviJKXbIP0B//gKSBHZh2rvJnQp3wsapMFz+VsTPNhPr0Hn9N57YOjywaxFSU6S79fUF39KBDgnt6yjZOeSffk+4IXDZovbQl9E96m34EzQKMepQcbzijAGiBmDsO+LaqzqG3m3kEf+DQ2mY+vdk5c2n2Iaj5QGi6n59FHDmcuP4t8MGlRaF39P6ENyIaB2EXdpjLnQq9IgdVxfax3ilBc10u4gowX9K6BaKiZNmCC7CF/WpkJvWxN00OjuoqGYLqAnpILLE68Ymrt9M0S9hcznUJ8RykdlLalUfFaDjvA8pT2kxmsl5fuMaM6mSWUpUhDoudSucdhiZFDwphEHwsMwhEpH0jsm+/UBK2wCzFIiitalN7YjWkyIBgTNPgpDXX4rjk4UH+yPPgfK4HNZQCP/KZ0fGnrnKl8+pXl3X7FwZuwNUdwDGO+BjPUn6XaKtbkm+MJ6vtaXSnIz6wBT/m+VvZNIhz7ayabQLSeRQDmYkjt0KlmHDa555v9DzFxx+CCvCG4K3dbx6mTYtfPs1Dgdh0i3W+cl4lnnhblMKKBBA23X1Ezc3E5ZoPS5KHjPiU1rKTviYe1fTsa6e3UwXGWI4ykB8uiGqkmA6Cbf3K4JTH3LOBlbX+yPWll57LKVeH8CTEvyVPV2TXL8kPnPqtA51CaFYxOH2rJoZunSnvsSj48WiaDccl6KEgiMSarITsa+rWWBnqFloYlT1qWW2GKw9nPSbEvoVHFst967XgNQjxdA66Q6VFEUh488xfaSo7cHB52XYzA4eRlVteeT8ostWfuPea0oF6MwzlwgZE9gQI+uUV0gzK+WlpUrNI8juhhX/OyNwZnRrsDfxOqS1aDR+gC6NUPvJpvQeVZ9eiNr9aDUuddY3bLnA4tH4r/49UboznH1ia8PV/uP3WUB3dxtzj1uxfDZgbEbZx17Itwrf0Jyc8N4en+5dhivtKeYjGJ8yXgUzKvSU/uWJZmsuAYtseDku+K3zMHi4lC1h0suPmtZaEp2tm3hEV2lXwb6zu7szv6f9glF5rPGT5xR7AAAAABJRU5ErkJggg==</data>
<instructions>You need a client that supports x:data and CAPTCHA to register</instructions>
</query>
</iq>`);
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(registerview.form_type).toBe('xform');
expect(registerview.el.querySelectorAll('#converse-register input[required="required"]').length).toBe(3);
// Hide the controlbox so that we can see whether the test
// passed or failed
u.addClass('hidden', _converse.chatboxviews.get('controlbox').el);
delete _converse.connection;
done();
}));
});

File diff suppressed because it is too large Load Diff

View File

@ -1,121 +1,119 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const _ = converse.env._,
$iq = converse.env.$iq,
Strophe = converse.env.Strophe,
sizzle = converse.env.sizzle,
u = converse.env.utils;
/*global mock */
describe("Chatrooms", function () {
const _ = converse.env._,
$iq = converse.env.$iq,
Strophe = converse.env.Strophe,
sizzle = converse.env.sizzle,
u = converse.env.utils;
describe("Chatrooms", function () {
describe("The /register commmand", function () {
describe("The /register commmand", function () {
it("allows you to register your nickname in a room",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {'auto_register_muc_nickname': true},
async function (done, _converse) {
it("allows you to register your nickname in a room",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {'auto_register_muc_nickname': true},
async function (done, _converse) {
const muc_jid = 'coven@chat.shakespeare.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo')
const view = _converse.chatboxviews.get(muc_jid);
const textarea = view.el.querySelector('.chat-textarea')
textarea.value = '/register';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13
});
let stanza = await u.waitUntil(() => _.filter(
_converse.connection.IQ_stanzas,
iq => sizzle(`iq[to="${muc_jid}"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
).pop());
expect(Strophe.serialize(stanza))
.toBe(`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" `+
`type="get" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:register"/></iq>`);
const result = $iq({
'from': view.model.get('jid'),
'id': stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('query', {'type': 'jabber:iq:register'})
.c('x', {'xmlns': 'jabber:x:data', 'type': 'form'})
.c('field', {
'label': 'Desired Nickname',
'type': 'text-single',
'var': 'muc#register_roomnick'
}).c('required');
_converse.connection._dataRecv(test_utils.createRequest(result));
stanza = await u.waitUntil(() => _.filter(
_converse.connection.IQ_stanzas,
iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
).pop());
expect(Strophe.serialize(stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:register">`+
`<x type="submit" xmlns="jabber:x:data">`+
`<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+
`<field var="muc#register_roomnick"><value>romeo</value></field>`+
`</x>`+
`</query>`+
`</iq>`);
done();
}));
});
describe("The auto_register_muc_nickname option", function () {
it("allows you to automatically register your nickname when joining a room",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {'auto_register_muc_nickname': true},
async function (done, _converse) {
const muc_jid = 'coven@chat.shakespeare.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
let stanza = await u.waitUntil(() => _.filter(
_converse.connection.IQ_stanzas,
iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
).pop());
expect(Strophe.serialize(stanza))
const muc_jid = 'coven@chat.shakespeare.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo')
const view = _converse.chatboxviews.get(muc_jid);
const textarea = view.el.querySelector('.chat-textarea')
textarea.value = '/register';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13
});
let stanza = await u.waitUntil(() => _.filter(
_converse.connection.IQ_stanzas,
iq => sizzle(`iq[to="${muc_jid}"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
).pop());
expect(Strophe.serialize(stanza))
.toBe(`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" `+
`type="get" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:register"/></iq>`);
const result = $iq({
'from': view.model.get('jid'),
'id': stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('query', {'type': 'jabber:iq:register'})
.c('x', {'xmlns': 'jabber:x:data', 'type': 'form'})
.c('field', {
'label': 'Desired Nickname',
'type': 'text-single',
'var': 'muc#register_roomnick'
}).c('required');
_converse.connection._dataRecv(test_utils.createRequest(result));
stanza = await u.waitUntil(() => _.filter(
_converse.connection.IQ_stanzas,
iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
).pop());
const result = $iq({
'from': view.model.get('jid'),
'id': stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('query', {'type': 'jabber:iq:register'})
.c('x', {'xmlns': 'jabber:x:data', 'type': 'form'})
.c('field', {
'label': 'Desired Nickname',
'type': 'text-single',
'var': 'muc#register_roomnick'
}).c('required');
_converse.connection._dataRecv(mock.createRequest(result));
stanza = await u.waitUntil(() => _.filter(
_converse.connection.IQ_stanzas,
iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
).pop());
expect(Strophe.serialize(stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:register">`+
`<x type="submit" xmlns="jabber:x:data">`+
`<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+
`<field var="muc#register_roomnick"><value>romeo</value></field>`+
`</x>`+
`</query>`+
`</iq>`);
done();
}));
});
expect(Strophe.serialize(stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:register">`+
`<x type="submit" xmlns="jabber:x:data">`+
`<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+
`<field var="muc#register_roomnick"><value>romeo</value></field>`+
`</x>`+
`</query>`+
`</iq>`);
done();
}));
});
describe("The auto_register_muc_nickname option", function () {
it("allows you to automatically register your nickname when joining a room",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {'auto_register_muc_nickname': true},
async function (done, _converse) {
const muc_jid = 'coven@chat.shakespeare.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
let stanza = await u.waitUntil(() => _.filter(
_converse.connection.IQ_stanzas,
iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
).pop());
expect(Strophe.serialize(stanza))
.toBe(`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" `+
`type="get" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:register"/></iq>`);
const result = $iq({
'from': view.model.get('jid'),
'id': stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('query', {'type': 'jabber:iq:register'})
.c('x', {'xmlns': 'jabber:x:data', 'type': 'form'})
.c('field', {
'label': 'Desired Nickname',
'type': 'text-single',
'var': 'muc#register_roomnick'
}).c('required');
_converse.connection._dataRecv(mock.createRequest(result));
stanza = await u.waitUntil(() => _.filter(
_converse.connection.IQ_stanzas,
iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
).pop());
expect(Strophe.serialize(stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:register">`+
`<x type="submit" xmlns="jabber:x:data">`+
`<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+
`<field var="muc#register_roomnick"><value>romeo</value></field>`+
`</x>`+
`</query>`+
`</iq>`);
done();
}));
});
});

View File

@ -1,333 +1,338 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const { Strophe, $iq, $msg, $pres, sizzle, _ } = converse.env;
const u = converse.env.utils;
/* global mock */
describe("A list of open groupchats", function () {
describe("A list of open groupchats", function () {
it("is shown in controlbox", mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'],
{ allow_bookmarks: false // Makes testing easier, otherwise we
// have to mock stanza traffic.
}, async function (done, _converse) {
await test_utils.openControlBox(_converse);
const controlbox = _converse.chatboxviews.get('controlbox');
let list = controlbox.el.querySelector('.list-container--openrooms');
expect(u.hasClass('hidden', list)).toBeTruthy();
await test_utils.openChatRoom(_converse, 'room', 'conference.shakespeare.lit', 'JC');
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit');
await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(2);
let view = _converse.chatboxviews.get('room@conference.shakespeare.lit');
await 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@montague.lit');
list = controlbox.el.querySelector('.list-container--openrooms');
u.waitUntil(() => _.includes(list.classList, 'hidden'));
view = _converse.chatboxviews.get('lounge@montague.lit');
await view.close();
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(0);
list = controlbox.el.querySelector('.list-container--openrooms');
expect(_.includes(list.classList, 'hidden')).toBeTruthy();
done();
}));
it("uses bookmarks to determine groupchat names",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'],
{'view_mode': 'fullscreen'},
async function (done, _converse) {
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
let stanza = $pres({
to: 'romeo@montague.lit/orchard',
from: 'lounge@montague.lit/newguy'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@montague.lit/_converse.js-290929789',
'role': 'participant'
}).tree();
_converse.connection._dataRecv(test_utils.createRequest(stanza));
spyOn(_converse.Bookmarks.prototype, 'fetchBookmarks').and.callThrough();
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type':'pep'}],
[`${Strophe.NS.PUBSUB}#publish-options`]
);
const IQ_stanzas = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(() => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop());
expect(Strophe.serialize(sent_stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<items node="storage:bookmarks"/>'+
'</pubsub>'+
'</iq>');
stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':sent_stanza.getAttribute('id')})
.c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
.c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'})
.c('conference', {
'name': 'Bookmarked Lounge',
'jid': 'lounge@montague.lit'
});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await _converse.api.waitUntil('roomsListInitialized');
const controlbox = _converse.chatboxviews.get('controlbox');
const list = controlbox.el.querySelector('.list-container--openrooms');
expect(_.includes(list.classList, 'hidden')).toBeFalsy();
const items = list.querySelectorAll('.list-item');
expect(items.length).toBe(1);
expect(items[0].textContent.trim()).toBe('Bookmarked Lounge');
expect(_converse.bookmarks.fetchBookmarks).toHaveBeenCalled();
done();
}));
});
describe("A groupchat shown in the groupchats list", function () {
it("is highlighted if it's currently open", mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'],
{ view_mode: 'fullscreen',
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, async function (done, _converse) {
const muc_jid = 'coven@chat.shakespeare.lit';
await _converse.api.rooms.open(muc_jid, {'nick': 'some1'}, true);
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".available-chatroom");
expect(room_els.length).toBe(1);
let item = room_els[0];
await u.waitUntil(() => lview.model.get(muc_jid).get('hidden') === false);
await u.waitUntil(() => u.hasClass('open', item), 1000);
expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit');
await _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'}, true);
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
room_els = lview.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(2);
room_els = lview.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.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'],
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we
it("is shown in controlbox", mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'],
{ allow_bookmarks: false // Makes testing easier, otherwise we
// have to mock stanza traffic.
}, async function (done, _converse) {
}, async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas;
const room_jid = 'coven@chat.shakespeare.lit';
await test_utils.openControlBox(_converse);
await _converse.api.rooms.open(room_jid, {'nick': 'some1'});
const view = _converse.chatboxviews.get(room_jid);
const u = converse.env.utils;
const selector = `iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`;
const features_query = await u.waitUntil(() => IQ_stanzas.filter(iq => iq.querySelector(selector)).pop());
const features_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
'id': features_query.getAttribute('id'),
'to': 'romeo@montague.lit/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));
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING)
let presence = $pres({
to: _converse.connection.jid,
from: 'coven@chat.shakespeare.lit/some1',
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(test_utils.createRequest(presence));
await mock.openControlBox(_converse);
const controlbox = _converse.chatboxviews.get('controlbox');
let list = controlbox.el.querySelector('.list-container--openrooms');
expect(u.hasClass('hidden', list)).toBeTruthy();
await mock.openChatRoom(_converse, 'room', 'conference.shakespeare.lit', 'JC');
await u.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".open-room").length, 500);
const room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
const info_el = _converse.rooms_list_view.el.querySelector(".room-info");
info_el.click();
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
expect(room_els[0].innerText).toBe('room@conference.shakespeare.lit');
const modal = view.model.room_details_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
let els = modal.el.querySelectorAll('p.room-info');
expect(els[0].textContent).toBe("Name: A Dark Cave")
expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit")
expect(els[2].textContent).toBe("Description: This is the description")
expect(els[3].textContent).toBe("Online users: 1")
const features_list = modal.el.querySelector('.features-list');
expect(features_list.textContent.replace(/(\n|\s{2,})/g, '')).toBe(
'Password protected - This groupchat requires a password before entry'+
'Hidden - This groupchat is not publicly searchable'+
'Open - Anyone can join this groupchat'+
'Temporary - This groupchat will disappear once the last person leaves'+
'Not anonymous - All other groupchat participants can see your XMPP address'+
'Not moderated - Participants entering this groupchat can write right away'
);
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/newguy'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@montague.lit/_converse.js-290929789',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
await mock.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(2);
els = modal.el.querySelectorAll('p.room-info');
expect(els[3].textContent).toBe("Online users: 2")
let view = _converse.chatboxviews.get('room@conference.shakespeare.lit');
await 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@montague.lit');
list = controlbox.el.querySelector('.list-container--openrooms');
u.waitUntil(() => Array.from(list.classList).includes('hidden'));
view.model.set({'subject': {'author': 'someone', 'text': 'Hatching dark plots'}});
els = modal.el.querySelectorAll('p.room-info');
expect(els[0].textContent).toBe("Name: A Dark Cave")
expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit")
expect(els[2].textContent).toBe("Description: This is the description")
expect(els[3].textContent).toBe("Topic: Hatching dark plots")
expect(els[4].textContent).toBe("Topic author: someone")
expect(els[5].textContent).toBe("Online users: 2")
done();
}));
view = _converse.chatboxviews.get('lounge@montague.lit');
await view.close();
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(0);
it("can be closed", mock.initConverse(
['rosterGroupsFetched'],
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
},
async function (done, _converse) {
list = controlbox.el.querySelector('.list-container--openrooms');
expect(Array.from(list.classList).includes('hidden')).toBeTruthy();
done();
}));
spyOn(window, 'confirm').and.callFake(() => true);
expect(_converse.chatboxes.length).toBe(1);
await test_utils.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC');
expect(_converse.chatboxes.length).toBe(2);
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
const 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?');
it("uses bookmarks to determine groupchat names",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'],
{'view_mode': 'fullscreen'},
async function (done, _converse) {
await new Promise(resolve => _converse.api.listen.once('chatBoxClosed', resolve));
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(0);
expect(_converse.chatboxes.length).toBe(1);
done();
}));
const { Strophe, $iq, $pres, sizzle } = converse.env;
const u = converse.env.utils;
it("shows unread messages directed at the user", mock.initConverse(
null,
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, async (done, _converse) => {
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
let stanza = $pres({
to: 'romeo@montague.lit/orchard',
from: 'lounge@montague.lit/newguy'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@montague.lit/_converse.js-290929789',
'role': 'participant'
}).tree();
_converse.connection._dataRecv(mock.createRequest(stanza));
await test_utils.openControlBox(_converse);
const room_jid = 'kitchen@conference.shakespeare.lit';
await u.waitUntil(() => _converse.rooms_list_view !== undefined, 500);
await test_utils.openAndEnterChatRoom(_converse, room_jid, 'romeo');
const view = _converse.chatboxviews.get(room_jid);
view.model.set({'minimized': true});
const nick = mock.chatroom_names[0];
await view.model.queueMessage(
$msg({
from: room_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('foo').tree());
spyOn(_converse.Bookmarks.prototype, 'fetchBookmarks').and.callThrough();
// If the user isn't mentioned, the counter doesn't get incremented, but the text of the groupchat is bold
const lview = _converse.rooms_list_view
let room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
await mock.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type':'pep'}],
[`${Strophe.NS.PUBSUB}#publish-options`]
);
// If the user is mentioned, the counter also gets updated
await view.model.queueMessage(
$msg({
from: room_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('romeo: Your attention is required').tree()
);
const IQ_stanzas = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(() => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop());
expect(Strophe.serialize(sent_stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<items node="storage:bookmarks"/>'+
'</pubsub>'+
'</iq>');
let indicator_el = await u.waitUntil(() => lview.el.querySelector(".msgs-indicator"));
expect(indicator_el.textContent).toBe('1');
stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':sent_stanza.getAttribute('id')})
.c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
.c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'})
.c('conference', {
'name': 'Bookmarked Lounge',
'jid': 'lounge@montague.lit'
});
_converse.connection._dataRecv(mock.createRequest(stanza));
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
await view.model.queueMessage(
$msg({
from: room_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('romeo: and another thing...').tree()
);
await u.waitUntil(() => view.model.incrementUnreadMsgCounter.calls.count());
await u.waitUntil(() => lview.el.querySelector(".msgs-indicator").textContent === '2', 1000);
// When the chat gets maximized again, the unread indicators are removed
view.model.set({'minimized': false});
indicator_el = lview.el.querySelector(".msgs-indicator");
expect(indicator_el === null);
room_el = lview.el.querySelector(".available-chatroom");
expect(_.includes(room_el.classList, 'unread-msgs')).toBeFalsy();
done();
}));
});
await _converse.api.waitUntil('roomsListInitialized');
const controlbox = _converse.chatboxviews.get('controlbox');
const list = controlbox.el.querySelector('.list-container--openrooms');
expect(Array.from(list.classList).includes('hidden')).toBeFalsy();
const items = list.querySelectorAll('.list-item');
expect(items.length).toBe(1);
expect(items[0].textContent.trim()).toBe('Bookmarked Lounge');
expect(_converse.bookmarks.fetchBookmarks).toHaveBeenCalled();
done();
}));
});
describe("A groupchat shown in the groupchats list", function () {
it("is highlighted if it's currently open", mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'],
{ view_mode: 'fullscreen',
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, async function (done, _converse) {
const u = converse.env.utils;
const muc_jid = 'coven@chat.shakespeare.lit';
await _converse.api.rooms.open(muc_jid, {'nick': 'some1'}, true);
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".available-chatroom");
expect(room_els.length).toBe(1);
let item = room_els[0];
await u.waitUntil(() => lview.model.get(muc_jid).get('hidden') === false);
await u.waitUntil(() => u.hasClass('open', item), 1000);
expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit');
await _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'}, true);
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
room_els = lview.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(2);
room_els = lview.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.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'],
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we
// have to mock stanza traffic.
}, async function (done, _converse) {
const { Strophe, $iq, $pres } = converse.env;
const u = converse.env.utils;
const IQ_stanzas = _converse.connection.IQ_stanzas;
const room_jid = 'coven@chat.shakespeare.lit';
await mock.openControlBox(_converse);
await _converse.api.rooms.open(room_jid, {'nick': 'some1'});
const view = _converse.chatboxviews.get(room_jid);
const selector = `iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`;
const features_query = await u.waitUntil(() => IQ_stanzas.filter(iq => iq.querySelector(selector)).pop());
const features_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
'id': features_query.getAttribute('id'),
'to': 'romeo@montague.lit/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(mock.createRequest(features_stanza));
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING)
let presence = $pres({
to: _converse.connection.jid,
from: 'coven@chat.shakespeare.lit/some1',
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(mock.createRequest(presence));
await u.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".open-room").length, 500);
const room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
const info_el = _converse.rooms_list_view.el.querySelector(".room-info");
info_el.click();
const modal = view.model.room_details_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
let els = modal.el.querySelectorAll('p.room-info');
expect(els[0].textContent).toBe("Name: A Dark Cave")
expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit")
expect(els[2].textContent).toBe("Description: This is the description")
expect(els[3].textContent).toBe("Online users: 1")
const features_list = modal.el.querySelector('.features-list');
expect(features_list.textContent.replace(/(\n|\s{2,})/g, '')).toBe(
'Password protected - This groupchat requires a password before entry'+
'Hidden - This groupchat is not publicly searchable'+
'Open - Anyone can join this groupchat'+
'Temporary - This groupchat will disappear once the last person leaves'+
'Not anonymous - All other groupchat participants can see your XMPP address'+
'Not moderated - Participants entering this groupchat can write right away'
);
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/newguy'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@montague.lit/_converse.js-290929789',
'role': 'participant'
});
_converse.connection._dataRecv(mock.createRequest(presence));
els = modal.el.querySelectorAll('p.room-info');
expect(els[3].textContent).toBe("Online users: 2")
view.model.set({'subject': {'author': 'someone', 'text': 'Hatching dark plots'}});
els = modal.el.querySelectorAll('p.room-info');
expect(els[0].textContent).toBe("Name: A Dark Cave")
expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit")
expect(els[2].textContent).toBe("Description: This is the description")
expect(els[3].textContent).toBe("Topic: Hatching dark plots")
expect(els[4].textContent).toBe("Topic author: someone")
expect(els[5].textContent).toBe("Online users: 2")
done();
}));
it("can be closed", mock.initConverse(
['rosterGroupsFetched'],
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
},
async function (done, _converse) {
const u = converse.env.utils;
spyOn(window, 'confirm').and.callFake(() => true);
expect(_converse.chatboxes.length).toBe(1);
await mock.openChatRoom(_converse, 'lounge', 'conference.shakespeare.lit', 'JC');
expect(_converse.chatboxes.length).toBe(2);
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
const 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?');
await new Promise(resolve => _converse.api.listen.once('chatBoxClosed', resolve));
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.initConverse(
null,
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, async (done, _converse) => {
const { $msg } = converse.env;
const u = converse.env.utils;
await mock.openControlBox(_converse);
const room_jid = 'kitchen@conference.shakespeare.lit';
await u.waitUntil(() => _converse.rooms_list_view !== undefined, 500);
await mock.openAndEnterChatRoom(_converse, room_jid, 'romeo');
const view = _converse.chatboxviews.get(room_jid);
view.model.set({'minimized': true});
const nick = mock.chatroom_names[0];
await view.model.queueMessage(
$msg({
from: room_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('foo').tree());
// If the user isn't mentioned, the counter doesn't get incremented, but the text of the groupchat is bold
const lview = _converse.rooms_list_view
let room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
// If the user is mentioned, the counter also gets updated
await view.model.queueMessage(
$msg({
from: room_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('romeo: Your attention is required').tree()
);
let indicator_el = await u.waitUntil(() => lview.el.querySelector(".msgs-indicator"));
expect(indicator_el.textContent).toBe('1');
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
await view.model.queueMessage(
$msg({
from: room_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('romeo: and another thing...').tree()
);
await u.waitUntil(() => view.model.incrementUnreadMsgCounter.calls.count());
await u.waitUntil(() => lview.el.querySelector(".msgs-indicator").textContent === '2', 1000);
// When the chat gets maximized again, the unread indicators are removed
view.model.set({'minimized': false});
indicator_el = lview.el.querySelector(".msgs-indicator");
expect(indicator_el === null);
room_el = lview.el.querySelector(".available-chatroom");
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();
done();
}));
});

File diff suppressed because it is too large Load Diff

View File

@ -1,282 +1,280 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const $iq = converse.env.$iq;
const $msg = converse.env.$msg;
const Strophe = converse.env.Strophe;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
/*global mock */
describe("XEP-0198 Stream Management", function () {
const $iq = converse.env.$iq;
const $msg = converse.env.$msg;
const Strophe = converse.env.Strophe;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
it("gets enabled with an <enable> stanza and resumed with a <resume> stanza",
mock.initConverse(
['chatBoxesInitialized'],
{ 'auto_login': false,
'enable_smacks': true,
'show_controlbox_by_default': true,
'smacks_max_unacked_stanzas': 2
},
async function (done, _converse) {
describe("XEP-0198 Stream Management", function () {
const view = _converse.chatboxviews.get('controlbox');
spyOn(view, 'renderControlBoxPane').and.callThrough();
it("gets enabled with an <enable> stanza and resumed with a <resume> stanza",
mock.initConverse(
['chatBoxesInitialized'],
{ 'auto_login': false,
'enable_smacks': true,
'show_controlbox_by_default': true,
'smacks_max_unacked_stanzas': 2
},
async function (done, _converse) {
await _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
const sent_stanzas = _converse.connection.sent_stanzas;
let stanza = await u.waitUntil(() =>
sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
const view = _converse.chatboxviews.get('controlbox');
spyOn(view, 'renderControlBoxPane').and.callThrough();
expect(_converse.session.get('smacks_enabled')).toBe(false);
expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
await _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
const sent_stanzas = _converse.connection.sent_stanzas;
let stanza = await u.waitUntil(() =>
sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
let result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true"/>`);
_converse.connection._dataRecv(test_utils.createRequest(result));
expect(_converse.session.get('smacks_enabled')).toBe(true);
expect(_converse.session.get('smacks_enabled')).toBe(false);
expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
await u.waitUntil(() => view.renderControlBoxPane.calls.count());
let result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true"/>`);
_converse.connection._dataRecv(mock.createRequest(result));
expect(_converse.session.get('smacks_enabled')).toBe(true);
let IQ_stanzas = _converse.connection.IQ_stanzas;
await u.waitUntil(() => IQ_stanzas.length === 4);
await u.waitUntil(() => view.renderControlBoxPane.calls.count());
let iq = IQ_stanzas[IQ_stanzas.length-1];
expect(Strophe.serialize(iq)).toBe(
`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
await test_utils.waitForRoster(_converse, 'current', 1);
IQ_stanzas.pop();
let IQ_stanzas = _converse.connection.IQ_stanzas;
await u.waitUntil(() => IQ_stanzas.length === 4);
const expected_IQs = disco_iq => ([
`<iq from="romeo@montague.lit" id="${disco_iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub></iq>`,
let iq = IQ_stanzas[IQ_stanzas.length-1];
expect(Strophe.serialize(iq)).toBe(
`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
await mock.waitForRoster(_converse, 'current', 1);
IQ_stanzas.pop();
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`,
const expected_IQs = disco_iq => ([
`<iq from="romeo@montague.lit" id="${disco_iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub></iq>`,
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`]);
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`,
const disco_iq = IQ_stanzas.pop();
expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
iq = IQ_stanzas.pop();
expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
iq = IQ_stanzas.pop();
expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`]);
expect(sent_stanzas.filter(s => (s.nodeName === 'r')).length).toBe(2);
expect(_converse.session.get('unacked_stanzas').length).toBe(5);
const disco_iq = IQ_stanzas.pop();
expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
iq = IQ_stanzas.pop();
expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
iq = IQ_stanzas.pop();
expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true);
// test handling of acks
let ack = u.toStanza(`<a xmlns="urn:xmpp:sm:3" h="2"/>`);
_converse.connection._dataRecv(test_utils.createRequest(ack));
expect(_converse.session.get('unacked_stanzas').length).toBe(3);
expect(sent_stanzas.filter(s => (s.nodeName === 'r')).length).toBe(2);
expect(_converse.session.get('unacked_stanzas').length).toBe(5);
// test handling of ack requests
let r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
_converse.connection._dataRecv(test_utils.createRequest(r));
// test handling of acks
let ack = u.toStanza(`<a xmlns="urn:xmpp:sm:3" h="2"/>`);
_converse.connection._dataRecv(mock.createRequest(ack));
expect(_converse.session.get('unacked_stanzas').length).toBe(3);
ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a')).pop());
expect(Strophe.serialize(ack)).toBe('<a h="1" xmlns="urn:xmpp:sm:3"/>');
// test handling of ack requests
let r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
_converse.connection._dataRecv(mock.createRequest(r));
ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a')).pop());
expect(Strophe.serialize(ack)).toBe('<a h="1" xmlns="urn:xmpp:sm:3"/>');
const disco_result = $iq({
'type': 'result',
'from': 'montague.lit',
'to': 'romeo@montague.lit/orchard',
'id': disco_iq.getAttribute('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(disco_result));
const disco_result = $iq({
'type': 'result',
'from': 'montague.lit',
'to': 'romeo@montague.lit/orchard',
'id': disco_iq.getAttribute('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(mock.createRequest(disco_result));
ack = u.toStanza(`<a xmlns="urn:xmpp:sm:3" h="3"/>`);
_converse.connection._dataRecv(test_utils.createRequest(ack));
expect(_converse.session.get('unacked_stanzas').length).toBe(2);
ack = u.toStanza(`<a xmlns="urn:xmpp:sm:3" h="3"/>`);
_converse.connection._dataRecv(mock.createRequest(ack));
expect(_converse.session.get('unacked_stanzas').length).toBe(2);
r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
_converse.connection._dataRecv(test_utils.createRequest(r));
ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a' && s.getAttribute('h') === '1')).pop());
expect(Strophe.serialize(ack)).toBe('<a h="1" xmlns="urn:xmpp:sm:3"/>');
await _converse.api.waitUntil('rosterInitialized');
r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
_converse.connection._dataRecv(mock.createRequest(r));
ack = await u.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a' && s.getAttribute('h') === '1')).pop());
expect(Strophe.serialize(ack)).toBe('<a h="1" xmlns="urn:xmpp:sm:3"/>');
await _converse.api.waitUntil('rosterInitialized');
// test session resumption
_converse.connection.IQ_stanzas = [];
IQ_stanzas = _converse.connection.IQ_stanzas;
await _converse.api.connection.reconnect();
stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
expect(Strophe.serialize(stanza)).toEqual('<resume h="2" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
// test session resumption
_converse.connection.IQ_stanzas = [];
IQ_stanzas = _converse.connection.IQ_stanzas;
await _converse.api.connection.reconnect();
stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
expect(Strophe.serialize(stanza)).toEqual('<resume h="2" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
result = u.toStanza(`<resumed xmlns="urn:xmpp:sm:3" h="another-sequence-number" previd="some-long-sm-id"/>`);
_converse.connection._dataRecv(test_utils.createRequest(result));
result = u.toStanza(`<resumed xmlns="urn:xmpp:sm:3" h="another-sequence-number" previd="some-long-sm-id"/>`);
_converse.connection._dataRecv(mock.createRequest(result));
// Another <enable> stanza doesn't get sent out
expect(sent_stanzas.filter(s => (s.tagName === 'enable')).length).toBe(1);
expect(_converse.session.get('smacks_enabled')).toBe(true);
// Another <enable> stanza doesn't get sent out
expect(sent_stanzas.filter(s => (s.tagName === 'enable')).length).toBe(1);
expect(_converse.session.get('smacks_enabled')).toBe(true);
await new Promise(resolve => _converse.api.listen.once('reconnected', resolve));
await u.waitUntil(() => IQ_stanzas.length === 1);
await new Promise(resolve => _converse.api.listen.once('reconnected', resolve));
await u.waitUntil(() => IQ_stanzas.length === 1);
// Test that unacked stanzas get resent out
iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
// Test that unacked stanzas get resent out
iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
expect(IQ_stanzas.filter(iq => sizzle('query[xmlns="jabber:iq:roster"]', iq).pop()).length).toBe(0);
done();
}));
expect(IQ_stanzas.filter(iq => sizzle('query[xmlns="jabber:iq:roster"]', iq).pop()).length).toBe(0);
done();
}));
it("might not resume and the session will then be reset",
mock.initConverse(
['chatBoxesInitialized'],
{ 'auto_login': false,
'enable_smacks': true,
'show_controlbox_by_default': true,
'smacks_max_unacked_stanzas': 2
},
async function (done, _converse) {
it("might not resume and the session will then be reset",
mock.initConverse(
['chatBoxesInitialized'],
{ 'auto_login': false,
'enable_smacks': true,
'show_controlbox_by_default': true,
'smacks_max_unacked_stanzas': 2
},
async function (done, _converse) {
await _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
const sent_stanzas = _converse.connection.sent_stanzas;
let stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
let result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true"/>`);
_converse.connection._dataRecv(test_utils.createRequest(result));
await _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
const sent_stanzas = _converse.connection.sent_stanzas;
let stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
let result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true"/>`);
_converse.connection._dataRecv(mock.createRequest(result));
await test_utils.waitForRoster(_converse, 'current', 1);
await mock.waitForRoster(_converse, 'current', 1);
// test session resumption
await _converse.api.connection.reconnect();
stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
expect(Strophe.serialize(stanza)).toEqual('<resume h="1" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
// test session resumption
await _converse.api.connection.reconnect();
stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
expect(Strophe.serialize(stanza)).toEqual('<resume h="1" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
result = u.toStanza(
`<failed xmlns="urn:xmpp:sm:3" h="another-sequence-number">`+
`<item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>`+
`</failed>`);
_converse.connection._dataRecv(test_utils.createRequest(result));
result = u.toStanza(
`<failed xmlns="urn:xmpp:sm:3" h="another-sequence-number">`+
`<item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>`+
`</failed>`);
_converse.connection._dataRecv(mock.createRequest(result));
// Session data gets reset
expect(_converse.session.get('smacks_enabled')).toBe(false);
expect(_converse.session.get('num_stanzas_handled')).toBe(0);
expect(_converse.session.get('num_stanzas_handled_by_server')).toBe(0);
expect(_converse.session.get('num_stanzas_since_last_ack')).toBe(0);
expect(_converse.session.get('unacked_stanzas').length).toBe(0);
expect(_converse.session.get('roster_cached')).toBeFalsy();
// Session data gets reset
expect(_converse.session.get('smacks_enabled')).toBe(false);
expect(_converse.session.get('num_stanzas_handled')).toBe(0);
expect(_converse.session.get('num_stanzas_handled_by_server')).toBe(0);
expect(_converse.session.get('num_stanzas_since_last_ack')).toBe(0);
expect(_converse.session.get('unacked_stanzas').length).toBe(0);
expect(_converse.session.get('roster_cached')).toBeFalsy();
await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).length === 2);
stanza = sent_stanzas.filter(s => (s.tagName === 'enable')).pop();
expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).length === 2);
stanza = sent_stanzas.filter(s => (s.tagName === 'enable')).pop();
expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="another-long-sm-id" resume="true"/>`);
_converse.connection._dataRecv(test_utils.createRequest(result));
expect(_converse.session.get('smacks_enabled')).toBe(true);
result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="another-long-sm-id" resume="true"/>`);
_converse.connection._dataRecv(mock.createRequest(result));
expect(_converse.session.get('smacks_enabled')).toBe(true);
// Check that the roster gets fetched
await test_utils.waitForRoster(_converse, 'current', 1);
await new Promise(resolve => _converse.api.listen.once('reconnected', resolve));
done();
}));
// Check that the roster gets fetched
await mock.waitForRoster(_converse, 'current', 1);
await new Promise(resolve => _converse.api.listen.once('reconnected', resolve));
done();
}));
it("can cause MUC messages to be received before chatboxes are initialized",
mock.initConverse(
['chatBoxesInitialized'],
{ 'auto_login': false,
'blacklisted_plugins': 'converse-mam',
'enable_smacks': true,
'muc_fetch_members': false,
'show_controlbox_by_default': true,
'smacks_max_unacked_stanzas': 2
},
async function (done, _converse) {
it("can cause MUC messages to be received before chatboxes are initialized",
mock.initConverse(
['chatBoxesInitialized'],
{ 'auto_login': false,
'blacklisted_plugins': 'converse-mam',
'enable_smacks': true,
'muc_fetch_members': false,
'show_controlbox_by_default': true,
'smacks_max_unacked_stanzas': 2
},
async function (done, _converse) {
const key = "converse-test-session/converse.session-romeo@montague.lit-converse.session-romeo@montague.lit";
sessionStorage.setItem(
key,
JSON.stringify({
"id": "converse.session-romeo@montague.lit",
"jid": "romeo@montague.lit/converse.js-100020907",
"bare_jid": "romeo@montague.lit",
"resource": "converse.js-100020907",
"domain": "montague.lit",
"active": false,
"smacks_enabled": true,
"num_stanzas_handled": 580,
"num_stanzas_handled_by_server": 525,
"num_stanzas_since_last_ack": 0,
"unacked_stanzas": [],
"smacks_stream_id": "some-long-sm-id",
"push_enabled": ["romeo@montague.lit"],
"carbons_enabled": true,
"roster_cached": true
})
);
const key = "converse-test-session/converse.session-romeo@montague.lit-converse.session-romeo@montague.lit";
sessionStorage.setItem(
key,
JSON.stringify({
"id": "converse.session-romeo@montague.lit",
"jid": "romeo@montague.lit/converse.js-100020907",
"bare_jid": "romeo@montague.lit",
"resource": "converse.js-100020907",
"domain": "montague.lit",
"active": false,
"smacks_enabled": true,
"num_stanzas_handled": 580,
"num_stanzas_handled_by_server": 525,
"num_stanzas_since_last_ack": 0,
"unacked_stanzas": [],
"smacks_stream_id": "some-long-sm-id",
"push_enabled": ["romeo@montague.lit"],
"carbons_enabled": true,
"roster_cached": true
})
);
const muc_jid = 'lounge@montague.lit';
const chatkey = `converse.chatboxes-romeo@montague.lit-${muc_jid}`;
sessionStorage.setItem('converse.chatboxes-romeo@montague.lit', JSON.stringify([chatkey]));
sessionStorage.setItem(chatkey,
JSON.stringify({
hidden: false,
message_type: "groupchat",
name: "lounge",
num_unread: 0,
type: "chatroom",
jid: muc_jid,
id: muc_jid,
box_id: "box-YXJnQGNvbmZlcmVuY2UuY2hhdC5leGFtcGxlLm9yZw==",
nick: "romeo"
})
);
const muc_jid = 'lounge@montague.lit';
const chatkey = `converse.chatboxes-romeo@montague.lit-${muc_jid}`;
sessionStorage.setItem('converse.chatboxes-romeo@montague.lit', JSON.stringify([chatkey]));
sessionStorage.setItem(chatkey,
JSON.stringify({
hidden: false,
message_type: "groupchat",
name: "lounge",
num_unread: 0,
type: "chatroom",
jid: muc_jid,
id: muc_jid,
box_id: "box-YXJnQGNvbmZlcmVuY2UuY2hhdC5leGFtcGxlLm9yZw==",
nick: "romeo"
})
);
_converse.no_connection_on_bind = true; // XXX Don't trigger CONNECTED in tests/mock.js
await _converse.api.user.login('romeo@montague.lit', 'secret');
delete _converse.no_connection_on_bind;
_converse.no_connection_on_bind = true; // XXX Don't trigger CONNECTED in tests/mock.js
await _converse.api.user.login('romeo@montague.lit', 'secret');
delete _converse.no_connection_on_bind;
const sent_stanzas = _converse.connection.sent_stanzas;
const stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
expect(Strophe.serialize(stanza)).toEqual('<resume h="580" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
const sent_stanzas = _converse.connection.sent_stanzas;
const stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
expect(Strophe.serialize(stanza)).toEqual('<resume h="580" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
const result = u.toStanza(`<resumed xmlns="urn:xmpp:sm:3" h="another-sequence-number" previd="some-long-sm-id"/>`);
_converse.connection._dataRecv(test_utils.createRequest(result));
expect(_converse.session.get('smacks_enabled')).toBe(true);
const result = u.toStanza(`<resumed xmlns="urn:xmpp:sm:3" h="another-sequence-number" previd="some-long-sm-id"/>`);
_converse.connection._dataRecv(mock.createRequest(result));
expect(_converse.session.get('smacks_enabled')).toBe(true);
const nick = 'romeo';
const func = _converse.chatboxes.onChatBoxesFetched;
spyOn(_converse.chatboxes, 'onChatBoxesFetched').and.callFake(collection => {
const muc = new _converse.ChatRoom({'jid': muc_jid, 'id': muc_jid, nick}, {'collection': _converse.chatboxes});
_converse.chatboxes.add(muc);
func.call(_converse.chatboxes, collection);
});
const nick = 'romeo';
const func = _converse.chatboxes.onChatBoxesFetched;
spyOn(_converse.chatboxes, 'onChatBoxesFetched').and.callFake(collection => {
const muc = new _converse.ChatRoom({'jid': muc_jid, 'id': muc_jid, nick}, {'collection': _converse.chatboxes});
_converse.chatboxes.add(muc);
func.call(_converse.chatboxes, collection);
});
// A MUC message gets received
const msg = $msg({
from: `${muc_jid}/juliet`,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('First message').tree();
// A MUC message gets received
const msg = $msg({
from: `${muc_jid}/juliet`,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t('First message').tree();
_converse.connection._dataRecv(test_utils.createRequest(msg));
_converse.connection._dataRecv(mock.createRequest(msg));
await _converse.api.waitUntil('chatBoxesFetched');
const muc = _converse.chatboxes.get(muc_jid);
await u.waitUntil(() => muc.message_queue.length === 1);
await _converse.api.waitUntil('chatBoxesFetched');
const muc = _converse.chatboxes.get(muc_jid);
await u.waitUntil(() => muc.message_queue.length === 1);
const view = _converse.chatboxviews.get(muc_jid);
await test_utils.getRoomFeatures(_converse, muc_jid);
await test_utils.receiveOwnMUCPresence(_converse, muc_jid, nick);
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
await view.model.messages.fetched;
const view = _converse.chatboxviews.get(muc_jid);
await mock.getRoomFeatures(_converse, muc_jid);
await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
await view.model.messages.fetched;
await u.waitUntil(() => muc.messages.length);
expect(muc.messages.at(0).get('message')).toBe('First message')
done();
}));
});
await u.waitUntil(() => muc.messages.length);
expect(muc.messages.at(0).get('message')).toBe('First message')
done();
}));
});

View File

@ -1,237 +1,239 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const _ = converse.env._;
const Strophe = converse.env.Strophe;
const $msg = converse.env.$msg;
const $pres = converse.env.$pres;
const u = converse.env.utils;
/* global mock */
describe("A spoiler message", function () {
describe("A spoiler message", function () {
it("can be received with a hint",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async (done, _converse) => {
it("can be received with a hint",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async (done, _converse) => {
await test_utils.waitForRoster(_converse, 'current');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.waitForRoster(_converse, 'current');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
/* <message to='romeo@montague.net/orchard' from='juliet@capulet.net/balcony' id='spoiler2'>
* <body>And at the end of the story, both of them die! It is so tragic!</body>
* <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler>
* </message>
*/
const spoiler_hint = "Love story end"
const spoiler = "And at the end of the story, both of them die! It is so tragic!";
const msg = $msg({
'xmlns': 'jabber:client',
'to': _converse.bare_jid,
'from': sender_jid,
'type': 'chat'
}).c('body').t(spoiler).up()
.c('spoiler', {
'xmlns': 'urn:xmpp:spoiler:0',
}).t(spoiler_hint)
.tree();
_converse.connection._dataRecv(test_utils.createRequest(msg));
await new Promise(resolve => _converse.api.listen.once('chatBoxViewInitialized', resolve));
const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Mercutio');
const message_content = view.el.querySelector('.chat-msg__text');
expect(message_content.textContent).toBe(spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
expect(spoiler_hint_el.textContent).toBe(spoiler_hint);
done();
}));
/* <message to='romeo@montague.net/orchard' from='juliet@capulet.net/balcony' id='spoiler2'>
* <body>And at the end of the story, both of them die! It is so tragic!</body>
* <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler>
* </message>
*/
const spoiler_hint = "Love story end"
const spoiler = "And at the end of the story, both of them die! It is so tragic!";
const $msg = converse.env.$msg;
const u = converse.env.utils;
const msg = $msg({
'xmlns': 'jabber:client',
'to': _converse.bare_jid,
'from': sender_jid,
'type': 'chat'
}).c('body').t(spoiler).up()
.c('spoiler', {
'xmlns': 'urn:xmpp:spoiler:0',
}).t(spoiler_hint)
.tree();
_converse.connection._dataRecv(mock.createRequest(msg));
await new Promise(resolve => _converse.api.listen.once('chatBoxViewInitialized', resolve));
const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Mercutio');
const message_content = view.el.querySelector('.chat-msg__text');
expect(message_content.textContent).toBe(spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
expect(spoiler_hint_el.textContent).toBe(spoiler_hint);
done();
}));
it("can be received without a hint",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async (done, _converse) => {
it("can be received without a hint",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async (done, _converse) => {
await test_utils.waitForRoster(_converse, 'current');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
/* <message to='romeo@montague.net/orchard' from='juliet@capulet.net/balcony' id='spoiler2'>
* <body>And at the end of the story, both of them die! It is so tragic!</body>
* <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler>
* </message>
*/
const spoiler = "And at the end of the story, both of them die! It is so tragic!";
const msg = $msg({
'xmlns': 'jabber:client',
'to': _converse.bare_jid,
'from': sender_jid,
'type': 'chat'
}).c('body').t(spoiler).up()
.c('spoiler', {
'xmlns': 'urn:xmpp:spoiler:0',
}).tree();
_converse.connection._dataRecv(test_utils.createRequest(msg));
await new Promise(resolve => _converse.api.listen.once('chatBoxViewInitialized', resolve));
const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => u.isVisible(view.el));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
expect(view.el.querySelector('.chat-msg__author').textContent.includes('Mercutio')).toBeTruthy();
const message_content = view.el.querySelector('.chat-msg__text');
expect(message_content.textContent).toBe(spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
expect(spoiler_hint_el.textContent).toBe('');
done();
}));
await mock.waitForRoster(_converse, 'current');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
/* <message to='romeo@montague.net/orchard' from='juliet@capulet.net/balcony' id='spoiler2'>
* <body>And at the end of the story, both of them die! It is so tragic!</body>
* <spoiler xmlns='urn:xmpp:spoiler:0'>Love story end</spoiler>
* </message>
*/
const $msg = converse.env.$msg;
const u = converse.env.utils;
const spoiler = "And at the end of the story, both of them die! It is so tragic!";
const msg = $msg({
'xmlns': 'jabber:client',
'to': _converse.bare_jid,
'from': sender_jid,
'type': 'chat'
}).c('body').t(spoiler).up()
.c('spoiler', {
'xmlns': 'urn:xmpp:spoiler:0',
}).tree();
_converse.connection._dataRecv(mock.createRequest(msg));
await new Promise(resolve => _converse.api.listen.once('chatBoxViewInitialized', resolve));
const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => u.isVisible(view.el));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
expect(view.el.querySelector('.chat-msg__author').textContent.includes('Mercutio')).toBeTruthy();
const message_content = view.el.querySelector('.chat-msg__text');
expect(message_content.textContent).toBe(spoiler);
const spoiler_hint_el = view.el.querySelector('.spoiler-hint');
expect(spoiler_hint_el.textContent).toBe('');
done();
}));
it("can be sent without a hint",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async (done, _converse) => {
it("can be sent without a hint",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async (done, _converse) => {
await test_utils.waitForRoster(_converse, 'current', 1);
test_utils.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.waitForRoster(_converse, 'current', 1);
mock.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
// XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see
// whether Strophe.NS.SPOILER is supported, in which case
// the spoiler button will appear.
const presence = $pres({
'from': contact_jid+'/phone',
'to': 'romeo@montague.lit'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.openChatBoxFor(_converse, contact_jid);
await test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
const view = _converse.api.chatviews.get(contact_jid);
spyOn(_converse.connection, 'send');
const { $pres, Strophe} = converse.env;
const u = converse.env.utils;
await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click();
// XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see
// whether Strophe.NS.SPOILER is supported, in which case
// the spoiler button will appear.
const presence = $pres({
'from': contact_jid+'/phone',
'to': 'romeo@montague.lit'
});
_converse.connection._dataRecv(mock.createRequest(presence));
await mock.openChatBoxFor(_converse, contact_jid);
await mock.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
const view = _converse.api.chatviews.get(contact_jid);
spyOn(_converse.connection, 'send');
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This is the spoiler';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13
});
await new Promise(resolve => view.once('messageInserted', resolve));
await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click();
/* Test the XML stanza
*
* <message from="romeo@montague.lit/orchard"
* to="max.frankfurter@montague.lit"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0"/>
* </message>"
*/
const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
const spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
expect(spoiler_el === null).toBeFalsy();
expect(spoiler_el.textContent).toBe('');
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This is the spoiler';
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13
});
await new Promise(resolve => view.once('messageInserted', resolve));
const body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler');
/* Test the XML stanza
*
* <message from="romeo@montague.lit/orchard"
* to="max.frankfurter@montague.lit"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0"/>
* </message>"
*/
const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
const spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
expect(spoiler_el === null).toBeFalsy();
expect(spoiler_el.textContent).toBe('');
/* Test the HTML spoiler message */
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Romeo Montague');
const body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler');
const spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
/* Test the HTML spoiler message */
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Romeo Montague');
spoiler_toggle = view.el.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent).toBe('Show more');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Show less');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done();
}));
const spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(Array.from(spoiler_msg_el.classList).includes('collapsed')).toBeTruthy();
it("can be sent with a hint",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async (done, _converse) => {
spoiler_toggle = view.el.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent).toBe('Show more');
spoiler_toggle.click();
expect(Array.from(spoiler_msg_el.classList).includes('collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Show less');
spoiler_toggle.click();
expect(Array.from(spoiler_msg_el.classList).includes('collapsed')).toBeTruthy();
done();
}));
await test_utils.waitForRoster(_converse, 'current', 1);
test_utils.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
it("can be sent with a hint",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async (done, _converse) => {
// XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see
// whether Strophe.NS.SPOILER is supported, in which case
// the spoiler button will appear.
const presence = $pres({
'from': contact_jid+'/phone',
'to': 'romeo@montague.lit'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.openChatBoxFor(_converse, contact_jid);
await test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
const view = _converse.api.chatviews.get(contact_jid);
await mock.waitForRoster(_converse, 'current', 1);
mock.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click();
const { $pres, Strophe} = converse.env;
const u = converse.env.utils;
spyOn(_converse.connection, 'send');
// XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see
// whether Strophe.NS.SPOILER is supported, in which case
// the spoiler button will appear.
const presence = $pres({
'from': contact_jid+'/phone',
'to': 'romeo@montague.lit'
});
_converse.connection._dataRecv(mock.createRequest(presence));
await mock.openChatBoxFor(_converse, contact_jid);
await mock.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
const view = _converse.api.chatviews.get(contact_jid);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This is the spoiler';
const hint_input = view.el.querySelector('.spoiler-hint');
hint_input.value = 'This is the hint';
await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click();
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13
});
await new Promise(resolve => view.once('messageInserted', resolve));
spyOn(_converse.connection, 'send');
/* Test the XML stanza
*
* <message from="romeo@montague.lit/orchard"
* to="max.frankfurter@montague.lit"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
* </message>"
*/
const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
const spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This is the spoiler';
const hint_input = view.el.querySelector('.spoiler-hint');
hint_input.value = 'This is the hint';
expect(spoiler_el === null).toBeFalsy();
expect(spoiler_el.textContent).toBe('This is the hint');
view.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
keyCode: 13
});
await new Promise(resolve => view.once('messageInserted', resolve));
const body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler');
/* Test the XML stanza
*
* <message from="romeo@montague.lit/orchard"
* to="max.frankfurter@montague.lit"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
* </message>"
*/
const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
const spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
/* Test the HTML spoiler message */
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Romeo Montague');
expect(spoiler_el === null).toBeFalsy();
expect(spoiler_el.textContent).toBe('This is the hint');
const spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
const body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler');
spoiler_toggle = view.el.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent).toBe('Show more');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Show less');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done();
}));
});
/* Test the HTML spoiler message */
expect(view.el.querySelector('.chat-msg__author').textContent.trim()).toBe('Romeo Montague');
const spoiler_msg_el = view.el.querySelector('.chat-msg__text.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(Array.from(spoiler_msg_el.classList).includes('collapsed')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.spoiler-toggle');
expect(spoiler_toggle.textContent).toBe('Show more');
spoiler_toggle.click();
expect(Array.from(spoiler_msg_el.classList).includes('collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Show less');
spoiler_toggle.click();
expect(Array.from(spoiler_msg_el.classList).includes('collapsed')).toBeTruthy();
done();
}));
});

View File

@ -1,77 +0,0 @@
(function (root, factory) {
define([
"jasmine",
"mock",
"test-utils",
"utils",
"transcripts"
], factory
);
} (this, function (jasmine, mock, test_utils, utils, transcripts) {
var Strophe = converse.env.Strophe;
var _ = converse.env._;
var IGNORED_TAGS = [
'stream:features',
'auth',
'challenge',
'success',
'stream:features',
'response'
];
function traverseElement (el, _stanza) {
if (typeof _stanza !== 'undefined') {
if (el.nodeType === 3) {
_stanza.t(el.nodeValue);
return _stanza;
} else {
_stanza = _stanza.c(el.nodeName.toLowerCase(), getAttributes(el));
}
} else {
_stanza = new Strophe.Builder(
el.nodeName.toLowerCase(),
getAttributes(el)
);
}
_.each(el.childNodes, _.partial(traverseElement, _, _stanza));
return _stanza.up();
}
function getAttributes (el) {
var attributes = {};
_.each(el.attributes, function (att) {
attributes[att.nodeName] = att.nodeValue;
});
return attributes;
}
return describe("Transcripts of chat logs", function () {
it("can be used to replay conversations",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
_converse.allow_non_roster_messaging = true;
await test_utils.openAndEnterChatRoom(_converse, 'discuss@conference.conversejs.org', 'romeo');
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
_.each(transcripts, function (transcript) {
const text = transcript();
const xml = Strophe.xmlHtmlNode(text);
_.each(xml.firstElementChild.children, function (el) {
_.each(el.children, function (el) {
if (el.nodeType === 3) {
return; // Ignore text
}
if (_.includes(IGNORED_TAGS, el.nodeName.toLowerCase())) {
return;
}
const _stanza = traverseElement(el);
_converse.connection._dataRecv(test_utils.createRequest(_stanza));
});
});
});
done();
}));
});
}));

View File

@ -1,77 +1,75 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const u = converse.env.utils;
/*global mock */
return describe("The User Details Modal", function () {
const u = converse.env.utils;
it("can be used to remove a contact",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
describe("The User Details Modal", function () {
await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
it("can be used to remove a contact",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid);
await u.waitUntil(() => _converse.chatboxes.length > 1);
const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
const modal = view.user_details_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
spyOn(window, 'confirm').and.returnValue(true);
spyOn(view.model.contact, 'removeFromRoster').and.callFake(callback => callback());
let remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(u.isVisible(remove_contact_button)).toBeTruthy();
remove_contact_button.click();
await u.waitUntil(() => modal.el.getAttribute('aria-hidden'), 1000);
await u.waitUntil(() => !u.isVisible(modal.el));
show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(remove_contact_button === null).toBeTruthy();
done();
}));
await mock.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
it("shows an alert when an error happened while removing the contact",
mock.initConverse(['rosterGroupsFetched'], {}, async function (done, _converse) {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid);
await u.waitUntil(() => _converse.chatboxes.length > 1);
const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
const modal = view.user_details_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1000);
spyOn(window, 'confirm').and.returnValue(true);
spyOn(view.model.contact, 'removeFromRoster').and.callFake(callback => callback());
let remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(u.isVisible(remove_contact_button)).toBeTruthy();
remove_contact_button.click();
await u.waitUntil(() => modal.el.getAttribute('aria-hidden'), 1000);
await u.waitUntil(() => !u.isVisible(modal.el));
show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(remove_contact_button === null).toBeTruthy();
done();
}));
await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
it("shows an alert when an error happened while removing the contact",
mock.initConverse(['rosterGroupsFetched'], {}, async function (done, _converse) {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid)
const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
const modal = view.user_details_modal;
await u.waitUntil(() => u.isVisible(modal.el), 2000);
spyOn(window, 'confirm').and.returnValue(true);
await mock.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
spyOn(view.model.contact, 'removeFromRoster').and.callFake((callback, errback) => errback());
let remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(u.isVisible(remove_contact_button)).toBeTruthy();
remove_contact_button.click();
await u.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
const modal = view.user_details_modal;
await u.waitUntil(() => u.isVisible(modal.el), 2000);
spyOn(window, 'confirm').and.returnValue(true);
const header = document.querySelector('.alert-danger .modal-title');
expect(header.textContent).toBe("Error");
expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
.toBe("Sorry, there was an error while trying to remove Mercutio as a contact.");
document.querySelector('.alert-danger button.close').click();
show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
await u.waitUntil(() => u.isVisible(modal.el), 2000)
spyOn(view.model.contact, 'removeFromRoster').and.callFake((callback, errback) => errback());
let remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(u.isVisible(remove_contact_button)).toBeTruthy();
remove_contact_button.click();
await u.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000);
show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
await u.waitUntil(() => u.isVisible(modal.el), 2000)
const header = document.querySelector('.alert-danger .modal-title');
expect(header.textContent).toBe("Error");
expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
.toBe("Sorry, there was an error while trying to remove Mercutio as a contact.");
document.querySelector('.alert-danger button.close').click();
show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
await u.waitUntil(() => u.isVisible(modal.el), 2000)
remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(u.isVisible(remove_contact_button)).toBeTruthy();
done();
}));
});
show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
await u.waitUntil(() => u.isVisible(modal.el), 2000)
remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(u.isVisible(remove_contact_button)).toBeTruthy();
done();
}));
});

View File

@ -1,62 +1,58 @@
window.addEventListener('converse-loaded', () => {
const utils = converse.env.utils;
const _ = converse.env._;
describe("Converse.js Utilities", function() {
return describe("Converse.js Utilities", function() {
it("applySiteSettings: recursively applies user settings", function () {
const context = {};
const settings = {
show_toolbar: true,
chatview_avatar_width: 32,
chatview_avatar_height: 32,
auto_join_rooms: [],
visible_toolbar_buttons: {
'emojis': true,
'call': false,
'clear': true,
'toggle_occupants': true
}
};
Object.assign(context, settings);
it("applySiteSettings: recursively applies user settings", function () {
var context = {};
var settings = {
show_toolbar: true,
chatview_avatar_width: 32,
chatview_avatar_height: 32,
auto_join_rooms: [],
visible_toolbar_buttons: {
'emojis': true,
'call': false,
'clear': true,
'toggle_occupants': true
}
};
_.extend(context, settings);
let user_settings = {
something_else: 'xxx',
show_toolbar: false,
chatview_avatar_width: 32,
chatview_avatar_height: 48,
auto_join_rooms: [
'anonymous@conference.nomnom.im',
],
visible_toolbar_buttons: {
'emojis': false,
'call': false,
'toggle_occupants':false,
'invalid': false
}
};
const utils = converse.env.utils;
utils.applySiteSettings(context, settings, user_settings);
var user_settings = {
something_else: 'xxx',
show_toolbar: false,
chatview_avatar_width: 32,
chatview_avatar_height: 48,
auto_join_rooms: [
'anonymous@conference.nomnom.im',
],
visible_toolbar_buttons: {
'emojis': false,
'call': false,
'toggle_occupants':false,
'invalid': false
}
};
utils.applySiteSettings(context, settings, user_settings);
expect(context.something_else).toBeUndefined();
expect(context.show_toolbar).toBeFalsy();
expect(context.chatview_avatar_width).toBe(32);
expect(context.chatview_avatar_height).toBe(48);
expect(Object.keys(context.visible_toolbar_buttons)).toEqual(Object.keys(settings.visible_toolbar_buttons));
expect(context.visible_toolbar_buttons.emojis).toBeFalsy();
expect(context.visible_toolbar_buttons.call).toBeFalsy();
expect(context.visible_toolbar_buttons.toggle_occupants).toBeFalsy();
expect(context.visible_toolbar_buttons.invalid).toBeFalsy();
expect(context.auto_join_rooms.length).toBe(1);
expect(context.auto_join_rooms[0]).toBe('anonymous@conference.nomnom.im');
expect(context.something_else).toBeUndefined();
expect(context.show_toolbar).toBeFalsy();
expect(context.chatview_avatar_width).toBe(32);
expect(context.chatview_avatar_height).toBe(48);
expect(_.keys(context.visible_toolbar_buttons)).toEqual(_.keys(settings.visible_toolbar_buttons));
expect(context.visible_toolbar_buttons.emojis).toBeFalsy();
expect(context.visible_toolbar_buttons.call).toBeFalsy();
expect(context.visible_toolbar_buttons.toggle_occupants).toBeFalsy();
expect(context.visible_toolbar_buttons.invalid).toBeFalsy();
expect(context.auto_join_rooms.length).toBe(1);
expect(context.auto_join_rooms[0]).toBe('anonymous@conference.nomnom.im');
user_settings = {
visible_toolbar_buttons: {
'toggle_occupants': true
}
};
utils.applySiteSettings(context, settings, user_settings);
expect(_.keys(context.visible_toolbar_buttons)).toEqual(_.keys(settings.visible_toolbar_buttons));
expect(context.visible_toolbar_buttons.toggle_occupants).toBeTruthy();
});
user_settings = {
visible_toolbar_buttons: {
'toggle_occupants': true
}
};
utils.applySiteSettings(context, settings, user_settings);
expect(Object.keys(context.visible_toolbar_buttons)).toEqual(Object.keys(settings.visible_toolbar_buttons));
expect(context.visible_toolbar_buttons.toggle_occupants).toBeTruthy();
});
});

View File

@ -1,23 +1,22 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const u = converse.env.utils;
/*global mock */
return describe("The XMPPStatus model", function () {
const u = converse.env.utils;
it("won't send <show>online</show> when setting a custom status message",
mock.initConverse(async (done, _converse) => {
_converse.xmppstatus.save({'status': 'online'});
spyOn(_converse.connection, 'send');
_converse.api.user.status.message.set("I'm also happy!");
await u.waitUntil(() => _converse.connection.send.calls.count());
const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
expect(stanza.childNodes.length).toBe(3);
expect(stanza.querySelectorAll('status').length).toBe(1);
expect(stanza.querySelector('status').textContent).toBe("I'm also happy!");
expect(stanza.querySelectorAll('show').length).toBe(0);
expect(stanza.querySelectorAll('priority').length).toBe(1);
expect(stanza.querySelector('priority').textContent).toBe('0');
done();
}));
});
describe("The XMPPStatus model", function () {
it("won't send <show>online</show> when setting a custom status message",
mock.initConverse(async (done, _converse) => {
_converse.xmppstatus.save({'status': 'online'});
spyOn(_converse.connection, 'send');
_converse.api.user.status.message.set("I'm also happy!");
await u.waitUntil(() => _converse.connection.send.calls.count());
const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
expect(stanza.childNodes.length).toBe(3);
expect(stanza.querySelectorAll('status').length).toBe(1);
expect(stanza.querySelector('status').textContent).toBe("I'm also happy!");
expect(stanza.querySelectorAll('show').length).toBe(0);
expect(stanza.querySelectorAll('priority').length).toBe(1);
expect(stanza.querySelector('priority').textContent).toBe('0');
done();
}));
});

View File

@ -1,244 +1,242 @@
window.addEventListener('converse-loaded', () => {
const mock = window.mock;
const test_utils = window.test_utils;
const $pres = converse.env.$pres;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
/*global mock */
describe("XSS", function () {
describe("A Chat Message", function () {
const $pres = converse.env.$pres;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
it("will escape IMG payload XSS attempts",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
describe("XSS", function () {
describe("A Chat Message", function () {
spyOn(window, 'alert').and.callThrough();
await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse);
it("will escape IMG payload XSS attempts",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid)
const view = _converse.api.chatviews.get(contact_jid);
spyOn(window, 'alert').and.callThrough();
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
let message = "<img src=x onerror=alert('XSS');>";
await test_utils.sendMessage(view, message);
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x onerror=alert('XSS');&gt;");
expect(window.alert).not.toHaveBeenCalled();
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.api.chatviews.get(contact_jid);
message = "<img src=x onerror=alert('XSS')//";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x onerror=alert('XSS')//");
let message = "<img src=x onerror=alert('XSS');>";
await mock.sendMessage(view, message);
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x onerror=alert('XSS');&gt;");
expect(window.alert).not.toHaveBeenCalled();
message = "<img src=x onerror=alert(String.fromCharCode(88,83,83));>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x onerror=alert(String.fromCharCode(88,83,83));&gt;");
message = "<img src=x onerror=alert('XSS')//";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x onerror=alert('XSS')//");
message = "<img src=x oneonerrorrror=alert(String.fromCharCode(88,83,83));>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x oneonerrorrror=alert(String.fromCharCode(88,83,83));&gt;");
message = "<img src=x onerror=alert(String.fromCharCode(88,83,83));>";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x onerror=alert(String.fromCharCode(88,83,83));&gt;");
message = "<img src=x:alert(alt) onerror=eval(src) alt=xss>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x:alert(alt) onerror=eval(src) alt=xss&gt;");
message = "<img src=x oneonerrorrror=alert(String.fromCharCode(88,83,83));>";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x oneonerrorrror=alert(String.fromCharCode(88,83,83));&gt;");
message = "><img src=x onerror=alert('XSS');>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&gt;&lt;img src=x onerror=alert('XSS');&gt;");
message = "<img src=x:alert(alt) onerror=eval(src) alt=xss>";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;img src=x:alert(alt) onerror=eval(src) alt=xss&gt;");
message = "><img src=x onerror=alert(String.fromCharCode(88,83,83));>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&gt;&lt;img src=x onerror=alert(String.fromCharCode(88,83,83));&gt;");
message = "><img src=x onerror=alert('XSS');>";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&gt;&lt;img src=x onerror=alert('XSS');&gt;");
expect(window.alert).not.toHaveBeenCalled();
done();
}));
message = "><img src=x onerror=alert(String.fromCharCode(88,83,83));>";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&gt;&lt;img src=x onerror=alert(String.fromCharCode(88,83,83));&gt;");
it("will escape SVG payload XSS attempts",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
expect(window.alert).not.toHaveBeenCalled();
done();
}));
spyOn(window, 'alert').and.callThrough();
await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse);
it("will escape SVG payload XSS attempts",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid)
const view = _converse.api.chatviews.get(contact_jid);
spyOn(window, 'alert').and.callThrough();
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
let message = "<svg onload=alert(1)>";
await test_utils.sendMessage(view, message);
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('&lt;svg onload=alert(1)&gt;');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.api.chatviews.get(contact_jid);
message = "<svg/onload=alert('XSS')>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;svg/onload=alert('XSS')&gt;");
let message = "<svg onload=alert(1)>";
await mock.sendMessage(view, message);
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('&lt;svg onload=alert(1)&gt;');
message = "<svg onload=alert(1)//";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;svg onload=alert(1)//");
message = "<svg/onload=alert('XSS')>";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;svg/onload=alert('XSS')&gt;");
message = "<svg/onload=alert(String.fromCharCode(88,83,83))>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;svg/onload=alert(String.fromCharCode(88,83,83))&gt;");
message = "<svg onload=alert(1)//";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;svg onload=alert(1)//");
message = "<svg id=alert(1) onload=eval(id)>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;svg id=alert(1) onload=eval(id)&gt;");
message = "<svg/onload=alert(String.fromCharCode(88,83,83))>";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;svg/onload=alert(String.fromCharCode(88,83,83))&gt;");
message = '"><svg/onload=alert(String.fromCharCode(88,83,83))>';
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('"&gt;&lt;svg/onload=alert(String.fromCharCode(88,83,83))&gt;');
message = "<svg id=alert(1) onload=eval(id)>";
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual("&lt;svg id=alert(1) onload=eval(id)&gt;");
message = '"><svg/onload=alert(/XSS/)';
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('"&gt;&lt;svg/onload=alert(/XSS/)');
message = '"><svg/onload=alert(String.fromCharCode(88,83,83))>';
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('"&gt;&lt;svg/onload=alert(String.fromCharCode(88,83,83))&gt;');
expect(window.alert).not.toHaveBeenCalled();
done();
}));
message = '"><svg/onload=alert(/XSS/)';
await mock.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('"&gt;&lt;svg/onload=alert(/XSS/)');
it("will have properly escaped URLs",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
expect(window.alert).not.toHaveBeenCalled();
done();
}));
await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse);
it("will have properly escaped URLs",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid)
const view = _converse.api.chatviews.get(contact_jid);
await mock.waitForRoster(_converse, 'current');
await mock.openControlBox(_converse);
let message = "http://www.opkode.com/'onmouseover='alert(1)'whatever";
await test_utils.sendMessage(view, message);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await mock.openChatBoxFor(_converse, contact_jid)
const view = _converse.api.chatviews.get(contact_jid);
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML)
.toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%27onmouseover=%27alert%281%29%27whatever">http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever</a>');
let message = "http://www.opkode.com/'onmouseover='alert(1)'whatever";
await mock.sendMessage(view, message);
message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
await test_utils.sendMessage(view, message);
let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML)
.toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%27onmouseover=%27alert%281%29%27whatever">http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever</a>');
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
await mock.sendMessage(view, message);
message = "https://en.wikipedia.org/wiki/Ender's_Game";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
message = "https://en.wikipedia.org/wiki/Ender's_Game";
await mock.sendMessage(view, message);
message = "<https://bugs.documentfoundation.org/show_bug.cgi?id=123737>";
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">'+message+'</a>');
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual(
`&lt;<a target="_blank" rel="noopener" href="https://bugs.documentfoundation.org/show_bug.cgi?id=123737">https://bugs.documentfoundation.org/show_bug.cgi?id=123737</a>&gt;`);
message = "<https://bugs.documentfoundation.org/show_bug.cgi?id=123737>";
await mock.sendMessage(view, message);
message = '<http://www.opkode.com/"onmouseover="alert(1)"whatever>';
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual(
`&lt;<a target="_blank" rel="noopener" href="https://bugs.documentfoundation.org/show_bug.cgi?id=123737">https://bugs.documentfoundation.org/show_bug.cgi?id=123737</a>&gt;`);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual(
'&lt;<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>&gt;');
message = '<http://www.opkode.com/"onmouseover="alert(1)"whatever>';
await mock.sendMessage(view, message);
message = `https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2`
await test_utils.sendMessage(view, message);
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual(
'&lt;<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>&gt;');
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual(
`<a target="_blank" rel="noopener" href="https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=%213m6%211e1%213m4%211sQ7SdHo_bPLPlLlU8GSGWaQ%212e0%217i13312%218i6656%214m5%213m4%211s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08%218m2%213d52.3773668%214d4.5489388%215m1%211e2">https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2</a>`);
done();
}));
});
message = `https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2`
await mock.sendMessage(view, message);
describe("A Groupchat", function () {
msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
expect(msg.textContent).toEqual(message);
expect(msg.innerHTML).toEqual(
`<a target="_blank" rel="noopener" href="https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=%213m6%211e1%213m4%211sQ7SdHo_bPLPlLlU8GSGWaQ%212e0%217i13312%218i6656%214m5%213m4%211s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08%218m2%213d52.3773668%214d4.5489388%215m1%211e2">https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2</a>`);
done();
}));
});
it("escapes occupant nicknames when rendering them, to avoid JS-injection attacks",
mock.initConverse(['rosterGroupsFetched'], {},
async function (done, _converse) {
describe("A Groupchat", function () {
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
/* <presence xmlns="jabber:client" to="jc@chat.example.org/converse.js-17184538"
* from="oo@conference.chat.example.org/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;">
* <x xmlns="http://jabber.org/protocol/muc#user">
* <item jid="jc@chat.example.org/converse.js-17184538" affiliation="owner" role="moderator"/>
* <status code="110"/>
* </x>
* </presence>"
*/
const presence = $pres({
to:'romeo@montague.lit/pda',
from:"lounge@montague.lit/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;"
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
jid: 'someone@montague.lit',
role: 'moderator',
}).up()
.c('status').attrs({code:'110'}).nodeTree;
it("escapes occupant nicknames when rendering them, to avoid JS-injection attacks",
mock.initConverse(['rosterGroupsFetched'], {},
async function (done, _converse) {
_converse.connection._dataRecv(test_utils.createRequest(presence));
const view = _converse.chatboxviews.get('lounge@montague.lit');
await u.waitUntil(() => view.el.querySelectorAll('li .occupant-nick').length, 500);
const occupants = view.el.querySelector('.occupant-list').querySelectorAll('li .occupant-nick');
expect(occupants.length).toBe(2);
expect(occupants[0].textContent.trim()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
done();
}));
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
/* <presence xmlns="jabber:client" to="jc@chat.example.org/converse.js-17184538"
* from="oo@conference.chat.example.org/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;">
* <x xmlns="http://jabber.org/protocol/muc#user">
* <item jid="jc@chat.example.org/converse.js-17184538" affiliation="owner" role="moderator"/>
* <status code="110"/>
* </x>
* </presence>"
*/
const presence = $pres({
to:'romeo@montague.lit/pda',
from:"lounge@montague.lit/&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;"
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
jid: 'someone@montague.lit',
role: 'moderator',
}).up()
.c('status').attrs({code:'110'}).nodeTree;
it("escapes the subject before rendering it, to avoid JS-injection attacks",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
_converse.connection._dataRecv(mock.createRequest(presence));
const view = _converse.chatboxviews.get('lounge@montague.lit');
await u.waitUntil(() => view.el.querySelectorAll('li .occupant-nick').length, 500);
const occupants = view.el.querySelector('.occupant-list').querySelectorAll('li .occupant-nick');
expect(occupants.length).toBe(2);
expect(occupants[0].textContent.trim()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
done();
}));
await test_utils.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'jc');
spyOn(window, 'alert');
const subject = '<img src="x" onerror="alert(\'XSS\');"/>';
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
view.model.set({'subject': {
'text': subject,
'author': 'ralphm'
}});
const text = await u.waitUntil(() => view.el.querySelector('.chat-head__desc')?.textContent.trim());
expect(text).toBe(subject);
done();
}));
});
it("escapes the subject before rendering it, to avoid JS-injection attacks",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
await mock.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'jc');
spyOn(window, 'alert');
const subject = '<img src="x" onerror="alert(\'XSS\');"/>';
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
view.model.set({'subject': {
'text': subject,
'author': 'ralphm'
}});
const text = await u.waitUntil(() => view.el.querySelector('.chat-head__desc')?.textContent.trim());
expect(text).toBe(subject);
done();
}));
});
});

View File

@ -1,140 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Converse Tests</title>
<meta name="description" content="Converse XMPP Chat" />
<link rel="shortcut icon" type="image/png" href="node_modules/jasmine-core/images/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" media="screen" href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
<link type="text/css" rel="stylesheet" media="screen" href="dist/website.css" />
<script src="tests/mock.js"></script>
<script src="tests/utils.js"></script>
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
<script src="node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
<script src="node_modules/sinon/pkg/sinon.js"></script>
<script src="tests/console-reporter.js"script>
<script src="spec/spoilers.js"></script>
<script src="spec/roomslist.js"></script>
<script src="spec/utils.js"></script>
<script src="spec/converse.js"></script>
<script src="spec/bookmarks.js"></script>
<script src="spec/headline.js"></script>
<script src="spec/disco.js"></script>
<script src="spec/protocol.js"></script>
<script src="spec/presence.js"></script>
<script src="spec/eventemitter.js"></script>
<script src="spec/smacks.js"></script>
<script src="spec/ping.js"></script>
<script src="spec/push.js"></script>
<script src="spec/xmppstatus.js"></script>
<script src="spec/mam.js"></script>
<script src="spec/omemo.js"></script>
<script src="spec/controlbox.js"></script>
<script src="spec/roster.js"></script>
<script src="spec/chatbox.js"></script>
<script src="spec/user-details-modal.js"></script>
<script src="spec/messages.js"></script>
<script src="spec/muc_messages.js"></script>
<script src="spec/retractions.js"></script>
<script src="spec/muc.js"></script>
<script src="spec/modtools.js"></script>
<script src="spec/room_registration.js"></script>
<script src="spec/autocomplete.js"></script>
<script src="spec/minchats.js"></script>
<script src="spec/notification.js"></script>
<script src="spec/login.js"></script>
<script src="spec/register.js"></script>
<script src="spec/hats.js"></script>
<script src="spec/http-file-upload.js"></script>
<script src="spec/emojis.js"></script>
<script src="spec/xss.js"></script>
<style>
.tests-brand-heading {
margin-top: 1em;
font-size: 200%;
}
.jasmine_html-reporter {
text-align: left;
width: 100vw;
background-color: rgba(255, 255, 255, .5);
}
.intro {
background: unset;
background-color: #397491;
}
</style>
</head>
<body id="page-top" data-spy="scroll" class="converse-website">
<section class="section-wrapper">
<section id="intro" class="intro" class="container">
<div class="row">
<div class="col-md-12 col-md-offset-2">
<h1 class="brand-heading fade-in">
<svg class="converse-svg-logo"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 364 364">
<title>Converse</title>
<g class="cls-1" id="g904">
<g data-name="Layer 2">
<g data-name="Layer 7">
<path
class="cls-3"
d="M221.46,103.71c0,18.83-29.36,18.83-29.12,0C192.1,84.88,221.46,84.88,221.46,103.71Z" />
<path
class="cls-4"
d="M179.9,4.15A175.48,175.48,0,1,0,355.38,179.63,175.48,175.48,0,0,0,179.9,4.15Zm-40.79,264.5c-.23-17.82,27.58-17.82,27.58,0S138.88,286.48,139.11,268.65ZM218.6,168.24A79.65,79.65,0,0,1,205.15,174a12.76,12.76,0,0,0-6.29,4.65L167.54,222a1.36,1.36,0,0,1-2.46-.8v-35.8a2.58,2.58,0,0,0-3.06-2.53c-15.43,3-30.23,7.7-42.73,19.94-38.8,38-29.42,105.69,16.09,133.16a162.25,162.25,0,0,1-91.47-67.27C-3.86,182.26,34.5,47.25,138.37,25.66c46.89-9.75,118.25,5.16,123.73,62.83C265.15,120.64,246.56,152.89,218.6,168.24Z" />
</g>
</g>
</g>
</svg>
<span class="brand-heading__text">
<span>converse<span class="subdued">.js</span></span>
<p class="byline">messaging freedom</p>
</span>
</h1>
<h2 id="project_tagline">Tests</h2>
</div>
</div>
<div class="row jasmine-output-container"></div>
</section>
</body>
<script>
jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000;
const env = jasmine.getEnv();
const queryString = new jasmine.QueryString({
getWindowLocation: () => window.location
});
env.clearReporters();
const htmlReporter = new jasmine.HtmlReporter({
env,
onRaiseExceptionsClick: () => { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); },
onThrowExpectationsClick: () => { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); },
onRandomClick: () => { queryString.navigateWithNewParam("random", !env.randomTests()); },
addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
getContainer: () => document.querySelector('.jasmine-output-container'),
createElement: function () { return document.createElement.apply(document, arguments); },
createTextNode: function () { return document.createTextNode.apply(document, arguments); },
timer: new jasmine.Timer(),
filterSpecs: !!queryString.getParam("spec")
});
//The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
const jasmineInterface = jasmineRequire.interface(jasmine, env);
env.addReporter(jasmineInterface.jsApiReporter);
env.addReporter(htmlReporter);
env.addReporter(new ConsoleReporter());
converse.load();
</script>
</html>

View File

@ -1,142 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Converse Tests</title>
<meta name="description" content="Converse XMPP Chat" />
<link rel="shortcut icon" type="image/png" href="../node_modules/jasmine-core/images/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" media="screen" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
<link type="text/css" rel="stylesheet" media="screen" href="../dist/converse.css" />
<script src="../dist/converse.js"></script>
<link type="text/css" rel="stylesheet" media="screen" href="../dist/website.css" />
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
<script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
<script src="../node_modules/sinon/pkg/sinon.js"></script>
<script src="console-reporter.js"></script>
<script src="../tests/mock.js"></script>
<script src="../tests/utils.js"></script>
<script src="../spec/spoilers.js"></script>
<script src="../spec/roomslist.js"></script>
<script src="../spec/utils.js"></script>
<script src="../spec/converse.js"></script>
<script src="../spec/bookmarks.js"></script>
<script src="../spec/headline.js"></script>
<script src="../spec/disco.js"></script>
<script src="../spec/protocol.js"></script>
<script src="../spec/presence.js"></script>
<script src="../spec/eventemitter.js"></script>
<script src="../spec/smacks.js"></script>
<script src="../spec/ping.js"></script>
<script src="../spec/push.js"></script>
<script src="../spec/xmppstatus.js"></script>
<script src="../spec/mam.js"></script>
<script src="../spec/omemo.js"></script>
<script src="../spec/controlbox.js"></script>
<script src="../spec/roster.js"></script>
<script src="../spec/chatbox.js"></script>
<script src="../spec/user-details-modal.js"></script>
<script src="../spec/messages.js"></script>
<script src="../spec/muc_messages.js"></script>
<script src="../spec/retractions.js"></script>
<script src="../spec/muc.js"></script>
<script src="../spec/modtools.js"></script>
<script src="../spec/room_registration.js"></script>
<script src="../spec/autocomplete.js"></script>
<script src="../spec/minchats.js"></script>
<script src="../spec/notification.js"></script>
<script src="../spec/login.js"></script>
<script src="../spec/register.js"></script>
<script src="../spec/hats.js"></script>
<script src="../spec/http-file-upload.js"></script>
<script src="../spec/emojis.js"></script>
<script src="../spec/xss.js"></script>
<style>
.tests-brand-heading {
margin-top: 1em;
font-size: 200%;
}
.jasmine_html-reporter {
text-align: left;
width: 100vw;
background-color: rgba(255, 255, 255, .5);
}
.intro {
background: unset;
background-color: #397491;
}
</style>
</head>
<body id="page-top" data-spy="scroll" class="converse-website">
<section class="section-wrapper">
<section id="intro" class="intro" class="container">
<div class="row">
<div class="col-md-12 col-md-offset-2">
<h1 class="brand-heading fade-in">
<svg class="converse-svg-logo"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 364 364">
<title>Converse</title>
<g class="cls-1" id="g904">
<g data-name="Layer 2">
<g data-name="Layer 7">
<path
class="cls-3"
d="M221.46,103.71c0,18.83-29.36,18.83-29.12,0C192.1,84.88,221.46,84.88,221.46,103.71Z" />
<path
class="cls-4"
d="M179.9,4.15A175.48,175.48,0,1,0,355.38,179.63,175.48,175.48,0,0,0,179.9,4.15Zm-40.79,264.5c-.23-17.82,27.58-17.82,27.58,0S138.88,286.48,139.11,268.65ZM218.6,168.24A79.65,79.65,0,0,1,205.15,174a12.76,12.76,0,0,0-6.29,4.65L167.54,222a1.36,1.36,0,0,1-2.46-.8v-35.8a2.58,2.58,0,0,0-3.06-2.53c-15.43,3-30.23,7.7-42.73,19.94-38.8,38-29.42,105.69,16.09,133.16a162.25,162.25,0,0,1-91.47-67.27C-3.86,182.26,34.5,47.25,138.37,25.66c46.89-9.75,118.25,5.16,123.73,62.83C265.15,120.64,246.56,152.89,218.6,168.24Z" />
</g>
</g>
</g>
</svg>
<span class="brand-heading__text">
<span>converse<span class="subdued">.js</span></span>
<p class="byline">messaging freedom</p>
</span>
</h1>
<h2 id="project_tagline">Tests</h2>
</div>
</div>
<div class="row jasmine-output-container"></div>
</section>
</body>
<script>
jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000;
const env = jasmine.getEnv();
const queryString = new jasmine.QueryString({
getWindowLocation: () => window.location
});
env.clearReporters();
const htmlReporter = new jasmine.HtmlReporter({
env,
onRaiseExceptionsClick: () => { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); },
onThrowExpectationsClick: () => { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); },
onRandomClick: () => { queryString.navigateWithNewParam("random", !env.randomTests()); },
addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
getContainer: () => document.querySelector('.jasmine-output-container'),
createElement: function () { return document.createElement.apply(document, arguments); },
createTextNode: function () { return document.createTextNode.apply(document, arguments); },
timer: new jasmine.Timer(),
filterSpecs: !!queryString.getParam("spec")
});
//The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
const jasmineInterface = jasmineRequire.interface(jasmine, env);
env.addReporter(jasmineInterface.jsApiReporter);
env.addReporter(htmlReporter);
env.addReporter(new ConsoleReporter());
converse.load();
</script>
</html>

View File

@ -2,6 +2,8 @@ const mock = {};
window.mock = mock;
let _converse, initConverse;
const converseLoaded = new Promise(resolve => window.addEventListener('converse-loaded', resolve));
mock.initConverse = function (promise_names=[], settings=null, func) {
if (typeof promise_names === "function") {
func = promise_names;
@ -19,6 +21,7 @@ mock.initConverse = function (promise_names=[], settings=null, func) {
}
document.title = "Converse Tests";
await converseLoaded;
await initConverse(settings);
await Promise.all((promise_names || []).map(_converse.api.waitUntil));
try {
@ -32,12 +35,432 @@ mock.initConverse = function (promise_names=[], settings=null, func) {
};
window.addEventListener('converse-loaded', () => {
const _ = converse.env._;
const u = converse.env.utils;
const Promise = converse.env.Promise;
const Strophe = converse.env.Strophe;
const dayjs = converse.env.dayjs;
const $iq = converse.env.$iq;
const { _, u, sizzle, Strophe, dayjs, $iq, $msg, $pres } = converse.env;
mock.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities, features=[], items=[], type='info') {
const iq = await u.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas,
(iq) => sizzle(`iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#${type}"]`, iq).length
).pop();
}, 300);
const stanza = $iq({
'type': 'result',
'from': entity_jid,
'to': 'romeo@montague.lit/orchard',
'id': iq.getAttribute('id'),
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#'+type});
_.forEach(identities, function (identity) {
stanza.c('identity', {'category': identity.category, 'type': identity.type}).up()
});
_.forEach(features, function (feature) {
stanza.c('feature', {'var': feature}).up();
});
_.forEach(items, function (item) {
stanza.c('item', {'jid': item}).up();
});
_converse.connection._dataRecv(mock.createRequest(stanza));
}
mock.createRequest = function (iq) {
iq = typeof iq.tree == "function" ? iq.tree() : iq;
var req = new Strophe.Request(iq, function() {});
req.getResponse = function () {
var env = new Strophe.Builder('env', {type: 'mock'}).tree();
env.appendChild(iq);
return env;
};
return req;
};
mock.closeAllChatBoxes = function (_converse) {
return Promise.all(_converse.chatboxviews.map(view => view.close()));
};
mock.openControlBox = async function (_converse) {
const model = await _converse.api.controlbox.open();
await u.waitUntil(() => model.get('connected'));
var toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
}
return this;
};
mock.closeControlBox = function () {
const controlbox = document.querySelector("#controlbox");
if (u.isVisible(controlbox)) {
const button = controlbox.querySelector(".close-chatbox-button");
if (!_.isNull(button)) {
button.click();
}
}
return this;
};
mock.waitUntilBookmarksReturned = async function (_converse, bookmarks=[]) {
await mock.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options']
);
const IQ_stanzas = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(
() => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop()
);
const stanza = $iq({
'to': _converse.connection.jid,
'type':'result',
'id':sent_stanza.getAttribute('id')
}).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
.c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'});
bookmarks.forEach(bookmark => {
stanza.c('conference', {
'name': bookmark.name,
'autojoin': bookmark.autojoin,
'jid': bookmark.jid
}).c('nick').t(bookmark.nick).up().up()
});
_converse.connection._dataRecv(mock.createRequest(stanza));
await _converse.api.waitUntil('bookmarksInitialized');
};
mock.openChatBoxes = function (converse, amount) {
const views = [];
for (let i=0; i<amount; i++) {
const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
views.push(converse.roster.get(jid).trigger("open"));
}
return views;
};
mock.openChatBoxFor = async function (_converse, jid) {
await _converse.api.waitUntil('rosterContactsFetched');
_converse.roster.get(jid).trigger("open");
return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
};
mock.openChatRoomViaModal = async function (_converse, jid, nick='') {
// Opens a new chatroom
const model = await _converse.api.controlbox.open('controlbox');
await u.waitUntil(() => model.get('connected'));
await mock.openControlBox(_converse);
const view = await _converse.chatboxviews.get('controlbox');
const roomspanel = view.roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click();
mock.closeControlBox(_converse);
const modal = roomspanel.add_room_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1500)
modal.el.querySelector('input[name="chatroom"]').value = jid;
if (nick) {
modal.el.querySelector('input[name="nickname"]').value = nick;
}
modal.el.querySelector('form input[type="submit"]').click();
await u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
return _converse.chatboxviews.get(jid);
};
mock.openChatRoom = function (_converse, room, server) {
return _converse.api.rooms.open(`${room}@${server}`);
};
mock.getRoomFeatures = async function (_converse, muc_jid, features=[]) {
const room = Strophe.getNodeFromJid(muc_jid);
muc_jid = muc_jid.toLowerCase();
const stanzas = _converse.connection.IQ_stanzas;
const stanza = await u.waitUntil(() => stanzas.filter(
iq => iq.querySelector(
`iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
)).pop()
);
const features_stanza = $iq({
'from': muc_jid,
'id': stanza.getAttribute('id'),
'to': 'romeo@montague.lit/desktop',
'type': 'result'
}).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
.c('identity', {
'category': 'conference',
'name': room[0].toUpperCase() + room.slice(1),
'type': 'text'
}).up();
features = features.length ? features : mock.default_muc_features;
features.forEach(f => features_stanza.c('feature', {'var': f}).up());
features_stanza.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(mock.createRequest(features_stanza));
};
mock.waitForReservedNick = async function (_converse, muc_jid, nick) {
const stanzas = _converse.connection.IQ_stanzas;
const selector = `iq[to="${muc_jid.toLowerCase()}"] query[node="x-roomuser-item"]`;
const iq = await u.waitUntil(() => stanzas.filter(s => sizzle(selector, s).length).pop());
// We remove the stanza, otherwise we might get stale stanzas returned in our filter above.
stanzas.splice(stanzas.indexOf(iq), 1)
// The XMPP server returns the reserved nick for this user.
const IQ_id = iq.getAttribute('id');
const stanza = $iq({
'type': 'result',
'id': IQ_id,
'from': muc_jid,
'to': _converse.connection.jid
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'});
if (nick) {
stanza.c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
}
_converse.connection._dataRecv(mock.createRequest(stanza));
if (nick) {
return u.waitUntil(() => nick);
}
};
mock.returnMemberLists = async function (_converse, muc_jid, members=[], affiliations=['member', 'owner', 'admin']) {
if (affiliations.length === 0) {
return;
}
const stanzas = _converse.connection.IQ_stanzas;
if (affiliations.includes('member')) {
const member_IQ = await u.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
).pop());
const member_list_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
'id': member_IQ.getAttribute('id'),
'to': 'romeo@montague.lit/orchard',
'type': 'result'
}).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
members.filter(m => m.affiliation === 'member').forEach(m => {
member_list_stanza.c('item', {
'affiliation': m.affiliation,
'jid': m.jid,
'nick': m.nick
});
});
_converse.connection._dataRecv(mock.createRequest(member_list_stanza));
}
if (affiliations.includes('admin')) {
const admin_IQ = await u.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
).pop());
const admin_list_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
'id': admin_IQ.getAttribute('id'),
'to': 'romeo@montague.lit/orchard',
'type': 'result'
}).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
members.filter(m => m.affiliation === 'admin').forEach(m => {
admin_list_stanza.c('item', {
'affiliation': m.affiliation,
'jid': m.jid,
'nick': m.nick
});
});
_converse.connection._dataRecv(mock.createRequest(admin_list_stanza));
}
if (affiliations.includes('owner')) {
const owner_IQ = await u.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
).pop());
const owner_list_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
'id': owner_IQ.getAttribute('id'),
'to': 'romeo@montague.lit/orchard',
'type': 'result'
}).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
members.filter(m => m.affiliation === 'owner').forEach(m => {
owner_list_stanza.c('item', {
'affiliation': m.affiliation,
'jid': m.jid,
'nick': m.nick
});
});
_converse.connection._dataRecv(mock.createRequest(owner_list_stanza));
}
return new Promise(resolve => _converse.api.listen.on('membersFetched', resolve));
};
mock.receiveOwnMUCPresence = async function (_converse, muc_jid, nick) {
const sent_stanzas = _converse.connection.sent_stanzas;
await u.waitUntil(() => sent_stanzas.filter(iq => sizzle('presence history', iq).length).pop());
const presence = $pres({
to: _converse.connection.jid,
from: `${muc_jid}/${nick}`,
id: u.getUniqueId()
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'owner',
jid: _converse.bare_jid,
role: 'moderator'
}).up()
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(mock.createRequest(presence));
};
mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[]) {
muc_jid = muc_jid.toLowerCase();
const room_creation_promise = _converse.api.rooms.open(muc_jid);
await mock.getRoomFeatures(_converse, muc_jid, features);
await mock.waitForReservedNick(_converse, muc_jid, nick);
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
// See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres
await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
await room_creation_promise;
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
const affs = _converse.muc_fetch_members;
const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
await mock.returnMemberLists(_converse, muc_jid, members, all_affiliations);
await view.model.messages.fetched;
};
mock.clearChatBoxMessages = function (converse, jid) {
const view = converse.chatboxviews.get(jid);
view.msgs_container.innerHTML = '';
return view.model.messages.clearStore();
};
mock.createContact = async function (_converse, name, ask, requesting, subscription) {
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
if (_converse.roster.get(jid)) {
return Promise.resolve();
}
const contact = await new Promise((success, error) => {
_converse.roster.create({
'ask': ask,
'fullname': name,
'jid': jid,
'requesting': requesting,
'subscription': subscription
}, {success, error});
});
return contact;
};
mock.createContacts = async function (_converse, type, length) {
/* Create current (as opposed to requesting or pending) contacts
* for the user's roster.
*
* These contacts are not grouped. See below.
*/
await _converse.api.waitUntil('rosterContactsFetched');
let names, subscription, requesting, ask;
if (type === 'requesting') {
names = mock.req_names;
subscription = 'none';
requesting = true;
ask = null;
} else if (type === 'pending') {
names = mock.pend_names;
subscription = 'none';
requesting = false;
ask = 'subscribe';
} else if (type === 'current') {
names = mock.cur_names;
subscription = 'both';
requesting = false;
ask = null;
} else if (type === 'all') {
await this.createContacts(_converse, 'current');
await this.createContacts(_converse, 'requesting')
await this.createContacts(_converse, 'pending');
return this;
} else {
throw Error("Need to specify the type of contact to create");
}
const promises = names.slice(0, length).map(n => this.createContact(_converse, n, ask, requesting, subscription));
await Promise.all(promises);
};
mock.waitForRoster = async function (_converse, type='current', length=-1, include_nick=true, grouped=true) {
const s = `iq[type="get"] query[xmlns="${Strophe.NS.ROSTER}"]`;
const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(s, iq).length).pop());
const result = $iq({
'to': _converse.connection.jid,
'type': 'result',
'id': iq.getAttribute('id')
}).c('query', {
'xmlns': 'jabber:iq:roster'
});
if (type === 'pending' || type === 'all') {
const pend_names = (length > -1) ? mock.pend_names.slice(0, length) : mock.pend_names;
pend_names.map(name =>
result.c('item', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined,
subscription: 'none',
ask: 'subscribe'
}).up()
);
}
if (type === 'current' || type === 'all') {
const cur_names = Object.keys(mock.current_contacts_map);
const names = (length > -1) ? cur_names.slice(0, length) : cur_names;
names.forEach(name => {
result.c('item', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined,
subscription: 'both',
ask: null
});
if (grouped) {
mock.current_contacts_map[name].forEach(g => result.c('group').t(g).up());
}
result.up();
});
}
_converse.connection._dataRecv(mock.createRequest(result));
await _converse.api.waitUntil('rosterContactsFetched');
};
mock.createChatMessage = function (_converse, sender_jid, message) {
return $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: (new Date()).getTime()
})
.c('body').t(message).up()
.c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
}
mock.sendMessage = function (view, message) {
const promise = new Promise(resolve => view.once('messageInserted', resolve));
view.el.querySelector('.chat-textarea').value = message;
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
preventDefault: _.noop,
keyCode: 13
});
return promise;
};
window.libsignal = {
'SignalProtocolAddress': function (name, device_id) {
@ -335,3 +758,5 @@ window.addEventListener('converse-loaded', () => {
return _converse;
}
});
converse.load();

View File

@ -1,34 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Converse.js Tests</title>
<meta name="description" content="Converse.js: A chat client for your website" />
<link rel="shortcut icon" type="image/png" href="../node_modules/jasmine-core/images/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" media="screen" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
<link rel="stylesheet" type="text/css" media="screen" href="../css/jasmine.css">
<link type="text/css" rel="stylesheet" media="screen" href="../css/website.css" />
<link type="text/css" rel="stylesheet" media="screen" href="../css/converse.css" />
<script src="../src/config.js"></script>
<script data-main="runner-transpiled" src="../node_modules/requirejs/require.js"></script>
<style>
.tests-brand-heading {
margin-top: 1em;
font-size: 200%;
}
</style>
</head>
<body>
<div id="header_wrap" class="outer">
<header class="inner">
<h1 class="brand-heading tests-brand-heading">
<i class="icon-conversejs"></i> Converse.js</h1>
<h2 id="project_tagline">Tests</h2>
</header>
</div>
</body>
</html>

View File

@ -1,437 +0,0 @@
window.addEventListener('converse-loaded', () => {
const _ = converse.env._;
const $msg = converse.env.$msg;
const $pres = converse.env.$pres;
const $iq = converse.env.$iq;
const Strophe = converse.env.Strophe;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
const mock = window.mock;
const utils = {};
window.test_utils = utils;
utils.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities, features=[], items=[], type='info') {
const iq = await u.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas,
(iq) => sizzle(`iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#${type}"]`, iq).length
).pop();
}, 300);
const stanza = $iq({
'type': 'result',
'from': entity_jid,
'to': 'romeo@montague.lit/orchard',
'id': iq.getAttribute('id'),
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#'+type});
_.forEach(identities, function (identity) {
stanza.c('identity', {'category': identity.category, 'type': identity.type}).up()
});
_.forEach(features, function (feature) {
stanza.c('feature', {'var': feature}).up();
});
_.forEach(items, function (item) {
stanza.c('item', {'jid': item}).up();
});
_converse.connection._dataRecv(utils.createRequest(stanza));
}
utils.createRequest = function (iq) {
iq = typeof iq.tree == "function" ? iq.tree() : iq;
var req = new Strophe.Request(iq, function() {});
req.getResponse = function () {
var env = new Strophe.Builder('env', {type: 'mock'}).tree();
env.appendChild(iq);
return env;
};
return req;
};
utils.closeAllChatBoxes = function (_converse) {
return Promise.all(_converse.chatboxviews.map(view => view.close()));
};
utils.openControlBox = async function (_converse) {
const model = await _converse.api.controlbox.open();
await u.waitUntil(() => model.get('connected'));
var toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
}
return this;
};
utils.closeControlBox = function () {
const controlbox = document.querySelector("#controlbox");
if (u.isVisible(controlbox)) {
const button = controlbox.querySelector(".close-chatbox-button");
if (!_.isNull(button)) {
button.click();
}
}
return this;
};
utils.waitUntilBookmarksReturned = async function (_converse, bookmarks=[]) {
await utils.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options']
);
const IQ_stanzas = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(
() => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop()
);
const stanza = $iq({
'to': _converse.connection.jid,
'type':'result',
'id':sent_stanza.getAttribute('id')
}).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
.c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'});
bookmarks.forEach(bookmark => {
stanza.c('conference', {
'name': bookmark.name,
'autojoin': bookmark.autojoin,
'jid': bookmark.jid
}).c('nick').t(bookmark.nick).up().up()
});
_converse.connection._dataRecv(utils.createRequest(stanza));
await _converse.api.waitUntil('bookmarksInitialized');
};
utils.openChatBoxes = function (converse, amount) {
const views = [];
for (let i=0; i<amount; i++) {
const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
views.push(converse.roster.get(jid).trigger("open"));
}
return views;
};
utils.openChatBoxFor = async function (_converse, jid) {
await _converse.api.waitUntil('rosterContactsFetched');
_converse.roster.get(jid).trigger("open");
return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
};
utils.openChatRoomViaModal = async function (_converse, jid, nick='') {
// Opens a new chatroom
const model = await _converse.api.controlbox.open('controlbox');
await u.waitUntil(() => model.get('connected'));
await utils.openControlBox(_converse);
const view = await _converse.chatboxviews.get('controlbox');
const roomspanel = view.roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click();
utils.closeControlBox(_converse);
const modal = roomspanel.add_room_modal;
await u.waitUntil(() => u.isVisible(modal.el), 1500)
modal.el.querySelector('input[name="chatroom"]').value = jid;
if (nick) {
modal.el.querySelector('input[name="nickname"]').value = nick;
}
modal.el.querySelector('form input[type="submit"]').click();
await u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
return _converse.chatboxviews.get(jid);
};
utils.openChatRoom = function (_converse, room, server) {
return _converse.api.rooms.open(`${room}@${server}`);
};
utils.getRoomFeatures = async function (_converse, muc_jid, features=[]) {
const room = Strophe.getNodeFromJid(muc_jid);
muc_jid = muc_jid.toLowerCase();
const stanzas = _converse.connection.IQ_stanzas;
const stanza = await u.waitUntil(() => stanzas.filter(
iq => iq.querySelector(
`iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
)).pop()
);
const features_stanza = $iq({
'from': muc_jid,
'id': stanza.getAttribute('id'),
'to': 'romeo@montague.lit/desktop',
'type': 'result'
}).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
.c('identity', {
'category': 'conference',
'name': room[0].toUpperCase() + room.slice(1),
'type': 'text'
}).up();
features = features.length ? features : mock.default_muc_features;
features.forEach(f => features_stanza.c('feature', {'var': f}).up());
features_stanza.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(utils.createRequest(features_stanza));
};
utils.waitForReservedNick = async function (_converse, muc_jid, nick) {
const stanzas = _converse.connection.IQ_stanzas;
const selector = `iq[to="${muc_jid.toLowerCase()}"] query[node="x-roomuser-item"]`;
const iq = await u.waitUntil(() => stanzas.filter(s => sizzle(selector, s).length).pop());
// We remove the stanza, otherwise we might get stale stanzas returned in our filter above.
stanzas.splice(stanzas.indexOf(iq), 1)
// The XMPP server returns the reserved nick for this user.
const IQ_id = iq.getAttribute('id');
const stanza = $iq({
'type': 'result',
'id': IQ_id,
'from': muc_jid,
'to': _converse.connection.jid
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'});
if (nick) {
stanza.c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
}
_converse.connection._dataRecv(utils.createRequest(stanza));
if (nick) {
return u.waitUntil(() => nick);
}
};
utils.returnMemberLists = async function (_converse, muc_jid, members=[], affiliations=['member', 'owner', 'admin']) {
if (affiliations.length === 0) {
return;
}
const stanzas = _converse.connection.IQ_stanzas;
if (affiliations.includes('member')) {
const member_IQ = await u.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
).pop());
const member_list_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
'id': member_IQ.getAttribute('id'),
'to': 'romeo@montague.lit/orchard',
'type': 'result'
}).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
members.filter(m => m.affiliation === 'member').forEach(m => {
member_list_stanza.c('item', {
'affiliation': m.affiliation,
'jid': m.jid,
'nick': m.nick
});
});
_converse.connection._dataRecv(utils.createRequest(member_list_stanza));
}
if (affiliations.includes('admin')) {
const admin_IQ = await u.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
).pop());
const admin_list_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
'id': admin_IQ.getAttribute('id'),
'to': 'romeo@montague.lit/orchard',
'type': 'result'
}).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
members.filter(m => m.affiliation === 'admin').forEach(m => {
admin_list_stanza.c('item', {
'affiliation': m.affiliation,
'jid': m.jid,
'nick': m.nick
});
});
_converse.connection._dataRecv(utils.createRequest(admin_list_stanza));
}
if (affiliations.includes('owner')) {
const owner_IQ = await u.waitUntil(() => _.filter(
stanzas,
s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
).pop());
const owner_list_stanza = $iq({
'from': 'coven@chat.shakespeare.lit',
'id': owner_IQ.getAttribute('id'),
'to': 'romeo@montague.lit/orchard',
'type': 'result'
}).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
members.filter(m => m.affiliation === 'owner').forEach(m => {
owner_list_stanza.c('item', {
'affiliation': m.affiliation,
'jid': m.jid,
'nick': m.nick
});
});
_converse.connection._dataRecv(utils.createRequest(owner_list_stanza));
}
return new Promise(resolve => _converse.api.listen.on('membersFetched', resolve));
};
utils.receiveOwnMUCPresence = async function (_converse, muc_jid, nick) {
const sent_stanzas = _converse.connection.sent_stanzas;
await u.waitUntil(() => sent_stanzas.filter(iq => sizzle('presence history', iq).length).pop());
const presence = $pres({
to: _converse.connection.jid,
from: `${muc_jid}/${nick}`,
id: u.getUniqueId()
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'owner',
jid: _converse.bare_jid,
role: 'moderator'
}).up()
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(utils.createRequest(presence));
};
utils.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[]) {
muc_jid = muc_jid.toLowerCase();
const room_creation_promise = _converse.api.rooms.open(muc_jid);
await utils.getRoomFeatures(_converse, muc_jid, features);
await utils.waitForReservedNick(_converse, muc_jid, nick);
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
// See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres
await utils.receiveOwnMUCPresence(_converse, muc_jid, nick);
await room_creation_promise;
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
const affs = _converse.muc_fetch_members;
const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
await utils.returnMemberLists(_converse, muc_jid, members, all_affiliations);
await view.model.messages.fetched;
};
utils.clearChatBoxMessages = function (converse, jid) {
const view = converse.chatboxviews.get(jid);
view.msgs_container.innerHTML = '';
return view.model.messages.clearStore();
};
utils.createContact = async function (_converse, name, ask, requesting, subscription) {
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
if (_converse.roster.get(jid)) {
return Promise.resolve();
}
const contact = await new Promise((success, error) => {
_converse.roster.create({
'ask': ask,
'fullname': name,
'jid': jid,
'requesting': requesting,
'subscription': subscription
}, {success, error});
});
return contact;
};
utils.createContacts = async function (_converse, type, length) {
/* Create current (as opposed to requesting or pending) contacts
* for the user's roster.
*
* These contacts are not grouped. See below.
*/
await _converse.api.waitUntil('rosterContactsFetched');
let names, subscription, requesting, ask;
if (type === 'requesting') {
names = mock.req_names;
subscription = 'none';
requesting = true;
ask = null;
} else if (type === 'pending') {
names = mock.pend_names;
subscription = 'none';
requesting = false;
ask = 'subscribe';
} else if (type === 'current') {
names = mock.cur_names;
subscription = 'both';
requesting = false;
ask = null;
} else if (type === 'all') {
await this.createContacts(_converse, 'current');
await this.createContacts(_converse, 'requesting')
await this.createContacts(_converse, 'pending');
return this;
} else {
throw Error("Need to specify the type of contact to create");
}
const promises = names.slice(0, length).map(n => this.createContact(_converse, n, ask, requesting, subscription));
await Promise.all(promises);
};
utils.waitForRoster = async function (_converse, type='current', length=-1, include_nick=true, grouped=true) {
const s = `iq[type="get"] query[xmlns="${Strophe.NS.ROSTER}"]`;
const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(s, iq).length).pop());
const result = $iq({
'to': _converse.connection.jid,
'type': 'result',
'id': iq.getAttribute('id')
}).c('query', {
'xmlns': 'jabber:iq:roster'
});
if (type === 'pending' || type === 'all') {
const pend_names = (length > -1) ? mock.pend_names.slice(0, length) : mock.pend_names;
pend_names.map(name =>
result.c('item', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined,
subscription: 'none',
ask: 'subscribe'
}).up()
);
}
if (type === 'current' || type === 'all') {
const cur_names = Object.keys(mock.current_contacts_map);
const names = (length > -1) ? cur_names.slice(0, length) : cur_names;
names.forEach(name => {
result.c('item', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined,
subscription: 'both',
ask: null
});
if (grouped) {
mock.current_contacts_map[name].forEach(g => result.c('group').t(g).up());
}
result.up();
});
}
_converse.connection._dataRecv(utils.createRequest(result));
await _converse.api.waitUntil('rosterContactsFetched');
};
utils.createChatMessage = function (_converse, sender_jid, message) {
return $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: (new Date()).getTime()
})
.c('body').t(message).up()
.c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
}
utils.sendMessage = function (view, message) {
const promise = new Promise(resolve => view.once('messageInserted', resolve));
view.el.querySelector('.chat-textarea').value = message;
view.onKeyDown({
target: view.el.querySelector('textarea.chat-textarea'),
preventDefault: _.noop,
keyCode: 13
});
return promise;
};
});