stream list: Use newer code for the list cursor.

The new list_cursor class is more generic and saves the state
of your cursor across redraws.

Note that we no longer cycle from bottom to top or vice versa.

The node test code that was removed here was kind of complex
and didn't actually assert useful things after calling methods.
This commit is contained in:
Steve Howell 2018-04-24 14:59:01 +00:00 committed by Tim Abbott
parent 779535fda3
commit e9c6f3a07d
6 changed files with 158 additions and 268 deletions

View File

@ -337,30 +337,36 @@ casper.waitForSelector('#stream_filters .highlighted_stream', function () {
// Use arrow keys to navigate through suggestions
casper.then(function () {
// Down: Denmark -> Scotland
casper.sendKeys('.stream-list-filter', casper.page.event.key.Down, {keepFocus: true});
// Up: Scotland -> Denmark
casper.sendKeys('.stream-list-filter', casper.page.event.key.Up, {keepFocus: true});
// Up: Denmark -> Verona
casper.sendKeys('.stream-list-filter', casper.page.event.key.Up, {keepFocus: true});
function arrow(key) {
casper.sendKeys('.stream-list-filter',
casper.page.event.key[key],
{keepFocus: true});
}
arrow('Down'); // Denmark -> Scotland
arrow('Up'); // Scotland -> Denmark
arrow('Up'); // Denmark -> Denmark
arrow('Down'); // Denmark -> Scotland
});
casper.waitForSelector('#stream_filters [data-stream-name="Verona"].highlighted_stream', function () {
casper.waitForSelector('#stream_filters [data-stream-name="Scotland"].highlighted_stream', function () {
casper.test.info('Suggestion highlighting - after arrow key navigation');
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Denmark"].highlighted_stream',
casper.test.assertDoesntExist(
'#stream_filters [data-stream-name="Denmark"].highlighted_stream',
'Stream Denmark is not highlighted');
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Scotland"].highlighted_stream',
'Stream Scotland is not highlighted');
casper.test.assertExist('#stream_filters [data-stream-name="Verona"].highlighted_stream',
'Stream Verona is highlighted');
casper.test.assertExist(
'#stream_filters [data-stream-name="Scotland"].highlighted_stream',
'Stream Scotland is highlighted');
casper.test.assertDoesntExist(
'#stream_filters [data-stream-name="Verona"].highlighted_stream',
'Stream Verona is not highlighted');
});
// We search for the beginning of "Verona", not case sensitive
// We search for the beginning of "Scotland", not case sensitive
casper.then(function () {
casper.evaluate(function () {
$('.stream-list-filter').expectOne()
.focus()
.val('ver')
.val('sCoT')
.trigger($.Event('input'))
.trigger($.Event('click'));
});
@ -373,16 +379,16 @@ casper.waitWhileVisible('#stream_filters [data-stream-name="Denmark"]', function
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Denmark"]',
'Filtered stream list does not contain Denmark');
});
casper.waitWhileVisible('#stream_filters [data-stream-name="Scotland"]', function () {
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Scotland"]',
'Filtered stream list does not contain Scotland');
casper.waitWhileVisible('#stream_filters [data-stream-name="Verona"]', function () {
casper.test.assertDoesntExist('#stream_filters [data-stream-name="Verona"]',
'Filtered stream list does not contain Verona');
});
casper.then(function () {
casper.test.assertExists('#stream_filters [data-stream-name="Verona"]',
'Filtered stream list does contain Verona');
casper.test.assertExists('#stream_filters [data-stream-name="Verona"].highlighted_stream',
'Stream Verona is highlighted');
casper.test.assertExists('#stream_filters [data-stream-name="Scotland"]',
'Filtered stream list does contain Scotland');
casper.test.assertExists('#stream_filters [data-stream-name="Scotland"].highlighted_stream',
'Stream Scotland is highlighted');
});
// Clearing the list should give us back all the streams in the list

View File

@ -694,6 +694,9 @@ function make_sidebar_helper() {
}
stream_list.stream_sidebar.set_row(social_stream.stream_id, row_widget());
stream_list.stream_cursor = {
redraw: noop,
};
return {
verify_actions: () => {

View File

@ -14,6 +14,7 @@ zrequire('narrow');
zrequire('unread');
zrequire('stream_data');
zrequire('scroll_util');
zrequire('list_cursor');
zrequire('stream_list');
var noop = function () {};
@ -23,6 +24,10 @@ var return_true = function () { return true; };
set_global('topic_list', {});
set_global('overlays', {});
set_global('keydown_util', {
handle: noop,
});
(function test_create_sidebar_row() {
// Make a couple calls to create_sidebar_row() and make sure they
// generate the right markup as well as play nice with get_stream_li().
@ -280,127 +285,6 @@ function initialize_stream_data() {
assert($('<cars sidebar row html>').hasClass('active-filter'));
}());
var keydown_handler = $('.stream-list-filter').get_on_handler('keydown');
(function test_arrow_navigation() {
stream_list.build_stream_list();
initialize_stream_data();
var stream_order = ['devel', 'Rome', 'test',
'-divider-', 'announce','Denmark',
'-divider-','cars'];
var stream_count = 8;
// Mock the jquery is func
$('.stream-list-filter').is = function (sel) {
if (sel === ':focus') {
return $('.stream-list-filter').is_focused();
}
};
// Mock the jquery first func
$('#stream_filters li.narrow-filter').first = function () {
return $('#stream_filters li[data-stream-name="' + stream_order[0] + '"]');
};
$('#stream_filters li.narrow-filter').last = function () {
return $('#stream_filters li[data-stream-name="' + stream_order[stream_count - 1] + '"]');
};
var sel_index = 0;
// Returns which element is highlighted
$('#stream_filters li.narrow-filter.highlighted_stream')
.expectOne().data = function () {
// Return random id (is further not used)
return 1;
};
// Returns element before highlighted one
$('#stream_filters li.narrow-filter.highlighted_stream')
.expectOne().prev = function () {
if (sel_index === 0) {
// Top, no prev element
return $('div.no_stream');
} else if (sel_index === 3 || sel_index === 6) {
return $('div.divider');
}
return $('#stream_filters li[data-stream-name="'
+ stream_order[sel_index-1] + '"]');
};
// Returns element after highlighted one
$('#stream_filters li.narrow-filter.highlighted_stream')
.expectOne().next = function () {
if (sel_index === stream_count - 1) {
// Bottom, no next element
return $('div.no_stream');
} else if (sel_index === 3 || sel_index === 6) {
return $('div.divider');
}
return $('#stream_filters li[data-stream-name="'
+ stream_order[sel_index + 1] + '"]');
};
for (var i = 0; i < stream_count; i = i + 1) {
if (i === 3 || i === 6) {
$('#stream_filters li[data-stream-name="' + stream_order[i] + '"]')
.is = return_false;
} else {
$('#stream_filters li[data-stream-name="' + stream_order[i] + '"]')
.is = return_true;
}
}
$('div.no_stream').is = return_false;
$('div.divider').is = return_false;
$('#stream_filters li.narrow-filter').length = stream_count;
// up
var e = {
keyCode: 38,
stopPropagation: function () {},
preventDefault: function () {},
};
keydown_handler(e);
// Now the last element is highlighted
sel_index = stream_count - 1;
keydown_handler(e);
sel_index = sel_index - 1;
// down
e = {
keyCode: 40,
stopPropagation: function () {},
preventDefault: function () {},
};
keydown_handler(e);
sel_index = sel_index + 1;
keydown_handler(e);
}());
(function test_enter_press() {
var e = {
keyCode: 13,
stopPropagation: function () {},
preventDefault: function () {},
};
overlays.is_active = return_false;
narrow_state.active = return_false;
stream_data.get_sub_by_id = function () {
return 'name';
};
narrow.by = noop;
stream_list.clear_and_hide_search = noop;
// Enter text and narrow users
$(".stream-list-filter").expectOne().val('');
keydown_handler(e);
}());
(function test_focusout_user_filter() {
var e = { };
var click_handler = $('.stream-list-filter').get_on_handler('focusout');

View File

@ -3,9 +3,9 @@ zrequire('stream_data');
zrequire('stream_sort');
var with_overrides = global.with_overrides;
// Test no subscribed streams
(function test_no_subscribed_streams() {
assert.equal(stream_sort.sort_groups(''), undefined);
assert.equal(stream_sort.first_stream_id(), undefined);
}());
const scalene = {
@ -56,12 +56,25 @@ with_overrides(function (override) {
assert.deepEqual(sorted.normal_streams, ['clarinet', 'fast tortoise']);
assert.deepEqual(sorted.dormant_streams, ['pneumonia']);
// Test cursor helpers.
assert.equal(stream_sort.first_stream_id(), scalene.stream_id);
assert.equal(stream_sort.prev_stream_id(scalene.stream_id), undefined);
assert.equal(stream_sort.prev_stream_id(clarinet.stream_id), scalene.stream_id);
assert.equal(stream_sort.next_stream_id(fast_tortoise.stream_id), pneumonia.stream_id);
assert.equal(stream_sort.next_stream_id(pneumonia.stream_id), undefined);
// Test filtering
sorted = stream_sort.sort_groups("s");
assert.deepEqual(sorted.pinned_streams, ['scalene']);
assert.deepEqual(sorted.normal_streams, []);
assert.deepEqual(sorted.dormant_streams, []);
assert.equal(stream_sort.prev_stream_id(clarinet.stream_id), undefined);
assert.equal(stream_sort.next_stream_id(clarinet.stream_id), undefined);
// Test searching entire word, case-insensitive
sorted = stream_sort.sort_groups("PnEuMoNiA");
assert.deepEqual(sorted.pinned_streams, []);

View File

@ -63,6 +63,7 @@ function get_search_term() {
exports.remove_sidebar_row = function (stream_id) {
exports.stream_sidebar.remove_row(stream_id);
exports.build_stream_list();
exports.stream_cursor.redraw();
};
exports.create_initial_sidebar_rows = function () {
@ -301,13 +302,7 @@ function set_stream_unread_count(stream_id, count) {
exports.update_streams_sidebar = function () {
exports.build_stream_list();
// highlight new top stream
$('#stream_filters li.highlighted_stream').removeClass('highlighted_stream');
if (exports.searching()) {
var all_streams = $('#stream_filters li.narrow-filter');
exports.highlight_first(all_streams);
}
exports.stream_cursor.redraw();
if (! narrow_state.active()) {
return;
@ -449,43 +444,36 @@ exports.handle_narrow_deactivated = function () {
};
function focus_stream_filter(e) {
if ($('#stream_filters li.narrow-filter.highlighted_stream').length === 0) {
// Highlight
var all_streams = $('#stream_filters li.narrow-filter');
exports.highlight_first(all_streams);
}
exports.stream_cursor.reset();
e.stopPropagation();
}
function focusout_stream_filter() {
// Undo highlighting
$('#stream_filters li.narrow-filter.highlighted_stream').removeClass('highlighted_stream');
}
function keydown_enter_key() {
// Is there at least one stream?
if ($('#stream_filters li.narrow-filter').length > 0) {
var selected_stream_id = $('#stream_filters li.narrow-filter.highlighted_stream')
.expectOne().data('stream-id');
var stream_id = exports.stream_cursor.get_key();
var top_stream = stream_data.get_sub_by_id(selected_stream_id);
if (stream_id === undefined) {
// This can happen for empty searches, no need to warn.
return;
}
var sub = stream_data.get_sub_by_id(stream_id);
if (sub === undefined) {
blueslip.error('Unknown stream_id for search/enter: ' + stream_id);
return;
}
if (overlays.is_active()) {
ui_util.change_tab_to('#home');
}
exports.clear_and_hide_search();
narrow.by('stream', top_stream.name, {trigger: 'sidebar enter key'});
}
}
function keydown_stream_filter(e) {
exports.keydown_filter(e, '#stream_filters li.narrow-filter',
$('#stream-filters-container'), keydown_enter_key);
narrow.by('stream', sub.name, {trigger: 'sidebar enter key'});
}
function actually_update_streams_for_search() {
exports.update_streams_sidebar();
resize.resize_page_components();
exports.stream_cursor.reset();
}
var update_streams_for_search = _.throttle(actually_update_streams_for_search, 50);
@ -502,6 +490,7 @@ exports.initialize = function () {
$(document).on('subscription_add_done.zulip', function (event) {
exports.create_sidebar_row(event.sub);
exports.build_stream_list();
exports.stream_cursor.redraw();
});
$(document).on('subscription_remove_done.zulip', function (event) {
@ -527,16 +516,50 @@ exports.initialize = function () {
e.stopPropagation();
});
$(".stream-list-filter").expectOne()
.on('click', focus_stream_filter)
.on('focusout', focusout_stream_filter)
.on('input', update_streams_for_search)
.on('keydown', keydown_stream_filter);
$('#clear_search_stream_button').on('click', exports.clear_search);
$("#streams_header").expectOne().click(function (e) {
exports.toggle_filter_displayed(e);
});
exports.stream_cursor = list_cursor({
list: {
container: $('#stream-filters-container'),
find_li: function (opts) {
var stream_id = opts.key;
var li = exports.get_stream_li(stream_id);
return li;
},
first_key: stream_sort.first_stream_id,
prev_key: stream_sort.prev_stream_id,
next_key: stream_sort.next_stream_id,
},
highlight_class: 'highlighted_stream',
});
var $search_input = $('.stream-list-filter').expectOne();
keydown_util.handle({
elem: $search_input,
handlers: {
enter_key: function () {
keydown_enter_key();
return true;
},
up_arrow: function () {
exports.stream_cursor.prev();
return true;
},
down_arrow: function () {
exports.stream_cursor.next();
return true;
},
},
});
$search_input.on('click', focus_stream_filter);
$search_input.on('focusout', exports.stream_cursor.clear);
$search_input.on('input', update_streams_for_search);
};
exports.searching = function () {
@ -573,9 +596,7 @@ exports.initiate_search = function () {
}
filter.focus();
// Highlight first result
var all_streams = $('#stream_filters li.narrow-filter');
exports.highlight_first(all_streams);
exports.stream_cursor.reset();
};
exports.clear_and_hide_search = function () {
@ -584,94 +605,11 @@ exports.clear_and_hide_search = function () {
filter.val('');
update_streams_for_search();
}
exports.stream_cursor.clear();
filter.blur();
filter.parent().addClass('notdisplayed');
};
function next_sibing_in_dir(elm, dir_up) {
if (dir_up) {
return elm.prev();
}
return elm.next();
}
function keydown_arrow_key(dir_up, all_streams_selector, scroll_container) {
// Are there streams to cyle through?
if ($(all_streams_selector).length > 0) {
var current_sel = $(all_streams_selector + '.highlighted_stream').expectOne();
var next_sibling = next_sibing_in_dir(current_sel, dir_up);
if (next_sibling.is('hr.stream-split')) {
// Skip separator
next_sibling = next_sibing_in_dir(next_sibling, dir_up);
}
if (!next_sibling.is('li.narrow-filter')) {
// At the every bottom or top
var all_streams = $(all_streams_selector);
if (dir_up) {
// top -> start at the bottom
next_sibling = all_streams.last();
} else {
// bottom -> start at the top
next_sibling = all_streams.first();
}
}
// Classes must be explicitly named
current_sel.removeClass('highlighted_stream');
next_sibling.addClass('highlighted_stream');
scroll_util.scroll_element_into_container(next_sibling, scroll_container);
}
}
exports.keydown_filter = function (e, all_streams_selector, scroll_container,
enter_press_function) {
// Function for left and right sidebar
// Could be placed somewhere else but ui.js is already very full
// Catch <enter> and <up-arrow>, <down-arrow> key presses
var handled = false;
switch (e.keyCode) {
case 13: {
// Enter key was pressed
enter_press_function();
handled = true;
break;
}
case 38: {
// Up-arrow key was pressed
keydown_arrow_key(true, all_streams_selector, scroll_container);
handled = true;
break;
}
case 40: {
// Down-arrow key was pressed
keydown_arrow_key(false, all_streams_selector, scroll_container);
handled = true;
break;
}
}
if (handled) {
// Since we already handled the key event above, suppress the browser handling it.
// We don't want the cursor to move when <arrow-up/down> is pressed.
// In the <enter> case:
// Prevent a newline from being entered into the soon-to-be-opened composebox
e.preventDefault();
e.stopPropagation();
}
};
exports.highlight_first = function (all_streams) {
if (all_streams.length > 0) {
// Classes must be explicitly named
all_streams.first().addClass('highlighted_stream');
}
};
exports.toggle_filter_displayed = function (e) {
if (e.target.id === 'streams_inline_cog') {
return;

View File

@ -91,6 +91,52 @@ exports.sort_groups = function (search_term) {
};
};
function pos(stream_id) {
var sub = stream_data.get_sub_by_id(stream_id);
var name = sub.name;
var i = all_streams.indexOf(name);
if (i < 0) {
return;
}
return i;
}
function maybe_get_stream_id(i) {
if (i < 0 || i >= all_streams.length) {
return;
}
var name = all_streams[i];
var stream_id = stream_data.get_stream_id(name);
return stream_id;
}
exports.first_stream_id = function () {
return maybe_get_stream_id(0);
};
exports.prev_stream_id = function (stream_id) {
var i = pos(stream_id);
if (i === undefined) {
return;
}
return maybe_get_stream_id(i - 1);
};
exports.next_stream_id = function (stream_id) {
var i = pos(stream_id);
if (i === undefined) {
return;
}
return maybe_get_stream_id(i + 1);
};
return exports;
}());
if (typeof module !== 'undefined') {