2020-07-14 14:06:52 +02:00
/*global mock, converse */
2020-04-22 13:11:48 +02:00
const { Promise , Strophe , $msg , dayjs , sizzle , _ } = converse . env ;
const u = converse . env . utils ;
describe ( "A Chat Message" , function ( ) {
2020-06-01 16:05:00 +02:00
it ( "will be demarcated if it's the first newly received message" ,
mock . initConverse ( [ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
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 ) ;
await _converse . handleMessageStanza ( mock . createChatMessage ( _converse , contact _jid , 'This message will be read' ) ) ;
2020-06-03 09:17:13 +02:00
const msg _el = await u . waitUntil ( ( ) => view . el . querySelector ( 'converse-chat-message' ) ) ;
expect ( msg _el . querySelector ( '.chat-msg__text' ) . textContent ) . toBe ( 'This message will be read' ) ;
expect ( view . model . get ( 'num_unread' ) ) . toBe ( 0 ) ;
2020-06-01 16:05:00 +02:00
_converse . windowState = 'hidden' ;
await _converse . handleMessageStanza ( mock . createChatMessage ( _converse , contact _jid , 'This message will be new' ) ) ;
await u . waitUntil ( ( ) => view . model . messages . length ) ;
expect ( view . model . get ( 'num_unread' ) ) . toBe ( 1 ) ;
expect ( view . model . get ( 'first_unread_id' ) ) . toBe ( view . model . messages . last ( ) . get ( 'id' ) ) ;
await u . waitUntil ( ( ) => view . el . querySelectorAll ( 'converse-chat-message' ) . length === 2 ) ;
const last _msg _el = view . el . querySelector ( 'converse-chat-message:last-child' ) ;
expect ( last _msg _el . firstElementChild ? . textContent ) . toBe ( 'New messages' ) ;
done ( ) ;
} ) ) ;
2020-04-22 13:11:48 +02:00
it ( "is rejected if it's an unencapsulated forwarded message" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' , 2 ) ;
const contact _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
const forwarded _contact _jid = mock . cur _names [ 1 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
await mock . openChatBoxFor ( _converse , contact _jid ) ;
let models = await _converse . api . chats . get ( ) ;
expect ( models . length ) . toBe ( 1 ) ;
const received _stanza = u . toStanza ( `
< message to = '${_converse.jid}' from = '${contact_jid}' type = 'chat' id = '${_converse.connection.getUniqueId()}' >
< body > A most courteous exposition ! < / b o d y >
< forwarded xmlns = 'urn:xmpp:forward:0' >
< delay xmlns = 'urn:xmpp:delay' stamp = '2019-07-10T23:08:25Z' / >
< message from = '${forwarded_contact_jid}'
id = '0202197'
to = '${_converse.bare_jid}'
type = 'chat'
xmlns = 'jabber:client' >
< body > Yet I should kill thee with much cherishing . < / b o d y >
< mood xmlns = 'http://jabber.org/protocol/mood' >
< amorous / >
< / m o o d >
< / m e s s a g e >
< / f o r w a r d e d >
< / m e s s a g e >
` );
_converse . connection . _dataRecv ( mock . createRequest ( received _stanza ) ) ;
const sent _stanzas = _converse . connection . sent _stanzas ;
const sent _stanza = await u . waitUntil ( ( ) => sent _stanzas . filter ( s => s . querySelector ( 'error' ) ) . pop ( ) ) ;
expect ( Strophe . serialize ( sent _stanza ) ) . toBe (
` <message id=" ${ received _stanza . getAttribute ( 'id' ) } " to=" ${ contact _jid } " type="error" xmlns="jabber:client"> ` +
'<error type="cancel">' +
'<not-allowed xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>' +
'<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">' +
'Forwarded messages not part of an encapsulating protocol are not supported</text>' +
'</error>' +
'</message>' ) ;
models = await _converse . api . chats . get ( ) ;
expect ( models . length ) . toBe ( 1 ) ;
done ( ) ;
} ) ) ;
it ( "can be sent as a correction by clicking the pencil icon" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
await mock . openControlBox ( _converse ) ;
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 ) ;
const textarea = view . el . querySelector ( 'textarea.chat-textarea' ) ;
textarea . value = 'But soft, what light through yonder airlock breaks?' ;
view . onKeyDown ( {
target : textarea ,
preventDefault : function preventDefault ( ) { } ,
keyCode : 13 // Enter
} ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
expect ( view . el . querySelector ( '.chat-msg__text' ) . textContent )
. toBe ( 'But soft, what light through yonder airlock breaks?' ) ;
expect ( textarea . value ) . toBe ( '' ) ;
const first _msg = view . model . messages . findWhere ( { 'message' : 'But soft, what light through yonder airlock breaks?' } ) ;
2020-07-09 06:27:56 +02:00
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-msg .chat-msg__action' ) . length === 2 ) ;
2020-04-22 13:11:48 +02:00
let action = view . el . querySelector ( '.chat-msg .chat-msg__action' ) ;
2020-06-04 17:26:18 +02:00
expect ( action . textContent . trim ( ) ) . toBe ( 'Edit' ) ;
2020-04-22 13:11:48 +02:00
action . style . opacity = 1 ;
action . click ( ) ;
expect ( textarea . value ) . toBe ( 'But soft, what light through yonder airlock breaks?' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBe ( true ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
await u . waitUntil ( ( ) => u . hasClass ( 'correcting' , view . el . querySelector ( '.chat-msg' ) ) ) ;
spyOn ( _converse . connection , 'send' ) ;
textarea . value = 'But soft, what light through yonder window breaks?' ;
view . onKeyDown ( {
target : textarea ,
preventDefault : function preventDefault ( ) { } ,
keyCode : 13 // Enter
} ) ;
expect ( _converse . connection . send ) . toHaveBeenCalled ( ) ;
const msg = _converse . connection . send . calls . all ( ) [ 0 ] . args [ 0 ] ;
expect ( msg . toLocaleString ( ) )
. toBe ( ` <message from="romeo@montague.lit/orchard" id=" ${ msg . nodeTree . getAttribute ( "id" ) } " ` +
` to="mercutio@montague.lit" type="chat" ` +
` xmlns="jabber:client"> ` +
` <body>But soft, what light through yonder window breaks?</body> ` +
` <active xmlns="http://jabber.org/protocol/chatstates"/> ` +
` <request xmlns="urn:xmpp:receipts"/> ` +
` <replace id=" ${ first _msg . get ( "msgid" ) } " xmlns="urn:xmpp:message-correct:0"/> ` +
` <origin-id id=" ${ msg . nodeTree . querySelector ( 'origin-id' ) . getAttribute ( "id" ) } " xmlns="urn:xmpp:sid:0"/> ` +
` </message> ` ) ;
expect ( view . model . messages . models . length ) . toBe ( 1 ) ;
const corrected _message = view . model . messages . at ( 0 ) ;
expect ( corrected _message . get ( 'msgid' ) ) . toBe ( first _msg . get ( 'msgid' ) ) ;
expect ( corrected _message . get ( 'correcting' ) ) . toBe ( false ) ;
const older _versions = corrected _message . get ( 'older_versions' ) ;
const keys = Object . keys ( older _versions ) ;
expect ( keys . length ) . toBe ( 1 ) ;
expect ( older _versions [ keys [ 0 ] ] ) . toBe ( 'But soft, what light through yonder airlock breaks?' ) ;
2020-05-15 14:33:31 +02:00
await u . waitUntil ( ( ) => u . hasClass ( 'correcting' , view . el . querySelector ( '.chat-msg' ) ) === false ) ;
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
// Test that clicking the pencil icon a second time cancels editing.
action = view . el . querySelector ( '.chat-msg .chat-msg__action' ) ;
action . style . opacity = 1 ;
action . click ( ) ;
expect ( textarea . value ) . toBe ( 'But soft, what light through yonder window breaks?' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBe ( true ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
await u . waitUntil ( ( ) => u . hasClass ( 'correcting' , view . el . querySelector ( '.chat-msg' ) ) === true ) ;
action = view . el . querySelector ( '.chat-msg .chat-msg__action' ) ;
action . style . opacity = 1 ;
action . click ( ) ;
expect ( textarea . value ) . toBe ( '' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBe ( false ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
await u . waitUntil ( ( ) => ( u . hasClass ( 'correcting' , view . el . querySelector ( '.chat-msg' ) ) === false ) , 500 ) ;
// Test that messages from other users don't have the pencil icon
_converse . handleMessageStanza (
$msg ( {
'from' : contact _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : u . getUniqueId ( )
} ) . c ( 'body' ) . t ( 'Hello' ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( )
) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelectorAll ( '.chat-msg .chat-msg__action' ) . length ) . toBe ( 2 ) ;
// Test confirmation dialog
spyOn ( window , 'confirm' ) . and . returnValue ( true ) ;
textarea . value = 'But soft, what light through yonder airlock breaks?' ;
action = view . el . querySelector ( '.chat-msg .chat-msg__action' ) ;
action . style . opacity = 1 ;
action . click ( ) ;
expect ( window . confirm ) . toHaveBeenCalledWith (
'You have an unsent message which will be lost if you continue. Are you sure?' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBe ( true ) ;
expect ( textarea . value ) . toBe ( 'But soft, what light through yonder window breaks?' ) ;
textarea . value = 'But soft, what light through yonder airlock breaks?'
action . click ( ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBe ( false ) ;
expect ( window . confirm . calls . count ( ) ) . toBe ( 2 ) ;
expect ( window . confirm . calls . argsFor ( 0 ) ) . toEqual (
[ 'You have an unsent message which will be lost if you continue. Are you sure?' ] ) ;
expect ( window . confirm . calls . argsFor ( 1 ) ) . toEqual (
[ 'You have an unsent message which will be lost if you continue. Are you sure?' ] ) ;
done ( ) ;
} ) ) ;
it ( "can be sent as a correction by using the up arrow" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
await mock . openControlBox ( _converse ) ;
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 ) ;
const textarea = view . el . querySelector ( 'textarea.chat-textarea' ) ;
expect ( textarea . value ) . toBe ( '' ) ;
view . onKeyDown ( {
target : textarea ,
keyCode : 38 // Up arrow
} ) ;
expect ( textarea . value ) . toBe ( '' ) ;
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
textarea . value = 'But soft, what light through yonder airlock breaks?' ;
view . onKeyDown ( {
target : textarea ,
preventDefault : function preventDefault ( ) { } ,
keyCode : 13 // Enter
} ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
expect ( view . el . querySelector ( '.chat-msg__text' ) . textContent )
. toBe ( 'But soft, what light through yonder airlock breaks?' ) ;
const first _msg = view . model . messages . findWhere ( { 'message' : 'But soft, what light through yonder airlock breaks?' } ) ;
expect ( textarea . value ) . toBe ( '' ) ;
view . onKeyDown ( {
target : textarea ,
keyCode : 38 // Up arrow
} ) ;
expect ( textarea . value ) . toBe ( 'But soft, what light through yonder airlock breaks?' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBe ( true ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
await u . waitUntil ( ( ) => u . hasClass ( 'correcting' , view . el . querySelector ( '.chat-msg' ) ) , 500 ) ;
spyOn ( _converse . connection , 'send' ) ;
textarea . value = 'But soft, what light through yonder window breaks?' ;
view . onKeyDown ( {
target : textarea ,
preventDefault : function preventDefault ( ) { } ,
keyCode : 13 // Enter
} ) ;
expect ( _converse . connection . send ) . toHaveBeenCalled ( ) ;
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
const msg = _converse . connection . send . calls . all ( ) [ 0 ] . args [ 0 ] ;
expect ( msg . toLocaleString ( ) )
. toBe ( ` <message from="romeo@montague.lit/orchard" id=" ${ msg . nodeTree . getAttribute ( "id" ) } " ` +
` to="mercutio@montague.lit" type="chat" ` +
` xmlns="jabber:client"> ` +
` <body>But soft, what light through yonder window breaks?</body> ` +
` <active xmlns="http://jabber.org/protocol/chatstates"/> ` +
` <request xmlns="urn:xmpp:receipts"/> ` +
` <replace id=" ${ first _msg . get ( "msgid" ) } " xmlns="urn:xmpp:message-correct:0"/> ` +
` <origin-id id=" ${ msg . nodeTree . querySelector ( 'origin-id' ) . getAttribute ( "id" ) } " xmlns="urn:xmpp:sid:0"/> ` +
` </message> ` ) ;
expect ( view . model . messages . models . length ) . toBe ( 1 ) ;
const corrected _message = view . model . messages . at ( 0 ) ;
expect ( corrected _message . get ( 'msgid' ) ) . toBe ( first _msg . get ( 'msgid' ) ) ;
expect ( corrected _message . get ( 'correcting' ) ) . toBe ( false ) ;
const older _versions = corrected _message . get ( 'older_versions' ) ;
const keys = Object . keys ( older _versions ) ;
expect ( keys . length ) . toBe ( 1 ) ;
expect ( older _versions [ keys [ 0 ] ] ) . toBe ( 'But soft, what light through yonder airlock breaks?' ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
await u . waitUntil ( ( ) => ( u . hasClass ( 'correcting' , view . el . querySelector ( '.chat-msg' ) ) === false ) , 500 ) ;
// Test that pressing the down arrow cancels message correction
expect ( textarea . value ) . toBe ( '' ) ;
view . onKeyDown ( {
target : textarea ,
keyCode : 38 // Up arrow
} ) ;
expect ( textarea . value ) . toBe ( 'But soft, what light through yonder window breaks?' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBe ( true ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
await u . waitUntil ( ( ) => u . hasClass ( 'correcting' , view . el . querySelector ( '.chat-msg' ) ) , 500 ) ;
expect ( textarea . value ) . toBe ( 'But soft, what light through yonder window breaks?' ) ;
view . onKeyDown ( {
target : textarea ,
keyCode : 40 // Down arrow
} ) ;
expect ( textarea . value ) . toBe ( '' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBe ( false ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
await u . waitUntil ( ( ) => ( u . hasClass ( 'correcting' , view . el . querySelector ( '.chat-msg' ) ) === false ) , 500 ) ;
textarea . value = 'It is the east, and Juliet is the one.' ;
view . onKeyDown ( {
target : textarea ,
preventDefault : function preventDefault ( ) { } ,
keyCode : 13 // Enter
} ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 2 ) ;
textarea . value = 'Arise, fair sun, and kill the envious moon' ;
view . onKeyDown ( {
target : textarea ,
preventDefault : function preventDefault ( ) { } ,
keyCode : 13 // Enter
} ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 3 ) ;
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
view . onKeyDown ( {
target : textarea ,
keyCode : 38 // Up arrow
} ) ;
expect ( textarea . value ) . toBe ( 'Arise, fair sun, and kill the envious moon' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBeFalsy ( ) ;
expect ( view . model . messages . at ( 1 ) . get ( 'correcting' ) ) . toBeFalsy ( ) ;
expect ( view . model . messages . at ( 2 ) . get ( 'correcting' ) ) . toBe ( true ) ;
await u . waitUntil ( ( ) => u . hasClass ( 'correcting' , sizzle ( '.chat-msg:last' , view . el ) . pop ( ) ) , 500 ) ;
textarea . selectionEnd = 0 ; // Happens by pressing up,
// but for some reason not in tests, so we set it manually.
view . onKeyDown ( {
target : textarea ,
keyCode : 38 // Up arrow
} ) ;
expect ( textarea . value ) . toBe ( 'It is the east, and Juliet is the one.' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBeFalsy ( ) ;
expect ( view . model . messages . at ( 1 ) . get ( 'correcting' ) ) . toBe ( true ) ;
expect ( view . model . messages . at ( 2 ) . get ( 'correcting' ) ) . toBeFalsy ( ) ;
await u . waitUntil ( ( ) => u . hasClass ( 'correcting' , sizzle ( '.chat-msg' , view . el ) [ 1 ] ) , 500 ) ;
textarea . value = 'It is the east, and Juliet is the sun.' ;
view . onKeyDown ( {
target : textarea ,
preventDefault : function preventDefault ( ) { } ,
keyCode : 13 // Enter
} ) ;
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
expect ( textarea . value ) . toBe ( '' ) ;
const messages = view . el . querySelectorAll ( '.chat-msg' ) ;
expect ( messages . length ) . toBe ( 3 ) ;
expect ( messages [ 0 ] . querySelector ( '.chat-msg__text' ) . textContent )
. toBe ( 'But soft, what light through yonder window breaks?' ) ;
expect ( messages [ 1 ] . querySelector ( '.chat-msg__text' ) . textContent )
. toBe ( 'It is the east, and Juliet is the sun.' ) ;
expect ( messages [ 2 ] . querySelector ( '.chat-msg__text' ) . textContent )
. toBe ( 'Arise, fair sun, and kill the envious moon' ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'correcting' ) ) . toBeFalsy ( ) ;
expect ( view . model . messages . at ( 1 ) . get ( 'correcting' ) ) . toBeFalsy ( ) ;
expect ( view . model . messages . at ( 2 ) . get ( 'correcting' ) ) . toBeFalsy ( ) ;
done ( ) ;
} ) ) ;
it ( "can be received out of order, and will still be displayed in the right order" ,
mock . initConverse (
[ 'rosterGroupsFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' ) ;
await mock . openControlBox ( _converse ) ;
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
await u . waitUntil ( ( ) => _converse . rosterview . el . querySelectorAll ( '.roster-group' ) . length )
_converse . filter _by _resource = true ;
let msg = $msg ( {
'xmlns' : 'jabber:client' ,
'id' : _converse . connection . getUniqueId ( ) ,
'to' : _converse . bare _jid ,
'from' : sender _jid ,
'type' : 'chat' } )
. c ( 'body' ) . t ( "message" ) . up ( )
. c ( 'delay' , { 'xmlns' : 'urn:xmpp:delay' , 'stamp' : '2018-01-02T13:08:25Z' } )
. tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
const view = _converse . api . chatviews . get ( sender _jid ) ;
msg = $msg ( {
'xmlns' : 'jabber:client' ,
'id' : _converse . connection . getUniqueId ( ) ,
'to' : _converse . bare _jid ,
'from' : sender _jid ,
'type' : 'chat' } )
. c ( 'body' ) . t ( "Older message" ) . up ( )
. c ( 'delay' , { 'xmlns' : 'urn:xmpp:delay' , 'stamp' : '2017-12-31T22:08:25Z' } )
. tree ( ) ;
_converse . handleMessageStanza ( msg ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
msg = $msg ( {
'xmlns' : 'jabber:client' ,
'id' : _converse . connection . getUniqueId ( ) ,
'to' : _converse . bare _jid ,
'from' : sender _jid ,
'type' : 'chat' } )
. c ( 'body' ) . t ( "Inbetween message" ) . up ( )
. c ( 'delay' , { 'xmlns' : 'urn:xmpp:delay' , 'stamp' : '2018-01-01T13:18:23Z' } )
. tree ( ) ;
_converse . handleMessageStanza ( msg ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
msg = $msg ( {
'xmlns' : 'jabber:client' ,
'id' : _converse . connection . getUniqueId ( ) ,
'to' : _converse . bare _jid ,
'from' : sender _jid ,
'type' : 'chat' } )
. c ( 'body' ) . t ( "another inbetween message" ) . up ( )
. c ( 'delay' , { 'xmlns' : 'urn:xmpp:delay' , 'stamp' : '2018-01-01T13:18:23Z' } )
. tree ( ) ;
_converse . handleMessageStanza ( msg ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
msg = $msg ( {
'xmlns' : 'jabber:client' ,
'id' : _converse . connection . getUniqueId ( ) ,
'to' : _converse . bare _jid ,
'from' : sender _jid ,
'type' : 'chat' } )
. c ( 'body' ) . t ( "An earlier message on the next day" ) . up ( )
. c ( 'delay' , { 'xmlns' : 'urn:xmpp:delay' , 'stamp' : '2018-01-02T12:18:23Z' } )
. tree ( ) ;
_converse . handleMessageStanza ( msg ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
msg = $msg ( {
'xmlns' : 'jabber:client' ,
'id' : _converse . connection . getUniqueId ( ) ,
'to' : _converse . bare _jid ,
'from' : sender _jid ,
'type' : 'chat' } )
. c ( 'body' ) . t ( "newer message from the next day" ) . up ( )
. c ( 'delay' , { 'xmlns' : 'urn:xmpp:delay' , 'stamp' : '2018-01-02T22:28:23Z' } )
. tree ( ) ;
_converse . handleMessageStanza ( msg ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
// Insert <composing> message, to also check that
// text messages are inserted correctly with
// temporary chat events in the chat contents.
msg = $msg ( {
'id' : _converse . connection . getUniqueId ( ) ,
'to' : _converse . bare _jid ,
'xmlns' : 'jabber:client' ,
'from' : sender _jid ,
'type' : 'chat' } )
. c ( 'composing' , { 'xmlns' : Strophe . NS . CHATSTATES } ) . up ( )
. tree ( ) ;
_converse . handleMessageStanza ( msg ) ;
2020-05-15 14:33:31 +02:00
const csntext = await u . waitUntil ( ( ) => view . el . querySelector ( '.chat-content__notifications' ) . textContent ) ;
expect ( csntext . trim ( ) ) . toEqual ( 'Mercutio is typing' ) ;
2020-04-22 13:11:48 +02:00
msg = $msg ( {
'id' : _converse . connection . getUniqueId ( ) ,
'to' : _converse . bare _jid ,
'xmlns' : 'jabber:client' ,
'from' : sender _jid ,
'type' : 'chat' } )
. c ( 'composing' , { 'xmlns' : Strophe . NS . CHATSTATES } ) . up ( )
. c ( 'body' ) . t ( "latest message" )
. tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
view . clearSpinner ( ) ; //cleanup
expect ( view . content . querySelectorAll ( '.date-separator' ) . length ) . toEqual ( 4 ) ;
let day = sizzle ( '.date-separator:first' , view . content ) . pop ( ) ;
expect ( day . getAttribute ( 'data-isodate' ) ) . toEqual ( dayjs ( '2017-12-31T00:00:00' ) . toISOString ( ) ) ;
let time = sizzle ( 'time:first' , view . content ) . pop ( ) ;
expect ( time . textContent ) . toEqual ( 'Sunday Dec 31st 2017' )
day = sizzle ( '.date-separator:first' , view . content ) . pop ( ) ;
expect ( day . nextElementSibling . querySelector ( '.chat-msg__text' ) . textContent ) . toBe ( 'Older message' ) ;
let el = sizzle ( '.chat-msg:first' , view . content ) . pop ( ) . querySelector ( '.chat-msg__text' )
expect ( u . hasClass ( 'chat-msg--followup' , el ) ) . toBe ( false ) ;
expect ( el . textContent ) . toEqual ( 'Older message' ) ;
time = sizzle ( 'time.separator-text:eq(1)' , view . content ) . pop ( ) ;
expect ( time . textContent ) . toEqual ( "Monday Jan 1st 2018" ) ;
day = sizzle ( '.date-separator:eq(1)' , view . content ) . pop ( ) ;
expect ( day . getAttribute ( 'data-isodate' ) ) . toEqual ( dayjs ( '2018-01-01T00:00:00' ) . toISOString ( ) ) ;
expect ( day . nextElementSibling . querySelector ( '.chat-msg__text' ) . textContent ) . toBe ( 'Inbetween message' ) ;
el = sizzle ( '.chat-msg:eq(1)' , view . content ) . pop ( ) ;
expect ( el . querySelector ( '.chat-msg__text' ) . textContent ) . toEqual ( 'Inbetween message' ) ;
2020-05-15 14:33:31 +02:00
expect ( el . parentElement . nextElementSibling . querySelector ( '.chat-msg__text' ) . textContent ) . toEqual ( 'another inbetween message' ) ;
2020-04-22 13:11:48 +02:00
el = sizzle ( '.chat-msg:eq(2)' , view . content ) . pop ( ) ;
expect ( el . querySelector ( '.chat-msg__text' ) . textContent )
. toEqual ( 'another inbetween message' ) ;
expect ( u . hasClass ( 'chat-msg--followup' , el ) ) . toBe ( true ) ;
time = sizzle ( 'time.separator-text:nth(2)' , view . content ) . pop ( ) ;
expect ( time . textContent ) . toEqual ( "Tuesday Jan 2nd 2018" ) ;
day = sizzle ( '.date-separator:nth(2)' , view . content ) . pop ( ) ;
expect ( day . getAttribute ( 'data-isodate' ) ) . toEqual ( dayjs ( '2018-01-02T00:00:00' ) . toISOString ( ) ) ;
expect ( day . nextElementSibling . querySelector ( '.chat-msg__text' ) . textContent ) . toBe ( 'An earlier message on the next day' ) ;
el = sizzle ( '.chat-msg:eq(3)' , view . content ) . pop ( ) ;
expect ( el . querySelector ( '.chat-msg__text' ) . textContent ) . toEqual ( 'An earlier message on the next day' ) ;
expect ( u . hasClass ( 'chat-msg--followup' , el ) ) . toBe ( false ) ;
el = sizzle ( '.chat-msg:eq(4)' , view . content ) . pop ( ) ;
expect ( el . querySelector ( '.chat-msg__text' ) . textContent ) . toEqual ( 'message' ) ;
2020-05-15 14:33:31 +02:00
expect ( el . parentElement . nextElementSibling . querySelector ( '.chat-msg__text' ) . textContent ) . toEqual ( 'newer message from the next day' ) ;
2020-04-22 13:11:48 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , el ) ) . toBe ( false ) ;
day = sizzle ( '.date-separator:last' , view . content ) . pop ( ) ;
expect ( day . getAttribute ( 'data-isodate' ) ) . toEqual ( dayjs ( ) . startOf ( 'day' ) . toISOString ( ) ) ;
expect ( day . nextElementSibling . querySelector ( '.chat-msg__text' ) . textContent ) . toBe ( 'latest message' ) ;
expect ( u . hasClass ( 'chat-msg--followup' , el ) ) . toBe ( false ) ;
done ( ) ;
} ) ) ;
it ( "is ignored if it's a malformed headline message" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2020-04-22 13:11:48 +02:00
[ 'rosterGroupsFetched' ] , { } ,
2018-11-20 18:10:55 +01:00
async function ( done , _converse ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' ) ;
await mock . openControlBox ( _converse ) ;
// Ideally we wouldn't have to filter out headline
// messages, but Prosody gives them the wrong 'type' :(
2020-04-24 17:33:32 +02:00
spyOn ( converse . env . log , 'info' ) ;
2020-04-22 13:11:48 +02:00
sinon . spy ( _converse . api . chatboxes , 'get' ) ;
const msg = $msg ( {
from : 'montague.lit' ,
to : _converse . bare _jid ,
type : 'chat' ,
id : u . getUniqueId ( )
} ) . c ( 'body' ) . t ( "This headline message will not be shown" ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
2020-04-24 17:33:32 +02:00
expect ( converse . env . log . info ) . toHaveBeenCalledWith (
"handleMessageStanza: Ignoring incoming server message from JID: montague.lit"
) ;
2020-04-22 13:11:48 +02:00
expect ( _converse . api . chatboxes . get . called ) . toBeFalsy ( ) ;
// Remove sinon spies
_converse . api . chatboxes . get . restore ( ) ;
done ( ) ;
} ) ) ;
it ( "can be a carbon message, as defined in XEP-0280" ,
mock . initConverse (
[ 'rosterGroupsFetched' ] , { } ,
async function ( done , _converse ) {
const include _nick = false ;
await mock . waitForRoster ( _converse , 'current' , 2 , include _nick ) ;
await mock . openControlBox ( _converse ) ;
// Send a message from a different resource
const msgtext = 'This is a carbon message' ;
const sender _jid = mock . cur _names [ 1 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
const msg = $msg ( {
'from' : _converse . bare _jid ,
'id' : u . getUniqueId ( ) ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'xmlns' : 'jabber:client'
} ) . c ( 'received' , { 'xmlns' : 'urn:xmpp:carbons:2' } )
. c ( 'forwarded' , { 'xmlns' : 'urn:xmpp:forward:0' } )
. c ( 'message' , {
'xmlns' : 'jabber:client' ,
'from' : sender _jid ,
'to' : _converse . bare _jid + '/another-resource' ,
'type' : 'chat'
} ) . c ( 'body' ) . t ( msgtext ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
const chatbox = _converse . chatboxes . get ( sender _jid ) ;
const view = _converse . chatboxviews . get ( sender _jid ) ;
expect ( chatbox ) . toBeDefined ( ) ;
expect ( view ) . toBeDefined ( ) ;
// Check that the message was received and check the message parameters
await u . waitUntil ( ( ) => chatbox . messages . length ) ;
const msg _obj = chatbox . messages . models [ 0 ] ;
expect ( msg _obj . get ( 'message' ) ) . toEqual ( msgtext ) ;
expect ( msg _obj . get ( 'fullname' ) ) . toBeUndefined ( ) ;
expect ( msg _obj . get ( 'nickname' ) ) . toBe ( null ) ;
expect ( msg _obj . get ( 'sender' ) ) . toEqual ( 'them' ) ;
expect ( msg _obj . get ( 'is_delayed' ) ) . toEqual ( false ) ;
// Now check that the message appears inside the chatbox in the DOM
await u . waitUntil ( ( ) => view . content . querySelector ( '.chat-msg .chat-msg__text' ) ) ;
expect ( view . content . querySelector ( '.chat-msg .chat-msg__text' ) . textContent ) . toEqual ( msgtext ) ;
expect ( view . content . querySelector ( '.chat-msg__time' ) . textContent . match ( /^[0-9][0-9]:[0-9][0-9]/ ) ) . toBeTruthy ( ) ;
await u . waitUntil ( ( ) => chatbox . vcard . get ( 'fullname' ) === 'Juliet Capulet' )
expect ( view . content . querySelector ( 'span.chat-msg__author' ) . textContent . trim ( ) ) . toBe ( 'Juliet Capulet' ) ;
done ( ) ;
} ) ) ;
it ( "can be a carbon message that this user sent from a different client, as defined in XEP-0280" ,
mock . initConverse (
[ 'rosterGroupsFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitUntilDiscoConfirmed ( _converse , 'montague.lit' , [ ] , [ 'vcard-temp' ] ) ;
await mock . waitForRoster ( _converse , 'current' ) ;
await mock . openControlBox ( _converse ) ;
// Send a message from a different resource
const msgtext = 'This is a sent carbon message' ;
const recipient _jid = mock . cur _names [ 5 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
const msg = $msg ( {
'from' : _converse . bare _jid ,
'id' : u . getUniqueId ( ) ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'xmlns' : 'jabber:client'
} ) . c ( 'sent' , { 'xmlns' : 'urn:xmpp:carbons:2' } )
. c ( 'forwarded' , { 'xmlns' : 'urn:xmpp:forward:0' } )
. c ( 'message' , {
'xmlns' : 'jabber:client' ,
'from' : _converse . bare _jid + '/another-resource' ,
'to' : recipient _jid ,
'type' : 'chat'
} ) . c ( 'body' ) . t ( msgtext ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
// Check that the chatbox and its view now exist
const chatbox = await _converse . api . chats . get ( recipient _jid ) ;
const view = _converse . api . chatviews . get ( recipient _jid ) ;
expect ( chatbox ) . toBeDefined ( ) ;
expect ( view ) . toBeDefined ( ) ;
// Check that the message was received and check the message parameters
expect ( chatbox . messages . length ) . toEqual ( 1 ) ;
const msg _obj = chatbox . messages . models [ 0 ] ;
expect ( msg _obj . get ( 'message' ) ) . toEqual ( msgtext ) ;
expect ( msg _obj . get ( 'fullname' ) ) . toEqual ( _converse . xmppstatus . get ( 'fullname' ) ) ;
expect ( msg _obj . get ( 'sender' ) ) . toEqual ( 'me' ) ;
expect ( msg _obj . get ( 'is_delayed' ) ) . toEqual ( false ) ;
// Now check that the message appears inside the chatbox in the DOM
2020-05-15 14:33:31 +02:00
const msg _el = await u . waitUntil ( ( ) => view . el . querySelector ( '.chat-content .chat-msg .chat-msg__text' ) ) ;
expect ( msg _el . textContent ) . toEqual ( msgtext ) ;
2020-04-22 13:11:48 +02:00
done ( ) ;
} ) ) ;
it ( "will be discarded if it's a malicious message meant to look like a carbon copy" ,
mock . initConverse (
[ 'rosterGroupsFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' ) ;
await mock . openControlBox ( _converse ) ;
/ * < m e s s a g e f r o m = " m a l l o r y @ e v i l . e x a m p l e " t o = " b @ x m p p . e x a m p l e " >
* < received xmlns = 'urn:xmpp:carbons:2' >
* < forwarded xmlns = 'urn:xmpp:forward:0' >
* < message from = "alice@xmpp.example" to = "bob@xmpp.example/client1" >
* < body > Please come to Creepy Valley tonight , alone ! < / b o d y >
* < / m e s s a g e >
* < / f o r w a r d e d >
* < / r e c e i v e d >
* < / m e s s a g e >
* /
const msgtext = 'Please come to Creepy Valley tonight, alone!' ;
const sender _jid = mock . cur _names [ 1 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
const impersonated _jid = mock . cur _names [ 2 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
const msg = $msg ( {
'from' : sender _jid ,
'id' : u . getUniqueId ( ) ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'xmlns' : 'jabber:client'
} ) . c ( 'received' , { 'xmlns' : 'urn:xmpp:carbons:2' } )
. c ( 'forwarded' , { 'xmlns' : 'urn:xmpp:forward:0' } )
. c ( 'message' , {
'xmlns' : 'jabber:client' ,
'from' : impersonated _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat'
} ) . c ( 'body' ) . t ( msgtext ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
// Check that chatbox for impersonated user is not created.
let chatbox = await _converse . api . chats . get ( impersonated _jid ) ;
expect ( chatbox ) . toBe ( null ) ;
// Check that the chatbox for the malicous user is not created
chatbox = await _converse . api . chats . get ( sender _jid ) ;
expect ( chatbox ) . toBe ( null ) ;
done ( ) ;
} ) ) ;
it ( "received for a minimized chat box will increment a counter on its header" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
if ( _converse . view _mode === 'fullscreen' ) {
return done ( ) ;
}
await mock . waitForRoster ( _converse , 'current' ) ;
const contact _name = mock . cur _names [ 0 ] ;
const contact _jid = contact _name . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
await mock . openControlBox ( _converse ) ;
spyOn ( _converse . api , "trigger" ) . and . callThrough ( ) ;
await u . waitUntil ( ( ) => _converse . rosterview . el . querySelectorAll ( '.roster-group' ) . length ) ;
await mock . openChatBoxFor ( _converse , contact _jid ) ;
const chatview = _converse . api . chatviews . get ( contact _jid ) ;
expect ( u . isVisible ( chatview . el ) ) . toBeTruthy ( ) ;
expect ( chatview . model . get ( 'minimized' ) ) . toBeFalsy ( ) ;
chatview . el . querySelector ( '.toggle-chatbox-button' ) . click ( ) ;
expect ( chatview . model . get ( 'minimized' ) ) . toBeTruthy ( ) ;
var message = 'This message is sent to a minimized chatbox' ;
var sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
var 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 ) ;
await u . waitUntil ( ( ) => chatview . model . messages . length ) ;
expect ( _converse . api . trigger ) . toHaveBeenCalledWith ( 'message' , jasmine . any ( Object ) ) ;
const trimmed _chatboxes = _converse . minimized _chats ;
const trimmedview = trimmed _chatboxes . get ( contact _jid ) ;
let count = trimmedview . el . querySelector ( '.message-count' ) ;
expect ( u . isVisible ( chatview . el ) ) . toBeFalsy ( ) ;
expect ( trimmedview . model . get ( 'minimized' ) ) . toBeTruthy ( ) ;
expect ( u . isVisible ( count ) ) . toBeTruthy ( ) ;
expect ( count . textContent ) . toBe ( '1' ) ;
_converse . handleMessageStanza (
$msg ( {
from : mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ,
2018-11-20 18:10:55 +01:00
to : _converse . connection . jid ,
type : 'chat' ,
2020-02-07 12:24:02 +01:00
id : u . getUniqueId ( )
2020-04-22 13:11:48 +02:00
} ) . c ( 'body' ) . t ( 'This message is also sent to a minimized chatbox' ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( )
) ;
await u . waitUntil ( ( ) => ( chatview . model . messages . length > 1 ) ) ;
expect ( u . isVisible ( chatview . el ) ) . toBeFalsy ( ) ;
expect ( trimmedview . model . get ( 'minimized' ) ) . toBeTruthy ( ) ;
count = trimmedview . el . querySelector ( '.message-count' ) ;
expect ( u . isVisible ( count ) ) . toBeTruthy ( ) ;
expect ( count . textContent ) . toBe ( '2' ) ;
trimmedview . el . querySelector ( '.restore-chat' ) . click ( ) ;
expect ( trimmed _chatboxes . keys ( ) . length ) . toBe ( 0 ) ;
done ( ) ;
} ) ) ;
it ( "will indicate when it has a time difference of more than a day between it and its predecessor" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
const include _nick = false ;
await mock . waitForRoster ( _converse , 'current' , 2 , include _nick ) ;
await mock . openControlBox ( _converse ) ;
spyOn ( _converse . api , "trigger" ) . and . callThrough ( ) ;
const contact _name = mock . cur _names [ 1 ] ;
const contact _jid = contact _name . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
await u . waitUntil ( ( ) => _converse . rosterview . el . querySelectorAll ( '.roster-group' ) . length ) ;
await mock . openChatBoxFor ( _converse , contact _jid ) ;
2020-05-15 14:33:31 +02:00
2020-04-22 13:11:48 +02:00
const one _day _ago = dayjs ( ) . subtract ( 1 , 'day' ) ;
const chatbox = _converse . chatboxes . get ( contact _jid ) ;
const view = _converse . chatboxviews . get ( contact _jid ) ;
let message = 'This is a day old message' ;
let msg = $msg ( {
from : contact _jid ,
to : _converse . connection . jid ,
type : 'chat' ,
id : one _day _ago . toDate ( ) . getTime ( )
} ) . c ( 'body' ) . t ( message ) . up ( )
. c ( 'delay' , { xmlns : 'urn:xmpp:delay' , from : 'montague.lit' , stamp : one _day _ago . toISOString ( ) } )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( _converse . api . trigger ) . toHaveBeenCalledWith ( 'message' , jasmine . any ( Object ) ) ;
expect ( chatbox . messages . length ) . toEqual ( 1 ) ;
let msg _obj = chatbox . messages . models [ 0 ] ;
expect ( msg _obj . get ( 'message' ) ) . toEqual ( message ) ;
expect ( msg _obj . get ( 'fullname' ) ) . toBeUndefined ( ) ;
expect ( msg _obj . get ( 'nickname' ) ) . toBe ( null ) ;
expect ( msg _obj . get ( 'sender' ) ) . toEqual ( 'them' ) ;
expect ( msg _obj . get ( 'is_delayed' ) ) . toEqual ( true ) ;
await u . waitUntil ( ( ) => chatbox . vcard . get ( 'fullname' ) === 'Juliet Capulet' )
expect ( view . msgs _container . querySelector ( '.chat-msg .chat-msg__text' ) . textContent ) . toEqual ( message ) ;
expect ( view . msgs _container . querySelector ( '.chat-msg__time' ) . textContent . match ( /^[0-9][0-9]:[0-9][0-9]/ ) ) . toBeTruthy ( ) ;
expect ( view . msgs _container . querySelector ( 'span.chat-msg__author' ) . textContent . trim ( ) ) . toBe ( 'Juliet Capulet' ) ;
expect ( view . msgs _container . querySelectorAll ( '.date-separator' ) . length ) . toEqual ( 1 ) ;
let day = view . msgs _container . querySelector ( '.date-separator' ) ;
expect ( day . getAttribute ( 'class' ) ) . toEqual ( 'message date-separator' ) ;
expect ( day . getAttribute ( 'data-isodate' ) ) . toEqual ( dayjs ( one _day _ago . startOf ( 'day' ) ) . toISOString ( ) ) ;
let time = view . msgs _container . querySelector ( 'time.separator-text' ) ;
expect ( time . textContent ) . toEqual ( dayjs ( one _day _ago . startOf ( 'day' ) ) . format ( "dddd MMM Do YYYY" ) ) ;
message = 'This is a current message' ;
msg = $msg ( {
from : contact _jid ,
to : _converse . connection . jid ,
type : 'chat' ,
id : new Date ( ) . getTime ( )
} ) . c ( 'body' ) . t ( message ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( _converse . api . trigger ) . toHaveBeenCalledWith ( 'message' , jasmine . any ( Object ) ) ;
// Check that there is a <time> element, with the required props.
expect ( view . msgs _container . querySelectorAll ( 'time.separator-text' ) . length ) . toEqual ( 2 ) ; // There are now two time elements
const message _date = new Date ( ) ;
day = sizzle ( '.date-separator:last' , view . msgs _container ) ;
expect ( day . length ) . toEqual ( 1 ) ;
expect ( day [ 0 ] . getAttribute ( 'class' ) ) . toEqual ( 'message date-separator' ) ;
expect ( day [ 0 ] . getAttribute ( 'data-isodate' ) ) . toEqual ( dayjs ( message _date ) . startOf ( 'day' ) . toISOString ( ) ) ;
time = sizzle ( 'time.separator-text:last' , view . msgs _container ) . pop ( ) ;
expect ( time . textContent ) . toEqual ( dayjs ( message _date ) . startOf ( 'day' ) . format ( "dddd MMM Do YYYY" ) ) ;
// Normal checks for the 2nd message
expect ( chatbox . messages . length ) . toEqual ( 2 ) ;
msg _obj = chatbox . messages . models [ 1 ] ;
expect ( msg _obj . get ( 'message' ) ) . toEqual ( message ) ;
expect ( msg _obj . get ( 'fullname' ) ) . toBeUndefined ( ) ;
expect ( msg _obj . get ( 'sender' ) ) . toEqual ( 'them' ) ;
expect ( msg _obj . get ( 'is_delayed' ) ) . toEqual ( false ) ;
const msg _txt = sizzle ( '.chat-msg:last .chat-msg__text' , view . msgs _container ) . pop ( ) . textContent ;
expect ( msg _txt ) . toEqual ( message ) ;
2020-05-15 14:33:31 +02:00
expect ( view . msgs _container . querySelector ( 'converse-chat-message:last-child .chat-msg__text' ) . textContent ) . toEqual ( message ) ;
expect ( view . msgs _container . querySelector ( 'converse-chat-message:last-child .chat-msg__time' ) . textContent . match ( /^[0-9][0-9]:[0-9][0-9]/ ) ) . toBeTruthy ( ) ;
expect ( view . msgs _container . querySelector ( 'converse-chat-message:last-child .chat-msg__author' ) . textContent . trim ( ) ) . toBe ( 'Juliet Capulet' ) ;
2020-04-22 13:11:48 +02:00
done ( ) ;
} ) ) ;
it ( "is sanitized to prevent Javascript injection attacks" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' ) ;
await mock . openControlBox ( _converse ) ;
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 ) ;
const message = '<p>This message contains <em>some</em> <b>markup</b></p>' ;
spyOn ( view . model , 'sendMessage' ) . and . callThrough ( ) ;
await mock . sendMessage ( view , message ) ;
expect ( view . model . sendMessage ) . toHaveBeenCalled ( ) ;
const msg = sizzle ( '.chat-content .chat-msg:last .chat-msg__text' , view . el ) . pop ( ) ;
expect ( msg . textContent ) . toEqual ( message ) ;
2020-05-15 14:33:31 +02:00
expect ( msg . innerHTML . replace ( /<!---->/g , '' ) ) . toEqual ( '<p>This message contains <em>some</em> <b>markup</b></p>' ) ;
2020-04-22 13:11:48 +02:00
done ( ) ;
} ) ) ;
it ( "can contain hyperlinks, which will be clickable" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' ) ;
await mock . openControlBox ( _converse ) ;
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 ) ;
const message = 'This message contains a hyperlink: www.opkode.com' ;
spyOn ( view . model , 'sendMessage' ) . and . callThrough ( ) ;
mock . sendMessage ( view , message ) ;
expect ( view . model . sendMessage ) . toHaveBeenCalled ( ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
const msg = sizzle ( '.chat-content .chat-msg:last .chat-msg__text' , view . el ) . pop ( ) ;
expect ( msg . textContent ) . toEqual ( message ) ;
2020-07-09 06:27:56 +02:00
await u . waitUntil ( ( ) => msg . innerHTML . replace ( /<!---->/g , '' ) ===
'This message contains a hyperlink: <a target="_blank" rel="noopener" href="http://www.opkode.com">www.opkode.com</a>' ) ;
2020-04-22 13:11:48 +02:00
done ( ) ;
} ) ) ;
it ( "will render newlines" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' ) ;
const contact _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
const view = await mock . openChatBoxFor ( _converse , contact _jid ) ;
let stanza = u . toStanza ( `
< message from = "${contact_jid}"
type = "chat"
to = "romeo@montague.lit/orchard" >
< body > Hey \ nHave you heard the news ? < / b o d y >
< / m e s s a g e > ` ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
expect ( view . content . querySelector ( '.chat-msg__text' ) . innerHTML . replace ( /<!---->/g , '' ) ) . toBe ( 'Hey\nHave you heard the news?' ) ;
2020-04-22 13:11:48 +02:00
stanza = u . toStanza ( `
< message from = "${contact_jid}"
type = "chat"
to = "romeo@montague.lit/orchard" >
< body > Hey \ n \ n \ nHave you heard the news ? < / b o d y >
< / m e s s a g e > ` ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-07-09 06:27:56 +02:00
await u . waitUntil ( ( ) => view . content . querySelector ( 'converse-chat-message:last-child .chat-msg__text' ) . innerHTML . replace ( /<!---->/g , '' ) === 'Hey\n\nHave you heard the news?' ) ;
2020-04-22 13:11:48 +02:00
stanza = u . toStanza ( `
< message from = "${contact_jid}"
type = "chat"
to = "romeo@montague.lit/orchard" >
< body > Hey \ nHave you heard \ nthe news ? < / b o d y >
< / m e s s a g e > ` ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
expect ( view . content . querySelector ( 'converse-chat-message:last-child .chat-msg__text' ) . innerHTML . replace ( /<!---->/g , '' ) ) . toBe ( 'Hey\nHave you heard\nthe news?' ) ;
2020-04-22 13:11:48 +02:00
done ( ) ;
} ) ) ;
it ( "will render images from their URLs" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' ) ;
const base _url = 'https://conversejs.org' ;
let message = base _url + "/logo/conversejs-filled.svg" ;
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 ) ;
spyOn ( view . model , 'sendMessage' ) . and . callThrough ( ) ;
mock . sendMessage ( view , message ) ;
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-content .chat-image' ) . length , 1000 )
expect ( view . model . sendMessage ) . toHaveBeenCalled ( ) ;
let msg = sizzle ( '.chat-content .chat-msg:last .chat-msg__text' ) . pop ( ) ;
2020-05-15 14:33:31 +02:00
expect ( msg . innerHTML . replace ( /<!---->/g , '' ) . trim ( ) ) . toEqual (
` <a class="chat-image__link" target="_blank" rel="noopener" href=" ${ base _url } /logo/conversejs-filled.svg"> ` +
2020-07-09 08:57:25 +02:00
` <img class="chat-image img-thumbnail" src="https://conversejs.org/logo/conversejs-filled.svg"> ` +
2020-05-15 14:33:31 +02:00
` </a> ` ) ;
2020-04-22 13:11:48 +02:00
message += "?param1=val1¶m2=val2" ;
mock . sendMessage ( view , message ) ;
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-content .chat-image' ) . length === 2 , 1000 ) ;
expect ( view . model . sendMessage ) . toHaveBeenCalled ( ) ;
msg = sizzle ( '.chat-content .chat-msg:last .chat-msg__text' ) . pop ( ) ;
2020-05-15 14:33:31 +02:00
expect ( msg . innerHTML . replace ( /<!---->/g , '' ) . trim ( ) ) . toEqual (
` <a class="chat-image__link" target="_blank" rel="noopener" href=" ${ base _url } /logo/conversejs-filled.svg?param1=val1&param2=val2"> ` +
` <img class="chat-image img-thumbnail" src=" ${ message . replace ( /&/g , '&' ) } "> ` +
` </a> ` ) ;
2020-04-22 13:11:48 +02:00
// Test now with two images in one message
message += ' hello world ' + base _url + "/logo/conversejs-filled.svg" ;
mock . sendMessage ( view , message ) ;
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-content .chat-image' ) . length === 4 , 1000 ) ;
expect ( view . model . sendMessage ) . toHaveBeenCalled ( ) ;
msg = sizzle ( '.chat-content .chat-msg:last .chat-msg__text' ) . pop ( ) ;
expect ( msg . textContent . trim ( ) ) . toEqual ( 'hello world' ) ;
expect ( msg . querySelectorAll ( 'img' ) . length ) . toEqual ( 2 ) ;
// Non-https images aren't rendered
message = base _url + "/logo/conversejs-filled.svg" ;
expect ( view . content . querySelectorAll ( 'img' ) . length ) . toBe ( 4 ) ;
mock . sendMessage ( view , message ) ;
expect ( view . content . querySelectorAll ( 'img' ) . length ) . toBe ( 4 ) ;
done ( ) ;
} ) ) ;
2020-07-09 08:57:25 +02:00
it ( "will fall back to rendering images as URLs" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' ) ;
const base _url = 'https://conversejs.org' ;
const message = base _url + "/logo/non-existing.svg" ;
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 ) ;
spyOn ( view . model , 'sendMessage' ) . and . callThrough ( ) ;
mock . sendMessage ( view , message ) ;
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-content .chat-image' ) . length , 1000 )
expect ( view . model . sendMessage ) . toHaveBeenCalled ( ) ;
const msg = sizzle ( '.chat-content .chat-msg:last .chat-msg__text' ) . pop ( ) ;
await u . waitUntil ( ( ) => msg . innerHTML . replace ( /<!---->/g , '' ) . trim ( ) ==
2020-07-14 14:06:52 +02:00
` <a target="_blank" rel="noopener" href="https://conversejs.org/logo/non-existing.svg">https://conversejs.org/logo/non-existing.svg</a> ` , 1000 ) ;
2020-07-09 08:57:25 +02:00
done ( ) ;
} ) ) ;
2020-04-22 13:11:48 +02:00
it ( "will render the message time as configured" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
2018-11-20 18:10:55 +01:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' ) ;
_converse . time _format = 'hh:mm' ;
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 ) ;
const message = 'This message is sent from this chatbox' ;
await mock . sendMessage ( view , message ) ;
const chatbox = await _converse . api . chats . get ( contact _jid ) ;
expect ( chatbox . messages . models . length , 1 ) ;
const msg _object = chatbox . messages . models [ 0 ] ;
const msg _author = view . el . querySelector ( '.chat-content .chat-msg:last-child .chat-msg__author' ) ;
expect ( msg _author . textContent . trim ( ) ) . toBe ( 'Romeo Montague' ) ;
const msg _time = view . el . querySelector ( '.chat-content .chat-msg:last-child .chat-msg__time' ) ;
const time = dayjs ( msg _object . get ( 'time' ) ) . format ( _converse . time _format ) ;
expect ( msg _time . textContent ) . toBe ( time ) ;
done ( ) ;
} ) ) ;
it ( "will be correctly identified and rendered as a followup message" ,
mock . initConverse (
2020-05-15 14:33:31 +02:00
[ 'rosterGroupsFetched' ] , { 'debounced_content_rendering' : false } ,
2020-04-22 13:11:48 +02:00
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' ) ;
await mock . openControlBox ( _converse ) ;
const base _time = new Date ( ) ;
const ONE _MINUTE _LATER = 60000 ;
await u . waitUntil ( ( ) => _converse . rosterview . el . querySelectorAll ( '.roster-group' ) . length , 300 ) ;
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
_converse . filter _by _resource = true ;
jasmine . clock ( ) . install ( ) ;
jasmine . clock ( ) . mockDate ( base _time ) ;
_converse . handleMessageStanza ( $msg ( {
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : u . getUniqueId ( )
} ) . c ( 'body' ) . t ( 'A message' ) . 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 ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
jasmine . clock ( ) . tick ( 3 * ONE _MINUTE _LATER ) ;
_converse . handleMessageStanza ( $msg ( {
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : u . getUniqueId ( )
} ) . c ( 'body' ) . t ( "Another message 3 minutes later" ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
jasmine . clock ( ) . tick ( 11 * ONE _MINUTE _LATER ) ;
_converse . handleMessageStanza ( $msg ( {
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : u . getUniqueId ( )
} ) . c ( 'body' ) . t ( "Another message 14 minutes since we started" ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
jasmine . clock ( ) . tick ( 1 * ONE _MINUTE _LATER ) ;
_converse . handleMessageStanza ( $msg ( {
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : _converse . connection . getUniqueId ( )
} ) . c ( 'body' ) . t ( "Another message 1 minute and 1 second since the previous one" ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
jasmine . clock ( ) . tick ( 1 * ONE _MINUTE _LATER ) ;
await mock . sendMessage ( view , "Another message within 10 minutes, but from a different person" ) ;
expect ( view . content . querySelectorAll ( '.message' ) . length ) . toBe ( 6 ) ;
expect ( view . content . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 5 ) ;
2020-05-15 14:33:31 +02:00
const nth _child = ( n ) => ` converse-chat-message:nth-child( ${ n } ) .chat-msg ` ;
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 2 ) ) ) ) . toBe ( false ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 2 ) } .chat-msg__text ` ) . textContent ) . toBe ( "A message" ) ;
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 3 ) ) ) ) . toBe ( true ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 3 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message 3 minutes later" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 4 ) ) ) ) . toBe ( false ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 4 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message 14 minutes since we started" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 5 ) ) ) ) . toBe ( true ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 5 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message 1 minute and 1 second since the previous one" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 6 ) ) ) ) . toBe ( false ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 6 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message within 10 minutes, but from a different person" ) ;
// Let's add a delayed, inbetween message
_converse . handleMessageStanza (
$msg ( {
'xmlns' : 'jabber:client' ,
'id' : _converse . connection . getUniqueId ( ) ,
'to' : _converse . bare _jid ,
'from' : sender _jid ,
'type' : 'chat'
} ) . c ( 'body' ) . t ( "A delayed message, sent 5 minutes since we started" ) . up ( )
. c ( 'delay' , { 'xmlns' : 'urn:xmpp:delay' , 'stamp' : dayjs ( base _time ) . add ( 5 , 'minutes' ) . toISOString ( ) } )
. tree ( ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( view . content . querySelectorAll ( '.message' ) . length ) . toBe ( 7 ) ;
expect ( view . content . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 6 ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 2 ) ) ) ) . toBe ( false ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 2 ) } .chat-msg__text ` ) . textContent ) . toBe ( "A message" ) ;
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 3 ) ) ) ) . toBe ( true ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 3 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message 3 minutes later" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 4 ) ) ) ) . toBe ( true ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 4 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"A delayed message, sent 5 minutes since we started" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 5 ) ) ) ) . toBe ( true ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 5 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message 14 minutes since we started" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 6 ) ) ) ) . toBe ( true ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 6 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message 1 minute and 1 second since the previous one" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 7 ) ) ) ) . toBe ( false ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 7 ) } .chat-msg__text ` ) . textContent ) . toBe (
"Another message within 10 minutes, but from a different person" ) ;
2020-04-22 13:11:48 +02:00
_converse . handleMessageStanza (
$msg ( {
'xmlns' : 'jabber:client' ,
'id' : _converse . connection . getUniqueId ( ) ,
'to' : sender _jid ,
'from' : _converse . bare _jid + "/some-other-resource" ,
'type' : 'chat' } )
. c ( 'body' ) . t ( "A carbon message 4 minutes later" ) . up ( )
. c ( 'delay' , { 'xmlns' : 'urn:xmpp:delay' , 'stamp' : dayjs ( base _time ) . add ( 4 , 'minutes' ) . toISOString ( ) } )
. tree ( ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( view . content . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 7 ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 2 ) ) ) ) . toBe ( false ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 2 ) } .chat-msg__text ` ) . textContent ) . toBe ( "A message" ) ;
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 3 ) ) ) ) . toBe ( true ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 3 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message 3 minutes later" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 4 ) ) ) ) . toBe ( false ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 4 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"A carbon message 4 minutes later" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 5 ) ) ) ) . toBe ( false ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 5 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"A delayed message, sent 5 minutes since we started" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 6 ) ) ) ) . toBe ( true ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 6 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message 14 minutes since we started" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 7 ) ) ) ) . toBe ( true ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 7 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message 1 minute and 1 second since the previous one" ) ;
2020-05-15 14:33:31 +02:00
expect ( u . hasClass ( 'chat-msg--followup' , view . content . querySelector ( nth _child ( 8 ) ) ) ) . toBe ( false ) ;
expect ( view . content . querySelector ( ` ${ nth _child ( 8 ) } .chat-msg__text ` ) . textContent ) . toBe (
2020-04-22 13:11:48 +02:00
"Another message within 10 minutes, but from a different person" ) ;
jasmine . clock ( ) . uninstall ( ) ;
done ( ) ;
} ) ) ;
it ( "received may emit a message delivery receipt" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' ) ;
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
const msg _id = u . getUniqueId ( ) ;
const sent _stanzas = [ ] ;
spyOn ( _converse . connection , 'send' ) . and . callFake ( stanza => sent _stanzas . push ( stanza ) ) ;
const msg = $msg ( {
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : msg _id ,
} ) . c ( 'body' ) . t ( 'Message!' ) . up ( )
. c ( 'request' , { 'xmlns' : Strophe . NS . RECEIPTS } ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
const sent _messages = sent _stanzas . map ( s => _ . isElement ( s ) ? s : s . nodeTree ) . filter ( s => s . nodeName === 'message' ) ;
// A chat state message is also included
expect ( sent _messages . length ) . toBe ( 2 ) ;
const receipt = sizzle ( ` received[xmlns=" ${ Strophe . NS . RECEIPTS } "] ` , sent _messages [ 1 ] ) . pop ( ) ;
expect ( Strophe . serialize ( receipt ) ) . toBe ( ` <received id=" ${ msg _id } " xmlns=" ${ Strophe . NS . RECEIPTS } "/> ` ) ;
done ( ) ;
} ) ) ;
it ( "carbon received does not emit a message delivery receipt" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
const msg _id = u . getUniqueId ( ) ;
const view = await mock . openChatBoxFor ( _converse , sender _jid ) ;
spyOn ( view . model , 'sendReceiptStanza' ) . and . callThrough ( ) ;
const msg = $msg ( {
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : u . getUniqueId ( ) ,
} ) . c ( 'received' , { 'xmlns' : 'urn:xmpp:carbons:2' } )
. c ( 'forwarded' , { 'xmlns' : 'urn:xmpp:forward:0' } )
. c ( 'message' , {
'xmlns' : 'jabber:client' ,
'from' : sender _jid ,
'to' : _converse . bare _jid + '/another-resource' ,
'type' : 'chat' ,
'id' : msg _id
} ) . c ( 'body' ) . t ( 'Message!' ) . up ( )
. c ( 'request' , { 'xmlns' : Strophe . NS . RECEIPTS } ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
expect ( view . model . sendReceiptStanza ) . not . toHaveBeenCalled ( ) ;
done ( ) ;
} ) ) ;
2018-11-20 18:10:55 +01:00
2020-04-22 13:11:48 +02:00
describe ( "when sent" , function ( ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
it ( "can have its delivery acknowledged by a receipt" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2019-10-11 16:38:01 +02:00
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
2018-10-13 23:25:01 +02:00
async function ( done , _converse ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
const contact _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
await mock . openChatBoxFor ( _converse , contact _jid ) ;
2018-10-13 23:25:01 +02:00
const view = _converse . chatboxviews . get ( contact _jid ) ;
2020-04-22 13:11:48 +02:00
const textarea = view . el . querySelector ( 'textarea.chat-textarea' ) ;
textarea . value = 'But soft, what light through yonder airlock breaks?' ;
view . onKeyDown ( {
target : textarea ,
preventDefault : function preventDefault ( ) { } ,
keyCode : 13 // Enter
} ) ;
const chatbox = _converse . chatboxes . get ( contact _jid ) ;
expect ( chatbox ) . toBeDefined ( ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2018-10-13 23:25:01 +02:00
let msg _obj = chatbox . messages . models [ 0 ] ;
2020-04-22 13:11:48 +02:00
let msg _id = msg _obj . get ( 'msgid' ) ;
let msg = $msg ( {
'from' : contact _jid ,
'to' : _converse . connection . jid ,
'id' : u . getUniqueId ( ) ,
} ) . c ( 'received' , { 'id' : msg _id , xmlns : Strophe . NS . RECEIPTS } ) . up ( ) . tree ( ) ;
_converse . connection . _dataRecv ( mock . createRequest ( msg ) ) ;
2020-05-15 14:33:31 +02:00
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-msg__receipt' ) . length === 1 ) ;
2018-10-13 23:25:01 +02:00
2020-04-22 13:11:48 +02:00
// Also handle receipts with type 'chat'. See #1353
spyOn ( _converse , 'handleMessageStanza' ) . and . callThrough ( ) ;
textarea . value = 'Another message' ;
view . onKeyDown ( {
target : textarea ,
preventDefault : function preventDefault ( ) { } ,
keyCode : 13 // Enter
} ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2018-10-13 23:25:01 +02:00
msg _obj = chatbox . messages . models [ 1 ] ;
2020-04-22 13:11:48 +02:00
msg _id = msg _obj . get ( 'msgid' ) ;
msg = $msg ( {
'from' : contact _jid ,
'type' : 'chat' ,
'to' : _converse . connection . jid ,
'id' : u . getUniqueId ( ) ,
} ) . c ( 'received' , { 'id' : msg _id , xmlns : Strophe . NS . RECEIPTS } ) . up ( ) . tree ( ) ;
_converse . connection . _dataRecv ( mock . createRequest ( msg ) ) ;
2020-05-15 14:33:31 +02:00
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-msg__receipt' ) . length === 2 ) ;
2020-04-22 13:11:48 +02:00
expect ( _converse . handleMessageStanza . calls . count ( ) ) . toBe ( 1 ) ;
2018-10-13 23:25:01 +02:00
done ( ) ;
} ) ) ;
2018-04-29 15:40:24 +02:00
2018-08-15 11:02:24 +02:00
2020-04-22 13:11:48 +02:00
it ( "will appear inside the chatbox it was sent from" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2019-10-11 16:38:01 +02:00
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
2018-10-13 23:25:01 +02:00
async function ( done , _converse ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' ) ;
await mock . openControlBox ( _converse ) ;
spyOn ( _converse . api , "trigger" ) . and . callThrough ( ) ;
2019-06-03 07:58:51 +02:00
const contact _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
2020-04-22 13:11:48 +02:00
await mock . openChatBoxFor ( _converse , contact _jid )
const view = _converse . chatboxviews . get ( contact _jid ) ;
const message = 'This message is sent from this chatbox' ;
2018-10-13 23:25:01 +02:00
spyOn ( view . model , 'sendMessage' ) . and . callThrough ( ) ;
2020-04-22 13:11:48 +02:00
await mock . sendMessage ( view , message ) ;
2018-10-13 23:25:01 +02:00
expect ( view . model . sendMessage ) . toHaveBeenCalled ( ) ;
2020-04-22 13:11:48 +02:00
expect ( view . model . messages . length , 2 ) ;
expect ( _converse . api . trigger . calls . mostRecent ( ) . args , [ 'messageSend' , message ] ) ;
expect ( sizzle ( '.chat-content .chat-msg:last .chat-msg__text' , view . el ) . pop ( ) . textContent ) . toEqual ( message ) ;
2018-10-13 23:25:01 +02:00
done ( ) ;
2018-08-15 11:02:24 +02:00
} ) ) ;
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
it ( "will be trimmed of leading and trailing whitespace" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2020-01-21 12:45:34 +01:00
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
2018-11-18 15:24:34 +01:00
async function ( done , _converse ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
2019-06-03 07:58:51 +02:00
const contact _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
2020-04-22 13:11:48 +02:00
await mock . openChatBoxFor ( _converse , contact _jid )
const view = _converse . chatboxviews . get ( contact _jid ) ;
const message = ' \nThis message is sent from this chatbox \n \n' ;
await mock . sendMessage ( view , message ) ;
expect ( view . model . messages . at ( 0 ) . get ( 'message' ) ) . toEqual ( message . trim ( ) ) ;
const message _el = sizzle ( '.chat-content .chat-msg:last .chat-msg__text' , view . el ) . pop ( ) ;
expect ( message _el . textContent ) . toEqual ( message . trim ( ) ) ;
2018-11-18 15:24:34 +01:00
done ( ) ;
2018-04-29 15:40:24 +02:00
} ) ) ;
2020-04-22 13:11:48 +02:00
} ) ;
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
describe ( "when received from someone else" , function ( ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
it ( "will open a chatbox and be displayed inside it" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2020-01-21 12:45:34 +01:00
[ 'rosterGroupsFetched' ] , { } ,
2018-10-13 23:25:01 +02:00
async function ( done , _converse ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
const include _nick = false ;
await mock . waitForRoster ( _converse , 'current' , 1 , include _nick ) ;
await mock . openControlBox ( _converse ) ;
2019-07-11 22:50:30 +02:00
await u . waitUntil ( ( ) => _converse . rosterview . el . querySelectorAll ( '.roster-group' ) . length , 300 ) ;
2020-04-22 13:11:48 +02:00
spyOn ( _converse . api , "trigger" ) . and . callThrough ( ) ;
const message = 'This is a received message' ;
2019-06-03 07:58:51 +02:00
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
2020-04-22 13:11:48 +02:00
// We don't already have an open chatbox for this user
expect ( _converse . chatboxes . get ( sender _jid ) ) . not . toBeDefined ( ) ;
await _converse . handleMessageStanza (
$msg ( {
2018-10-13 23:25:01 +02:00
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
2020-02-07 12:24:02 +01:00
'id' : u . getUniqueId ( )
2020-04-22 13:11:48 +02:00
} ) . c ( 'body' ) . t ( message ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( )
) ;
const chatbox = await _converse . chatboxes . get ( sender _jid ) ;
expect ( chatbox ) . toBeDefined ( ) ;
2019-02-22 21:43:49 +01:00
const view = _converse . api . chatviews . get ( sender _jid ) ;
2020-04-22 13:11:48 +02:00
expect ( view ) . toBeDefined ( ) ;
2018-10-13 23:25:01 +02:00
2020-04-22 13:11:48 +02:00
expect ( _converse . api . trigger ) . toHaveBeenCalledWith ( 'message' , jasmine . any ( Object ) ) ;
// Check that the message was received and check the message parameters
await u . waitUntil ( ( ) => chatbox . messages . length ) ;
expect ( chatbox . messages . length ) . toEqual ( 1 ) ;
const msg _obj = chatbox . messages . models [ 0 ] ;
expect ( msg _obj . get ( 'message' ) ) . toEqual ( message ) ;
expect ( msg _obj . get ( 'fullname' ) ) . toBeUndefined ( ) ;
expect ( msg _obj . get ( 'sender' ) ) . toEqual ( 'them' ) ;
expect ( msg _obj . get ( 'is_delayed' ) ) . toEqual ( false ) ;
// Now check that the message appears inside the chatbox in the DOM
const mel = await u . waitUntil ( ( ) => view . content . querySelector ( '.chat-msg .chat-msg__text' ) ) ;
expect ( mel . textContent ) . toEqual ( message ) ;
expect ( view . content . querySelector ( '.chat-msg__time' ) . textContent . match ( /^[0-9][0-9]:[0-9][0-9]/ ) ) . toBeTruthy ( ) ;
await u . waitUntil ( ( ) => chatbox . vcard . get ( 'fullname' ) === mock . cur _names [ 0 ] ) ;
expect ( view . content . querySelector ( 'span.chat-msg__author' ) . textContent . trim ( ) ) . toBe ( 'Mercutio' ) ;
done ( ) ;
} ) ) ;
2018-10-13 23:25:01 +02:00
2020-04-22 13:11:48 +02:00
it ( "will be trimmed of leading and trailing whitespace" ,
mock . initConverse (
[ 'rosterGroupsFetched' ] , { } ,
async function ( done , _converse ) {
2020-03-18 19:32:03 +01:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' , 1 , false ) ;
await u . waitUntil ( ( ) => _converse . rosterview . el . querySelectorAll ( '.roster-group' ) . length , 300 ) ;
const message = '\n\n This is a received message \n\n' ;
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
await _converse . handleMessageStanza (
$msg ( {
2018-10-13 23:25:01 +02:00
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
2020-04-22 13:11:48 +02:00
'id' : u . getUniqueId ( )
} ) . c ( 'body' ) . t ( message ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( )
) ;
const view = _converse . api . chatviews . get ( sender _jid ) ;
await u . waitUntil ( ( ) => view . model . messages . length ) ;
expect ( view . model . messages . length ) . toEqual ( 1 ) ;
const msg _obj = view . model . messages . at ( 0 ) ;
expect ( msg _obj . get ( 'message' ) ) . toEqual ( message . trim ( ) ) ;
const mel = await u . waitUntil ( ( ) => view . content . querySelector ( '.chat-msg .chat-msg__text' ) ) ;
expect ( mel . textContent ) . toEqual ( message . trim ( ) ) ;
2018-10-13 23:25:01 +02:00
done ( ) ;
2018-04-29 15:40:24 +02:00
} ) ) ;
2020-04-22 13:11:48 +02:00
it ( "can be replaced with a correction" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2019-10-11 16:38:01 +02:00
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
2019-01-25 11:53:07 +01:00
async function ( done , _converse ) {
2019-02-24 11:44:34 +01:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
await mock . openControlBox ( _converse ) ;
2019-06-03 07:58:51 +02:00
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
2018-11-03 14:37:57 +01:00
const msg _id = u . getUniqueId ( ) ;
2020-04-22 13:11:48 +02:00
const view = await mock . openChatBoxFor ( _converse , sender _jid ) ;
_converse . handleMessageStanza ( $msg ( {
2018-11-03 14:37:57 +01:00
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : msg _id ,
2020-04-22 13:11:48 +02:00
} ) . c ( 'body' ) . t ( 'But soft, what light through yonder airlock breaks?' ) . tree ( ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
expect ( view . el . querySelector ( '.chat-msg__text' ) . textContent )
. toBe ( 'But soft, what light through yonder airlock breaks?' ) ;
2018-11-03 14:37:57 +01:00
2020-04-22 13:11:48 +02:00
_converse . handleMessageStanza ( $msg ( {
2018-12-12 16:42:29 +01:00
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : u . getUniqueId ( ) ,
2020-04-22 13:11:48 +02:00
} ) . c ( 'body' ) . t ( 'But soft, what light through yonder chimney breaks?' ) . up ( )
. c ( 'replace' , { 'id' : msg _id , 'xmlns' : 'urn:xmpp:message-correct:0' } ) . tree ( ) ) ;
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2018-11-03 14:37:57 +01:00
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelector ( '.chat-msg__text' ) . textContent )
. toBe ( 'But soft, what light through yonder chimney breaks?' ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
expect ( view . el . querySelectorAll ( '.chat-msg__content .fa-edit' ) . length ) . toBe ( 1 ) ;
expect ( view . model . messages . models . length ) . toBe ( 1 ) ;
2018-12-16 10:58:48 +01:00
2020-04-22 13:11:48 +02:00
_converse . handleMessageStanza ( $msg ( {
'from' : sender _jid ,
'to' : _converse . connection . jid ,
'type' : 'chat' ,
'id' : u . getUniqueId ( ) ,
} ) . c ( 'body' ) . t ( 'But soft, what light through yonder window breaks?' ) . up ( )
. c ( 'replace' , { 'id' : msg _id , 'xmlns' : 'urn:xmpp:message-correct:0' } ) . tree ( ) ) ;
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2019-06-26 11:09:07 +02:00
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelector ( '.chat-msg__text' ) . textContent )
. toBe ( 'But soft, what light through yonder window breaks?' ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
expect ( view . el . querySelectorAll ( '.chat-msg__content .fa-edit' ) . length ) . toBe ( 1 ) ;
view . el . querySelector ( '.chat-msg__content .fa-edit' ) . click ( ) ;
2020-05-15 14:33:31 +02:00
const modal = await u . waitUntil ( ( ) => view . el . querySelector ( 'converse-chat-message' ) . message _versions _modal ) ;
2020-04-22 13:11:48 +02:00
await u . waitUntil ( ( ) => u . isVisible ( modal . el ) , 1000 ) ;
const older _msgs = modal . el . querySelectorAll ( '.older-msg' ) ;
expect ( older _msgs . length ) . toBe ( 2 ) ;
expect ( older _msgs [ 0 ] . childNodes [ 0 ] . nodeName ) . toBe ( 'TIME' ) ;
expect ( older _msgs [ 0 ] . childNodes [ 2 ] . textContent ) . toBe ( 'But soft, what light through yonder airlock breaks?' ) ;
expect ( view . model . messages . models . length ) . toBe ( 1 ) ;
done ( ) ;
} ) ) ;
2018-12-16 10:58:48 +01:00
2018-11-03 14:37:57 +01:00
2020-04-22 13:11:48 +02:00
describe ( "when a chatbox is opened for someone who is not in the roster" , function ( ) {
it ( "the VCard for that user is fetched and the chatbox updated with the results" ,
2019-06-26 11:09:07 +02:00
mock . initConverse (
2020-04-22 13:11:48 +02:00
[ 'rosterGroupsFetched' ] , { 'allow_non_roster_messaging' : true } ,
2019-06-26 11:09:07 +02:00
async function ( done , _converse ) {
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' , 0 ) ;
2019-08-01 10:26:35 +02:00
spyOn ( _converse . api , "trigger" ) . and . callThrough ( ) ;
2019-06-26 11:09:07 +02:00
2020-04-22 13:11:48 +02:00
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
var vcard _fetched = false ;
spyOn ( _converse . api . vcard , "get" ) . and . callFake ( function ( ) {
vcard _fetched = true ;
return Promise . resolve ( {
'fullname' : mock . cur _names [ 0 ] ,
'vcard_updated' : ( new Date ( ) ) . toISOString ( ) ,
'jid' : sender _jid
} ) ;
} ) ;
const message = 'This is a received message from someone not on the roster' ;
const 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 ( ) ;
2019-06-26 10:59:02 +02:00
2020-04-22 13:11:48 +02:00
// We don't already have an open chatbox for this user
expect ( _converse . chatboxes . get ( sender _jid ) ) . not . toBeDefined ( ) ;
2019-06-26 10:59:02 +02:00
2020-04-22 13:11:48 +02:00
await _converse . handleMessageStanza ( msg ) ;
const view = await u . waitUntil ( ( ) => _converse . api . chatviews . get ( sender _jid ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( _converse . api . trigger ) . toHaveBeenCalledWith ( 'message' , jasmine . any ( Object ) ) ;
// Check that the chatbox and its view now exist
const chatbox = await _converse . api . chats . get ( sender _jid ) ;
expect ( chatbox . get ( 'fullname' ) === sender _jid ) ;
await u . waitUntil ( ( ) => view . el . querySelector ( '.chat-msg__author' ) . textContent . trim ( ) === 'Mercutio' ) ;
let author _el = view . el . querySelector ( '.chat-msg__author' ) ;
expect ( _ . includes ( author _el . textContent . trim ( ) , 'Mercutio' ) ) . toBeTruthy ( ) ;
await u . waitUntil ( ( ) => vcard _fetched , 100 ) ;
expect ( _converse . api . vcard . get ) . toHaveBeenCalled ( ) ;
await u . waitUntil ( ( ) => chatbox . vcard . get ( 'fullname' ) === mock . cur _names [ 0 ] )
author _el = view . el . querySelector ( '.chat-msg__author' ) ;
expect ( _ . includes ( author _el . textContent . trim ( ) , 'Mercutio' ) ) . toBeTruthy ( ) ;
2019-06-26 10:59:02 +02:00
done ( ) ;
} ) ) ;
} ) ;
2018-08-15 17:22:24 +02:00
2020-04-22 13:11:48 +02:00
describe ( "who is not on the roster" , function ( ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
it ( "will open a chatbox and be displayed inside it if allow_non_roster_messaging is true" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2020-04-22 13:11:48 +02:00
[ 'rosterGroupsFetched' ] , { 'allow_non_roster_messaging' : false } ,
2018-10-13 23:25:01 +02:00
async function ( done , _converse ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' , 0 ) ;
2019-03-29 21:10:45 +01:00
spyOn ( _converse . api , "trigger" ) . and . callThrough ( ) ;
2020-04-22 13:11:48 +02:00
const message = 'This is a received message from someone not on the roster' ;
2019-06-03 07:58:51 +02:00
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
2020-04-22 13:11:48 +02:00
const 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 ( ) ;
2018-10-13 23:25:01 +02:00
// We don't already have an open chatbox for this user
expect ( _converse . chatboxes . get ( sender _jid ) ) . not . toBeDefined ( ) ;
2020-04-22 13:11:48 +02:00
let chatbox = await _converse . api . chats . get ( sender _jid ) ;
expect ( chatbox ) . toBe ( null ) ;
await _converse . handleMessageStanza ( msg ) ;
let view = _converse . chatboxviews . get ( sender _jid ) ;
expect ( view ) . not . toBeDefined ( ) ;
_converse . allow _non _roster _messaging = true ;
await _converse . handleMessageStanza ( msg ) ;
view = _converse . chatboxviews . get ( sender _jid ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2019-03-29 21:10:45 +01:00
expect ( _converse . api . trigger ) . toHaveBeenCalledWith ( 'message' , jasmine . any ( Object ) ) ;
2020-04-22 13:11:48 +02:00
// Check that the chatbox and its view now exist
chatbox = await _converse . api . chats . get ( sender _jid ) ;
expect ( chatbox ) . toBeDefined ( ) ;
expect ( view ) . toBeDefined ( ) ;
2018-10-13 23:25:01 +02:00
// Check that the message was received and check the message parameters
expect ( chatbox . messages . length ) . toEqual ( 1 ) ;
const msg _obj = chatbox . messages . models [ 0 ] ;
expect ( msg _obj . get ( 'message' ) ) . toEqual ( message ) ;
2020-04-22 13:11:48 +02:00
expect ( msg _obj . get ( 'fullname' ) ) . toEqual ( undefined ) ;
2018-10-13 23:25:01 +02:00
expect ( msg _obj . get ( 'sender' ) ) . toEqual ( 'them' ) ;
expect ( msg _obj . get ( 'is_delayed' ) ) . toEqual ( false ) ;
2020-04-22 13:11:48 +02:00
await u . waitUntil ( ( ) => view . el . querySelector ( '.chat-msg__author' ) . textContent . trim ( ) === 'Mercutio' ) ;
2018-10-13 23:25:01 +02:00
// Now check that the message appears inside the chatbox in the DOM
2020-04-22 13:11:48 +02:00
expect ( view . content . querySelector ( '.chat-msg .chat-msg__text' ) . textContent ) . toEqual ( message ) ;
2020-03-24 10:20:11 +01:00
expect ( view . content . querySelector ( '.chat-msg__time' ) . textContent . match ( /^[0-9][0-9]:[0-9][0-9]/ ) ) . toBeTruthy ( ) ;
expect ( view . content . querySelector ( 'span.chat-msg__author' ) . textContent . trim ( ) ) . toBe ( 'Mercutio' ) ;
2018-10-13 23:25:01 +02:00
done ( ) ;
2018-08-15 11:02:24 +02:00
} ) ) ;
2020-04-22 13:11:48 +02:00
} ) ;
2018-04-29 15:40:24 +02:00
2019-06-26 10:59:02 +02:00
2020-04-22 13:11:48 +02:00
describe ( "and for which then an error message is received from the server" , function ( ) {
2019-06-26 10:59:02 +02:00
2020-04-22 13:11:48 +02:00
it ( "will have the error message displayed after itself" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2019-10-11 16:38:01 +02:00
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
2018-10-13 23:25:01 +02:00
async function ( done , _converse ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
// TODO: what could still be done for error
// messages... if the <error> element has type
// "cancel", then we know the messages wasn't sent,
// and can give the user a nicer indication of
// that.
/ * < m e s s a g e f r o m = " s c o t t y @ e n t e r p r i s e . c o m / _ c o n v e r s e . j s - 8 4 8 4 3 5 2 6 "
* to = "kirk@enterprise.com.com"
* type = "chat"
* id = "82bc02ce-9651-4336-baf0-fa04762ed8d2"
* xmlns = "jabber:client" >
* < body > yo < / b o d y >
* < active xmlns = "http://jabber.org/protocol/chatstates" / >
* < / m e s s a g e >
* /
const error _txt = 'Server-to-server connection failed: Connecting failed: connection timeout' ;
2020-04-24 17:33:32 +02:00
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
2020-04-22 13:11:48 +02:00
await _converse . api . chats . open ( sender _jid )
let msg _text = 'This message will not be sent, due to an error' ;
const view = _converse . api . chatviews . get ( sender _jid ) ;
const message = await view . model . sendMessage ( msg _text ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
let msg _txt = sizzle ( '.chat-msg:last .chat-msg__text' , view . content ) . pop ( ) . textContent ;
expect ( msg _txt ) . toEqual ( msg _text ) ;
// We send another message, for which an error will
// not be received, to test that errors appear
// after the relevant message.
msg _text = 'This message will be sent, and also receive an error' ;
const second _message = await view . model . sendMessage ( msg _text ) ;
await u . waitUntil ( ( ) => sizzle ( '.chat-msg .chat-msg__text' , view . content ) . length === 2 , 1000 ) ;
msg _txt = sizzle ( '.chat-msg:last .chat-msg__text' , view . content ) . pop ( ) . textContent ;
expect ( msg _txt ) . toEqual ( msg _text ) ;
/ * < m e s s a g e x m l n s = " j a b b e r : c l i e n t "
* to = "scotty@enterprise.com/_converse.js-84843526"
* type = "error"
* id = "82bc02ce-9651-4336-baf0-fa04762ed8d2"
* from = "kirk@enterprise.com.com" >
* < error type = "cancel" >
* < remote - server - not - found xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" / >
* < text xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" > Server - to - server connection failed : Connecting failed : connection timeout < / t e x t >
* < / e r r o r >
* < / m e s s a g e >
* /
let stanza = $msg ( {
'to' : _converse . connection . jid ,
'type' : 'error' ,
'id' : message . get ( 'msgid' ) ,
'from' : sender _jid
} )
. c ( 'error' , { 'type' : 'cancel' } )
. c ( 'remote-server-not-found' , { 'xmlns' : "urn:ietf:params:xml:ns:xmpp-stanzas" } ) . up ( )
. c ( 'text' , { 'xmlns' : "urn:ietf:params:xml:ns:xmpp-stanzas" } )
. t ( 'Server-to-server connection failed: Connecting failed: connection timeout' ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await u . waitUntil ( ( ) => view . content . querySelector ( '.chat-msg__error' ) . textContent . trim ( ) === error _txt ) ;
const other _error _txt = 'Server-to-server connection failed: Connecting failed: connection timeout' ;
2020-04-22 13:11:48 +02:00
stanza = $msg ( {
2018-10-13 23:25:01 +02:00
'to' : _converse . connection . jid ,
2020-04-22 13:11:48 +02:00
'type' : 'error' ,
'id' : second _message . get ( 'id' ) ,
'from' : sender _jid
} )
. c ( 'error' , { 'type' : 'cancel' } )
. c ( 'remote-server-not-found' , { 'xmlns' : "urn:ietf:params:xml:ns:xmpp-stanzas" } ) . up ( )
. c ( 'text' , { 'xmlns' : "urn:ietf:params:xml:ns:xmpp-stanzas" } )
2020-05-15 14:33:31 +02:00
. t ( other _error _txt ) ;
2020-04-22 13:11:48 +02:00
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await u . waitUntil ( ( ) =>
view . content . querySelector ( 'converse-chat-message:last-child .chat-msg__error' ) . textContent . trim ( ) === other _error _txt ) ;
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
// We don't render duplicates
stanza = $msg ( {
2018-10-13 23:25:01 +02:00
'to' : _converse . connection . jid ,
2020-04-22 13:11:48 +02:00
'type' : 'error' ,
'id' : '6fcdeee3-000f-4ce8-a17e-9ce28f0ae104' ,
'from' : sender _jid
} )
. c ( 'error' , { 'type' : 'cancel' } )
. c ( 'remote-server-not-found' , { 'xmlns' : "urn:ietf:params:xml:ns:xmpp-stanzas" } ) . up ( )
. c ( 'text' , { 'xmlns' : "urn:ietf:params:xml:ns:xmpp-stanzas" } )
. t ( 'Server-to-server connection failed: Connecting failed: connection timeout' ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
expect ( view . content . querySelectorAll ( '.chat-msg__error' ) . length ) . toEqual ( 2 ) ;
2020-04-22 13:11:48 +02:00
msg _text = 'This message will be sent, and also receive an error' ;
const third _message = await view . model . sendMessage ( msg _text ) ;
2020-05-15 14:33:31 +02:00
await u . waitUntil ( ( ) => sizzle ( 'converse-chat-message:last-child .chat-msg__text' , view . content ) . pop ( ) ? . textContent === msg _text ) ;
2018-07-30 18:16:32 +02:00
2020-04-22 13:11:48 +02:00
// A different error message will however render
stanza = $msg ( {
2018-10-13 23:25:01 +02:00
'to' : _converse . connection . jid ,
2020-04-22 13:11:48 +02:00
'type' : 'error' ,
'id' : third _message . get ( 'id' ) ,
'from' : sender _jid
} )
. c ( 'error' , { 'type' : 'cancel' } )
. c ( 'not-allowed' , { 'xmlns' : "urn:ietf:params:xml:ns:xmpp-stanzas" } ) . up ( )
. c ( 'text' , { 'xmlns' : "urn:ietf:params:xml:ns:xmpp-stanzas" } )
. t ( 'Something else went wrong as well' ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
await u . waitUntil ( ( ) => view . model . messages . length > 3 ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
expect ( view . content . querySelectorAll ( '.chat-error' ) . length ) . toEqual ( 1 ) ;
2020-06-25 10:22:44 +02:00
// Ensure messages with error are not editable
document . querySelectorAll ( '.chat-msg__actions' ) . forEach ( elem => {
expect ( elem . querySelector ( '.chat-msg__action-edit' ) ) . toBe ( null )
} )
view . model . messages . forEach ( message => {
const isEditable = message . get ( 'editable' ) ;
isEditable && expect ( isEditable ) . toBe ( false ) ;
} )
2018-10-13 23:25:01 +02:00
done ( ) ;
} ) ) ;
2020-04-22 13:11:48 +02:00
it ( "will not show to the user an error message for a CSI message" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2019-10-11 16:38:01 +02:00
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
2018-10-13 23:25:01 +02:00
async function ( done , _converse ) {
2018-04-30 12:15:26 +02:00
2020-04-22 13:11:48 +02:00
// See #1317
// https://github.com/conversejs/converse.js/issues/1317
await mock . waitForRoster ( _converse , 'current' ) ;
await mock . openControlBox ( _converse ) ;
2018-04-30 12:15:26 +02:00
2020-04-22 13:11:48 +02:00
const contact _jid = mock . cur _names [ 5 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
await mock . openChatBoxFor ( _converse , contact _jid ) ;
2019-11-01 16:04:55 +01:00
2020-04-22 13:11:48 +02:00
const messages = _converse . connection . sent _stanzas . filter ( s => s . nodeName === 'message' ) ;
expect ( messages . length ) . toBe ( 1 ) ;
expect ( Strophe . serialize ( messages [ 0 ] ) ) . toBe (
` <message id=" ${ messages [ 0 ] . getAttribute ( 'id' ) } " to="tybalt@montague.lit" type="chat" xmlns="jabber:client"> ` +
` <active xmlns="http://jabber.org/protocol/chatstates"/> ` +
` <no-store xmlns="urn:xmpp:hints"/> ` +
` <no-permanent-store xmlns="urn:xmpp:hints"/> ` +
` </message> ` ) ;
2018-10-13 23:25:01 +02:00
2020-04-22 13:11:48 +02:00
const stanza = $msg ( {
'from' : contact _jid ,
'type' : 'error' ,
'id' : messages [ 0 ] . getAttribute ( 'id' )
} ) . c ( 'error' , { 'type' : 'cancel' , 'code' : '503' } )
. c ( 'service-unavailable' , { 'xmlns' : 'urn:ietf:params:xml:ns:xmpp-stanzas' } ) . up ( )
. c ( 'text' , { 'xmlns' : 'urn:ietf:params:xml:ns:xmpp-stanzas' } )
. t ( 'User session not found' )
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
const view = _converse . chatboxviews . get ( contact _jid ) ;
expect ( view . content . querySelectorAll ( '.chat-error' ) . length ) . toEqual ( 0 ) ;
2018-10-13 23:25:01 +02:00
done ( ) ;
2018-08-15 11:02:24 +02:00
} ) ) ;
} ) ;
2018-04-30 12:15:26 +02:00
2020-04-22 13:11:48 +02:00
it ( "will cause the chat area to be scrolled down only if it was at the bottom originally" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' ) ;
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
await mock . openChatBoxFor ( _converse , sender _jid )
const view = _converse . api . chatviews . get ( sender _jid ) ;
// Create enough messages so that there's a scrollbar.
const promises = [ ] ;
2020-06-30 10:59:11 +02:00
view . content . scrollTop = 0 ;
view . model . set ( 'scrolled' , true ) ;
2020-04-22 13:11:48 +02:00
for ( let i = 0 ; i < 20 ; i ++ ) {
_converse . handleMessageStanza ( $msg ( {
from : sender _jid ,
to : _converse . connection . jid ,
type : 'chat' ,
id : _converse . connection . getUniqueId ( ) ,
} ) . c ( 'body' ) . t ( 'Message: ' + i ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( ) ) ;
2020-05-15 14:33:31 +02:00
promises . push ( new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ) ;
2020-04-22 13:11:48 +02:00
}
await Promise . all ( promises ) ;
2020-06-30 10:59:11 +02:00
const indicator _el = view . el . querySelector ( '.new-msgs-indicator' ) ;
expect ( u . isVisible ( indicator _el ) ) . toBeTruthy ( ) ;
2020-04-22 13:11:48 +02:00
expect ( view . model . get ( 'scrolled' ) ) . toBe ( true ) ;
expect ( view . content . scrollTop ) . toBe ( 0 ) ;
2020-06-30 10:59:11 +02:00
indicator _el . click ( ) ;
expect ( u . isVisible ( indicator _el ) ) . toBeFalsy ( ) ;
expect ( view . model . get ( 'scrolled' ) ) . toBe ( false ) ;
2020-04-22 13:11:48 +02:00
done ( ) ;
} ) ) ;
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
it ( "is ignored if it's intended for a different resource and filter_by_resource is set to true" ,
mock . initConverse (
[ 'rosterGroupsFetched' ] , { } ,
async function ( done , _converse ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' ) ;
await u . waitUntil ( ( ) => _converse . rosterview . el . querySelectorAll ( '.roster-group' ) . length )
// Send a message from a different resource
2020-04-24 17:33:32 +02:00
spyOn ( converse . env . log , 'error' ) ;
2020-04-22 13:11:48 +02:00
spyOn ( _converse . api . chatboxes , 'create' ) . and . callThrough ( ) ;
_converse . filter _by _resource = true ;
const sender _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
let msg = $msg ( {
from : sender _jid ,
to : _converse . bare _jid + "/some-other-resource" ,
type : 'chat' ,
id : u . getUniqueId ( )
} ) . c ( 'body' ) . t ( "This message will not be shown" ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
2018-04-29 15:40:24 +02:00
2020-04-24 17:33:32 +02:00
expect ( converse . env . log . error . calls . all ( ) . pop ( ) . args [ 0 ] ) . toBe (
"Ignoring incoming message intended for a different resource: romeo@montague.lit/some-other-resource" ,
2020-04-22 13:11:48 +02:00
) ;
expect ( _converse . api . chatboxes . create ) . not . toHaveBeenCalled ( ) ;
_converse . filter _by _resource = false ;
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
const message = "This message sent to a different resource will be shown" ;
msg = $msg ( {
from : sender _jid ,
to : _converse . bare _jid + "/some-other-resource" ,
type : 'chat' ,
id : '134234623462346'
} ) . c ( 'body' ) . t ( message ) . up ( )
. c ( 'active' , { 'xmlns' : 'http://jabber.org/protocol/chatstates' } ) . tree ( ) ;
await _converse . handleMessageStanza ( msg ) ;
await u . waitUntil ( ( ) => _converse . chatboxviews . keys ( ) . length > 1 , 1000 ) ;
const view = _converse . chatboxviews . get ( sender _jid ) ;
await u . waitUntil ( ( ) => view . model . messages . length ) ;
expect ( _converse . api . chatboxes . create ) . toHaveBeenCalled ( ) ;
const last _message = await u . waitUntil ( ( ) => sizzle ( '.chat-content:last .chat-msg__text' , view . el ) . pop ( ) ) ;
const msg _txt = last _message . textContent ;
expect ( msg _txt ) . toEqual ( message ) ;
done ( ) ;
} ) ) ;
} ) ;
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
describe ( "which contains an OOB URL" , function ( ) {
2018-04-29 15:40:24 +02:00
2020-04-22 13:11:48 +02:00
it ( "will render audio from oob mp3 URLs" ,
mock . initConverse (
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
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 ) ;
spyOn ( view . model , 'sendMessage' ) . and . callThrough ( ) ;
2018-07-07 14:20:49 +02:00
2020-04-22 13:11:48 +02:00
let stanza = u . toStanza ( `
< message from = "${contact_jid}"
type = "chat"
to = "romeo@montague.lit/orchard" >
< body > Have you heard this funny audio ? < / b o d y >
< x xmlns = "jabber:x:oob" > < url > https : //montague.lit/audio.mp3</url></x>
< / m e s s a g e > ` )
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-content .chat-msg audio' ) . length , 1000 ) ;
let msg = view . el . querySelector ( '.chat-msg .chat-msg__text' ) ;
expect ( msg . classList . length ) . toEqual ( 1 ) ;
expect ( u . hasClass ( 'chat-msg__text' , msg ) ) . toBe ( true ) ;
expect ( msg . textContent ) . toEqual ( 'Have you heard this funny audio?' ) ;
let media = view . el . querySelector ( '.chat-msg .chat-msg__media' ) ;
2020-05-15 14:33:31 +02:00
expect ( media . innerHTML . replace ( /<!---->/g , '' ) . replace ( /(\r\n|\n|\r)/gm , "" ) . trim ( ) ) . toEqual (
` <audio controls="" src="https://montague.lit/audio.mp3"></audio> ` +
` <a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3">Download audio file "audio.mp3"</a> ` ) ;
2020-04-22 13:11:48 +02:00
// If the <url> and <body> contents is the same, don't duplicate.
stanza = u . toStanza ( `
< message from = "${contact_jid}"
type = "chat"
to = "romeo@montague.lit/orchard" >
< body > https : //montague.lit/audio.mp3</body>
< x xmlns = "jabber:x:oob" > < url > https : //montague.lit/audio.mp3</url></x>
< / m e s s a g e > ` ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
msg = view . el . querySelector ( '.chat-msg:last-child .chat-msg__text' ) ;
2020-05-15 14:33:31 +02:00
expect ( msg . innerHTML . replace ( /<!---->/g , '' ) ) . toEqual ( 'Have you heard this funny audio?' ) ; // Emtpy
2020-04-22 13:11:48 +02:00
media = view . el . querySelector ( '.chat-msg:last-child .chat-msg__media' ) ;
2020-05-15 14:33:31 +02:00
expect ( media . innerHTML . replace ( /<!---->/g , '' ) . replace ( /(\r\n|\n|\r)/gm , "" ) . trim ( ) ) . toEqual (
` <audio controls="" src="https://montague.lit/audio.mp3"></audio> ` +
2020-04-22 13:11:48 +02:00
` <a target="_blank" rel="noopener" href="https://montague.lit/audio.mp3"> ` +
2020-05-15 14:33:31 +02:00
` Download audio file "audio.mp3"</a> ` ) ;
2020-04-22 13:11:48 +02:00
done ( ) ;
} ) ) ;
2019-01-31 19:22:30 +01:00
2020-04-22 13:11:48 +02:00
it ( "will render video from oob mp4 URLs" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2020-04-22 13:11:48 +02:00
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
2019-01-31 19:22:30 +01:00
async function ( done , _converse ) {
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
2019-06-03 07:58:51 +02:00
const contact _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
2020-04-22 13:11:48 +02:00
await mock . openChatBoxFor ( _converse , contact _jid )
2019-02-22 21:43:49 +01:00
const view = _converse . api . chatviews . get ( contact _jid ) ;
2020-04-22 13:11:48 +02:00
spyOn ( view . model , 'sendMessage' ) . and . callThrough ( ) ;
let stanza = u . toStanza ( `
< message from = "${contact_jid}"
type = "chat"
to = "romeo@montague.lit/orchard" >
< body > Have you seen this funny video ? < / b o d y >
< x xmlns = "jabber:x:oob" > < url > https : //montague.lit/video.mp4</url></x>
2019-02-12 14:21:45 +01:00
< / m e s s a g e > ` ) ;
2020-04-22 13:11:48 +02:00
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-content .chat-msg video' ) . length , 2000 )
let msg = view . el . querySelector ( '.chat-msg .chat-msg__text' ) ;
expect ( msg . classList . length ) . toBe ( 1 ) ;
expect ( msg . textContent ) . toEqual ( 'Have you seen this funny video?' ) ;
let media = view . el . querySelector ( '.chat-msg .chat-msg__media' ) ;
expect ( media . innerHTML . replace ( /(\r\n|\n|\r)/gm , "" ) ) . toEqual (
` <!----><video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video><!----> ` ) ;
2019-01-31 19:22:30 +01:00
2020-04-22 13:11:48 +02:00
// If the <url> and <body> contents is the same, don't duplicate.
stanza = u . toStanza ( `
< message from = "${contact_jid}"
type = "chat"
to = "romeo@montague.lit/orchard" >
< body > https : //montague.lit/video.mp4</body>
< x xmlns = "jabber:x:oob" > < url > https : //montague.lit/video.mp4</url></x>
< / m e s s a g e > ` ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
msg = view . el . querySelector ( '.chat-msg:last-child .chat-msg__text' ) ;
2020-05-15 14:33:31 +02:00
expect ( msg . innerHTML . replace ( /<!---->/g , '' ) ) . toEqual ( 'Have you seen this funny video?' ) ;
2020-04-22 13:11:48 +02:00
media = view . el . querySelector ( '.chat-msg:last-child .chat-msg__media' ) ;
expect ( media . innerHTML . replace ( /(\r\n|\n|\r)/gm , "" ) ) . toEqual (
` <!----><video controls="" preload="metadata" style="max-height: 50vh" src="https://montague.lit/video.mp4"></video><!----> ` ) ;
2019-01-31 19:22:30 +01:00
done ( ) ;
} ) ) ;
2020-04-22 13:11:48 +02:00
it ( "will render download links for files from oob URLs" ,
2019-02-13 19:31:05 +01:00
mock . initConverse (
2020-04-22 13:11:48 +02:00
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
2019-02-13 19:31:05 +01:00
async function ( done , _converse ) {
2020-04-22 13:11:48 +02:00
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
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 ) ;
spyOn ( view . model , 'sendMessage' ) . and . callThrough ( ) ;
2019-02-13 19:31:05 +01:00
const stanza = u . toStanza ( `
2020-04-22 13:11:48 +02:00
< message from = "${contact_jid}"
type = "chat"
to = "romeo@montague.lit/orchard" >
< body > Have you downloaded this funny file ? < / b o d y >
< x xmlns = "jabber:x:oob" > < url > https : //montague.lit/funny.pdf</url></x>
2019-02-13 19:31:05 +01:00
< / m e s s a g e > ` ) ;
2020-04-22 13:11:48 +02:00
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-content .chat-msg a' ) . length , 1000 ) ;
const msg = view . el . querySelector ( '.chat-msg .chat-msg__text' ) ;
expect ( u . hasClass ( 'chat-msg__text' , msg ) ) . toBe ( true ) ;
expect ( msg . textContent ) . toEqual ( 'Have you downloaded this funny file?' ) ;
const media = view . el . querySelector ( '.chat-msg .chat-msg__media' ) ;
expect ( media . innerHTML . replace ( /(\r\n|\n|\r)/gm , "" ) ) . toEqual (
` <!----><a target="_blank" rel="noopener" href="https://montague.lit/funny.pdf"><!---->Download file "funny.pdf"<!----></a><!----> ` ) ;
2019-02-13 19:31:05 +01:00
done ( ) ;
} ) ) ;
2020-04-22 13:11:48 +02:00
it ( "will render images from oob URLs" ,
2019-02-12 14:21:45 +01:00
mock . initConverse (
2020-04-22 13:11:48 +02:00
[ 'rosterGroupsFetched' , 'chatBoxesFetched' ] , { } ,
2019-01-31 19:22:30 +01:00
async function ( done , _converse ) {
2020-04-22 13:11:48 +02:00
const base _url = 'https://conversejs.org' ;
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
2019-06-03 07:58:51 +02:00
const contact _jid = mock . cur _names [ 0 ] . replace ( / /g , '.' ) . toLowerCase ( ) + '@montague.lit' ;
2020-04-22 13:11:48 +02:00
await mock . openChatBoxFor ( _converse , contact _jid )
2019-02-22 21:43:49 +01:00
const view = _converse . api . chatviews . get ( contact _jid ) ;
2020-04-22 13:11:48 +02:00
spyOn ( view . model , 'sendMessage' ) . and . callThrough ( ) ;
const url = base _url + "/logo/conversejs-filled.svg" ;
2019-01-31 19:22:30 +01:00
2020-04-22 13:11:48 +02:00
const stanza = u . toStanza ( `
< message from = "${contact_jid}"
2019-01-31 19:22:30 +01:00
type = "chat"
2020-04-22 13:11:48 +02:00
to = "romeo@montague.lit/orchard" >
< body > Have you seen this funny image ? < / b o d y >
< x xmlns = "jabber:x:oob" > < url > $ { url } < / u r l > < / x >
2019-02-12 14:21:45 +01:00
< / m e s s a g e > ` ) ;
2020-04-22 13:11:48 +02:00
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
await u . waitUntil ( ( ) => view . el . querySelectorAll ( '.chat-content .chat-msg img' ) . length , 2000 ) ;
const msg = view . el . querySelector ( '.chat-msg .chat-msg__text' ) ;
expect ( u . hasClass ( 'chat-msg__text' , msg ) ) . toBe ( true ) ;
expect ( msg . textContent ) . toEqual ( 'Have you seen this funny image?' ) ;
const media = view . el . querySelector ( '.chat-msg .chat-msg__media' ) ;
2020-07-09 08:57:25 +02:00
expect ( media . innerHTML . replace ( /<!---->/g , '' ) . replace ( /(\r\n|\n|\r)/gm , "" ) ) . toEqual (
` <a class="chat-image__link" target="_blank" rel="noopener" href=" ${ base _url } /logo/conversejs-filled.svg"> ` +
` <img class="chat-image img-thumbnail" src=" ${ base _url } /logo/conversejs-filled.svg"></a> ` ) ;
2019-01-31 19:22:30 +01:00
done ( ) ;
} ) ) ;
} ) ;
2020-04-22 12:10:39 +02:00
} ) ;
2020-04-22 13:11:48 +02:00
describe ( "A XEP-0333 Chat Marker" , function ( ) {
it ( "is sent when a markable message is received from a roster contact" ,
mock . initConverse (
[ 'rosterGroupsFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
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 ) ;
const msgid = u . getUniqueId ( ) ;
const stanza = u . toStanza ( `
< message from = '${contact_jid}'
id = '${msgid}'
type = "chat"
to = '${_converse.jid}' >
< body > My lord , dispatch ; read o ' er these articles . < / b o d y >
< markable xmlns = 'urn:xmpp:chat-markers:0' / >
< / m e s s a g e > ` ) ;
const sent _stanzas = [ ] ;
spyOn ( _converse . connection , 'send' ) . and . callFake ( s => sent _stanzas . push ( s ) ) ;
spyOn ( view . model , 'sendMarker' ) . and . callThrough ( ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-06-01 17:30:20 +02:00
await u . waitUntil ( ( ) => view . model . sendMarker . calls . count ( ) === 2 ) ;
2020-04-22 13:11:48 +02:00
expect ( Strophe . serialize ( sent _stanzas [ 0 ] ) ) . toBe (
` <message from="romeo@montague.lit/orchard" ` +
` id=" ${ sent _stanzas [ 0 ] . nodeTree . getAttribute ( 'id' ) } " ` +
` to=" ${ contact _jid } " type="chat" xmlns="jabber:client"> ` +
` <received id=" ${ msgid } " xmlns="urn:xmpp:chat-markers:0"/> ` +
` </message> ` ) ;
done ( ) ;
} ) ) ;
it ( "is not sent when a markable message is received from someone not on the roster" ,
mock . initConverse (
[ 'rosterGroupsFetched' ] , { 'allow_non_roster_messaging' : true } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' , 0 ) ;
const contact _jid = 'someone@montague.lit' ;
const msgid = u . getUniqueId ( ) ;
const stanza = u . toStanza ( `
< message from = '${contact_jid}'
id = '${msgid}'
type = "chat"
to = '${_converse.jid}' >
< body > My lord , dispatch ; read o ' er these articles . < / b o d y >
< markable xmlns = 'urn:xmpp:chat-markers:0' / >
< / m e s s a g e > ` ) ;
const sent _stanzas = [ ] ;
spyOn ( _converse . connection , 'send' ) . and . callFake ( s => sent _stanzas . push ( s ) ) ;
await _converse . handleMessageStanza ( stanza ) ;
const sent _messages = sent _stanzas
. map ( s => _ . isElement ( s ) ? s : s . nodeTree )
. filter ( e => e . nodeName === 'message' ) ;
2020-06-03 09:57:14 +02:00
await u . waitUntil ( ( ) => sent _messages . length === 2 ) ;
2020-04-22 13:11:48 +02:00
expect ( Strophe . serialize ( sent _messages [ 0 ] ) ) . toBe (
` <message id=" ${ sent _messages [ 0 ] . getAttribute ( 'id' ) } " to=" ${ contact _jid } " type="chat" xmlns="jabber:client"> ` +
` <active xmlns="http://jabber.org/protocol/chatstates"/> ` +
` <no-store xmlns="urn:xmpp:hints"/> ` +
` <no-permanent-store xmlns="urn:xmpp:hints"/> ` +
` </message> `
) ;
done ( ) ;
} ) ) ;
it ( "is ignored if it's a carbon copy of one that I sent from a different client" ,
mock . initConverse (
[ 'rosterGroupsFetched' ] , { } ,
async function ( done , _converse ) {
await mock . waitForRoster ( _converse , 'current' , 1 ) ;
await mock . waitUntilDiscoConfirmed ( _converse , _converse . bare _jid , [ ] , [ Strophe . NS . SID ] ) ;
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 stanza = u . toStanza ( `
< message xmlns = "jabber:client"
to = "${_converse.bare_jid}"
type = "chat"
id = "2e972ea0-0050-44b7-a830-f6638a2595b3"
from = "${contact_jid}" >
< body > 😊 < / b o d y >
< markable xmlns = "urn:xmpp:chat-markers:0" / >
< origin - id xmlns = "urn:xmpp:sid:0" id = "2e972ea0-0050-44b7-a830-f6638a2595b3" / >
< stanza - id xmlns = "urn:xmpp:sid:0" id = "IxVDLJ0RYbWcWvqC" by = "${_converse.bare_jid}" / >
< / m e s s a g e > ` ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
2020-05-15 14:33:31 +02:00
await new Promise ( resolve => view . model . messages . once ( 'rendered' , resolve ) ) ;
2020-04-22 13:11:48 +02:00
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
expect ( view . model . messages . length ) . toBe ( 1 ) ;
stanza = u . toStanza (
` <message xmlns="jabber:client" to=" ${ _converse . bare _jid } " type="chat" from=" ${ contact _jid } ">
< sent xmlns = "urn:xmpp:carbons:2" >
< forwarded xmlns = "urn:xmpp:forward:0" >
< message xmlns = "jabber:client" to = "${contact_jid}" type = "chat" from = "${_converse.bare_jid}/other-resource" >
< received xmlns = "urn:xmpp:chat-markers:0" id = "2e972ea0-0050-44b7-a830-f6638a2595b3" / >
< store xmlns = "urn:xmpp:hints" / >
< stanza - id xmlns = "urn:xmpp:sid:0" id = "F4TC6CvHwzqRbeHb" by = "${_converse.bare_jid}" / >
< / m e s s a g e >
< / f o r w a r d e d >
< / s e n t >
< / m e s s a g e > ` ) ;
spyOn ( _converse . api , "trigger" ) . and . callThrough ( ) ;
_converse . connection . _dataRecv ( mock . createRequest ( stanza ) ) ;
await u . waitUntil ( ( ) => _converse . api . trigger . calls . count ( ) , 500 ) ;
expect ( view . el . querySelectorAll ( '.chat-msg' ) . length ) . toBe ( 1 ) ;
expect ( view . model . messages . length ) . toBe ( 1 ) ;
done ( ) ;
} ) ) ;
} ) ;