2017-03-20 06:05:54 +01:00
// Important note on these tests:
//
// The way the Zulip hotkey tests work is as follows. First, we set
// up various contexts by monkey-patching the various hotkeys exports
2017-05-27 15:40:54 +02:00
// functions (like overlays.settings_open). Within that context, to
2017-03-20 06:05:54 +01:00
// test whether a given key (e.g. `x`) results in a specific function
// (e.g. `ui.foo()`), we fail to import any modules other than
// hotkey.js so that accessing them will result in a ReferenceError.
//
// Then we create a stub `ui.foo`, and call the hotkey function. If
// it calls any external module other than `ui.foo`, it'll crash.
// Future work includes making sure it actually does call `ui.foo()`.
2017-03-10 22:24:54 +01:00
set _global ( 'activity' , {
} ) ;
2018-03-29 22:29:10 +02:00
set _global ( 'navigator' , {
userAgent : '' ,
} ) ;
2017-09-16 17:30:07 +02:00
set _global ( 'page_params' , {
} ) ;
2017-05-27 15:40:54 +02:00
set _global ( 'overlays' , {
2017-05-10 00:37:08 +02:00
} ) ;
2018-04-12 22:38:31 +02:00
var noop = ( ) => { } ;
2017-03-14 18:29:38 +01:00
2018-04-12 22:38:31 +02:00
// jQuery stuff should go away if we make an initialize() method.
set _global ( 'document' , 'document-stub' ) ;
set _global ( '$' , global . make _zjquery ( ) ) ;
$ . fn . keydown = noop ;
$ . fn . keypress = noop ;
2017-03-14 18:29:38 +01:00
2017-11-08 20:32:18 +01:00
var hotkey = zrequire ( 'hotkey' ) ;
2017-03-10 22:24:54 +01:00
2017-04-04 18:14:27 +02:00
set _global ( 'list_util' , {
} ) ;
2017-03-10 22:24:54 +01:00
set _global ( 'current_msg_list' , {
2017-03-19 05:19:36 +01:00
selected _id : function ( ) {
return 42 ;
} ,
selected _message : function ( ) {
return {
sent _by _me : true ,
flags : [ "read" , "starred" ] ,
} ;
} ,
selected _row : function ( ) { } ,
2017-03-10 22:24:54 +01:00
} ) ;
function return _true ( ) { return true ; }
function return _false ( ) { return false ; }
function stubbing ( func _name _to _stub , test _function ) {
global . with _overrides ( function ( override ) {
global . with _stub ( function ( stub ) {
override ( func _name _to _stub , stub . f ) ;
test _function ( stub ) ;
} ) ;
} ) ;
}
2017-03-13 21:41:28 +01:00
( function test _mappings ( ) {
function map _press ( which , shiftKey ) {
return hotkey . get _keypress _hotkey ( {
which : which ,
shiftKey : shiftKey ,
} ) ;
}
2018-03-29 22:29:10 +02:00
function map _down ( which , shiftKey , ctrlKey , metaKey ) {
2017-03-13 21:41:28 +01:00
return hotkey . get _keydown _hotkey ( {
which : which ,
shiftKey : shiftKey ,
2017-03-19 19:26:13 +01:00
ctrlKey : ctrlKey ,
2018-03-29 22:29:10 +02:00
metaKey : metaKey ,
2017-03-13 21:41:28 +01:00
} ) ;
}
// The next assertion protects against an iOS bug where we
// treat "!" as a hotkey, because iOS sends the wrong code.
assert . equal ( map _press ( 33 ) , undefined ) ;
// Test page-up does work.
assert . equal ( map _down ( 33 ) . name , 'page_up' ) ;
// Test other mappings.
assert . equal ( map _down ( 9 ) . name , 'tab' ) ;
assert . equal ( map _down ( 9 , true ) . name , 'shift_tab' ) ;
assert . equal ( map _down ( 27 ) . name , 'escape' ) ;
assert . equal ( map _down ( 37 ) . name , 'left_arrow' ) ;
assert . equal ( map _down ( 13 ) . name , 'enter' ) ;
2018-03-07 21:27:55 +01:00
assert . equal ( map _down ( 46 ) . name , 'delete' ) ;
2017-03-13 21:41:28 +01:00
assert . equal ( map _down ( 13 , true ) . name , 'enter' ) ;
assert . equal ( map _press ( 47 ) . name , 'search' ) ; // slash
assert . equal ( map _press ( 106 ) . name , 'vim_down' ) ; // j
2018-03-29 22:29:10 +02:00
assert . equal ( map _down ( 219 , false , true ) . name , 'escape' ) ; // ctrl + [
2018-05-07 04:30:31 +02:00
assert . equal ( map _down ( 75 , false , true ) . name , 'search_with_k' ) ; // ctrl + k
2017-03-19 19:26:13 +01:00
2017-03-13 21:41:28 +01:00
// More negative tests.
assert . equal ( map _down ( 47 ) , undefined ) ;
assert . equal ( map _press ( 27 ) , undefined ) ;
assert . equal ( map _down ( 27 , true ) , undefined ) ;
2017-03-19 19:26:13 +01:00
assert . equal ( map _down ( 67 , false , true ) , undefined ) ; // ctrl + c
assert . equal ( map _down ( 86 , false , true ) , undefined ) ; // ctrl + v
assert . equal ( map _down ( 90 , false , true ) , undefined ) ; // ctrl + z
assert . equal ( map _down ( 84 , false , true ) , undefined ) ; // ctrl + t
assert . equal ( map _down ( 82 , false , true ) , undefined ) ; // ctrl + r
assert . equal ( map _down ( 79 , false , true ) , undefined ) ; // ctrl + o
assert . equal ( map _down ( 80 , false , true ) , undefined ) ; // ctrl + p
assert . equal ( map _down ( 65 , false , true ) , undefined ) ; // ctrl + a
assert . equal ( map _down ( 83 , false , true ) , undefined ) ; // ctrl + s
assert . equal ( map _down ( 70 , false , true ) , undefined ) ; // ctrl + f
assert . equal ( map _down ( 72 , false , true ) , undefined ) ; // ctrl + h
assert . equal ( map _down ( 88 , false , true ) , undefined ) ; // ctrl + x
assert . equal ( map _down ( 78 , false , true ) , undefined ) ; // ctrl + n
assert . equal ( map _down ( 77 , false , true ) , undefined ) ; // ctrl + m
2018-03-29 22:29:10 +02:00
assert . equal ( map _down ( 75 , false , false , true ) , undefined ) ; // cmd + k
// CMD tests for MacOS
global . navigator . userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36" ;
assert . equal ( map _down ( 219 , false , false , true ) . name , 'escape' ) ; // cmd + [
2018-05-07 04:30:31 +02:00
assert . equal ( map _down ( 75 , false , false , true ) . name , 'search_with_k' ) ; // cmd + k
2018-03-29 22:29:10 +02:00
assert . equal ( map _down ( 75 , false , true , false ) , undefined ) ; // ctrl + k
// Reset userAgent
global . navigator . userAgent = '' ;
2017-03-13 21:41:28 +01:00
} ( ) ) ;
2017-03-10 22:24:54 +01:00
( function test _basic _chars ( ) {
function process ( s ) {
var e = {
which : s . charCodeAt ( 0 ) ,
} ;
2017-03-18 22:05:07 +01:00
try {
return hotkey . process _keypress ( e ) ;
} catch ( err ) {
// An exception will be thrown here if a different
// function is called than the one declared. Try to
// provide a useful error message.
2017-11-09 16:26:38 +01:00
// add a newline to separate from other console output.
2017-03-18 22:05:07 +01:00
console . log ( '\nERROR: Mapping for character "' + e . which + '" does not match tests.' ) ;
2017-03-18 22:05:07 +01:00
}
2017-03-10 22:24:54 +01:00
}
function assert _mapping ( c , func _name , shiftKey ) {
stubbing ( func _name , function ( ) {
assert ( process ( c , shiftKey ) ) ;
} ) ;
}
function assert _unmapped ( s ) {
_ . each ( s , function ( c ) {
assert . equal ( process ( c ) , false ) ;
} ) ;
}
// Unmapped keys should immediately return false, without
// calling any functions outside of hotkey.js.
2018-02-08 15:17:26 +01:00
assert _unmapped ( 'abefhlmotyz' ) ;
2017-11-14 21:06:04 +01:00
assert _unmapped ( 'BEFHILNOQTUWXYZ' ) ;
2017-03-10 22:24:54 +01:00
// We have to skip some checks due to the way the code is
// currently organized for mapped keys.
hotkey . is _editing _stream _name = return _false ;
2017-05-27 15:40:54 +02:00
overlays . settings _open = return _false ;
2017-03-10 22:24:54 +01:00
set _global ( 'popovers' , {
actions _popped : return _false ,
2017-04-27 07:27:25 +02:00
} ) ;
set _global ( 'emoji_picker' , {
2017-04-19 07:37:03 +02:00
reactions _popped : return _false ,
2017-03-10 22:24:54 +01:00
} ) ;
2017-09-26 22:54:28 +02:00
set _global ( 'emoji_codes' , {
codepoint _to _name : {
'1f44d' : 'thumbs_up' ,
} ,
} ) ;
2017-08-02 04:46:56 +02:00
set _global ( 'hotspots' , {
is _open : return _false ,
} ) ;
2017-04-27 07:27:25 +02:00
2017-03-10 22:24:54 +01:00
// All letters should return false if we are composing text.
hotkey . processing _text = return _true ;
function test _normal _typing ( ) {
assert _unmapped ( 'abcdefghijklmnopqrstuvwxyz' ) ;
assert _unmapped ( ' ' ) ;
assert _unmapped ( '[]\\.,;' ) ;
assert _unmapped ( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ) ;
2017-09-07 03:01:17 +02:00
assert _unmapped ( '~!@#$%^*()_+{}:"<>' ) ;
2017-03-10 22:24:54 +01:00
}
2017-05-10 00:37:08 +02:00
_ . each ( [ return _true , return _false ] , function ( settings _open ) {
2017-05-06 02:40:32 +02:00
_ . each ( [ return _true , return _false ] , function ( is _active ) {
2017-05-09 16:45:02 +02:00
_ . each ( [ return _true , return _false ] , function ( info _overlay _open ) {
2017-05-27 15:40:54 +02:00
set _global ( 'overlays' , {
2017-05-09 16:45:02 +02:00
is _active : is _active ,
2017-05-10 00:37:08 +02:00
settings _open : settings _open ,
2017-10-21 19:21:44 +02:00
info _overlay _open : info _overlay _open ,
} ) ;
2017-04-12 20:58:31 +02:00
test _normal _typing ( ) ;
} ) ;
2017-03-10 22:24:54 +01:00
} ) ;
} ) ;
// Ok, now test keys that work when we're viewing messages.
hotkey . processing _text = return _false ;
2017-05-27 15:40:54 +02:00
overlays . settings _open = return _false ;
2017-10-21 19:21:44 +02:00
overlays . streams _open = return _false ;
overlays . lightbox _open = return _false ;
overlays . drafts _open = return _false ;
2017-03-23 03:06:38 +01:00
2017-09-16 17:30:07 +02:00
page _params . can _create _streams = true ;
2017-05-27 15:40:54 +02:00
overlays . streams _open = return _true ;
overlays . is _active = return _true ;
2017-05-24 20:15:51 +02:00
assert _mapping ( 'S' , 'subs.keyboard_sub' ) ;
2017-03-23 06:41:31 +01:00
assert _mapping ( 'V' , 'subs.view_stream' ) ;
2017-03-23 07:54:10 +01:00
assert _mapping ( 'n' , 'subs.new_stream_clicked' ) ;
2017-09-16 17:30:07 +02:00
page _params . can _create _streams = false ;
assert _unmapped ( 'n' ) ;
2017-05-27 15:40:54 +02:00
overlays . streams _open = return _false ;
2017-10-21 19:21:44 +02:00
test _normal _typing ( ) ;
overlays . is _active = return _false ;
2017-03-10 22:24:54 +01:00
2018-03-30 14:09:11 +02:00
assert _mapping ( '?' , 'info_overlay.maybe_show_keyboard_shortcuts' ) ;
2017-03-10 22:24:54 +01:00
assert _mapping ( '/' , 'search.initiate_search' ) ;
2017-09-25 06:20:55 +02:00
assert _mapping ( 'w' , 'activity.initiate_search' ) ;
assert _mapping ( 'q' , 'stream_list.initiate_search' ) ;
2017-03-10 22:24:54 +01:00
2017-08-16 19:06:07 +02:00
assert _mapping ( 'A' , 'narrow.stream_cycle_backward' ) ;
assert _mapping ( 'D' , 'narrow.stream_cycle_forward' ) ;
2017-03-10 22:24:54 +01:00
2017-03-18 17:41:47 +01:00
assert _mapping ( 'c' , 'compose_actions.start' ) ;
2018-02-08 15:17:26 +01:00
assert _mapping ( 'x' , 'compose_actions.start' ) ;
2017-03-19 19:41:02 +01:00
assert _mapping ( 'P' , 'narrow.by' ) ;
2017-03-18 20:30:20 +01:00
assert _mapping ( 'g' , 'gear_menu.open' ) ;
2017-10-03 00:41:43 +02:00
overlays . is _active = return _true ;
overlays . drafts _open = return _true ;
2017-10-21 19:21:44 +02:00
assert _mapping ( 'd' , 'overlays.close_overlay' ) ;
2017-10-03 00:41:43 +02:00
overlays . drafts _open = return _false ;
2017-10-21 19:21:44 +02:00
test _normal _typing ( ) ;
2017-10-03 00:41:43 +02:00
overlays . is _active = return _false ;
2017-10-21 19:21:44 +02:00
assert _mapping ( 'd' , 'drafts.launch' ) ;
2017-03-10 22:24:54 +01:00
// Next, test keys that only work on a selected message.
2018-01-31 04:23:02 +01:00
var message _view _only _keys = '@*+>RjJkKsSuvi:GM' ;
2017-03-23 19:38:08 +01:00
// Check that they do nothing without a selected message
2017-03-10 22:24:54 +01:00
global . current _msg _list . empty = return _true ;
2017-03-23 19:38:08 +01:00
assert _unmapped ( message _view _only _keys ) ;
2017-03-10 22:24:54 +01:00
global . current _msg _list . empty = return _false ;
2017-03-23 19:38:08 +01:00
// Check that they do nothing while in the settings overlay
2017-05-27 15:40:54 +02:00
overlays . settings _open = return _true ;
2018-01-31 04:23:02 +01:00
assert _unmapped ( '@*+->rRjJkKsSuvi:GM' ) ;
2017-05-27 15:40:54 +02:00
overlays . settings _open = return _false ;
2017-03-23 19:38:08 +01:00
// TODO: Similar check for being in the subs page
2017-04-14 19:27:12 +02:00
assert _mapping ( '@' , 'compose_actions.reply_with_mention' ) ;
2017-03-19 00:30:32 +01:00
assert _mapping ( '*' , 'message_flags.toggle_starred' ) ;
2017-05-29 23:37:32 +02:00
assert _mapping ( '+' , 'reactions.toggle_emoji_reaction' ) ;
2017-06-16 06:46:46 +02:00
assert _mapping ( '-' , 'condense.toggle_collapse' ) ;
2017-04-14 19:27:12 +02:00
assert _mapping ( 'r' , 'compose_actions.respond_to_message' ) ;
assert _mapping ( 'R' , 'compose_actions.respond_to_message' , true ) ;
2017-03-10 22:24:54 +01:00
assert _mapping ( 'j' , 'navigate.down' ) ;
assert _mapping ( 'J' , 'navigate.page_down' ) ;
assert _mapping ( 'k' , 'navigate.up' ) ;
assert _mapping ( 'K' , 'navigate.page_up' ) ;
assert _mapping ( 's' , 'narrow.by_recipient' ) ;
assert _mapping ( 'S' , 'narrow.by_subject' ) ;
2017-05-23 20:01:24 +02:00
assert _mapping ( 'u' , 'popovers.show_sender_info' ) ;
2017-03-10 22:24:54 +01:00
assert _mapping ( 'i' , 'popovers.open_message_menu' ) ;
2017-09-18 22:06:39 +02:00
assert _mapping ( ':' , 'reactions.open_reactions_popover' , true ) ;
2018-01-31 04:23:02 +01:00
assert _mapping ( '>' , 'compose_actions.quote_and_reply' ) ;
2017-10-02 23:10:55 +02:00
2017-10-21 19:21:44 +02:00
overlays . is _active = return _true ;
overlays . lightbox _open = return _true ;
assert _mapping ( 'v' , 'overlays.close_overlay' ) ;
overlays . lightbox _open = return _false ;
test _normal _typing ( ) ;
overlays . is _active = return _false ;
assert _mapping ( 'v' , 'lightbox.show_from_selected_message' ) ;
2017-10-02 23:10:55 +02:00
global . emoji _picker . reactions _popped = return _true ;
assert _mapping ( ':' , 'emoji_picker.navigate' , true ) ;
global . emoji _picker . reactions _popped = return _false ;
2017-03-23 08:11:00 +01:00
assert _mapping ( 'G' , 'navigate.to_end' ) ;
2017-03-23 05:46:19 +01:00
assert _mapping ( 'M' , 'muting_ui.toggle_mute' ) ;
2017-03-23 13:23:49 +01:00
// Test keys that work when a message is selected and
// also when the message list is empty.
assert _mapping ( 'n' , 'narrow.narrow_to_next_topic' ) ;
2018-02-16 15:56:25 +01:00
assert _mapping ( 'p' , 'narrow.narrow_to_next_pm_string' ) ;
2017-03-23 13:23:49 +01:00
global . current _msg _list . empty = return _true ;
assert _mapping ( 'n' , 'narrow.narrow_to_next_topic' ) ;
global . current _msg _list . empty = return _false ;
2017-03-10 22:24:54 +01:00
} ( ) ) ;
2017-03-14 18:29:38 +01:00
( function test _motion _keys ( ) {
var codes = {
down _arrow : 40 ,
end : 35 ,
home : 36 ,
left _arrow : 37 ,
2017-03-19 01:51:20 +01:00
right _arrow : 39 ,
2017-03-14 18:29:38 +01:00
page _up : 33 ,
page _down : 34 ,
spacebar : 32 ,
up _arrow : 38 ,
2017-03-19 03:08:09 +01:00
'+' : 187 ,
2017-03-14 18:29:38 +01:00
} ;
2017-03-19 03:08:09 +01:00
function process ( name , shiftKey , ctrlKey ) {
2017-03-14 18:29:38 +01:00
var e = {
which : codes [ name ] ,
2017-03-19 03:08:09 +01:00
shiftKey : shiftKey ,
2017-03-19 19:26:13 +01:00
ctrlKey : ctrlKey ,
2017-03-14 18:29:38 +01:00
} ;
2017-03-18 22:05:07 +01:00
try {
return hotkey . process _keydown ( e ) ;
} catch ( err ) {
// An exception will be thrown here if a different
// function is called than the one declared. Try to
// provide a useful error message.
2017-11-09 16:26:38 +01:00
// add a newline to separate from other console output.
2017-03-18 22:05:07 +01:00
console . log ( '\nERROR: Mapping for character "' + e . which + '" does not match tests.' ) ;
2017-03-18 22:05:07 +01:00
}
2017-03-14 18:29:38 +01:00
}
function assert _unmapped ( name ) {
assert . equal ( process ( name ) , false ) ;
}
2017-03-19 03:08:09 +01:00
function assert _mapping ( key _name , func _name , shiftKey , ctrlKey ) {
2017-03-14 18:29:38 +01:00
stubbing ( func _name , function ( ) {
2017-03-19 03:08:09 +01:00
assert ( process ( key _name , shiftKey , ctrlKey ) ) ;
2017-03-14 18:29:38 +01:00
} ) ;
}
2017-04-04 18:14:27 +02:00
list _util . inside _list = return _false ;
2017-03-14 18:29:38 +01:00
global . current _msg _list . empty = return _true ;
2017-05-27 15:40:54 +02:00
overlays . settings _open = return _false ;
overlays . streams _open = return _false ;
overlays . lightbox _open = return _false ;
2017-03-14 18:29:38 +01:00
assert _unmapped ( 'down_arrow' ) ;
assert _unmapped ( 'end' ) ;
assert _unmapped ( 'home' ) ;
assert _unmapped ( 'page_up' ) ;
assert _unmapped ( 'page_down' ) ;
assert _unmapped ( 'spacebar' ) ;
assert _unmapped ( 'up_arrow' ) ;
2017-04-04 18:14:27 +02:00
global . list _util . inside _list = return _true ;
assert _mapping ( 'up_arrow' , 'list_util.go_up' ) ;
assert _mapping ( 'down_arrow' , 'list_util.go_down' ) ;
list _util . inside _list = return _false ;
2017-03-14 18:29:38 +01:00
global . current _msg _list . empty = return _false ;
assert _mapping ( 'down_arrow' , 'navigate.down' ) ;
assert _mapping ( 'end' , 'navigate.to_end' ) ;
assert _mapping ( 'home' , 'navigate.to_home' ) ;
assert _mapping ( 'left_arrow' , 'message_edit.edit_last_sent_message' ) ;
assert _mapping ( 'page_up' , 'navigate.page_up' ) ;
assert _mapping ( 'page_down' , 'navigate.page_down' ) ;
assert _mapping ( 'spacebar' , 'navigate.page_down' ) ;
assert _mapping ( 'up_arrow' , 'navigate.up' ) ;
2017-05-27 15:40:54 +02:00
overlays . info _overlay _open = return _true ;
2017-04-12 20:58:31 +02:00
assert _unmapped ( 'down_arrow' ) ;
assert _unmapped ( 'up_arrow' ) ;
2017-05-27 15:40:54 +02:00
overlays . info _overlay _open = return _false ;
2017-04-12 20:58:31 +02:00
2017-05-27 15:40:54 +02:00
overlays . streams _open = return _true ;
2017-03-22 22:18:34 +01:00
assert _mapping ( 'up_arrow' , 'subs.switch_rows' ) ;
assert _mapping ( 'down_arrow' , 'subs.switch_rows' ) ;
2017-05-27 15:40:54 +02:00
overlays . streams _open = return _false ;
2017-03-23 06:02:01 +01:00
2017-05-27 15:40:54 +02:00
overlays . lightbox _open = return _true ;
2017-03-23 06:02:01 +01:00
assert _mapping ( 'left_arrow' , 'lightbox.prev' ) ;
assert _mapping ( 'right_arrow' , 'lightbox.next' ) ;
2017-05-27 15:40:54 +02:00
overlays . lightbox _open = return _false ;
2017-03-22 02:49:52 +01:00
2017-04-04 19:59:04 +02:00
hotkey . is _editing _stream _name = return _true ;
assert _unmapped ( 'down_arrow' ) ;
assert _unmapped ( 'up_arrow' ) ;
2017-04-05 20:59:48 +02:00
hotkey . is _editing _stream _name = return _false ;
2017-04-04 19:59:04 +02:00
2017-05-27 15:40:54 +02:00
overlays . settings _open = return _true ;
2017-03-14 18:29:38 +01:00
assert _unmapped ( 'end' ) ;
assert _unmapped ( 'home' ) ;
assert _unmapped ( 'left_arrow' ) ;
assert _unmapped ( 'page_up' ) ;
assert _unmapped ( 'page_down' ) ;
assert _unmapped ( 'spacebar' ) ;
2017-04-04 19:59:04 +02:00
assert _mapping ( 'up_arrow' , 'settings.handle_up_arrow' ) ;
assert _mapping ( 'down_arrow' , 'settings.handle_down_arrow' ) ;
2017-05-27 15:40:54 +02:00
overlays . settings _open = return _false ;
2017-04-04 20:48:08 +02:00
2017-10-03 00:41:43 +02:00
overlays . is _active = return _true ;
overlays . drafts _open = return _true ;
2017-04-04 20:48:08 +02:00
assert _mapping ( 'up_arrow' , 'drafts.drafts_handle_events' ) ;
assert _mapping ( 'down_arrow' , 'drafts.drafts_handle_events' ) ;
2017-10-03 00:41:43 +02:00
overlays . is _active = return _false ;
overlays . drafts _open = return _false ;
2017-03-14 18:29:38 +01:00
} ( ) ) ;