converse.js
Version:
Browser based XMPP chat client
429 lines (369 loc) • 20.2 kB
JavaScript
/*global mock, converse */
const { Strophe, u, stx } = converse.env;
describe("The nickname autocomplete feature", function () {
it("shows all autocompletion options when the user presses @",
mock.initConverse(['chatBoxesFetched'], {},
async function (_converse) {
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'tom');
const view = _converse.chatboxviews.get('lounge@montague.lit');
// Nicknames from presences
['dick', 'harry'].forEach((nick) => {
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
to="tom@montague.lit/resource"
from="lounge@montague.lit/${nick}"
xmlns="jabber:client">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="none" jid="${nick}@montague.lit/resource" role="participant"/>
</x>
</presence>`));
});
// Nicknames from messages
await view.model.handleMessageStanza(
stx`<message
from="lounge@montague.lit/jane"
id="${u.getUniqueId()}"
to="romeo@montague.lit"
type="groupchat"
xmlns="jabber:client">
<body>Hello world</body>
</message>`.tree());
await u.waitUntil(() => view.model.messages.last()?.get('received'));
// Test that pressing @ brings up all options
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
const at_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'key': '@'
};
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown(at_event);
textarea.value = '@';
message_form.onKeyUp(at_event);
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 4);
const first_child = view.querySelector('.suggestion-box__results li:first-child');
const first_child_avatar = first_child.querySelector('converse-avatar');
expect(first_child_avatar.textContent).toBe('D');
expect(first_child.textContent).toBe('D dick');
const second_child = view.querySelector('.suggestion-box__results li:nth-child(2)');
const second_child_avatar = second_child.querySelector('converse-avatar');
expect(second_child_avatar.textContent).toBe('H');
expect(second_child.textContent).toBe('H harry');
const third_child = view.querySelector('.suggestion-box__results li:nth-child(3)');
const third_child_avatar = third_child.querySelector('converse-avatar');
expect(third_child_avatar.textContent).toBe('J');
expect(third_child.textContent).toBe('J jane');
const fourth_child = view.querySelector('.suggestion-box__results li:nth-child(4)');
const fourth_child_avatar = fourth_child.querySelector('converse-avatar');
expect(fourth_child_avatar.textContent).toBe('T');
expect(fourth_child.textContent).toBe('T tom');
}));
it("shows all autocompletion options when the user presses @ right after a new line",
mock.initConverse(['chatBoxesFetched'], {},
async function (_converse) {
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'tom');
const view = _converse.chatboxviews.get('lounge@montague.lit');
// Nicknames from presences
['dick', 'harry'].forEach((nick) => {
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
to="tom@montague.lit/resource"
from="lounge@montague.lit/${nick}"
xmlns="jabber:client">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="none" jid="${nick}@montague.lit/resource" role="participant"/>
</x>
</presence>`));
});
// Nicknames from messages
await view.model.handleMessageStanza(
stx`<message
from="lounge@montague.lit/jane"
id="${u.getUniqueId()}"
to="romeo@montague.lit"
type="groupchat"
xmlns="jabber:client">
<body>Hello world</body>
</message>`.tree());
await u.waitUntil(() => view.model.messages.last()?.get('received'));
// Test that pressing @ brings up all options
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
const at_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'key': '@'
};
const message_form = view.querySelector('converse-muc-message-form');
textarea.value = '\n'
message_form.onKeyDown(at_event);
textarea.value = '\n@';
message_form.onKeyUp(at_event);
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 4);
const first_child = view.querySelector('.suggestion-box__results li:first-child');
const first_child_avatar = first_child.querySelector('converse-avatar');
expect(first_child_avatar.textContent).toBe('D');
expect(first_child.textContent).toBe('D dick');
const second_child = view.querySelector('.suggestion-box__results li:nth-child(2)');
const second_child_avatar = second_child.querySelector('converse-avatar');
expect(second_child_avatar.textContent).toBe('H');
expect(second_child.textContent).toBe('H harry');
const third_child = view.querySelector('.suggestion-box__results li:nth-child(3)');
const third_child_avatar = third_child.querySelector('converse-avatar');
expect(third_child_avatar.textContent).toBe('J');
expect(third_child.textContent).toBe('J jane');
const fourth_child = view.querySelector('.suggestion-box__results li:nth-child(4)');
const fourth_child_avatar = fourth_child.querySelector('converse-avatar');
expect(fourth_child_avatar.textContent).toBe('T');
expect(fourth_child.textContent).toBe('T tom');
}));
it("shows all autocompletion options when the user presses @ right after an allowed character",
mock.initConverse(
['chatBoxesFetched'], {'opening_mention_characters':['(']},
async function (_converse) {
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'tom');
const view = _converse.chatboxviews.get('lounge@montague.lit');
// Nicknames from presences
['dick', 'harry'].forEach((nick) => {
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
to="tom@montague.lit/resource"
from="lounge@montague.lit/${nick}"
xmlns="jabber:client">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="none" jid="${nick}@montague.lit/resource" role="participant"/>
</x>
</presence>`))
});
// Nicknames from messages
await view.model.handleMessageStanza(
stx`<message
from="lounge@montague.lit/jane"
id="${u.getUniqueId()}"
to="romeo@montague.lit"
type="groupchat"
xmlns="jabber:client">
<body>Hello world</body>
</message>`);
await u.waitUntil(() => view.model.messages.last()?.get('received'));
// Test that pressing @ brings up all options
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
const at_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
'key': '@'
};
textarea.value = '('
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown(at_event);
textarea.value = '(@';
message_form.onKeyUp(at_event);
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 4);
const first_child = view.querySelector('.suggestion-box__results li:first-child');
const first_child_avatar = first_child.querySelector('converse-avatar');
expect(first_child_avatar.textContent).toBe('D');
expect(first_child.textContent).toBe('D dick');
const second_child = view.querySelector('.suggestion-box__results li:nth-child(2)');
const second_child_avatar = second_child.querySelector('converse-avatar');
expect(second_child_avatar.textContent).toBe('H');
expect(second_child.textContent).toBe('H harry');
const third_child = view.querySelector('.suggestion-box__results li:nth-child(3)');
const third_child_avatar = third_child.querySelector('converse-avatar');
expect(third_child_avatar.textContent).toBe('J');
expect(third_child.textContent).toBe('J jane');
const fourth_child = view.querySelector('.suggestion-box__results li:nth-child(4)');
const fourth_child_avatar = fourth_child.querySelector('converse-avatar');
expect(fourth_child_avatar.textContent).toBe('T');
expect(fourth_child.textContent).toBe('T tom');
}));
it("should order by query index position and length", mock.initConverse(
['chatBoxesFetched'], {}, async function (_converse) {
await mock.waitUntilBookmarksReturned(_converse);
const model = await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'tom');
const view = _converse.chatboxviews.get('lounge@montague.lit');
// Nicknames from presences
['bernard', 'naber', 'helberlo', 'john', 'jones'].forEach((nick) => {
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
to="tom@montague.lit/resource"
from="lounge@montague.lit/${nick}"
xmlns="jabber:client">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="none" jid="${nick}@montague.lit/resource" role="participant"/>
</x>
</presence>`));
});
await u.waitUntil(() => model.getOccupantByNickname('jones'));
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
const at_event = {
'target': textarea,
'preventDefault': function preventDefault() { },
'stopPropagation': function stopPropagation() { },
'key': '@'
};
const message_form = view.querySelector('converse-muc-message-form');
// Test that results are sorted by query index
message_form.onKeyDown(at_event);
textarea.value = '@ber';
message_form.onKeyUp(at_event);
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 3);
const first_child = view.querySelector('.suggestion-box__results li:first-child');
expect(first_child.textContent).toBe('B bernard');
const first_child_avatar = first_child.querySelector('converse-avatar');
expect(first_child_avatar.textContent).toBe('B');
const first_child_mark = first_child.querySelector('mark');
expect(first_child_mark.textContent).toBe('ber');
const second_child = view.querySelector('.suggestion-box__results li:nth-child(2)');
expect(second_child.textContent).toBe('N naber');
const second_child_avatar = second_child.querySelector('converse-avatar');
expect(second_child_avatar.textContent).toBe('N');
const second_child_mark = second_child.querySelector('mark');
expect(second_child_mark.textContent).toBe('ber');
const third_child = view.querySelector('.suggestion-box__results li:nth-child(3)');
expect(third_child.textContent).toBe('H helberlo');
const third_child_avatar = third_child.querySelector('converse-avatar');
expect(third_child_avatar.textContent).toBe('H');
const third_child_mark = third_child.querySelector('mark');
expect(third_child_mark.textContent).toBe('ber');
// Test that when the query index is equal, results should be sorted by length
textarea.value = '@jo';
message_form.onKeyUp(at_event);
await u.waitUntil(() => view.querySelectorAll('.suggestion-box__results li').length === 2);
// First char is the avatar initial
expect(view.querySelector('.suggestion-box__results li:first-child').textContent).toBe('J john');
expect(view.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('J jones');
}));
it("autocompletes when the user presses tab",
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
await mock.waitUntilBookmarksReturned(_converse);
const model = await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.model.occupants.length).toBe(1);
let presence = stx`<presence
to="romeo@montague.lit/orchard"
from="lounge@montague.lit/some1"
xmlns="jabber:client">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="none" jid="some1@montague.lit/resource" role="participant"/>
</x>
</presence>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.model.occupants.length === 2);
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = "hello som";
// Press tab
const tab_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
key: 'Tab'
}
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown(tab_event);
message_form.onKeyUp(tab_event);
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === false);
expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(1);
// First char is the avatar initial
expect(view.querySelector('.suggestion-box__results li').textContent).toBe('S some1');
const backspace_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
key: "Backspace",
}
for (let i=0; i<3; i++) {
// Press backspace 3 times to remove "som"
message_form.onKeyDown(backspace_event);
textarea.value = textarea.value.slice(0, textarea.value.length-1)
message_form.onKeyUp(backspace_event);
}
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === true);
presence = stx`<presence
to="romeo@montague.lit/orchard"
from="lounge@montague.lit/some2"
xmlns="jabber:client">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="none" jid="some2@montague.lit/resource" role="participant"/>
</x>
</presence>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => model.getOccupantByNickname('some2'));
textarea.value = "hello s s";
message_form.onKeyDown(tab_event);
message_form.onKeyUp(tab_event);
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === false);
expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(2);
const up_arrow_event = {
'target': textarea,
'preventDefault': () => (up_arrow_event.defaultPrevented = true),
'stopPropagation': function stopPropagation () {},
key: "ArrowUp",
}
message_form.onKeyDown(up_arrow_event);
message_form.onKeyUp(up_arrow_event);
expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(2);
// First char is the avatar initial
expect(view.querySelector('.suggestion-box__results li[aria-selected="false"]').textContent).toBe('S some1');
expect(view.querySelector('.suggestion-box__results li[aria-selected="true"]').textContent).toBe('S some2');
message_form.onKeyDown({
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
key: "Enter",
});
expect(textarea.value).toBe('hello s @some2 ');
// Test that pressing tab twice selects
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
to="romeo@montague.lit/orchard"
from="lounge@montague.lit/z3r0"
xmlns="jabber:client">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="none" jid="z3r0@montague.lit/resource" role="participant"/>
</x>
</presence>`));
await u.waitUntil(() => model.getOccupantByNickname('z3r0'));
textarea.value = "hello z";
message_form.onKeyDown(tab_event);
message_form.onKeyUp(tab_event);
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === false);
message_form.onKeyDown(tab_event);
message_form.onKeyUp(tab_event);
await u.waitUntil(() => textarea.value === 'hello @z3r0 ');
}));
it("autocompletes when the user presses backspace",
mock.initConverse([], {}, async function (_converse) {
await mock.waitUntilBookmarksReturned(_converse);
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
expect(view.model.occupants.length).toBe(1);
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
to="romeo@montague.lit/orchard"
from="lounge@montague.lit/some1"
xmlns="jabber:client">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="none" jid="some1@montague.lit/resource" role="participant"/>
</x>
</presence>`));
await u.waitUntil(() => view.model.occupants.length === 2);
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = "hello @some1 ";
// Press backspace
const backspace_event = {
'target': textarea,
'preventDefault': function preventDefault () {},
'stopPropagation': function stopPropagation () {},
key: 'Backspace'
}
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown(backspace_event);
textarea.value = "hello @some1"; // Mimic backspace
message_form.onKeyUp(backspace_event);
await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === false);
expect(view.querySelectorAll('.suggestion-box__results li').length).toBe(1);
// First char is the avatar initial
expect(view.querySelector('.suggestion-box__results li').textContent).toBe('S some1');
}));
});