mirror of
https://github.com/zulip/zulip.git
synced 2025-11-08 16:01:58 +00:00
This run_test helper sets up a convention that allows us to give really short tracebacks for errors, and eventually we can have more control over running individual tests. (The latter goal has some complications, since we often intentionally leak setup in tests.)
422 lines
14 KiB
JavaScript
422 lines
14 KiB
JavaScript
// 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
|
|
// functions (like overlays.settings_open). Within that context, to
|
|
// 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()`.
|
|
|
|
set_global('activity', {
|
|
});
|
|
|
|
set_global('navigator', {
|
|
userAgent: '',
|
|
});
|
|
|
|
set_global('page_params', {
|
|
});
|
|
|
|
set_global('overlays', {
|
|
});
|
|
|
|
var noop = () => {};
|
|
|
|
// 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;
|
|
|
|
var hotkey = zrequire('hotkey');
|
|
|
|
set_global('list_util', {
|
|
});
|
|
|
|
set_global('current_msg_list', {
|
|
selected_id: function () {
|
|
return 42;
|
|
},
|
|
selected_message: function () {
|
|
return {
|
|
sent_by_me: true,
|
|
flags: ["read", "starred"],
|
|
};
|
|
},
|
|
selected_row: function () {},
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|
|
}
|
|
|
|
run_test('mappings', () => {
|
|
function map_press(which, shiftKey) {
|
|
return hotkey.get_keypress_hotkey({
|
|
which: which,
|
|
shiftKey: shiftKey,
|
|
});
|
|
}
|
|
|
|
function map_down(which, shiftKey, ctrlKey, metaKey) {
|
|
return hotkey.get_keydown_hotkey({
|
|
which: which,
|
|
shiftKey: shiftKey,
|
|
ctrlKey: ctrlKey,
|
|
metaKey: metaKey,
|
|
});
|
|
}
|
|
|
|
// 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');
|
|
assert.equal(map_down(46).name, 'delete');
|
|
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
|
|
|
|
assert.equal(map_down(219, false, true).name, 'escape'); // ctrl + [
|
|
assert.equal(map_down(75, false, true).name, 'search_with_k'); // ctrl + k
|
|
|
|
// More negative tests.
|
|
assert.equal(map_down(47), undefined);
|
|
assert.equal(map_press(27), undefined);
|
|
assert.equal(map_down(27, true), undefined);
|
|
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
|
|
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 + [
|
|
assert.equal(map_down(75, false, false, true).name, 'search_with_k'); // cmd + k
|
|
|
|
assert.equal(map_down(75, false, true, false), undefined); // ctrl + k
|
|
|
|
// Reset userAgent
|
|
global.navigator.userAgent = '';
|
|
});
|
|
|
|
run_test('basic_chars', () => {
|
|
function process(s) {
|
|
var e = {
|
|
which: s.charCodeAt(0),
|
|
};
|
|
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.
|
|
// add a newline to separate from other console output.
|
|
console.log('\nERROR: Mapping for character "' + e.which + '" does not match tests.');
|
|
}
|
|
}
|
|
|
|
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.
|
|
assert_unmapped('abefhlmotyz');
|
|
assert_unmapped('BEFHILNOQTUWXYZ');
|
|
|
|
// 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;
|
|
overlays.settings_open = return_false;
|
|
|
|
set_global('popovers', {
|
|
actions_popped: return_false,
|
|
});
|
|
set_global('emoji_picker', {
|
|
reactions_popped: return_false,
|
|
});
|
|
set_global('emoji_codes', {
|
|
codepoint_to_name: {
|
|
'1f44d': 'thumbs_up',
|
|
},
|
|
});
|
|
set_global('hotspots', {
|
|
is_open: return_false,
|
|
});
|
|
|
|
// 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');
|
|
assert_unmapped('~!@#$%^*()_+{}:"<>');
|
|
}
|
|
|
|
_.each([return_true, return_false], function (settings_open) {
|
|
_.each([return_true, return_false], function (is_active) {
|
|
_.each([return_true, return_false], function (info_overlay_open) {
|
|
set_global('overlays', {
|
|
is_active: is_active,
|
|
settings_open: settings_open,
|
|
info_overlay_open: info_overlay_open,
|
|
});
|
|
test_normal_typing();
|
|
});
|
|
});
|
|
});
|
|
|
|
// Ok, now test keys that work when we're viewing messages.
|
|
hotkey.processing_text = return_false;
|
|
overlays.settings_open = return_false;
|
|
overlays.streams_open = return_false;
|
|
overlays.lightbox_open = return_false;
|
|
overlays.drafts_open = return_false;
|
|
|
|
page_params.can_create_streams = true;
|
|
overlays.streams_open = return_true;
|
|
overlays.is_active = return_true;
|
|
assert_mapping('S', 'subs.keyboard_sub');
|
|
assert_mapping('V', 'subs.view_stream');
|
|
assert_mapping('n', 'subs.new_stream_clicked');
|
|
page_params.can_create_streams = false;
|
|
assert_unmapped('n');
|
|
overlays.streams_open = return_false;
|
|
test_normal_typing();
|
|
overlays.is_active = return_false;
|
|
|
|
assert_mapping('?', 'info_overlay.maybe_show_keyboard_shortcuts');
|
|
assert_mapping('/', 'search.initiate_search');
|
|
assert_mapping('w', 'activity.initiate_search');
|
|
assert_mapping('q', 'stream_list.initiate_search');
|
|
|
|
assert_mapping('A', 'narrow.stream_cycle_backward');
|
|
assert_mapping('D', 'narrow.stream_cycle_forward');
|
|
|
|
assert_mapping('c', 'compose_actions.start');
|
|
assert_mapping('x', 'compose_actions.start');
|
|
assert_mapping('P', 'narrow.by');
|
|
assert_mapping('g', 'gear_menu.open');
|
|
|
|
overlays.is_active = return_true;
|
|
overlays.drafts_open = return_true;
|
|
assert_mapping('d', 'overlays.close_overlay');
|
|
overlays.drafts_open = return_false;
|
|
test_normal_typing();
|
|
overlays.is_active = return_false;
|
|
assert_mapping('d', 'drafts.launch');
|
|
|
|
// Next, test keys that only work on a selected message.
|
|
var message_view_only_keys = '@*+>RjJkKsSuvi:GM';
|
|
|
|
// Check that they do nothing without a selected message
|
|
global.current_msg_list.empty = return_true;
|
|
assert_unmapped(message_view_only_keys);
|
|
|
|
global.current_msg_list.empty = return_false;
|
|
|
|
// Check that they do nothing while in the settings overlay
|
|
overlays.settings_open = return_true;
|
|
assert_unmapped('@*+->rRjJkKsSuvi:GM');
|
|
overlays.settings_open = return_false;
|
|
|
|
// TODO: Similar check for being in the subs page
|
|
|
|
assert_mapping('@', 'compose_actions.reply_with_mention');
|
|
assert_mapping('*', 'message_flags.toggle_starred');
|
|
assert_mapping('+', 'reactions.toggle_emoji_reaction');
|
|
assert_mapping('-', 'condense.toggle_collapse');
|
|
assert_mapping('r', 'compose_actions.respond_to_message');
|
|
assert_mapping('R', 'compose_actions.respond_to_message', true);
|
|
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');
|
|
assert_mapping('u', 'popovers.show_sender_info');
|
|
assert_mapping('i', 'popovers.open_message_menu');
|
|
assert_mapping(':', 'reactions.open_reactions_popover', true);
|
|
assert_mapping('>', 'compose_actions.quote_and_reply');
|
|
|
|
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');
|
|
|
|
global.emoji_picker.reactions_popped = return_true;
|
|
assert_mapping(':', 'emoji_picker.navigate', true);
|
|
global.emoji_picker.reactions_popped = return_false;
|
|
|
|
assert_mapping('G', 'navigate.to_end');
|
|
assert_mapping('M', 'muting_ui.toggle_mute');
|
|
|
|
// 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');
|
|
assert_mapping('p', 'narrow.narrow_to_next_pm_string');
|
|
|
|
global.current_msg_list.empty = return_true;
|
|
assert_mapping('n', 'narrow.narrow_to_next_topic');
|
|
|
|
global.current_msg_list.empty = return_false;
|
|
|
|
});
|
|
|
|
run_test('motion_keys', () => {
|
|
var codes = {
|
|
down_arrow: 40,
|
|
end: 35,
|
|
home: 36,
|
|
left_arrow: 37,
|
|
right_arrow: 39,
|
|
page_up: 33,
|
|
page_down: 34,
|
|
spacebar: 32,
|
|
up_arrow: 38,
|
|
'+': 187,
|
|
};
|
|
|
|
function process(name, shiftKey, ctrlKey) {
|
|
var e = {
|
|
which: codes[name],
|
|
shiftKey: shiftKey,
|
|
ctrlKey: ctrlKey,
|
|
};
|
|
|
|
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.
|
|
// add a newline to separate from other console output.
|
|
console.log('\nERROR: Mapping for character "' + e.which + '" does not match tests.');
|
|
}
|
|
}
|
|
|
|
function assert_unmapped(name) {
|
|
assert.equal(process(name), false);
|
|
}
|
|
|
|
function assert_mapping(key_name, func_name, shiftKey, ctrlKey) {
|
|
stubbing(func_name, function () {
|
|
assert(process(key_name, shiftKey, ctrlKey));
|
|
});
|
|
}
|
|
|
|
list_util.inside_list = return_false;
|
|
global.current_msg_list.empty = return_true;
|
|
overlays.settings_open = return_false;
|
|
overlays.streams_open = return_false;
|
|
overlays.lightbox_open = return_false;
|
|
|
|
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');
|
|
|
|
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;
|
|
|
|
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');
|
|
|
|
overlays.info_overlay_open = return_true;
|
|
assert_unmapped('down_arrow');
|
|
assert_unmapped('up_arrow');
|
|
overlays.info_overlay_open = return_false;
|
|
|
|
overlays.streams_open = return_true;
|
|
assert_mapping('up_arrow', 'subs.switch_rows');
|
|
assert_mapping('down_arrow', 'subs.switch_rows');
|
|
overlays.streams_open = return_false;
|
|
|
|
overlays.lightbox_open = return_true;
|
|
assert_mapping('left_arrow', 'lightbox.prev');
|
|
assert_mapping('right_arrow', 'lightbox.next');
|
|
overlays.lightbox_open = return_false;
|
|
|
|
hotkey.is_editing_stream_name = return_true;
|
|
assert_unmapped('down_arrow');
|
|
assert_unmapped('up_arrow');
|
|
hotkey.is_editing_stream_name = return_false;
|
|
|
|
overlays.settings_open = return_true;
|
|
assert_unmapped('end');
|
|
assert_unmapped('home');
|
|
assert_unmapped('left_arrow');
|
|
assert_unmapped('page_up');
|
|
assert_unmapped('page_down');
|
|
assert_unmapped('spacebar');
|
|
|
|
assert_mapping('up_arrow', 'settings.handle_up_arrow');
|
|
assert_mapping('down_arrow', 'settings.handle_down_arrow');
|
|
overlays.settings_open = return_false;
|
|
|
|
overlays.is_active = return_true;
|
|
overlays.drafts_open = return_true;
|
|
assert_mapping('up_arrow', 'drafts.drafts_handle_events');
|
|
assert_mapping('down_arrow', 'drafts.drafts_handle_events');
|
|
overlays.is_active = return_false;
|
|
overlays.drafts_open = return_false;
|
|
});
|