2018-08-01 21:17:03 +02:00
global . patch _builtin ( 'window' , {
bridge : false ,
} ) ;
set _global ( 'blueslip' , global . make _zblueslip ( {
error : false , // Ignore errors. We only check for warnings in this module.
} ) ) ;
var noop = function ( ) { } ;
2017-06-27 14:55:11 +02:00
set _global ( '$' , global . make _zjquery ( ) ) ;
2017-06-29 20:46:27 +02:00
set _global ( 'i18n' , global . stub _i18n ) ;
2017-02-24 16:18:56 +01:00
2018-08-01 21:17:03 +02:00
const _navigator = {
2019-06-24 14:11:21 +02:00
platform : '' ,
2018-08-01 21:17:03 +02:00
} ;
2017-02-24 16:18:56 +01:00
2018-08-01 21:17:03 +02:00
const _document = {
2017-12-06 14:46:23 +01:00
getElementById : function ( ) { return $ ( '#compose-textarea' ) ; } ,
execCommand : function ( ) { return false ; } ,
location : { } ,
2018-08-01 21:17:03 +02:00
} ;
2017-06-27 14:55:11 +02:00
2018-08-01 21:17:03 +02:00
const _drafts = {
2017-06-29 06:54:10 +02:00
delete _draft _after _send : noop ,
2018-08-01 21:17:03 +02:00
} ;
const _resize = {
2017-06-29 06:54:10 +02:00
resize _bottom _whitespace : noop ,
2018-08-01 21:17:03 +02:00
} ;
2017-06-29 06:54:10 +02:00
2018-08-01 21:17:03 +02:00
const _sent _messages = {
sending messages: Extract sent_messages.js.
This commit extract send_messages.js to clean up code related
to the following things:
* sending data to /json/report_send_time
* restarting the event loop if events don't arrive on time
The code related to /json/report changes the following ways:
* We track the state almost completely in the new
send_messages.js module, with other modules just
making one-line calls.
* We no longer send "displayed" times to the servers, since
we were kind of lying about them anyway.
* We now explicitly track the state of each single sent
message in its own object.
* We now look up data related to the messages by local_id,
instead of message_id. The problem with message_id was
that is was mutable. Now we use local_id, and we extend
the local_id concept to messages that don't get rendered
client side. We no longer need to react to the
'message_id_changed' event to change our hash key.
* The code used to live in many places:
* various big chunks were scattered among compose.js,
and those were all moved or reduced to one-line
calls into the new module
* echo.js continues to make basically one-line calls,
but it no longer calls compose.report_as_received(),
nor does it set the "start" time.
* message_util.js used to report received events, but
only when they finally got drawn in the home view;
this code is gone now
The code related to restarting the event loop if events don't arrive
changes as follows:
* The timer now gets set up from within
send_messages.message_state.report_server_ack,
where we can easily inspect the current state of the
possibly-still-in-flight message.
* The code to confirm that an event was received happens now
in server_events.js, rather than later, so that we don't
falsely blame the event loop for a downstream bug. (Plus
it's easier to just do it one place.)
This change removes a fair amount of code from our node tests. Some
of the removal is good stuff related to us completing killing off
unnecessary code. Other removals are more expediency-driven, and
we should make another sweep at ramping up our coverage on compose.js,
with possibly a little more mocking of the new `send_messages` code
layer, since it's now abstracted better.
There is also some minor cleanup to echo.resend_message() in this
commit.
See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
start _tracking _message : noop ,
2018-08-01 21:17:03 +02:00
} ;
const _notifications = {
2018-01-04 21:31:23 +01:00
notify _above _composebox : noop ,
clear _compose _notifications : noop ,
2018-08-01 21:17:03 +02:00
} ;
const _reminder = {
2018-02-06 22:54:53 +01:00
is _deferred _delivery : noop ,
2018-08-01 21:17:03 +02:00
} ;
set _global ( 'document' , _document ) ;
set _global ( 'drafts' , _drafts ) ;
set _global ( 'navigator' , _navigator ) ;
set _global ( 'notifications' , _notifications ) ;
set _global ( 'reminder' , _reminder ) ;
set _global ( 'resize' , _resize ) ;
set _global ( 'sent_messages' , _sent _messages ) ;
set _global ( 'transmit' , { } ) ;
set _global ( 'channel' , { } ) ;
set _global ( 'echo' , { } ) ;
set _global ( 'stream_edit' , { } ) ;
set _global ( 'markdown' , { } ) ;
set _global ( 'loading' , { } ) ;
set _global ( 'page_params' , { } ) ;
set _global ( 'subs' , { } ) ;
set _global ( 'ui_util' , { } ) ;
sending messages: Extract sent_messages.js.
This commit extract send_messages.js to clean up code related
to the following things:
* sending data to /json/report_send_time
* restarting the event loop if events don't arrive on time
The code related to /json/report changes the following ways:
* We track the state almost completely in the new
send_messages.js module, with other modules just
making one-line calls.
* We no longer send "displayed" times to the servers, since
we were kind of lying about them anyway.
* We now explicitly track the state of each single sent
message in its own object.
* We now look up data related to the messages by local_id,
instead of message_id. The problem with message_id was
that is was mutable. Now we use local_id, and we extend
the local_id concept to messages that don't get rendered
client side. We no longer need to react to the
'message_id_changed' event to change our hash key.
* The code used to live in many places:
* various big chunks were scattered among compose.js,
and those were all moved or reduced to one-line
calls into the new module
* echo.js continues to make basically one-line calls,
but it no longer calls compose.report_as_received(),
nor does it set the "start" time.
* message_util.js used to report received events, but
only when they finally got drawn in the home view;
this code is gone now
The code related to restarting the event loop if events don't arrive
changes as follows:
* The timer now gets set up from within
send_messages.message_state.report_server_ack,
where we can easily inspect the current state of the
possibly-still-in-flight message.
* The code to confirm that an event was received happens now
in server_events.js, rather than later, so that we don't
falsely blame the event loop for a downstream bug. (Plus
it's easier to just do it one place.)
This change removes a fair amount of code from our node tests. Some
of the removal is good stuff related to us completing killing off
unnecessary code. Other removals are more expediency-driven, and
we should make another sweep at ramping up our coverage on compose.js,
with possibly a little more mocking of the new `send_messages` code
layer, since it's now abstracted better.
There is also some minor cleanup to echo.resend_message() in this
commit.
See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
2017-07-06 21:57:25 +02:00
// Setting these up so that we can test that links to uploads within messages are
// automatically converted to server relative links.
global . document . location . protocol = 'https:' ;
global . document . location . host = 'foo.com' ;
2018-06-02 13:59:02 +02:00
zrequire ( 'zcommand' ) ;
2017-11-08 16:54:06 +01:00
zrequire ( 'compose_ui' ) ;
zrequire ( 'util' ) ;
2018-07-05 12:17:20 +02:00
zrequire ( 'rtl' ) ;
2017-11-08 16:54:06 +01:00
zrequire ( 'common' ) ;
2019-07-12 02:03:55 +02:00
set _global ( 'Handlebars' , global . make _handlebars ( ) ) ;
2017-11-08 16:54:06 +01:00
zrequire ( 'stream_data' ) ;
zrequire ( 'compose_state' ) ;
zrequire ( 'people' ) ;
2018-03-06 15:07:55 +01:00
zrequire ( 'input_pill' ) ;
zrequire ( 'user_pill' ) ;
zrequire ( 'compose_pm_pill' ) ;
2017-11-08 16:54:06 +01:00
zrequire ( 'compose' ) ;
2017-11-23 15:00:05 +01:00
zrequire ( 'upload' ) ;
2017-02-24 16:18:56 +01:00
2018-06-27 20:55:56 +02:00
people . small _avatar _url _for _person = function ( ) {
return 'http://example.com/example.png' ;
} ;
2017-02-24 23:51:23 +01:00
var me = {
email : 'me@example.com' ,
user _id : 30 ,
full _name : 'Me Myself' ,
2018-08-13 00:46:29 +02:00
date _joined : new Date ( ) ,
2017-02-24 23:51:23 +01:00
} ;
var alice = {
email : 'alice@example.com' ,
user _id : 31 ,
full _name : 'Alice' ,
} ;
var bob = {
email : 'bob@example.com' ,
user _id : 32 ,
full _name : 'Bob' ,
} ;
people . add ( me ) ;
people . initialize _current _user ( me . user _id ) ;
people . add ( alice ) ;
people . add ( bob ) ;
2018-05-15 12:40:07 +02:00
run _test ( 'validate_stream_message_address_info' , ( ) => {
2017-06-28 12:55:04 +02:00
var sub = {
stream _id : 101 ,
name : 'social' ,
subscribed : true ,
} ;
stream _data . add _sub ( 'social' , sub ) ;
assert ( compose . validate _stream _message _address _info ( 'social' ) ) ;
2017-06-28 13:10:46 +02:00
2018-11-13 17:16:02 +01:00
$ ( '#stream_message_recipient_stream' ) . select ( noop ) ;
2017-06-28 13:10:46 +02:00
assert ( ! compose . validate _stream _message _address _info ( 'foobar' ) ) ;
2018-02-05 09:21:58 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , "translated: <p>The stream <b>foobar</b> does not exist.</p><p>Manage your subscriptions <a href='#streams/all'>on your Streams page</a>.</p>" ) ;
2017-06-28 19:24:53 +02:00
sub . subscribed = false ;
stream _data . add _sub ( 'social' , sub ) ;
2019-07-11 05:06:20 +02:00
global . stub _templates ( function ( template _name ) {
2018-04-04 19:38:09 +02:00
assert . equal ( template _name , 'compose_not_subscribed' ) ;
return 'compose_not_subscribed_stub' ;
2019-07-11 05:06:20 +02:00
} ) ;
2017-06-28 19:24:53 +02:00
assert ( ! compose . validate _stream _message _address _info ( 'social' ) ) ;
2018-04-04 19:38:09 +02:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , 'compose_not_subscribed_stub' ) ;
2017-06-28 19:24:53 +02:00
2018-08-01 21:17:03 +02:00
page _params . narrow _stream = false ;
2017-06-28 19:24:53 +02:00
channel . post = function ( payload ) {
assert . equal ( payload . data . stream , 'social' ) ;
payload . data . subscribed = true ;
payload . success ( payload . data ) ;
} ;
assert ( compose . validate _stream _message _address _info ( 'social' ) ) ;
sub . name = 'Frontend' ;
sub . stream _id = 102 ;
stream _data . add _sub ( 'Frontend' , sub ) ;
channel . post = function ( payload ) {
assert . equal ( payload . data . stream , 'Frontend' ) ;
payload . data . subscribed = false ;
payload . success ( payload . data ) ;
} ;
assert ( ! compose . validate _stream _message _address _info ( 'Frontend' ) ) ;
2018-04-04 19:38:09 +02:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , 'compose_not_subscribed_stub' ) ;
2017-06-28 19:24:53 +02:00
channel . post = function ( payload ) {
assert . equal ( payload . data . stream , 'Frontend' ) ;
payload . error ( { status : 404 } ) ;
} ;
assert ( ! compose . validate _stream _message _address _info ( 'Frontend' ) ) ;
2018-02-05 09:21:58 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , "translated: <p>The stream <b>Frontend</b> does not exist.</p><p>Manage your subscriptions <a href='#streams/all'>on your Streams page</a>.</p>" ) ;
2017-06-28 19:24:53 +02:00
channel . post = function ( payload ) {
assert . equal ( payload . data . stream , 'social' ) ;
payload . error ( { status : 500 } ) ;
} ;
assert ( ! compose . validate _stream _message _address _info ( 'social' ) ) ;
2017-11-26 20:03:46 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( "Error checking subscription" ) ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-02-24 23:51:23 +01:00
2018-05-15 12:40:07 +02:00
run _test ( 'validate' , ( ) => {
2018-03-06 15:07:55 +01:00
function initialize _pm _pill ( ) {
set _global ( '$' , global . make _zjquery ( ) ) ;
$ ( "#compose-send-button" ) . prop ( 'disabled' , false ) ;
$ ( "#compose-send-button" ) . focus ( ) ;
$ ( "#sending-indicator" ) . hide ( ) ;
$ ( "#compose-textarea" ) . select ( noop ) ;
var pm _pill _container = $ . create ( 'fake-pm-pill-container' ) ;
$ ( '#private_message_recipient' ) . set _parent ( pm _pill _container ) ;
pm _pill _container . set _find _results ( '.input' , $ ( '#private_message_recipient' ) ) ;
$ ( '#private_message_recipient' ) . before = noop ;
compose _pm _pill . initialize ( ) ;
2018-08-01 21:17:03 +02:00
ui _util . place _caret _at _end = noop ;
2018-03-06 15:07:55 +01:00
$ ( "#zephyr-mirror-error" ) . is = noop ;
$ ( "#private_message_recipient" ) . select ( noop ) ;
2018-03-31 13:22:29 +02:00
2019-07-11 05:06:20 +02:00
global . stub _templates ( function ( fn ) {
2018-08-01 21:17:03 +02:00
assert . equal ( fn , 'input_pill' ) ;
return '<div>pill-html</div>' ;
2019-07-11 05:06:20 +02:00
} ) ;
2018-03-06 15:07:55 +01:00
}
function add _content _to _compose _box ( ) {
$ ( "#compose-textarea" ) . val ( 'foobarfoobar' ) ;
}
initialize _pm _pill ( ) ;
2017-06-27 14:55:11 +02:00
assert ( ! compose . validate ( ) ) ;
assert ( ! $ ( "#sending-indicator" ) . visible ( ) ) ;
assert ( ! $ ( "#compose-send-button" ) . is _focused ( ) ) ;
2017-06-30 00:57:46 +02:00
assert . equal ( $ ( "#compose-send-button" ) . prop ( 'disabled' ) , false ) ;
2017-11-26 20:03:46 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( 'You have nothing to send!' ) ) ;
2017-06-27 14:55:11 +02:00
2018-07-26 09:46:10 +02:00
reminder . is _deferred _delivery = ( ) => true ;
compose . validate ( ) ;
assert . equal ( $ ( '#sending-indicator' ) . html ( ) , 'translated: Scheduling...' ) ;
reminder . is _deferred _delivery = noop ;
2018-03-06 15:07:55 +01:00
add _content _to _compose _box ( ) ;
2017-06-27 14:55:11 +02:00
var zephyr _checked = false ;
$ ( "#zephyr-mirror-error" ) . is = function ( ) {
if ( ! zephyr _checked ) {
zephyr _checked = true ;
return true ;
}
return false ;
} ;
assert ( ! compose . validate ( ) ) ;
assert ( zephyr _checked ) ;
2017-11-26 20:03:46 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( 'You need to be running Zephyr mirroring in order to send messages!' ) ) ;
2017-06-27 14:55:11 +02:00
2018-03-06 15:07:55 +01:00
initialize _pm _pill ( ) ;
add _content _to _compose _box ( ) ;
2018-07-23 08:19:48 +02:00
// test validating private messages
2017-06-27 14:55:11 +02:00
compose _state . set _message _type ( 'private' ) ;
2018-07-23 08:19:48 +02:00
2017-06-27 14:55:11 +02:00
compose _state . recipient ( '' ) ;
assert ( ! compose . validate ( ) ) ;
2018-03-06 15:07:55 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( 'Please specify at least one valid recipient' ) ) ;
2017-06-27 14:55:11 +02:00
2018-03-06 15:07:55 +01:00
initialize _pm _pill ( ) ;
add _content _to _compose _box ( ) ;
2017-06-27 14:55:11 +02:00
compose _state . recipient ( 'foo@zulip.com' ) ;
assert ( ! compose . validate ( ) ) ;
2018-03-06 15:07:55 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( 'Please specify at least one valid recipient' , { } ) ) ;
2017-06-27 14:55:11 +02:00
compose _state . recipient ( 'foo@zulip.com,alice@zulip.com' ) ;
assert ( ! compose . validate ( ) ) ;
2018-03-06 15:07:55 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( 'Please specify at least one valid recipient' , { } ) ) ;
2017-06-27 14:55:11 +02:00
people . add _in _realm ( bob ) ;
compose _state . recipient ( 'bob@example.com' ) ;
assert ( compose . validate ( ) ) ;
2018-07-23 08:19:48 +02:00
page _params . realm _is _zephyr _mirror _realm = true ;
assert ( compose . validate ( ) ) ;
page _params . realm _is _zephyr _mirror _realm = false ;
2017-06-27 14:55:11 +02:00
compose _state . set _message _type ( 'stream' ) ;
compose _state . stream _name ( '' ) ;
2018-11-13 17:16:02 +01:00
$ ( "#stream_message_recipient_stream" ) . select ( noop ) ;
2017-06-27 14:55:11 +02:00
assert ( ! compose . validate ( ) ) ;
2017-11-26 20:03:46 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( 'Please specify a stream' ) ) ;
2017-06-27 14:55:11 +02:00
compose _state . stream _name ( 'Denmark' ) ;
2018-08-01 21:17:03 +02:00
page _params . realm _mandatory _topics = true ;
2018-11-04 17:04:17 +01:00
compose _state . topic ( '' ) ;
2018-11-13 17:16:02 +01:00
$ ( "#stream_message_recipient_topic" ) . select ( noop ) ;
2017-06-27 14:55:11 +02:00
assert ( ! compose . validate ( ) ) ;
2017-11-26 20:03:46 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( 'Please specify a topic' ) ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-06-27 14:55:11 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'get_invalid_recipient_emails' , ( ) => {
2017-06-28 21:36:57 +02:00
var feedback _bot = {
email : 'feedback@example.com' ,
user _id : 124 ,
full _name : 'Feedback Bot' ,
} ;
2018-08-01 21:17:03 +02:00
page _params . cross _realm _bots = [ feedback _bot ] ;
page _params . user _id = 30 ;
2017-06-28 21:36:57 +02:00
people . initialize ( ) ;
compose _state . recipient ( 'feedback@example.com' ) ;
assert . deepEqual ( compose . get _invalid _recipient _emails ( ) , [ ] ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-06-28 21:36:57 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'validate_stream_message' , ( ) => {
2017-06-28 20:28:18 +02:00
// This test is in kind of continuation to test_validate but since it is
2017-11-09 16:26:38 +01:00
// primarily used to get coverage over functions called from validate()
// we are separating it up in different test. Though their relative position
2017-06-28 20:28:18 +02:00
// of execution should not be changed.
2018-08-01 21:17:03 +02:00
page _params . realm _mandatory _topics = false ;
2017-06-28 20:28:18 +02:00
var sub = {
stream _id : 101 ,
name : 'social' ,
subscribed : true ,
} ;
stream _data . add _sub ( 'social' , sub ) ;
compose _state . stream _name ( 'social' ) ;
assert ( compose . validate ( ) ) ;
assert ( ! $ ( "#compose-all-everyone" ) . visible ( ) ) ;
2017-11-26 19:58:36 +01:00
assert ( ! $ ( "#compose-send-status" ) . visible ( ) ) ;
2017-06-28 20:28:18 +02:00
stream _data . get _subscriber _count = function ( stream _name ) {
assert . equal ( stream _name , 'social' ) ;
return 16 ;
} ;
2019-07-11 05:06:20 +02:00
global . stub _templates ( function ( template _name , data ) {
2017-06-28 20:28:18 +02:00
assert . equal ( template _name , 'compose_all_everyone' ) ;
assert . equal ( data . count , 16 ) ;
return 'compose_all_everyone_stub' ;
2019-07-11 05:06:20 +02:00
} ) ;
2017-06-28 20:28:18 +02:00
var compose _content ;
$ ( '#compose-all-everyone' ) . append = function ( data ) {
compose _content = data ;
} ;
2018-01-24 17:18:07 +01:00
compose _state . message _content ( 'Hey @**all**' ) ;
2017-06-28 20:28:18 +02:00
assert ( ! compose . validate ( ) ) ;
2017-06-30 00:57:46 +02:00
assert . equal ( $ ( "#compose-send-button" ) . prop ( 'disabled' ) , false ) ;
2017-11-26 19:58:36 +01:00
assert ( ! $ ( "#compose-send-status" ) . visible ( ) ) ;
2017-06-28 20:28:18 +02:00
assert . equal ( compose _content , 'compose_all_everyone_stub' ) ;
assert ( $ ( "#compose-all-everyone" ) . visible ( ) ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-06-28 20:28:18 +02:00
2018-05-14 12:06:56 +02:00
run _test ( 'test_validate_stream_message_announcement_only' , ( ) => {
// This test is in continuation with test_validate but it has been seperated out
// for better readabilty. Their relative position of execution should not be changed.
// Although the position with respect to test_validate_stream_message does not matter
// as `get_announcement_only` is reset at the end.
2018-08-01 21:17:03 +02:00
page _params . is _admin = false ;
2018-05-14 12:06:56 +02:00
var sub = {
stream _id : 102 ,
name : 'stream102' ,
subscribed : true ,
announcement _only : true ,
} ;
stream _data . get _announcement _only = function ( ) {
return true ;
} ;
2018-11-04 17:04:17 +01:00
compose _state . topic ( 'subject102' ) ;
2018-05-14 12:06:56 +02:00
stream _data . add _sub ( 'stream102' , sub ) ;
assert ( ! compose . validate ( ) ) ;
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( "Only organization admins are allowed to post to this stream." ) ) ;
// reset `get_announcement_only` so that any tests occurung after this
// do not reproduce this error.
stream _data . get _announcement _only = function ( ) {
return false ;
} ;
} ) ;
2018-07-11 14:07:26 +02:00
run _test ( 'markdown_rtl' , ( ) => {
var textarea = $ ( '#compose-textarea' ) ;
var event = {
keyCode : 65 , // A
} ;
rtl . get _direction = ( text ) => {
assert . equal ( text , ' foo' ) ;
return 'rtl' ;
} ;
assert . equal ( textarea . hasClass ( 'rtl' ) , false ) ;
textarea . val ( '```quote foo' ) ;
2018-08-11 06:43:38 +02:00
compose . handle _keyup ( event , $ ( "#compose-textarea" ) ) ;
2018-07-11 14:07:26 +02:00
assert . equal ( textarea . hasClass ( 'rtl' ) , true ) ;
} ) ;
// This is important for subsequent tests--put
// us back to the "normal" ltr case.
rtl . get _direction = ( ) => 'ltr' ;
run _test ( 'markdown_ltr' , ( ) => {
var textarea = $ ( '#compose-textarea' ) ;
var event = {
keyCode : 65 , // A
} ;
assert . equal ( textarea . hasClass ( 'rtl' ) , true ) ;
textarea . val ( '```quote foo' ) ;
2018-08-11 06:43:38 +02:00
compose . handle _keyup ( event , textarea ) ;
2018-07-11 14:07:26 +02:00
assert . equal ( textarea . hasClass ( 'rtl' ) , false ) ;
} ) ;
2018-05-15 12:40:07 +02:00
run _test ( 'markdown_shortcuts' , ( ) => {
2017-12-06 14:46:23 +01:00
var queryCommandEnabled = true ;
var event = {
keyCode : 66 ,
target : {
id : 'compose-textarea' ,
} ,
stopPropagation : noop ,
preventDefault : noop ,
} ;
var input _text = "" ;
var range _start = 0 ;
var range _length = 0 ;
var compose _value = $ ( "#compose_textarea" ) . val ( ) ;
var selected _word = "" ;
global . document . queryCommandEnabled = function ( ) {
return queryCommandEnabled ;
} ;
global . document . execCommand = function ( cmd , bool , markdown ) {
var compose _textarea = $ ( "#compose-textarea" ) ;
var value = compose _textarea . val ( ) ;
2018-06-04 21:13:07 +02:00
$ ( "#compose-textarea" ) . val ( value . substring ( 0 , compose _textarea . range ( ) . start ) +
markdown + value . substring ( compose _textarea . range ( ) . end , value . length ) ) ;
2017-12-06 14:46:23 +01:00
} ;
$ ( "#compose-textarea" ) . range = function ( ) {
return {
start : range _start ,
end : range _start + range _length ,
length : range _length ,
range : noop ,
2018-06-04 21:13:07 +02:00
text : $ ( "#compose-textarea" ) . val ( ) . substring ( range _start , range _length + range _start ) ,
2017-12-06 14:46:23 +01:00
} ;
} ;
$ ( '#compose-textarea' ) . caret = noop ;
2018-02-17 17:21:45 +01:00
function test _i _typed ( isCtrl , isCmd ) {
// Test 'i' is typed correctly.
$ ( "#compose-textarea" ) . val ( 'i' ) ;
event . keyCode = undefined ;
event . which = 73 ;
event . metaKey = isCmd ;
event . ctrlKey = isCtrl ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
assert . equal ( "i" , $ ( '#compose-textarea' ) . val ( ) ) ;
}
function all _markdown _test ( isCtrl , isCmd ) {
input _text = "Any text." ;
$ ( "#compose-textarea" ) . val ( input _text ) ;
compose _value = $ ( "#compose-textarea" ) . val ( ) ;
// Select "text" word in compose box.
selected _word = "text" ;
range _start = compose _value . search ( selected _word ) ;
range _length = selected _word . length ;
// Test bold:
// Mac env = cmd+b
// Windows/Linux = ctrl+b
event . keyCode = 66 ;
event . ctrlKey = isCtrl ;
event . metaKey = isCmd ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
assert . equal ( "Any **text**." , $ ( '#compose-textarea' ) . val ( ) ) ;
// Test if no text is selected.
range _start = 0 ;
// Change cursor to first position.
range _length = 0 ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
assert . equal ( "****Any **text**." , $ ( '#compose-textarea' ) . val ( ) ) ;
// Test italic:
// Mac = cmd+i
// Windows/Linux = ctrl+i
$ ( "#compose-textarea" ) . val ( input _text ) ;
range _start = compose _value . search ( selected _word ) ;
range _length = selected _word . length ;
event . keyCode = 73 ;
event . shiftKey = false ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
assert . equal ( "Any *text*." , $ ( '#compose-textarea' ) . val ( ) ) ;
// Test if no text is selected.
range _length = 0 ;
// Change cursor to first position.
range _start = 0 ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
assert . equal ( "**Any *text*." , $ ( '#compose-textarea' ) . val ( ) ) ;
// Test link insertion:
// Mac = cmd+shift+l
// Windows/Linux = ctrl+shift+l
$ ( "#compose-textarea" ) . val ( input _text ) ;
range _start = compose _value . search ( selected _word ) ;
range _length = selected _word . length ;
event . keyCode = 76 ;
event . which = undefined ;
event . shiftKey = true ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
assert . equal ( "Any [text](url)." , $ ( '#compose-textarea' ) . val ( ) ) ;
// Test if exec command is not enabled in browser.
queryCommandEnabled = false ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
}
// This function cross tests the cmd/ctrl + markdown shortcuts in
// Mac and Linux/Windows environments. So in short, this tests
// that e.g. Cmd+B should be ignored on Linux/Windows and Ctrl+B
// should be ignored on Mac.
function os _specific _markdown _test ( isCtrl , isCmd ) {
input _text = "Any text." ;
$ ( "#compose-textarea" ) . val ( input _text ) ;
compose _value = $ ( "#compose-textarea" ) . val ( ) ;
selected _word = "text" ;
range _start = compose _value . search ( selected _word ) ;
range _length = selected _word . length ;
event . metaKey = isCmd ;
event . ctrlKey = isCtrl ;
event . keyCode = 66 ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
assert . equal ( input _text , $ ( '#compose-textarea' ) . val ( ) ) ;
event . keyCode = 73 ;
event . shiftKey = false ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
assert . equal ( input _text , $ ( '#compose-textarea' ) . val ( ) ) ;
event . keyCode = 76 ;
event . shiftKey = true ;
2018-07-18 05:59:55 +02:00
compose . handle _keydown ( event , $ ( "#compose-textarea" ) ) ;
2018-02-17 17:21:45 +01:00
assert . equal ( input _text , $ ( '#compose-textarea' ) . val ( ) ) ;
}
// These keyboard shortcuts differ as to what key one should use
// on MacOS vs. other platforms: Cmd (Mac) vs. Ctrl (non-Mac).
// Default (Linux/Windows) userAgent tests:
test _i _typed ( false , false ) ;
// Check all the ctrl + markdown shortcuts work correctly
all _markdown _test ( true , false ) ;
// The Cmd + markdown shortcuts should do nothing on Linux/Windows
os _specific _markdown _test ( false , true ) ;
2019-06-24 14:11:21 +02:00
// Setting following platform to test in mac env
_navigator . platform = "MacIntel" ;
2018-02-17 17:21:45 +01:00
// Mac userAgent tests:
test _i _typed ( false , false ) ;
// The ctrl + markdown shortcuts should do nothing on mac
os _specific _markdown _test ( true , false ) ;
// Check all the Cmd + markdown shortcuts work correctly
all _markdown _test ( false , true ) ;
// Reset userAgent
2018-08-01 21:17:03 +02:00
_navigator . userAgent = "" ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-12-06 14:46:23 +01:00
2018-05-15 12:40:07 +02:00
run _test ( 'send_message_success' , ( ) => {
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( 'foobarfoobar' ) ;
$ ( "#compose-textarea" ) . blur ( ) ;
2017-11-26 19:58:36 +01:00
$ ( "#compose-send-status" ) . show ( ) ;
2017-06-29 06:54:10 +02:00
$ ( "#compose-send-button" ) . attr ( 'disabled' , 'disabled' ) ;
$ ( "#sending-indicator" ) . show ( ) ;
sending messages: Extract sent_messages.js.
This commit extract send_messages.js to clean up code related
to the following things:
* sending data to /json/report_send_time
* restarting the event loop if events don't arrive on time
The code related to /json/report changes the following ways:
* We track the state almost completely in the new
send_messages.js module, with other modules just
making one-line calls.
* We no longer send "displayed" times to the servers, since
we were kind of lying about them anyway.
* We now explicitly track the state of each single sent
message in its own object.
* We now look up data related to the messages by local_id,
instead of message_id. The problem with message_id was
that is was mutable. Now we use local_id, and we extend
the local_id concept to messages that don't get rendered
client side. We no longer need to react to the
'message_id_changed' event to change our hash key.
* The code used to live in many places:
* various big chunks were scattered among compose.js,
and those were all moved or reduced to one-line
calls into the new module
* echo.js continues to make basically one-line calls,
but it no longer calls compose.report_as_received(),
nor does it set the "start" time.
* message_util.js used to report received events, but
only when they finally got drawn in the home view;
this code is gone now
The code related to restarting the event loop if events don't arrive
changes as follows:
* The timer now gets set up from within
send_messages.message_state.report_server_ack,
where we can easily inspect the current state of the
possibly-still-in-flight message.
* The code to confirm that an event was received happens now
in server_events.js, rather than later, so that we don't
falsely blame the event loop for a downstream bug. (Plus
it's easier to just do it one place.)
This change removes a fair amount of code from our node tests. Some
of the removal is good stuff related to us completing killing off
unnecessary code. Other removals are more expediency-driven, and
we should make another sweep at ramping up our coverage on compose.js,
with possibly a little more mocking of the new `send_messages` code
layer, since it's now abstracted better.
There is also some minor cleanup to echo.resend_message() in this
commit.
See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
2017-06-29 06:54:10 +02:00
var reify _message _id _checked ;
echo . reify _message _id = function ( local _id , message _id ) {
assert . equal ( local _id , 1001 ) ;
assert . equal ( message _id , 12 ) ;
reify _message _id _checked = true ;
} ;
sending messages: Extract sent_messages.js.
This commit extract send_messages.js to clean up code related
to the following things:
* sending data to /json/report_send_time
* restarting the event loop if events don't arrive on time
The code related to /json/report changes the following ways:
* We track the state almost completely in the new
send_messages.js module, with other modules just
making one-line calls.
* We no longer send "displayed" times to the servers, since
we were kind of lying about them anyway.
* We now explicitly track the state of each single sent
message in its own object.
* We now look up data related to the messages by local_id,
instead of message_id. The problem with message_id was
that is was mutable. Now we use local_id, and we extend
the local_id concept to messages that don't get rendered
client side. We no longer need to react to the
'message_id_changed' event to change our hash key.
* The code used to live in many places:
* various big chunks were scattered among compose.js,
and those were all moved or reduced to one-line
calls into the new module
* echo.js continues to make basically one-line calls,
but it no longer calls compose.report_as_received(),
nor does it set the "start" time.
* message_util.js used to report received events, but
only when they finally got drawn in the home view;
this code is gone now
The code related to restarting the event loop if events don't arrive
changes as follows:
* The timer now gets set up from within
send_messages.message_state.report_server_ack,
where we can easily inspect the current state of the
possibly-still-in-flight message.
* The code to confirm that an event was received happens now
in server_events.js, rather than later, so that we don't
falsely blame the event loop for a downstream bug. (Plus
it's easier to just do it one place.)
This change removes a fair amount of code from our node tests. Some
of the removal is good stuff related to us completing killing off
unnecessary code. Other removals are more expediency-driven, and
we should make another sweep at ramping up our coverage on compose.js,
with possibly a little more mocking of the new `send_messages` code
layer, since it's now abstracted better.
There is also some minor cleanup to echo.resend_message() in this
commit.
See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
compose . send _message _success ( 1001 , 12 , false ) ;
2017-11-26 20:37:44 +01:00
assert . equal ( $ ( "#compose-textarea" ) . val ( ) , '' ) ;
assert ( $ ( "#compose-textarea" ) . is _focused ( ) ) ;
2017-11-26 19:58:36 +01:00
assert ( ! $ ( "#compose-send-status" ) . visible ( ) ) ;
2017-06-30 00:57:46 +02:00
assert . equal ( $ ( "#compose-send-button" ) . prop ( 'disabled' ) , false ) ;
2017-06-29 06:54:10 +02:00
assert ( ! $ ( "#sending-indicator" ) . visible ( ) ) ;
sending messages: Extract sent_messages.js.
This commit extract send_messages.js to clean up code related
to the following things:
* sending data to /json/report_send_time
* restarting the event loop if events don't arrive on time
The code related to /json/report changes the following ways:
* We track the state almost completely in the new
send_messages.js module, with other modules just
making one-line calls.
* We no longer send "displayed" times to the servers, since
we were kind of lying about them anyway.
* We now explicitly track the state of each single sent
message in its own object.
* We now look up data related to the messages by local_id,
instead of message_id. The problem with message_id was
that is was mutable. Now we use local_id, and we extend
the local_id concept to messages that don't get rendered
client side. We no longer need to react to the
'message_id_changed' event to change our hash key.
* The code used to live in many places:
* various big chunks were scattered among compose.js,
and those were all moved or reduced to one-line
calls into the new module
* echo.js continues to make basically one-line calls,
but it no longer calls compose.report_as_received(),
nor does it set the "start" time.
* message_util.js used to report received events, but
only when they finally got drawn in the home view;
this code is gone now
The code related to restarting the event loop if events don't arrive
changes as follows:
* The timer now gets set up from within
send_messages.message_state.report_server_ack,
where we can easily inspect the current state of the
possibly-still-in-flight message.
* The code to confirm that an event was received happens now
in server_events.js, rather than later, so that we don't
falsely blame the event loop for a downstream bug. (Plus
it's easier to just do it one place.)
This change removes a fair amount of code from our node tests. Some
of the removal is good stuff related to us completing killing off
unnecessary code. Other removals are more expediency-driven, and
we should make another sweep at ramping up our coverage on compose.js,
with possibly a little more mocking of the new `send_messages` code
layer, since it's now abstracted better.
There is also some minor cleanup to echo.resend_message() in this
commit.
See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
assert ( reify _message _id _checked ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-06-29 14:31:26 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'send_message' , ( ) => {
2017-07-06 21:57:25 +02:00
// This is the common setup stuff for all of the four tests.
var stub _state ;
function initialize _state _stub _dict ( ) {
stub _state = { } ;
stub _state . local _id _counter = 0 ;
2018-02-20 13:08:50 +01:00
stub _state . send _msg _called = 0 ;
2017-07-06 21:57:25 +02:00
stub _state . get _events _running _called = 0 ;
stub _state . reify _message _id _checked = 0 ;
return stub _state ;
}
global . patch _builtin ( 'setTimeout' , function ( func ) {
func ( ) ;
} ) ;
global . server _events = {
assert _get _events _running : function ( ) {
stub _state . get _events _running _called += 1 ;
} ,
} ;
// Tests start here.
( function test _message _send _success _codepath ( ) {
stub _state = initialize _state _stub _dict ( ) ;
2018-11-04 17:04:17 +01:00
compose _state . topic ( '' ) ;
2017-07-06 21:57:25 +02:00
compose _state . set _message _type ( 'private' ) ;
page _params . user _id = 101 ;
2018-03-31 13:22:29 +02:00
compose _state . recipient = function ( ) {
return 'alice@example.com' ;
} ;
2017-07-06 21:57:25 +02:00
echo . try _deliver _locally = function ( ) {
stub _state . local _id _counter += 1 ;
return stub _state . local _id _counter ;
} ;
2018-02-20 13:08:50 +01:00
transmit . send _message = function ( payload , success ) {
2017-07-06 21:57:25 +02:00
var single _msg = {
2018-05-07 03:30:13 +02:00
type : 'private' ,
content : '[foobar](/user_uploads/123456)' ,
sender _id : 101 ,
queue _id : undefined ,
stream : '' ,
2018-12-23 16:49:14 +01:00
topic : '' ,
2019-05-23 22:18:58 +02:00
to : ` [ ${ alice . user _id } ] ` ,
2018-05-07 03:30:13 +02:00
reply _to : 'alice@example.com' ,
private _message _recipient : 'alice@example.com' ,
to _user _ids : '31' ,
local _id : 1 ,
locally _echoed : true ,
2017-07-06 21:57:25 +02:00
} ;
2019-05-23 22:18:58 +02:00
2018-02-20 13:08:50 +01:00
assert . deepEqual ( payload , single _msg ) ;
payload . id = stub _state . local _id _counter ;
success ( payload ) ;
stub _state . send _msg _called += 1 ;
2017-07-06 21:57:25 +02:00
} ;
echo . reify _message _id = function ( local _id , message _id ) {
2018-06-05 08:12:06 +02:00
assert . equal ( typeof local _id , 'number' ) ;
assert . equal ( typeof message _id , 'number' ) ;
2017-07-06 21:57:25 +02:00
stub _state . reify _message _id _checked += 1 ;
} ;
sending messages: Extract sent_messages.js.
This commit extract send_messages.js to clean up code related
to the following things:
* sending data to /json/report_send_time
* restarting the event loop if events don't arrive on time
The code related to /json/report changes the following ways:
* We track the state almost completely in the new
send_messages.js module, with other modules just
making one-line calls.
* We no longer send "displayed" times to the servers, since
we were kind of lying about them anyway.
* We now explicitly track the state of each single sent
message in its own object.
* We now look up data related to the messages by local_id,
instead of message_id. The problem with message_id was
that is was mutable. Now we use local_id, and we extend
the local_id concept to messages that don't get rendered
client side. We no longer need to react to the
'message_id_changed' event to change our hash key.
* The code used to live in many places:
* various big chunks were scattered among compose.js,
and those were all moved or reduced to one-line
calls into the new module
* echo.js continues to make basically one-line calls,
but it no longer calls compose.report_as_received(),
nor does it set the "start" time.
* message_util.js used to report received events, but
only when they finally got drawn in the home view;
this code is gone now
The code related to restarting the event loop if events don't arrive
changes as follows:
* The timer now gets set up from within
send_messages.message_state.report_server_ack,
where we can easily inspect the current state of the
possibly-still-in-flight message.
* The code to confirm that an event was received happens now
in server_events.js, rather than later, so that we don't
falsely blame the event loop for a downstream bug. (Plus
it's easier to just do it one place.)
This change removes a fair amount of code from our node tests. Some
of the removal is good stuff related to us completing killing off
unnecessary code. Other removals are more expediency-driven, and
we should make another sweep at ramping up our coverage on compose.js,
with possibly a little more mocking of the new `send_messages` code
layer, since it's now abstracted better.
There is also some minor cleanup to echo.resend_message() in this
commit.
See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
2017-07-06 21:57:25 +02:00
// Setting message content with a host server link and we will assert
// later that this has been converted to a relative link.
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( '[foobar]' +
2017-07-06 21:57:25 +02:00
'(https://foo.com/user_uploads/123456)' ) ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . blur ( ) ;
2017-11-26 19:58:36 +01:00
$ ( "#compose-send-status" ) . show ( ) ;
2017-07-06 21:57:25 +02:00
$ ( "#compose-send-button" ) . attr ( 'disabled' , 'disabled' ) ;
$ ( "#sending-indicator" ) . show ( ) ;
compose . send _message ( ) ;
var state = {
local _id _counter : 1 ,
get _events _running _called : 1 ,
reify _message _id _checked : 1 ,
2018-02-20 13:08:50 +01:00
send _msg _called : 1 ,
2017-07-06 21:57:25 +02:00
} ;
assert . deepEqual ( stub _state , state ) ;
2017-11-26 20:37:44 +01:00
assert . equal ( $ ( "#compose-textarea" ) . val ( ) , '' ) ;
assert ( $ ( "#compose-textarea" ) . is _focused ( ) ) ;
2017-11-26 19:58:36 +01:00
assert ( ! $ ( "#compose-send-status" ) . visible ( ) ) ;
2017-06-30 00:57:46 +02:00
assert . equal ( $ ( "#compose-send-button" ) . prop ( 'disabled' ) , false ) ;
2017-07-06 21:57:25 +02:00
assert ( ! $ ( "#sending-indicator" ) . visible ( ) ) ;
} ( ) ) ;
// This is the additional setup which is common to both the tests below.
2018-02-20 13:08:50 +01:00
transmit . send _message = function ( payload , success , error ) {
stub _state . send _msg _called += 1 ;
error ( 'Error sending message: Server says 408' ) ;
2017-07-06 21:57:25 +02:00
} ;
2018-02-20 13:08:50 +01:00
var echo _error _msg _checked ;
2017-07-06 21:57:25 +02:00
echo . message _send _error = function ( local _id , error _response ) {
assert . equal ( local _id , 1 ) ;
assert . equal ( error _response , 'Error sending message: Server says 408' ) ;
echo _error _msg _checked = true ;
} ;
// Tests start here.
( function test _param _error _function _passed _from _send _message ( ) {
stub _state = initialize _state _stub _dict ( ) ;
compose . send _message ( ) ;
var state = {
local _id _counter : 1 ,
get _events _running _called : 1 ,
reify _message _id _checked : 0 ,
2018-02-20 13:08:50 +01:00
send _msg _called : 1 ,
2017-07-06 21:57:25 +02:00
} ;
assert . deepEqual ( stub _state , state ) ;
assert ( echo _error _msg _checked ) ;
} ( ) ) ;
( function test _error _codepath _local _id _undefined ( ) {
stub _state = initialize _state _stub _dict ( ) ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( 'foobarfoobar' ) ;
$ ( "#compose-textarea" ) . blur ( ) ;
2017-11-26 19:58:36 +01:00
$ ( "#compose-send-status" ) . show ( ) ;
2017-07-06 21:57:25 +02:00
$ ( "#compose-send-button" ) . attr ( 'disabled' , 'disabled' ) ;
$ ( "#sending-indicator" ) . show ( ) ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . select ( noop ) ;
2017-07-06 21:57:25 +02:00
echo _error _msg _checked = false ;
echo . try _deliver _locally = function ( ) {
return ;
} ;
sending messages: Extract sent_messages.js.
This commit extract send_messages.js to clean up code related
to the following things:
* sending data to /json/report_send_time
* restarting the event loop if events don't arrive on time
The code related to /json/report changes the following ways:
* We track the state almost completely in the new
send_messages.js module, with other modules just
making one-line calls.
* We no longer send "displayed" times to the servers, since
we were kind of lying about them anyway.
* We now explicitly track the state of each single sent
message in its own object.
* We now look up data related to the messages by local_id,
instead of message_id. The problem with message_id was
that is was mutable. Now we use local_id, and we extend
the local_id concept to messages that don't get rendered
client side. We no longer need to react to the
'message_id_changed' event to change our hash key.
* The code used to live in many places:
* various big chunks were scattered among compose.js,
and those were all moved or reduced to one-line
calls into the new module
* echo.js continues to make basically one-line calls,
but it no longer calls compose.report_as_received(),
nor does it set the "start" time.
* message_util.js used to report received events, but
only when they finally got drawn in the home view;
this code is gone now
The code related to restarting the event loop if events don't arrive
changes as follows:
* The timer now gets set up from within
send_messages.message_state.report_server_ack,
where we can easily inspect the current state of the
possibly-still-in-flight message.
* The code to confirm that an event was received happens now
in server_events.js, rather than later, so that we don't
falsely blame the event loop for a downstream bug. (Plus
it's easier to just do it one place.)
This change removes a fair amount of code from our node tests. Some
of the removal is good stuff related to us completing killing off
unnecessary code. Other removals are more expediency-driven, and
we should make another sweep at ramping up our coverage on compose.js,
with possibly a little more mocking of the new `send_messages` code
layer, since it's now abstracted better.
There is also some minor cleanup to echo.resend_message() in this
commit.
See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
sent _messages . get _new _local _id = function ( ) {
return 'loc-55' ;
} ;
2017-07-06 21:57:25 +02:00
compose . send _message ( ) ;
var state = {
local _id _counter : 0 ,
get _events _running _called : 1 ,
reify _message _id _checked : 0 ,
2018-02-20 13:08:50 +01:00
send _msg _called : 1 ,
2017-07-06 21:57:25 +02:00
} ;
assert . deepEqual ( stub _state , state ) ;
assert ( ! echo _error _msg _checked ) ;
2017-06-30 00:57:46 +02:00
assert . equal ( $ ( "#compose-send-button" ) . prop ( 'disabled' ) , false ) ;
2017-11-26 20:03:46 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) ,
2018-05-07 03:30:13 +02:00
'Error sending message: Server says 408' ) ;
2017-11-26 20:37:44 +01:00
assert . equal ( $ ( "#compose-textarea" ) . val ( ) , 'foobarfoobar' ) ;
assert ( $ ( "#compose-textarea" ) . is _focused ( ) ) ;
2017-11-26 19:58:36 +01:00
assert ( $ ( "#compose-send-status" ) . visible ( ) ) ;
2017-06-30 00:57:46 +02:00
assert . equal ( $ ( "#compose-send-button" ) . prop ( 'disabled' ) , false ) ;
2017-07-06 21:57:25 +02:00
assert ( ! $ ( "#sending-indicator" ) . visible ( ) ) ;
} ( ) ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-07-06 21:57:25 +02:00
2018-04-12 18:06:39 +02:00
set _global ( 'document' , 'document-stub' ) ;
2018-05-15 12:40:07 +02:00
run _test ( 'enter_with_preview_open' , ( ) => {
2017-07-07 19:31:32 +02:00
// Test sending a message with content.
2018-02-20 13:08:50 +01:00
compose _state . set _message _type ( 'stream' ) ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( 'message me' ) ;
$ ( "#compose-textarea" ) . hide ( ) ;
2017-07-07 00:33:46 +02:00
$ ( "#undo_markdown_preview" ) . show ( ) ;
$ ( "#preview_message_area" ) . show ( ) ;
$ ( "#markdown_preview" ) . hide ( ) ;
page _params . enter _sends = true ;
var send _message _called = false ;
compose . send _message = function ( ) {
send _message _called = true ;
} ;
compose . enter _with _preview _open ( ) ;
2017-11-26 20:37:44 +01:00
assert ( $ ( "#compose-textarea" ) . visible ( ) ) ;
2017-07-07 00:33:46 +02:00
assert ( ! $ ( "#undo_markdown_preview" ) . visible ( ) ) ;
assert ( ! $ ( "#preview_message_area" ) . visible ( ) ) ;
assert ( $ ( "#markdown_preview" ) . visible ( ) ) ;
assert ( send _message _called ) ;
page _params . enter _sends = false ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . blur ( ) ;
2017-07-07 00:33:46 +02:00
compose . enter _with _preview _open ( ) ;
2017-11-26 20:37:44 +01:00
assert ( $ ( "#compose-textarea" ) . is _focused ( ) ) ;
2017-07-07 19:31:32 +02:00
// Test sending a message without content.
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( '' ) ;
2017-07-07 19:31:32 +02:00
$ ( "#preview_message_area" ) . show ( ) ;
$ ( "#enter_sends" ) . prop ( "checked" , true ) ;
page _params . enter _sends = true ;
compose . enter _with _preview _open ( ) ;
assert ( $ ( "#enter_sends" ) . prop ( "checked" ) ) ;
2017-11-26 20:03:46 +01:00
assert . equal ( $ ( "#compose-error-msg" ) . html ( ) , i18n . t ( 'You have nothing to send!' ) ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-07-07 00:33:46 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'finish' , ( ) => {
2017-07-07 00:48:51 +02:00
( function test _when _compose _validation _fails ( ) {
$ ( "#compose_invite_users" ) . show ( ) ;
2017-06-30 00:57:46 +02:00
$ ( "#compose-send-button" ) . prop ( 'disabled' , false ) ;
2017-07-07 00:48:51 +02:00
$ ( "#compose-send-button" ) . focus ( ) ;
$ ( "#sending-indicator" ) . hide ( ) ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . select ( noop ) ;
$ ( "#compose-textarea" ) . val ( '' ) ;
2017-07-07 00:48:51 +02:00
var res = compose . finish ( ) ;
assert . equal ( res , false ) ;
assert ( ! $ ( "#compose_invite_users" ) . visible ( ) ) ;
assert ( ! $ ( "#sending-indicator" ) . visible ( ) ) ;
assert ( ! $ ( "#compose-send-button" ) . is _focused ( ) ) ;
2017-06-30 00:57:46 +02:00
assert . equal ( $ ( "#compose-send-button" ) . prop ( 'disabled' ) , false ) ;
2017-11-26 20:03:46 +01:00
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , i18n . t ( 'You have nothing to send!' ) ) ;
2017-07-07 00:48:51 +02:00
} ( ) ) ;
( function test _when _compose _validation _succeed ( ) {
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . hide ( ) ;
2017-07-07 00:48:51 +02:00
$ ( "#undo_markdown_preview" ) . show ( ) ;
$ ( "#preview_message_area" ) . show ( ) ;
$ ( "#markdown_preview" ) . hide ( ) ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( 'foobarfoobar' ) ;
2017-07-07 00:48:51 +02:00
compose _state . set _message _type ( 'private' ) ;
2018-03-31 13:22:29 +02:00
compose _state . recipient = function ( ) {
return 'bob@example.com' ;
} ;
2017-07-07 00:48:51 +02:00
var compose _finished _event _checked = false ;
2018-04-12 18:06:39 +02:00
$ ( document ) . trigger = function ( e ) {
assert . equal ( e . name , 'compose_finished.zulip' ) ;
compose _finished _event _checked = true ;
} ;
2017-07-07 00:48:51 +02:00
var send _message _called = false ;
compose . send _message = function ( ) {
send _message _called = true ;
} ;
assert ( compose . finish ( ) ) ;
2017-11-26 20:37:44 +01:00
assert ( $ ( "#compose-textarea" ) . visible ( ) ) ;
2017-07-07 00:48:51 +02:00
assert ( ! $ ( "#undo_markdown_preview" ) . visible ( ) ) ;
assert ( ! $ ( "#preview_message_area" ) . visible ( ) ) ;
assert ( $ ( "#markdown_preview" ) . visible ( ) ) ;
assert ( send _message _called ) ;
assert ( compose _finished _event _checked ) ;
} ( ) ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-07-07 00:48:51 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'abort_xhr' , ( ) => {
2017-07-07 15:44:39 +02:00
$ ( "#compose-send-button" ) . attr ( 'disabled' , 'disabled' ) ;
var compose _removedata _checked = false ;
$ ( '#compose' ) . removeData = function ( sel ) {
assert . equal ( sel , 'filedrop_xhr' ) ;
compose _removedata _checked = true ;
} ;
var xhr _abort _checked = false ;
$ ( "#compose" ) . data = function ( sel ) {
assert . equal ( sel , 'filedrop_xhr' ) ;
return {
abort : function ( ) {
xhr _abort _checked = true ;
} ,
} ;
} ;
compose . abort _xhr ( ) ;
assert . equal ( $ ( "#compose-send-button" ) . attr ( ) , undefined ) ;
assert ( xhr _abort _checked ) ;
assert ( compose _removedata _checked ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-07-07 15:44:39 +02:00
2017-07-07 15:14:00 +02:00
function verify _filedrop _payload ( payload ) {
2017-07-31 20:52:17 +02:00
assert . equal ( payload . url , '/json/user_uploads' ) ;
2017-07-07 15:14:00 +02:00
assert . equal ( payload . fallback _id , 'file_input' ) ;
assert . equal ( payload . paramname , 'file' ) ;
2019-05-03 17:55:04 +02:00
assert . equal ( payload . max _file _upload _size , 512 ) ;
2017-07-07 15:14:00 +02:00
assert . equal ( payload . data . csrfmiddlewaretoken , 'fake-csrf-token' ) ;
assert . deepEqual ( payload . raw _droppable , [ 'text/uri-list' , 'text/plain' ] ) ;
2018-06-05 08:12:06 +02:00
assert . equal ( typeof payload . drop , 'function' ) ;
assert . equal ( typeof payload . progressUpdated , 'function' ) ;
assert . equal ( typeof payload . error , 'function' ) ;
assert . equal ( typeof payload . uploadFinished , 'function' ) ;
assert . equal ( typeof payload . rawDrop , 'function' ) ;
2017-07-07 15:14:00 +02:00
}
function test _raw _file _drop ( raw _drop _func ) {
compose _state . set _message _type ( false ) ;
var compose _actions _start _checked = false ;
global . compose _actions = {
start : function ( msg _type ) {
assert . equal ( msg _type , 'stream' ) ;
compose _actions _start _checked = true ;
} ,
} ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( 'Old content ' ) ;
2017-07-07 15:14:00 +02:00
var compose _ui _autosize _textarea _checked = false ;
compose _ui . autosize _textarea = function ( ) {
compose _ui _autosize _textarea _checked = true ;
} ;
// Call the method here!
raw _drop _func ( 'new contents' ) ;
assert ( compose _actions _start _checked ) ;
2017-11-26 20:37:44 +01:00
assert . equal ( $ ( "#compose-textarea" ) . val ( ) , 'Old content new contents' ) ;
2017-07-07 15:14:00 +02:00
assert ( compose _ui _autosize _textarea _checked ) ;
}
2018-05-15 12:40:07 +02:00
run _test ( 'initialize' , ( ) => {
2017-07-07 15:14:00 +02:00
// In this test we mostly do the setup stuff in addition to testing the
// normal workflow of the function. All the tests for the on functions are
// done in subsequent tests directly below this test.
var resize _watch _manual _resize _checked = false ;
resize . watch _manual _resize = function ( elem ) {
2017-11-26 20:37:44 +01:00
assert . equal ( '#compose-textarea' , elem ) ;
2017-07-07 15:14:00 +02:00
resize _watch _manual _resize _checked = true ;
} ;
global . window = {
XMLHttpRequest : true ,
bridge : true ,
} ;
var xmlhttprequest _checked = false ;
set _global ( 'XMLHttpRequest' , function ( ) {
this . upload = true ;
xmlhttprequest _checked = true ;
} ) ;
$ ( "#compose #attach_files" ) . addClass ( "notdisplayed" ) ;
2017-07-08 15:16:19 +02:00
global . document = 'document-stub' ;
2017-07-07 15:14:00 +02:00
global . csrf _token = 'fake-csrf-token' ;
var filedrop _in _compose _checked = false ;
2019-05-03 17:55:04 +02:00
page _params . max _file _upload _size = 512 ;
2017-07-07 15:14:00 +02:00
$ ( "#compose" ) . filedrop = function ( payload ) {
verify _filedrop _payload ( payload ) ;
test _raw _file _drop ( payload . rawDrop ) ;
filedrop _in _compose _checked = true ;
} ;
compose . initialize ( ) ;
assert ( resize _watch _manual _resize _checked ) ;
assert ( xmlhttprequest _checked ) ;
assert ( ! $ ( "#compose #attach_files" ) . hasClass ( "notdisplayed" ) ) ;
assert ( filedrop _in _compose _checked ) ;
function reset _jquery ( ) {
// Avoid leaks.
set _global ( '$' , global . make _zjquery ( ) ) ;
// Bypass filedrop (we already tested it above).
$ ( "#compose" ) . filedrop = noop ;
}
var compose _actions _start _checked ;
function set _up _compose _start _mock ( expected _opts ) {
compose _actions _start _checked = false ;
global . compose _actions = {
start : function ( msg _type , opts ) {
assert . equal ( msg _type , 'stream' ) ;
assert . deepEqual ( opts , expected _opts ) ;
compose _actions _start _checked = true ;
} ,
} ;
}
( function test _page _params _narrow _path ( ) {
page _params . narrow = true ;
reset _jquery ( ) ;
set _up _compose _start _mock ( { } ) ;
compose . initialize ( ) ;
assert ( compose _actions _start _checked ) ;
} ( ) ) ;
( function test _page _params _narrow _topic ( ) {
page _params . narrow _topic = 'testing' ;
reset _jquery ( ) ;
2018-11-15 19:14:16 +01:00
set _up _compose _start _mock ( { topic : 'testing' } ) ;
2017-07-07 15:14:00 +02:00
compose . initialize ( ) ;
assert ( compose _actions _start _checked ) ;
} ( ) ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-07-07 03:59:08 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'update_fade' , ( ) => {
2018-11-13 17:16:02 +01:00
var selector = '#stream_message_recipient_stream,#stream_message_recipient_topic,#private_message_recipient' ;
2017-07-07 15:41:13 +02:00
var keyup _handler _func = $ ( selector ) . get _on _handler ( 'keyup' ) ;
var set _focused _recipient _checked = false ;
2018-04-22 16:25:46 +02:00
var update _all _called = false ;
2017-07-07 15:41:13 +02:00
global . compose _fade = {
set _focused _recipient : function ( msg _type ) {
assert . equal ( msg _type , 'private' ) ;
set _focused _recipient _checked = true ;
} ,
2018-04-22 16:25:46 +02:00
update _all : function ( ) {
update _all _called = true ;
2017-07-07 15:41:13 +02:00
} ,
} ;
compose _state . set _message _type ( false ) ;
keyup _handler _func ( ) ;
assert ( ! set _focused _recipient _checked ) ;
2018-04-22 16:25:46 +02:00
assert ( ! update _all _called ) ;
2017-07-07 15:41:13 +02:00
compose _state . set _message _type ( 'private' ) ;
keyup _handler _func ( ) ;
assert ( set _focused _recipient _checked ) ;
2018-04-22 16:25:46 +02:00
assert ( update _all _called ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-07-07 15:41:13 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'trigger_submit_compose_form' , ( ) => {
2017-07-07 15:42:51 +02:00
var prevent _default _checked = false ;
var compose _finish _checked = false ;
var e = {
preventDefault : function ( ) {
prevent _default _checked = true ;
} ,
} ;
compose . finish = function ( ) {
compose _finish _checked = true ;
} ;
var submit _handler = $ ( '#compose form' ) . get _on _handler ( 'submit' ) ;
submit _handler ( e ) ;
assert ( prevent _default _checked ) ;
assert ( compose _finish _checked ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-07-07 15:42:51 +02:00
2018-05-18 16:23:35 +02:00
run _test ( 'needs_subscribe_warning' , ( ) => {
people . get _active _user _for _email = function ( ) {
return ;
} ;
assert . equal ( compose . needs _subscribe _warning ( ) , false ) ;
compose _state . stream _name ( 'random' ) ;
assert . equal ( compose . needs _subscribe _warning ( ) , false ) ;
var sub = {
stream _id : 111 ,
name : 'random' ,
subscribed : true ,
} ;
stream _data . add _sub ( 'random' , sub ) ;
assert . equal ( compose . needs _subscribe _warning ( ) , false ) ;
people . get _active _user _for _email = function ( ) {
return {
user _id : 99 ,
is _bot : true ,
} ;
} ;
assert . equal ( compose . needs _subscribe _warning ( ) , false ) ;
people . get _active _user _for _email = function ( ) {
return {
user _id : 99 ,
is _bot : false ,
} ;
} ;
stream _data . is _user _subscribed = function ( ) {
return true ;
} ;
assert . equal ( compose . needs _subscribe _warning ( ) , false ) ;
stream _data . is _user _subscribed = function ( ) {
return false ;
} ;
assert . equal ( compose . needs _subscribe _warning ( ) , true ) ;
} ) ;
2018-05-15 12:40:07 +02:00
run _test ( 'on_events' , ( ) => {
2017-07-08 00:06:38 +02:00
( function test _usermention _completed _zulip _triggered ( ) {
var handler = $ ( document ) . get _on _handler ( 'usermention_completed.zulip' ) ;
var data = {
mentioned : {
2018-05-07 03:30:13 +02:00
email : 'foo@bar.com' ,
2017-07-08 00:06:38 +02:00
} ,
} ;
2017-07-09 14:13:42 +02:00
$ ( '#compose_invite_users .compose_invite_user' ) . length = 0 ;
function test _noop _case ( msg _type , is _zephyr _mirror , mentioned _full _name ) {
2017-07-08 00:06:38 +02:00
compose _state . set _message _type ( msg _type ) ;
page _params . realm _is _zephyr _mirror _realm = is _zephyr _mirror ;
data . mentioned . full _name = mentioned _full _name ;
handler ( { } , data ) ;
2017-07-09 14:13:42 +02:00
assert . equal ( $ ( '#compose_invite_users' ) . visible ( ) , false ) ;
2017-07-08 00:06:38 +02:00
}
2017-07-09 14:13:42 +02:00
test _noop _case ( 'private' , true , 'everyone' ) ;
test _noop _case ( 'stream' , true , 'everyone' ) ;
test _noop _case ( 'stream' , false , 'everyone' ) ;
// Test mentioning a user that should gets a warning.
$ ( "#compose_invite_users" ) . hide ( ) ;
compose _state . set _message _type ( 'stream' ) ;
page _params . realm _is _zephyr _mirror _realm = false ;
var checks = [
( function ( ) {
var called ;
2018-04-24 22:40:00 +02:00
compose . needs _subscribe _warning = function ( email ) {
2017-07-09 14:13:42 +02:00
called = true ;
assert . equal ( email , 'foo@bar.com' ) ;
2018-04-24 22:40:00 +02:00
return true ;
2017-07-09 14:13:42 +02:00
} ;
return function ( ) { assert ( called ) ; } ;
} ( ) ) ,
( function ( ) {
var called ;
2019-07-11 05:06:20 +02:00
global . stub _templates ( function ( template _name , context ) {
2017-07-09 14:13:42 +02:00
called = true ;
2019-07-12 00:52:56 +02:00
assert . equal ( template _name , 'compose_invite_users' ) ;
2017-07-09 14:13:42 +02:00
assert . equal ( context . email , 'foo@bar.com' ) ;
assert . equal ( context . name , 'Foo Barson' ) ;
return 'fake-compose-invite-user-template' ;
2019-07-11 05:06:20 +02:00
} ) ;
2017-07-09 14:13:42 +02:00
return function ( ) { assert ( called ) ; } ;
} ( ) ) ,
( function ( ) {
var called ;
$ ( "#compose_invite_users" ) . append = function ( html ) {
called = true ;
assert . equal ( html , 'fake-compose-invite-user-template' ) ;
} ;
return function ( ) { assert ( called ) ; } ;
} ( ) ) ,
] ;
data = {
mentioned : {
2018-05-07 03:30:13 +02:00
email : 'foo@bar.com' ,
full _name : 'Foo Barson' ,
2017-07-09 14:13:42 +02:00
} ,
} ;
handler ( { } , data ) ;
assert . equal ( $ ( '#compose_invite_users' ) . visible ( ) , true ) ;
_ . each ( checks , function ( f ) { f ( ) ; } ) ;
// Simulate that the row was added to the DOM.
var warning _row = $ ( '<warning row>' ) ;
var looked _for _existing ;
warning _row . data = function ( field ) {
assert . equal ( field , 'useremail' ) ;
looked _for _existing = true ;
return 'foo@bar.com' ;
} ;
var previous _users = $ ( '#compose_invite_users .compose_invite_user' ) ;
previous _users . length = 1 ;
previous _users [ 0 ] = warning _row ;
2017-07-10 14:50:37 +02:00
$ ( '#compose_invite_users' ) . hide ( ) ;
2017-07-09 14:13:42 +02:00
// Now try to mention the same person again. The template should
// not render.
2019-07-11 05:06:20 +02:00
global . stub _templates ( noop ) ;
2017-07-09 14:13:42 +02:00
handler ( { } , data ) ;
assert . equal ( $ ( '#compose_invite_users' ) . visible ( ) , true ) ;
assert ( looked _for _existing ) ;
2017-07-08 00:06:38 +02:00
} ( ) ) ;
2017-07-08 00:09:13 +02:00
2017-07-08 23:16:19 +02:00
function setup _parents _and _mock _remove ( container _sel , target _sel , parent ) {
2019-02-02 15:44:51 +01:00
var container = $ . create ( 'fake ' + container _sel ) ;
var container _removed = false ;
2017-07-08 00:09:13 +02:00
container . remove = function ( ) {
container _removed = true ;
} ;
2019-02-02 15:44:51 +01:00
var target = $ . create ( 'fake click target (' + target _sel + ')' ) ;
2017-07-08 23:16:19 +02:00
target . set _parents _result ( parent , container ) ;
2017-07-08 00:09:13 +02:00
2019-02-02 15:44:51 +01:00
var event = {
2017-07-08 23:16:19 +02:00
preventDefault : noop ,
target : target ,
} ;
2019-02-02 15:44:51 +01:00
var helper = {
event : event ,
container : container ,
target : target ,
container _was _removed : ( ) => container _removed ,
} ;
return helper ;
2017-07-08 23:16:19 +02:00
}
( function test _compose _all _everyone _confirm _clicked ( ) {
var handler = $ ( "#compose-all-everyone" )
2018-05-07 03:30:13 +02:00
. get _on _handler ( 'click' , '.compose-all-everyone-confirm' ) ;
2017-07-08 23:16:19 +02:00
2019-02-02 15:44:51 +01:00
var helper = setup _parents _and _mock _remove (
'compose-all-everyone' ,
'compose-all-everyone' ,
'.compose-all-everyone'
) ;
2017-07-08 00:09:13 +02:00
$ ( "#compose-all-everyone" ) . show ( ) ;
2017-11-26 19:58:36 +01:00
$ ( "#compose-send-status" ) . show ( ) ;
2017-07-08 00:09:13 +02:00
var compose _finish _checked = false ;
compose . finish = function ( ) {
compose _finish _checked = true ;
} ;
2019-02-02 15:44:51 +01:00
handler ( helper . event ) ;
2017-07-08 00:09:13 +02:00
2019-02-02 15:44:51 +01:00
assert ( helper . container _was _removed ( ) ) ;
2017-07-08 00:09:13 +02:00
assert ( compose _finish _checked ) ;
assert ( ! $ ( "#compose-all-everyone" ) . visible ( ) ) ;
2017-11-26 19:58:36 +01:00
assert ( ! $ ( "#compose-send-status" ) . visible ( ) ) ;
2017-07-08 00:09:13 +02:00
} ( ) ) ;
2017-07-08 00:11:52 +02:00
( function test _compose _invite _users _clicked ( ) {
var handler = $ ( "#compose_invite_users" )
2018-05-07 03:30:13 +02:00
. get _on _handler ( 'click' , '.compose_invite_link' ) ;
2017-07-08 00:11:52 +02:00
var subscription = {
stream _id : 102 ,
name : 'test' ,
subscribed : true ,
} ;
var invite _user _to _stream _called = false ;
stream _edit . invite _user _to _stream = function ( email , sub , success ) {
invite _user _to _stream _called = true ;
assert . equal ( email , 'foo@bar.com' ) ;
assert . equal ( sub , subscription ) ;
success ( ) ; // This will check success callback path.
} ;
2019-02-02 15:44:51 +01:00
var helper = setup _parents _and _mock _remove (
'compose_invite_users' ,
'compose_invite_link' ,
'.compose_invite_user'
) ;
2017-07-08 00:11:52 +02:00
// .data in zjquery is a noop by default, so handler should just return
2019-02-02 15:44:51 +01:00
handler ( helper . event ) ;
2017-07-08 00:11:52 +02:00
assert ( ! invite _user _to _stream _called ) ;
2019-02-02 15:44:51 +01:00
assert ( ! helper . container _was _removed ( ) ) ;
2017-07-08 00:11:52 +02:00
// !sub will result false here and we check the failure code path.
2018-04-09 11:13:19 +02:00
blueslip . set _test _data ( 'warn' , 'Stream no longer exists: no-stream' ) ;
2018-11-13 17:16:02 +01:00
$ ( '#stream_message_recipient_stream' ) . val ( 'no-stream' ) ;
2019-02-02 15:44:51 +01:00
helper . container . data = function ( field ) {
2017-07-08 00:11:52 +02:00
assert . equal ( field , 'useremail' ) ;
return 'foo@bar.com' ;
} ;
2018-08-12 22:02:50 +02:00
$ ( "#compose-textarea" ) . select ( noop ) ;
2019-02-02 15:44:51 +01:00
helper . target . prop ( 'disabled' , false ) ;
2017-07-08 00:11:52 +02:00
2019-02-02 15:44:51 +01:00
handler ( helper . event ) ;
assert ( helper . target . attr ( 'disabled' ) ) ;
2017-07-08 00:11:52 +02:00
assert ( ! invite _user _to _stream _called ) ;
2019-02-02 15:44:51 +01:00
assert ( ! helper . container _was _removed ( ) ) ;
2018-08-12 22:02:50 +02:00
assert ( ! $ ( "#compose_invite_users" ) . visible ( ) ) ;
assert . equal ( $ ( '#compose-error-msg' ) . html ( ) , "Stream no longer exists: no-stream" ) ;
2018-04-09 11:13:19 +02:00
assert . equal ( blueslip . get _test _logs ( 'warn' ) . length , 1 ) ;
blueslip . clear _test _data ( ) ;
2017-07-08 00:11:52 +02:00
// !sub will result in true here and we check the success code path.
stream _data . add _sub ( 'test' , subscription ) ;
2018-11-13 17:16:02 +01:00
$ ( '#stream_message_recipient_stream' ) . val ( 'test' ) ;
2017-07-08 00:11:52 +02:00
var all _invite _children _called = false ;
$ ( "#compose_invite_users" ) . children = function ( ) {
all _invite _children _called = true ;
return [ ] ;
} ;
$ ( "#compose_invite_users" ) . show ( ) ;
2019-02-02 15:44:51 +01:00
handler ( helper . event ) ;
2017-07-08 00:11:52 +02:00
2019-02-02 15:44:51 +01:00
assert ( helper . container _was _removed ( ) ) ;
2017-07-08 00:11:52 +02:00
assert ( ! $ ( "#compose_invite_users" ) . visible ( ) ) ;
assert ( invite _user _to _stream _called ) ;
assert ( all _invite _children _called ) ;
} ( ) ) ;
2017-07-08 00:13:14 +02:00
( function test _compose _invite _close _clicked ( ) {
var handler = $ ( "#compose_invite_users" )
2018-05-07 03:30:13 +02:00
. get _on _handler ( 'click' , '.compose_invite_close' ) ;
2017-07-08 00:13:14 +02:00
2019-02-02 15:44:51 +01:00
var helper = setup _parents _and _mock _remove (
'compose_invite_users_close' ,
'compose_invite_close' ,
'.compose_invite_user'
) ;
2017-07-08 00:13:14 +02:00
var all _invite _children _called = false ;
$ ( "#compose_invite_users" ) . children = function ( ) {
all _invite _children _called = true ;
return [ ] ;
} ;
$ ( "#compose_invite_users" ) . show ( ) ;
2019-02-02 15:44:51 +01:00
handler ( helper . event ) ;
2017-07-08 00:13:14 +02:00
2019-02-02 15:44:51 +01:00
assert ( helper . container _was _removed ( ) ) ;
2017-07-08 00:13:14 +02:00
assert ( all _invite _children _called ) ;
assert ( ! $ ( "#compose_invite_users" ) . visible ( ) ) ;
} ( ) ) ;
2017-07-08 00:22:11 +02:00
2018-04-07 05:01:53 +02:00
( function test _compose _not _subscribed _clicked ( ) {
var handler = $ ( "#compose-send-status" )
2018-05-07 03:30:13 +02:00
. get _on _handler ( 'click' , '.sub_unsub_button' ) ;
2018-04-07 05:01:53 +02:00
var subscription = {
stream _id : 102 ,
name : 'test' ,
subscribed : false ,
} ;
var compose _not _subscribed _called = false ;
subs . sub _or _unsub = function ( ) {
compose _not _subscribed _called = true ;
} ;
2019-02-02 15:44:51 +01:00
var helper = setup _parents _and _mock _remove (
'compose-send-status' ,
'sub_unsub_button' ,
'.compose_not_subscribed'
) ;
2018-04-07 05:01:53 +02:00
2019-02-02 15:44:51 +01:00
handler ( helper . event ) ;
2018-04-07 05:01:53 +02:00
assert ( compose _not _subscribed _called ) ;
stream _data . add _sub ( 'test' , subscription ) ;
2018-11-13 17:16:02 +01:00
$ ( '#stream_message_recipient_stream' ) . val ( 'test' ) ;
2018-04-07 05:01:53 +02:00
$ ( "#compose-send-status" ) . show ( ) ;
2019-02-02 15:44:51 +01:00
handler ( helper . event ) ;
2018-04-07 05:01:53 +02:00
assert ( ! $ ( "#compose-send-status" ) . visible ( ) ) ;
} ( ) ) ;
( function test _compose _not _subscribed _close _clicked ( ) {
var handler = $ ( "#compose-send-status" )
2018-05-07 03:30:13 +02:00
. get _on _handler ( 'click' , '#compose_not_subscribed_close' ) ;
2018-04-07 05:01:53 +02:00
2019-02-02 15:44:51 +01:00
var helper = setup _parents _and _mock _remove (
'compose_user_not_subscribed_close' ,
'compose_not_subscribed_close' ,
'.compose_not_subscribed'
) ;
2018-04-07 05:01:53 +02:00
$ ( "#compose-send-status" ) . show ( ) ;
2019-02-02 15:44:51 +01:00
handler ( helper . event ) ;
2018-04-07 05:01:53 +02:00
assert ( ! $ ( "#compose-send-status" ) . visible ( ) ) ;
} ( ) ) ;
2017-10-14 15:18:21 +02:00
( function test _stream _name _completed _triggered ( ) {
var handler = $ ( document ) . get _on _handler ( 'streamname_completed.zulip' ) ;
2018-03-09 15:15:54 +01:00
stream _data . add _sub ( compose _state . stream _name ( ) , {
subscribers : Dict . from _array ( [ 1 , 2 ] ) ,
} ) ;
2017-10-14 15:18:21 +02:00
var data = {
stream : {
name : 'Denmark' ,
2018-03-09 15:15:54 +01:00
subscribers : Dict . from _array ( [ 1 , 2 , 3 ] ) ,
2017-10-14 15:18:21 +02:00
} ,
} ;
function test _noop _case ( invite _only ) {
compose _state . set _message _type ( 'stream' ) ;
data . stream . invite _only = invite _only ;
handler ( { } , data ) ;
assert . equal ( $ ( '#compose_private_stream_alert' ) . visible ( ) , false ) ;
}
test _noop _case ( false ) ;
2018-03-09 15:15:54 +01:00
// invite_only=true and current compose stream subscribers are a subset
// of mentioned_stream subscribers.
test _noop _case ( true ) ;
2017-10-14 15:18:21 +02:00
$ ( "#compose_private" ) . hide ( ) ;
compose _state . set _message _type ( 'stream' ) ;
var checks = [
2018-05-07 03:30:13 +02:00
( function ( ) {
var called ;
2019-07-11 05:06:20 +02:00
global . stub _templates ( function ( template _name , context ) {
2018-05-07 03:30:13 +02:00
called = true ;
assert . equal ( template _name , 'compose_private_stream_alert' ) ;
assert . equal ( context . stream _name , 'Denmark' ) ;
return 'fake-compose_private_stream_alert-template' ;
2019-07-11 05:06:20 +02:00
} ) ;
2018-05-07 03:30:13 +02:00
return function ( ) { assert ( called ) ; } ;
} ( ) ) ,
( function ( ) {
var called ;
$ ( "#compose_private_stream_alert" ) . append = function ( html ) {
called = true ;
assert . equal ( html , 'fake-compose_private_stream_alert-template' ) ;
} ;
return function ( ) { assert ( called ) ; } ;
} ( ) ) ,
2017-10-14 15:18:21 +02:00
] ;
data = {
2018-05-07 03:30:13 +02:00
stream : {
invite _only : true ,
name : 'Denmark' ,
subscribers : Dict . from _array ( [ 1 ] ) ,
} ,
2017-10-14 15:18:21 +02:00
} ;
handler ( { } , data ) ;
assert . equal ( $ ( '#compose_private_stream_alert' ) . visible ( ) , true ) ;
_ . each ( checks , function ( f ) { f ( ) ; } ) ;
} ( ) ) ;
2017-07-08 00:22:11 +02:00
( function test _attach _files _compose _clicked ( ) {
var handler = $ ( "#compose" )
2018-05-07 03:30:13 +02:00
. get _on _handler ( "click" , "#attach_files" ) ;
2017-07-08 00:22:11 +02:00
$ ( '#file_input' ) . clone = function ( param ) {
assert ( param ) ;
} ;
var compose _file _input _clicked = false ;
$ ( '#compose #file_input' ) . trigger = function ( ev _name ) {
assert . equal ( ev _name , 'click' ) ;
compose _file _input _clicked = true ;
} ;
2019-02-02 15:48:30 +01:00
var event = {
preventDefault : noop ,
} ;
2017-07-08 00:22:11 +02:00
handler ( event ) ;
assert ( compose _file _input _clicked ) ;
} ( ) ) ;
2017-07-08 01:21:20 +02:00
2017-08-11 01:51:48 +02:00
( function test _video _link _compose _clicked ( ) {
2018-04-03 01:46:55 +02:00
page _params . jitsi _server _url = 'https://meet.jit.si' ;
2017-12-08 16:17:20 +01:00
var syntax _to _insert ;
2018-07-23 01:19:57 +02:00
var called = false ;
2017-12-08 16:17:20 +01:00
2019-01-23 08:04:53 +01:00
var textarea = $ . create ( 'target-stub' ) ;
var ev = {
preventDefault : noop ,
target : {
to _$ : ( ) => textarea ,
} ,
} ;
2019-05-09 09:54:38 +02:00
page _params . realm _available _video _chat _providers = {
google _hangouts : {
id : 2 ,
name : "Google Hangouts" ,
} ,
zoom : {
id : 3 ,
name : "Zoom" ,
} ,
} ;
2019-01-23 08:04:53 +01:00
2017-12-08 16:17:20 +01:00
compose _ui . insert _syntax _and _focus = function ( syntax ) {
syntax _to _insert = syntax ;
2018-07-23 01:19:57 +02:00
called = true ;
2017-10-31 07:06:04 +01:00
} ;
2019-01-23 08:04:53 +01:00
var handler = $ ( "body" ) . get _on _handler ( "click" , ".video_link" ) ;
2017-12-08 16:17:20 +01:00
$ ( '#compose-textarea' ) . val ( '' ) ;
2017-08-11 01:51:48 +02:00
2019-01-23 08:04:53 +01:00
handler ( ev ) ;
2017-08-11 01:51:48 +02:00
// video link ids consist of 15 random digits
var video _link _regex = /\[Click to join video call\]\(https:\/\/meet.jit.si\/\d{15}\)/ ;
2017-12-08 16:17:20 +01:00
assert ( video _link _regex . test ( syntax _to _insert ) ) ;
2018-07-23 01:19:57 +02:00
2019-05-09 09:54:38 +02:00
page _params . realm _video _chat _provider =
page _params . realm _available _video _chat _providers . google _hangouts . id ;
2018-07-23 01:19:57 +02:00
page _params . realm _google _hangouts _domain = 'zulip' ;
2019-01-23 08:04:53 +01:00
handler ( ev ) ;
2018-07-23 01:19:57 +02:00
video _link _regex = /\[Click to join video call\]\(https:\/\/hangouts.google.com\/hangouts\/\_\/zulip\/\d{15}\)/ ;
assert ( video _link _regex . test ( syntax _to _insert ) ) ;
2019-05-09 09:54:38 +02:00
page _params . realm _video _chat _provider =
page _params . realm _available _video _chat _providers . zoom . id ;
2018-12-28 20:45:54 +01:00
page _params . realm _zoom _user _id = 'example@example.com' ;
page _params . realm _zoom _api _key = 'abc' ;
page _params . realm _zoom _api _secret = 'abc' ;
channel . get = function ( options ) {
assert ( options . url === '/json/calls/create' ) ;
options . success ( { zoom _url : 'example.zoom.com' } ) ;
} ;
2019-01-23 08:04:53 +01:00
handler ( ev ) ;
2018-12-28 20:45:54 +01:00
video _link _regex = /\[Click to join video call\]\(example\.zoom\.com\)/ ;
assert ( video _link _regex . test ( syntax _to _insert ) ) ;
2018-07-23 01:19:57 +02:00
page _params . jitsi _server _url = null ;
called = false ;
2019-01-23 08:04:53 +01:00
handler ( ev ) ;
2018-07-23 01:19:57 +02:00
assert ( ! called ) ;
2017-08-11 01:51:48 +02:00
} ( ) ) ;
2017-07-08 01:21:20 +02:00
( function test _markdown _preview _compose _clicked ( ) {
// Tests setup
function setup _visibilities ( ) {
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . show ( ) ;
2017-07-08 01:21:20 +02:00
$ ( "#markdown_preview" ) . show ( ) ;
$ ( "#undo_markdown_preview" ) . hide ( ) ;
$ ( "#preview_message_area" ) . hide ( ) ;
}
function assert _visibilities ( ) {
2017-11-26 20:37:44 +01:00
assert ( ! $ ( "#compose-textarea" ) . visible ( ) ) ;
2017-07-08 01:21:20 +02:00
assert ( ! $ ( "#markdown_preview" ) . visible ( ) ) ;
assert ( $ ( "#undo_markdown_preview" ) . visible ( ) ) ;
assert ( $ ( "#preview_message_area" ) . visible ( ) ) ;
}
2017-07-29 02:51:33 +02:00
function setup _mock _markdown _contains _backend _only _syntax ( msg _content , return _val ) {
markdown . contains _backend _only _syntax = function ( msg ) {
2017-07-08 01:21:20 +02:00
assert . equal ( msg , msg _content ) ;
return return _val ;
} ;
}
2018-01-21 19:27:36 +01:00
function setup _mock _markdown _is _status _message ( msg _content , msg _rendered , return _val ) {
markdown . is _status _message = function ( content , rendered ) {
assert . equal ( content , msg _content ) ;
assert . equal ( rendered , msg _rendered ) ;
return return _val ;
} ;
}
2017-07-08 01:21:20 +02:00
function test _post _success ( success _callback ) {
var resp = {
rendered : 'Server: foobarfoobar' ,
} ;
success _callback ( resp ) ;
assert . equal ( $ ( "#preview_content" ) . html ( ) , 'Server: foobarfoobar' ) ;
}
function test _post _error ( error _callback ) {
error _callback ( ) ;
assert . equal ( $ ( "#preview_content" ) . html ( ) ,
2018-05-07 03:30:13 +02:00
'translated: Failed to generate preview' ) ;
2017-07-08 01:21:20 +02:00
}
function mock _channel _post ( msg ) {
channel . post = function ( payload ) {
assert . equal ( payload . url , '/json/messages/render' ) ;
assert ( payload . idempotent ) ;
assert ( payload . data ) ;
assert . deepEqual ( payload . data . content , msg ) ;
function test ( func , param ) {
var destroy _indicator _called = false ;
loading . destroy _indicator = function ( spinner ) {
assert . equal ( spinner , $ ( "#markdown_preview_spinner" ) ) ;
destroy _indicator _called = true ;
} ;
2017-07-29 02:51:33 +02:00
setup _mock _markdown _contains _backend _only _syntax ( msg , true ) ;
2017-07-08 01:21:20 +02:00
func ( param ) ;
assert ( destroy _indicator _called ) ;
}
test ( test _post _error , payload . error ) ;
test ( test _post _success , payload . success ) ;
} ;
}
var handler = $ ( "#compose" )
2018-05-07 03:30:13 +02:00
. get _on _handler ( "click" , "#markdown_preview" ) ;
2017-07-08 01:21:20 +02:00
// Tests start here
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( '' ) ;
2017-07-08 01:21:20 +02:00
setup _visibilities ( ) ;
2019-02-02 15:48:30 +01:00
var event = {
preventDefault : noop ,
} ;
2017-07-08 01:21:20 +02:00
handler ( event ) ;
assert . equal ( $ ( "#preview_content" ) . html ( ) ,
2018-05-07 03:30:13 +02:00
'translated: Nothing to preview' ) ;
2017-07-08 01:21:20 +02:00
assert _visibilities ( ) ;
var make _indicator _called = false ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( '```foobarfoobar```' ) ;
2017-07-08 01:21:20 +02:00
setup _visibilities ( ) ;
2017-07-29 02:51:33 +02:00
setup _mock _markdown _contains _backend _only _syntax ( '```foobarfoobar```' , true ) ;
2018-01-21 19:27:36 +01:00
setup _mock _markdown _is _status _message ( '```foobarfoobar```' , 'Server: foobarfoobar' , false ) ;
2017-07-08 01:21:20 +02:00
loading . make _indicator = function ( spinner ) {
2019-04-18 21:11:30 +02:00
assert . equal ( spinner . selector , "#markdown_preview_spinner" ) ;
2017-07-08 01:21:20 +02:00
make _indicator _called = true ;
} ;
mock _channel _post ( '```foobarfoobar```' ) ;
handler ( event ) ;
assert ( make _indicator _called ) ;
assert _visibilities ( ) ;
var apply _markdown _called = false ;
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . val ( 'foobarfoobar' ) ;
2017-07-08 01:21:20 +02:00
setup _visibilities ( ) ;
2017-07-29 02:51:33 +02:00
setup _mock _markdown _contains _backend _only _syntax ( 'foobarfoobar' , false ) ;
2018-01-21 19:27:36 +01:00
setup _mock _markdown _is _status _message ( 'foobarfoobar' , 'Server: foobarfoobar' , false ) ;
2017-07-08 01:21:20 +02:00
mock _channel _post ( 'foobarfoobar' ) ;
markdown . apply _markdown = function ( msg ) {
2017-08-27 18:26:02 +02:00
assert . equal ( msg . raw _content , 'foobarfoobar' ) ;
2017-07-08 01:21:20 +02:00
apply _markdown _called = true ;
return msg ;
} ;
handler ( event ) ;
assert ( apply _markdown _called ) ;
assert _visibilities ( ) ;
assert . equal ( $ ( "#preview_content" ) . html ( ) ,
2018-05-07 03:30:13 +02:00
'Server: foobarfoobar' ) ;
2017-07-08 01:21:20 +02:00
} ( ) ) ;
2017-07-08 01:27:11 +02:00
( function test _undo _markdown _preview _clicked ( ) {
var handler = $ ( "#compose" )
2018-05-07 03:30:13 +02:00
. get _on _handler ( "click" , "#undo_markdown_preview" ) ;
2017-07-08 01:27:11 +02:00
2017-11-26 20:37:44 +01:00
$ ( "#compose-textarea" ) . hide ( ) ;
2017-07-08 01:27:11 +02:00
$ ( "#undo_markdown_preview" ) . show ( ) ;
$ ( "#preview_message_area" ) . show ( ) ;
$ ( "#markdown_preview" ) . hide ( ) ;
2019-02-02 15:48:30 +01:00
var event = {
preventDefault : noop ,
} ;
2017-07-08 01:27:11 +02:00
handler ( event ) ;
2017-11-26 20:37:44 +01:00
assert ( $ ( "#compose-textarea" ) . visible ( ) ) ;
2017-07-08 01:27:11 +02:00
assert ( ! $ ( "#undo_markdown_preview" ) . visible ( ) ) ;
assert ( ! $ ( "#preview_message_area" ) . visible ( ) ) ;
assert ( $ ( "#markdown_preview" ) . visible ( ) ) ;
} ( ) ) ;
2017-07-14 19:30:23 +02:00
2018-05-15 12:40:07 +02:00
} ) ;
2017-07-08 00:06:38 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'create_message_object' , ( ) => {
2017-02-24 16:18:56 +01:00
var sub = {
stream _id : 101 ,
name : 'social' ,
subscribed : true ,
} ;
stream _data . add _sub ( 'social' , sub ) ;
var page = {
2018-11-13 17:16:02 +01:00
'#stream_message_recipient_stream' : 'social' ,
'#stream_message_recipient_topic' : 'lunch' ,
2017-11-26 20:37:44 +01:00
'#compose-textarea' : 'burrito' ,
2017-02-24 16:18:56 +01:00
} ;
global . $ = function ( selector ) {
return {
val : function ( ) {
return page [ selector ] ;
} ,
} ;
} ;
2017-04-24 20:35:26 +02:00
global . compose _state . get _message _type = function ( ) {
2017-02-24 16:18:56 +01:00
return 'stream' ;
} ;
global . $ . trim = function ( s ) {
return s ;
} ;
2017-03-29 08:54:26 +02:00
var message = compose . create _message _object ( ) ;
2017-02-24 16:18:56 +01:00
assert . equal ( message . to , 'social' ) ;
2018-12-23 16:49:14 +01:00
assert . equal ( message . topic , 'lunch' ) ;
2017-02-24 16:18:56 +01:00
assert . equal ( message . content , 'burrito' ) ;
2017-04-24 20:35:26 +02:00
global . compose _state . get _message _type = function ( ) {
2017-02-24 16:18:56 +01:00
return 'private' ;
} ;
2018-03-31 13:22:29 +02:00
compose _state . recipient = function ( ) {
2019-05-23 22:18:58 +02:00
return 'alice@example.com, bob@example.com' ;
2018-03-31 13:22:29 +02:00
} ;
2017-03-29 08:54:26 +02:00
message = compose . create _message _object ( ) ;
2019-05-23 22:18:58 +02:00
assert . deepEqual ( message . to , [ alice . user _id , bob . user _id ] ) ;
2017-02-24 23:51:23 +01:00
assert . equal ( message . to _user _ids , '31,32' ) ;
2017-02-24 16:18:56 +01:00
assert . equal ( message . content , 'burrito' ) ;
2019-05-23 22:18:58 +02:00
var { email _list _to _user _ids _string } = people ;
people . email _list _to _user _ids _string = ( ) => undefined ;
message = compose . create _message _object ( ) ;
assert . deepEqual ( message . to , [ alice . email , bob . email ] ) ;
people . email _list _to _user _ids _string = email _list _to _user _ids _string ;
2018-05-15 12:40:07 +02:00
} ) ;
2018-07-23 02:41:55 +02:00
run _test ( 'nonexistent_stream_reply_error' , ( ) => {
set _global ( '$' , global . make _zjquery ( ) ) ;
var shown ;
var hidden ;
$ ( "#nonexistent_stream_reply_error" ) . show = ( ) => {
shown = _ . uniqueId ( ) ;
} ;
$ ( "#nonexistent_stream_reply_error" ) . hide = ( ) => {
hidden = _ . uniqueId ( ) ;
} ;
compose . nonexistent _stream _reply _error ( ) ;
assert . equal ( $ ( "#compose-reply-error-msg" ) . html ( ) , 'There are no messages to reply to yet.' ) ;
assert ( shown < hidden ) ; // test shown before hidden
} ) ;
2018-08-13 23:17:45 +02:00
run _test ( 'narrow_button_titles' , ( ) => {
util . is _mobile = ( ) => { return false ; } ;
2019-03-06 23:46:53 +01:00
compose . update _closed _compose _buttons _for _private ( ) ;
2018-08-13 23:17:45 +02:00
assert . equal ( $ ( "#left_bar_compose_stream_button_big" ) . text ( ) , i18n . t ( "New stream message" ) ) ;
2019-06-05 03:27:12 +02:00
assert . equal ( $ ( "#left_bar_compose_private_button_big" ) . text ( ) , i18n . t ( "New private message" ) ) ;
2018-08-13 23:17:45 +02:00
2019-03-06 23:46:53 +01:00
compose . update _closed _compose _buttons _for _stream ( ) ;
2018-08-13 23:17:45 +02:00
assert . equal ( $ ( "#left_bar_compose_stream_button_big" ) . text ( ) , i18n . t ( "New topic" ) ) ;
2019-03-06 23:46:53 +01:00
assert . equal ( $ ( "#left_bar_compose_private_button_big" ) . text ( ) , i18n . t ( "New private message" ) ) ;
2018-08-13 23:17:45 +02:00
} ) ;