message_list: Don't always cache "Combined feed" view.

Important changes in this commit:
* We only cache message list for "Combined feed" if it is the
  default view.

* We modify existing handling of home message list code so that
  it can be used to for any message list that we want to cache
  using a new `preserve_rendered_state` variable.

* narrow_state.filter() returns the filter of combined feed view  list
  instead of `undefined`.

* We start fetching messages from the latest message on app load.

* Messages in all messages data and Recent view are always synced.

* If combined feed view list is not cached, we don't track it's
  last pointer, effectively sending user to the latest unread
  message always .
This commit is contained in:
Aman Agrawal 2024-04-21 03:31:35 +00:00 committed by Tim Abbott
parent 2ccbb9bc00
commit 103c37f23a
31 changed files with 183 additions and 850 deletions

View File

@ -108,6 +108,7 @@ EXEMPT_FILES = make_set(
"web/src/emojisets.ts",
"web/src/favicon.ts",
"web/src/feedback_widget.ts",
"web/src/fetch_status.ts",
"web/src/flatpickr.ts",
"web/src/gear_menu.js",
"web/src/giphy.js",

View File

@ -11,6 +11,12 @@ async function get_stream_li(page: Page, stream_name: string): Promise<string> {
async function expect_home(page: Page): Promise<void> {
const message_list_id = await common.get_current_msg_list_id(page, true);
await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
visible: true,
});
// Assert that there is only one message list.
assert.equal((await page.$$(".message-list")).length, 1);
assert.strictEqual(await page.title(), "Combined feed - Zulip Dev - Zulip");
await common.check_messages_sent(page, message_list_id, [
["Verona > test", ["verona test a", "verona test b"]],
["Verona > other topic", ["verona other topic c"]],
@ -107,10 +113,6 @@ async function un_narrow(page: Page): Promise<void> {
await page.keyboard.press("Escape");
}
await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector(".message-list .message_row", {visible: true});
// Assert that there is only one message list.
assert.equal((await page.$$(".message-list")).length, 1);
assert.strictEqual(await page.title(), "Combined feed - Zulip Dev - Zulip");
}
async function un_narrow_by_clicking_org_icon(page: Page): Promise<void> {
@ -322,6 +324,7 @@ async function test_narrow_by_clicking_the_left_sidebar(page: Page): Promise<voi
await expect_all_direct_messages(page);
await un_narrow(page);
await expect_home(page);
}
async function arrow(page: Page, direction: "Up" | "Down"): Promise<void> {

View File

@ -206,7 +206,8 @@ export function initialize(): void {
const narrow_filter = narrow_state.filter();
let display_current_view;
if (narrow_state.is_message_feed_visible()) {
if (narrow_filter === undefined) {
assert(narrow_filter !== undefined);
if (narrow_filter.is_in_home()) {
display_current_view = $t({
defaultMessage: "Currently viewing your combined feed.",
});

View File

@ -12,7 +12,6 @@ import * as inbox_ui from "./inbox_ui";
import * as inbox_util from "./inbox_util";
import * as info_overlay from "./info_overlay";
import * as message_fetch from "./message_fetch";
import * as message_lists from "./message_lists";
import * as message_viewport from "./message_viewport";
import * as modals from "./modals";
import * as narrow from "./narrow";
@ -161,7 +160,6 @@ function do_hashchange_normal(from_reload) {
if (from_reload) {
blueslip.debug("We are narrowing as part of a reload.");
if (message_fetch.initial_narrow_pointer !== undefined) {
message_lists.home.pre_narrow_offset = message_fetch.initial_offset;
narrow_opts.then_select_id = message_fetch.initial_narrow_pointer;
narrow_opts.then_select_offset = message_fetch.initial_narrow_offset;
}

View File

@ -1241,7 +1241,7 @@ export function delete_topic(stream_id, topic_name, failures = 0) {
});
}
export function handle_narrow_deactivated() {
export function restore_edit_state_after_message_view_change() {
assert(message_lists.current !== undefined);
for (const [idx, elem] of currently_editing_messages) {
if (message_lists.current.get(idx) !== undefined) {

View File

@ -166,7 +166,6 @@ export function insert_new_messages(messages, sent_by_this_client) {
message_notifications.received_messages(messages);
stream_list.update_streams_sidebar();
pm_list.update_private_messages();
recent_view_ui.process_messages(messages);
}
export function update_messages(events) {
@ -532,7 +531,6 @@ export function update_messages(events) {
// propagated edits to be updated (since the topic edits can have
// changed the correct grouping of messages).
if (any_topic_edited || any_stream_changed) {
message_lists.home.update_muting_and_rerender();
// However, we don't need to rerender message_list if
// we just changed the narrow earlier in this function.
//
@ -542,8 +540,15 @@ export function update_messages(events) {
// edit. Doing so could save significant work, since most
// topic edits will not match the current topic narrow in
// large organizations.
if (!changed_narrow && message_lists.current?.narrowed) {
message_lists.current.update_muting_and_rerender();
for (const list of message_lists.all_rendered_message_lists()) {
if (changed_narrow && list === message_lists.current) {
// Avoid updating current message list if user switched to a different narrow and
// we don't want to preserver the rendered state for the current one.
continue;
}
list.view.rerender_messages(messages_to_rerender, any_message_content_edited);
}
} else {
// If the content of the message was edited, we do a special animation.

View File

@ -3,12 +3,10 @@ import $ from "jquery";
import {all_messages_data} from "./all_messages_data";
import * as blueslip from "./blueslip";
import * as channel from "./channel";
import {Filter} from "./filter";
import * as huddle_data from "./huddle_data";
import * as message_feed_loading from "./message_feed_loading";
import * as message_feed_top_notices from "./message_feed_top_notices";
import * as message_helper from "./message_helper";
import * as message_list_data from "./message_list_data";
import * as message_lists from "./message_lists";
import * as message_util from "./message_util";
import * as narrow_banner from "./narrow_banner";
@ -19,21 +17,17 @@ import * as stream_data from "./stream_data";
import * as stream_list from "./stream_list";
import * as ui_report from "./ui_report";
export let initial_pointer;
export let initial_offset;
let first_messages_fetch = true;
export let initial_narrow_pointer;
export let initial_narrow_offset;
let is_all_messages_data_loaded = false;
const consts = {
backfill_idle_time: 10 * 1000,
backfill_batch_size: 1000,
narrow_before: 50,
narrow_after: 50,
num_before_home_anchor: 200,
num_after_home_anchor: 200,
recent_view_initial_fetch_size: 400,
initial_backfill_fetch_size: 400,
maximum_initial_backfill_size: 4000,
narrowed_view_backward_batch_size: 100,
narrowed_view_forward_batch_size: 100,
recent_view_fetch_more_batch_size: 1000,
@ -54,13 +48,6 @@ function process_result(data, opts) {
// messages not tracked in unread.ts during this fetching process.
message_util.do_unread_count_updates(messages, true);
// If we're loading more messages into the home view, save them to
// the all_messages_data as well, as the message_lists.home is
// reconstructed from all_messages_data.
if (opts.msg_list === message_lists.home) {
all_messages_data.add_messages(messages);
}
if (messages.length !== 0) {
if (opts.msg_list) {
// Since this adds messages to the MessageList and renders MessageListView,
@ -69,21 +56,6 @@ function process_result(data, opts) {
} else {
opts.msg_list_data.add_messages(messages);
}
// To avoid non-contiguous blocks of data in recent view from
// message_lists.home and recent_view_message_list_data, we
// only process data from message_lists.home if we have found
// the newest message in message_lists.home. We check this via
// is_all_messages_data_loaded, to avoid unnecessary
// double-processing of the last batch of messages;
// is_all_messages_data_loaded is set via opts.cont, below.
if (
opts.is_recent_view_data ||
(opts.msg_list === message_lists.home && is_all_messages_data_loaded)
) {
const msg_list_data = opts.msg_list_data ?? opts.msg_list.data;
recent_view_ui.process_messages(messages, msg_list_data);
}
}
huddle_data.process_loaded_messages(messages);
@ -137,15 +109,6 @@ function get_messages_success(data, opts) {
found_oldest: data.found_oldest,
history_limited: data.history_limited,
});
if (opts.msg_list === message_lists.home) {
// When we update message_lists.home, we need to also update
// the fetch_status data structure for all_messages_data.
all_messages_data.fetch_status.finish_older_batch({
update_loading_indicator: false,
found_oldest: data.found_oldest,
history_limited: data.history_limited,
});
}
message_feed_top_notices.update_top_of_narrow_notices(opts.msg_list);
}
@ -154,14 +117,6 @@ function get_messages_success(data, opts) {
update_loading_indicator,
found_newest: data.found_newest,
});
if (opts.msg_list === message_lists.home) {
// When we update message_lists.home, we need to also update
// the fetch_status data structure for all_messages_data.
opts.fetch_again = all_messages_data.fetch_status.finish_newer_batch(data.messages, {
update_loading_indicator: false,
found_newest: data.found_newest,
});
}
}
if (opts.msg_list && opts.msg_list.narrowed && opts.msg_list !== message_lists.current) {
@ -255,16 +210,16 @@ export function load_messages(opts, attempt = 1) {
// data.narrow = opts.msg_list.data.filter.public_terms()
//
// But support for the all_messages_data sharing of data with
// message_lists.home and the (hacky) page_params.narrow feature
// the combined feed view and the (hacky) page_params.narrow feature
// requires a somewhat ugly bundle of conditionals.
if (msg_list_data.filter.is_in_home()) {
if (page_params.narrow_stream !== undefined) {
data.narrow = JSON.stringify(page_params.narrow);
}
// Otherwise, we don't pass narrow for message_lists.home; this is
// required because it shares its data with all_msg_list, and
// so we need the server to send us message history from muted
// streams and topics even though message_lists.home's in:home
// Otherwise, we don't pass narrow for the combined feed view; this is
// required to display messages if their muted status changes without a new
// network request, and so we need the server to send us message history from muted
// streams and topics even though the combined feed view's in:home
// operators will filter those.
} else {
let terms = msg_list_data.filter.public_terms();
@ -280,11 +235,6 @@ export function load_messages(opts, attempt = 1) {
msg_list_data.fetch_status.start_older_batch({
update_loading_indicator,
});
if (opts.msg_list === message_lists.home) {
all_messages_data.fetch_status.start_older_batch({
update_loading_indicator,
});
}
}
if (opts.num_after > 0) {
@ -293,11 +243,6 @@ export function load_messages(opts, attempt = 1) {
msg_list_data.fetch_status.start_newer_batch({
update_loading_indicator,
});
if (opts.msg_list === message_lists.home) {
all_messages_data.fetch_status.start_newer_batch({
update_loading_indicator,
});
}
}
data.client_gravatar = true;
@ -416,12 +361,8 @@ export function load_messages_for_narrow(opts) {
});
}
export function get_backfill_anchor(msg_list) {
const oldest_msg =
msg_list === message_lists.home
? all_messages_data.first_including_muted()
: msg_list.data.first_including_muted();
export function get_backfill_anchor(msg_list_data) {
const oldest_msg = msg_list_data.first_including_muted();
if (oldest_msg) {
return oldest_msg.id;
}
@ -430,10 +371,7 @@ export function get_backfill_anchor(msg_list) {
}
export function get_frontfill_anchor(msg_list) {
const last_msg =
msg_list === message_lists.home
? all_messages_data.last_including_muted()
: msg_list.data.last_including_muted();
const last_msg = msg_list.data.last_including_muted();
if (last_msg) {
return last_msg.id;
@ -469,15 +407,15 @@ export function maybe_load_older_messages(opts) {
// This function gets called when you scroll to the top
// of your window, and you want to get messages older
// than what the browsers originally fetched.
const msg_list = opts.msg_list;
if (!msg_list.data.fetch_status.can_load_older_messages()) {
const msg_list_data = opts.msg_list_data ?? opts.msg_list.data;
if (!msg_list_data.fetch_status.can_load_older_messages()) {
// We may already be loading old messages or already
// got the oldest one.
return;
}
do_backfill({
msg_list,
...opts,
num_before: opts.recent_view
? consts.recent_view_fetch_more_batch_size
: consts.narrowed_view_backward_batch_size,
@ -485,14 +423,18 @@ export function maybe_load_older_messages(opts) {
}
export function do_backfill(opts) {
const msg_list = opts.msg_list;
const anchor = get_backfill_anchor(msg_list);
const msg_list_data = opts.msg_list_data ?? opts.msg_list.data;
const anchor = get_backfill_anchor(msg_list_data);
// `load_messages` behaves differently for `msg_list` and `msg_list_data` as
// parameters as which one is passed affects the behavior of the function.
// So, we need to need them as they were provided to us.
load_messages({
anchor,
num_before: opts.num_before,
num_after: 0,
msg_list,
msg_list: opts.msg_list,
msg_list_data: opts.msg_list_data,
cont() {
if (opts.cont) {
opts.cont();
@ -541,15 +483,13 @@ export function start_backfilling_messages() {
onIdle() {
do_backfill({
num_before: consts.backfill_batch_size,
msg_list: message_lists.home,
msg_list_data: all_messages_data,
});
},
});
}
export function set_initial_pointer_and_offset({pointer, offset, narrow_pointer, narrow_offset}) {
initial_pointer = pointer;
initial_offset = offset;
export function set_initial_pointer_and_offset({narrow_pointer, narrow_offset}) {
initial_narrow_pointer = narrow_pointer;
initial_narrow_offset = narrow_offset;
}
@ -557,36 +497,19 @@ export function set_initial_pointer_and_offset({pointer, offset, narrow_pointer,
export function initialize(finished_initial_fetch) {
// get the initial message list
function load_more(data) {
// If we haven't selected a message in the home view yet, and
// the home view isn't empty, we select the anchor message here.
if (message_lists.home.selected_id() === -1 && !message_lists.home.visibly_empty()) {
// We fall back to the closest selected id, as the user
// may have removed a stream from the home view while we
// were loading data.
message_lists.home.select_id(data.anchor, {
then_scroll: true,
use_closest: true,
target_scroll_offset: initial_offset,
});
}
if (data.found_newest) {
// Mark that we've finishing loading all the way to the
// present in the all_messages_data data set. At this
// time, it's safe to call recent_view_ui.process_messages
// with all the messages in our cache.
is_all_messages_data_loaded = true;
recent_view_ui.process_messages(all_messages_data.all_messages(), all_messages_data);
if (page_params.is_spectator) {
// Since for spectators, this is the main fetch, we
// hide the Recent Conversations loading indicator here.
recent_view_ui.hide_loading_indicator();
}
if (first_messages_fetch) {
// See server_events.js for this callback.
// Start processing server events.
finished_initial_fetch();
recent_view_ui.hide_loading_indicator();
first_messages_fetch = false;
}
if (data.found_oldest) {
return;
}
if (all_messages_data.num_items() >= consts.maximum_initial_backfill_size) {
start_backfilling_messages();
return;
}
@ -596,72 +519,25 @@ export function initialize(finished_initial_fetch) {
//
// But we do it with a bit of delay, to reduce risk that we
// hit rate limits with these backfills.
const latest_id = data.messages.at(-1).id;
const oldest_id = data.messages.at(0).id;
setTimeout(() => {
load_messages({
anchor: latest_id,
num_before: 0,
num_after: consts.catch_up_batch_size,
msg_list: message_lists.home,
anchor: oldest_id,
num_before: consts.catch_up_batch_size,
num_after: 0,
msg_list_data: all_messages_data,
cont: load_more,
});
}, consts.catch_up_backfill_delay);
}
let anchor;
if (initial_pointer !== undefined) {
// If we're doing a server-initiated reload, similar to a
// near: narrow query, we want to select a specific message.
anchor = initial_pointer;
} else {
// Otherwise, we should just use the first unread message in
// the user's unmuted history as our anchor.
anchor = "first_unread";
}
load_messages({
anchor,
num_before: consts.num_before_home_anchor,
num_after: consts.num_after_home_anchor,
msg_list: message_lists.home,
cont: load_more,
// Since `all_messages_data` contains continuous message history
// which always contains the latest message, it makes sense for
// Recent view to display the same data and be in sync.
all_messages_data.set_add_messages_callback((messages) => {
recent_view_ui.process_messages(messages, all_messages_data);
});
if (page_params.is_spectator) {
// Since spectators never have old unreads, we can skip the
// hacky fetch below for them (which would just waste resources).
// This optimization requires a bit of duplicated loading
// indicator code, here and hiding logic in hide_more.
recent_view_ui.show_loading_indicator();
return;
}
// In addition to the algorithm above, which is designed to ensure
// that we fetch all message history eventually starting with the
// first unread message, we also need to ensure that the Recent
// Topics page contains the very most recent threads on page load.
//
// Long term, we'll want to replace this with something that's
// more performant (i.e. avoids this unnecessary extra fetch the
// results of which are basically discarded) and better represents
// more than a few hundred messages' history, but this strategy
// allows "Recent Conversations" to always show current data (with gaps)
// on page load; the data will be complete once the algorithm
// above catches up to present.
//
// (Users will see a weird artifact where Recent Conversations has a gap
// between E.g. 6 days ago and 37 days ago while the catchup
// process runs, so this strategy still results in problematic
// visual artifacts shortly after page load; just more forgivable
// ones).
//
// We only initialize MessageListData here, since we don't
// want update the UI and confuse the functions in MessageList.
// Recent view can handle the UI updates itself.
const recent_view_message_list_data = new message_list_data.MessageListData({
filter: new Filter([{operator: "in", operand: "home"}]),
excludes_muted_topics: true,
});
// TODO: Ideally we'd have loading indicators for Recent Conversations
// at both top and bottom be managed by load_messages, but that
// likely depends on other reorganizations of the early loading
@ -669,10 +545,9 @@ export function initialize(finished_initial_fetch) {
recent_view_ui.show_loading_indicator();
load_messages({
anchor: "newest",
num_before: consts.recent_view_initial_fetch_size,
num_before: consts.initial_backfill_fetch_size,
num_after: 0,
msg_list_data: recent_view_message_list_data,
is_recent_view_data: true,
cont: recent_view_ui.hide_loading_indicator,
msg_list_data: all_messages_data,
cont: load_more,
});
}

View File

@ -2,13 +2,10 @@ import autosize from "autosize";
import $ from "jquery";
import assert from "minimalistic-assert";
import {all_messages_data} from "./all_messages_data";
import * as blueslip from "./blueslip";
import {Filter} from "./filter";
import {MessageListData} from "./message_list_data";
import * as message_list_tooltips from "./message_list_tooltips";
import {MessageListView} from "./message_list_view";
import * as message_lists from "./message_lists";
import * as narrow_banner from "./narrow_banner";
import * as narrow_state from "./narrow_state";
import {page_params} from "./page_params";
@ -59,8 +56,7 @@ export class MessageList {
// DOM.
this.view = new MessageListView(this, collapse_messages, opts.is_node_test);
// Whether this is a narrowed message list. The only message
// list that is not is the home_msg_list global.
// If this message list is not for the global feed.
this.narrowed = !this.data.filter.is_in_home();
// Keeps track of whether the user has done a UI interaction,
@ -72,6 +68,17 @@ export class MessageList {
// the user. Possibly this can be unified in some nice way.
this.reading_prevented = false;
// Whether this message list's is preserved in the DOM even
// when viewing other views -- a valuable optimization for
// fast toggling between the combined feed and other views,
// which we enable only when that is the user's home view.
//
// This is intentionally not live-updated when web_home_view
// changes, since it's easier to reason about if this
// optimization is active or not for an entire session.
this.preserve_rendered_state =
user_settings.web_home_view === "all_messages" && !this.narrowed;
return this;
}
@ -110,14 +117,14 @@ export class MessageList {
render_info = this.append_to_view(bottom_messages, opts);
}
if (this.narrowed && !this.visibly_empty()) {
if (!this.visibly_empty()) {
// If adding some new messages to the message tables caused
// our current narrow to no longer be empty, hide the empty
// feed placeholder text.
narrow_banner.hide_empty_narrow_message();
}
if (this.narrowed && !this.visibly_empty() && this.selected_id() === -1) {
if (!this.visibly_empty() && this.selected_id() === -1) {
// The message list was previously empty, but now isn't
// due to adding these messages, and we need to select a
// message. Regardless of whether the messages are new or
@ -468,19 +475,6 @@ export class MessageList {
}
update_muting_and_rerender() {
// For the home message list, we need to re-initialize
// _all_items for stream muting/topic unmuting from
// all_messages_data, since otherwise unmuting a previously
// muted stream won't work.
//
// "in-home" filter doesn't included muted stream messages, so we
// need to repopulate the message list with all messages to include
// the previous messages in muted streams so that update_items_for_muting works.
if (this.data.filter.is_in_home()) {
this.data.clear();
this.data.add_messages(all_messages_data.all_messages());
}
this.data.update_items_for_muting();
// We need to rerender whether or not the narrow hides muted
// topics, because we need to update recipient bars for topics
@ -529,12 +523,3 @@ export class MessageList {
return this.data.get_last_message_sent_by_me();
}
}
export function initialize() {
/* Create home_msg_list and register it. */
const home_msg_list = new MessageList({
filter: new Filter([{operator: "in", operand: "home"}]),
excludes_muted_topics: true,
});
message_lists.set_home(home_msg_list);
}

View File

@ -41,6 +41,8 @@ export class MessageListData {
// there are no messages matching the current filter.
_selected_id: number;
predicate?: (message: Message) => boolean;
// This is a callback that is called when messages are added to the message list.
add_messages_callback?: (messages: Message[]) => void;
// MessageListData is a core data structure for keeping track of a
// contiguous block of messages matching a given narrow that can
@ -60,6 +62,10 @@ export class MessageListData {
this._selected_id = -1;
}
set_add_messages_callback(callback: () => void): void {
this.add_messages_callback = callback;
}
all_messages(): Message[] {
return this._items;
}
@ -333,6 +339,10 @@ export class MessageListData {
bottom_messages = this.append(bottom_messages);
}
if (this.add_messages_callback) {
this.add_messages_callback(messages);
}
const info = {
top_messages,
bottom_messages,

View File

@ -1,5 +1,4 @@
import $ from "jquery";
import assert from "minimalistic-assert";
import * as blueslip from "./blueslip";
import * as inbox_util from "./inbox_util";
@ -33,6 +32,7 @@ export type SelectIdOpts = {
export type MessageList = {
id: number;
preserve_rendered_state: boolean;
view: MessageListView;
selected_id: () => number;
selected_row: () => JQuery;
@ -52,39 +52,37 @@ export type MessageList = {
) => RenderInfo | undefined;
};
export let home: MessageList | undefined;
export let current: MessageList | undefined;
export const rendered_message_lists = new Map<number, MessageList>();
export function set_current(msg_list: MessageList | undefined): void {
// NOTE: Use update_current_message_list instead of this function.
// NOTE: Strictly used for mocking in node tests.
// Use `update_current_message_list` instead in production.
current = msg_list;
}
export function update_current_message_list(msg_list: MessageList | undefined): void {
if (msg_list !== home) {
home?.view.$list.removeClass("focused-message-list");
if (current && !current.preserve_rendered_state) {
// Remove the current message list from the DOM.
current.view.$list.remove();
rendered_message_lists.delete(current.id);
} else {
current?.view.$list.removeClass("focused-message-list");
}
if (current !== home) {
// Remove old msg list from DOM.
current?.view.$list.remove();
current = msg_list;
if (current !== undefined) {
rendered_message_lists.set(current.id, current);
current.view.$list.addClass("focused-message-list");
}
set_current(msg_list);
current?.view.$list.addClass("focused-message-list");
}
export function set_home(msg_list: MessageList): void {
home = msg_list;
}
export function all_rendered_message_lists(): MessageList[] {
assert(home !== undefined);
const rendered_message_lists = [home];
if (current !== undefined && current !== home) {
rendered_message_lists.push(current);
return [...rendered_message_lists.values()];
}
return rendered_message_lists;
export function add_rendered_message_list(msg_list: MessageList): void {
rendered_message_lists.set(msg_list.id, msg_list);
}
export function all_rendered_row_for_message_id(message_id: number): JQuery {
@ -112,7 +110,11 @@ export function update_recipient_bar_background_color(): void {
}
export function save_pre_narrow_offset_for_reload(): void {
if (current === undefined) {
// Only save the pre_narrow_offset if the message list will be cached if user
// switches to a different narrow, otherwise the pre_narrow_offset would just be lost when
// user switches to a different narrow. In case of a reload, offset for the current
// message is captured and restored by `reload` library.
if (!current?.preserve_rendered_state) {
return;
}

View File

@ -59,7 +59,6 @@ import * as util from "./util";
import * as widgetize from "./widgetize";
const LARGER_THAN_MAX_MESSAGE_ID = 10000000000000000;
export let has_visited_all_messages = false;
export function reset_ui_state() {
// Resets the state of various visual UI elements that are
@ -98,7 +97,7 @@ export function activate(raw_terms, opts) {
raw_terms: Narrowing/search terms; used to construct
a Filter object that decides which messages belong in the
view. Required (See the above note on how `message_lists.home` works)
view.
All other options are encoded via the `opts` dictionary:
@ -128,14 +127,14 @@ export function activate(raw_terms, opts) {
raw_terms = [{operator: "is", operand: "home"}];
}
const filter = new Filter(raw_terms);
const is_narrowed_to_all_messages_view = narrow_state.filter()?.is_in_home();
if (has_visited_all_messages && is_narrowed_to_all_messages_view && filter.is_in_home()) {
const is_combined_feed_global_view = filter.is_in_home();
const is_narrowed_to_combined_feed_view = narrow_state.filter()?.is_in_home();
if (is_narrowed_to_combined_feed_view && is_combined_feed_global_view) {
// If we're already looking at the combined feed, exit without doing any work.
return;
}
if (filter.is_in_home() && message_scroll_state.actively_scrolling) {
if (is_combined_feed_global_view && message_scroll_state.actively_scrolling) {
// TODO: Figure out why puppeteer test for this fails when run for narrows
// other than `Combined feed`.
@ -149,8 +148,7 @@ export function activate(raw_terms, opts) {
}
// Use to determine if user read any unread messages outside the combined feed.
// BUG: This doesn't check for the combined feed?
const was_narrowed_already = narrow_state.filter() !== undefined;
const was_narrowed_already = message_lists.current?.narrowed;
// Since narrow.activate is called directly from various
// places in our code without passing through hashchange,
@ -161,7 +159,7 @@ export function activate(raw_terms, opts) {
// TODO: is:home is currently not permitted for spectators
// because they can't mute things; maybe that's the wrong
// policy?
!filter.is_in_home() &&
!is_combined_feed_global_view &&
raw_terms.some(
(raw_term) => !hash_parser.allowed_web_public_narrows.includes(raw_term.operator),
)
@ -424,11 +422,21 @@ export function activate(raw_terms, opts) {
const excludes_muted_topics = filter.excludes_muted_topics();
// Check if we already have a rendered message list for the `filter`.
// TODO: If we add a message list other than `is_in_home` to be save as rendered,
// we need to add a `is_equal` function to `Filter` to compare the filters.
let msg_list;
if (filter.is_in_home()) {
has_visited_all_messages = true;
msg_list = message_lists.home;
} else {
let restore_rendered_list = false;
for (const list of message_lists.all_rendered_message_lists()) {
if (is_combined_feed_global_view && list.preserve_rendered_state) {
assert(list.data.filter.is_in_home());
msg_list = list;
restore_rendered_list = true;
break;
}
}
if (!restore_rendered_list) {
let msg_data = new MessageListData({
filter,
excludes_muted_topics,
@ -461,7 +469,6 @@ export function activate(raw_terms, opts) {
}
assert(msg_list !== undefined);
narrow_state.set_has_shown_message_list_view();
// Show the new set of messages. It is important to set message_lists.current to
// the view right as it's being shown, because we rely on message_lists.current
// being shown for deciding when to condense messages.
@ -473,7 +480,7 @@ export function activate(raw_terms, opts) {
let select_immediately;
let select_opts;
let then_select_offset;
if (filter.is_in_home()) {
if (restore_rendered_list) {
select_immediately = true;
select_opts = {
empty_ok: true,
@ -502,7 +509,7 @@ export function activate(raw_terms, opts) {
message_lists.current.resume_reading();
// Reset the collapsed status of messages rows.
condense.condense_and_collapse(message_lists.current.view.$list.find(".message_row"));
message_edit.handle_narrow_deactivated();
message_edit.restore_edit_state_after_message_view_change();
widgetize.set_widgets_for_list();
message_feed_top_notices.update_top_of_narrow_notices(msg_list);

View File

@ -92,7 +92,7 @@ function pick_empty_narrow_banner(): NarrowBannerData {
const current_filter = narrow_state.filter();
if (current_filter === undefined) {
if (current_filter === undefined || current_filter.is_in_home()) {
return default_banner;
}

View File

@ -8,15 +8,9 @@ import * as stream_data from "./stream_data";
import type {StreamSubscription} from "./sub_store";
import * as unread from "./unread";
export let has_shown_message_list_view = false;
export function filter(): Filter | undefined {
// `Combined feed`, `Recent Conversations` and `Inbox` return undefined;
if (message_lists.current === undefined || message_lists.current.data.filter.is_in_home()) {
return undefined;
}
return message_lists.current.data.filter;
// `Recent Conversations` and `Inbox` return undefined;
return message_lists.current?.data.filter;
}
export function search_terms(current_filter: Filter | undefined = filter()): NarrowTerm[] {
@ -381,7 +375,3 @@ export function is_for_stream_id(stream_id: number, filter?: Filter): boolean {
return stream_id === narrow_sub.stream_id;
}
export function set_has_shown_message_list_view(): void {
has_shown_message_list_view = true;
}

View File

@ -403,14 +403,7 @@ export function hide_loading_indicator() {
}
export function process_messages(messages, msg_list_data) {
// This code path processes messages from 3 sources:
// 1. Newly sent messages from the server_events system. This is safe to
// process because we always will have the latest previously sent messages.
// 2. Messages in all_messages_data, the main cache of contiguous
// message history that the client maintains.
// 3. Latest messages fetched specifically for Recent view when
// the browser first loads. We will be able to remove this once
// all_messages_data is fetched in a better order.
// Always synced with messages in all_messages_data.
let conversation_data_updated = false;
if (messages.length > 0) {

View File

@ -26,7 +26,7 @@ function call_reload_hooks() {
}
}
function preserve_state(send_after_reload, save_pointer, save_compose) {
function preserve_state(send_after_reload, save_compose) {
if (!localstorage.supported()) {
// If local storage is not supported by the browser, we can't
// save the browser's position across reloads (since there's
@ -68,23 +68,7 @@ function preserve_state(send_after_reload, save_pointer, save_compose) {
}
}
if (save_pointer) {
const pointer = message_lists.home.selected_id();
if (pointer !== -1) {
url += "+pointer=" + pointer;
}
}
if (message_lists.current === message_lists.home) {
const $row = message_lists.home.selected_row();
if ($row.length > 0) {
url += "+offset=" + $row.get_offset_to_window().top;
}
} else if (message_lists.current !== undefined) {
url += "+offset=" + message_lists.home.pre_narrow_offset;
// narrow_state.filter() is not undefined, so this is the current
// narrowed message list.
if (message_lists.current !== undefined) {
const narrow_pointer = message_lists.current.selected_id();
if (narrow_pointer !== -1) {
url += "+narrow_pointer=" + narrow_pointer;
@ -93,8 +77,6 @@ function preserve_state(send_after_reload, save_pointer, save_compose) {
if ($narrow_row.length > 0) {
url += "+narrow_offset=" + $narrow_row.get_offset_to_window().top;
}
} else {
url += "+offset=" + message_lists.home.pre_narrow_offset;
}
url += hash_util.build_reload_url();
@ -146,7 +128,7 @@ function delete_stale_tokens(ls) {
);
}
function do_reload_app(send_after_reload, save_pointer, save_compose, message_html) {
function do_reload_app(send_after_reload, save_compose, message_html) {
if (reload_state.is_in_progress()) {
blueslip.log("do_reload_app: Doing nothing since reload_in_progress");
return;
@ -154,7 +136,7 @@ function do_reload_app(send_after_reload, save_pointer, save_compose, message_ht
// TODO: we should completely disable the UI here
try {
preserve_state(send_after_reload, save_pointer, save_compose);
preserve_state(send_after_reload, save_compose);
} catch (error) {
blueslip.error("Failed to preserve state", undefined, error);
}
@ -199,13 +181,12 @@ function do_reload_app(send_after_reload, save_pointer, save_compose, message_ht
export function initiate({
immediate = false,
save_pointer = true,
save_compose = true,
send_after_reload = false,
message_html = "Reloading ...",
}) {
if (immediate) {
do_reload_app(send_after_reload, save_pointer, save_compose, message_html);
do_reload_app(send_after_reload, save_compose, message_html);
}
if (reload_state.is_pending() || reload_state.is_in_progress()) {
@ -242,7 +223,7 @@ export function initiate({
let compose_started_handler;
function reload_from_idle() {
do_reload_app(false, save_pointer, save_compose, message_html);
do_reload_app(false, save_compose, message_html);
}
// Make sure we always do a reload eventually after
@ -296,7 +277,6 @@ reload_state.set_csrf_failed_handler(() => {
initiate({
immediate: true,
save_pointer: true,
save_compose: true,
});
});

View File

@ -68,13 +68,15 @@ export function initialize() {
}
}
const pointer = Number.parseInt(vars.pointer, 10);
const offset = Number.parseInt(vars.offset, 10);
// We only restore pointer and offset for the current narrow, even if there are narrows that
// were cached before the reload, they are no longer cached after the reload. We could possibly
// store the pointer and offset for these narrows but it might lead to a confusing experience if
// user gets back to these narrow much later (maybe days) and finds them at a random position in
// narrow which they didn't navigate to while they were trying to just get to the latest unread
// message in that narrow which will now take more effort to find.
const narrow_pointer = Number.parseInt(vars.narrow_pointer, 10);
const narrow_offset = Number.parseInt(vars.narrow_offset, 10);
message_fetch.set_initial_pointer_and_offset({
pointer: Number.isNaN(pointer) ? undefined : pointer,
offset: Number.isNaN(offset) ? undefined : offset,
narrow_pointer: Number.isNaN(narrow_pointer) ? undefined : narrow_pointer,
narrow_offset: Number.isNaN(narrow_offset) ? undefined : narrow_offset,
});

View File

@ -5,7 +5,6 @@ import * as blueslip from "./blueslip";
import * as channel from "./channel";
import * as echo from "./echo";
import * as message_events from "./message_events";
import * as message_lists from "./message_lists";
import {page_params} from "./page_params";
import * as reload from "./reload";
import * as reload_state from "./reload_state";
@ -128,10 +127,6 @@ function get_events_success(events) {
}
}
if (message_lists.home.selected_id() === -1 && !message_lists.home.visibly_empty()) {
message_lists.home.select_id(message_lists.home.first().id, {then_scroll: false});
}
if (update_message_events.length !== 0) {
try {
message_events.update_messages(update_message_events);
@ -221,7 +216,6 @@ function get_events({dont_block = false} = {}) {
event_queue_expired = true;
reload.initiate({
immediate: true,
save_pointer: false,
save_compose: true,
});
return;

View File

@ -175,7 +175,6 @@ export function dispatch_normal_event(event) {
case "web_reload_client": {
const reload_options = {
save_pointer: true,
save_compose: true,
message_html: "The application has been updated; reloading!",
};

View File

@ -62,7 +62,6 @@ export function send_message(request, on_success, error) {
// The error might be due to the server changing
reload.initiate({
immediate: true,
save_pointer: true,
save_compose: true,
send_after_reload: true,
});

View File

@ -14,6 +14,7 @@ import * as activity from "./activity";
import * as activity_ui from "./activity_ui";
import * as add_stream_options_popover from "./add_stream_options_popover";
import * as alert_words from "./alert_words";
import {all_messages_data} from "./all_messages_data";
import * as audible_notifications from "./audible_notifications";
import * as blueslip from "./blueslip";
import * as bot_data from "./bot_data";
@ -63,7 +64,6 @@ import * as markdown_config from "./markdown_config";
import * as message_actions_popover from "./message_actions_popover";
import * as message_edit_history from "./message_edit_history";
import * as message_fetch from "./message_fetch";
import * as message_list from "./message_list";
import * as message_list_hover from "./message_list_hover";
import * as message_list_tooltips from "./message_list_tooltips";
import * as message_lists from "./message_lists";
@ -726,7 +726,6 @@ export function initialize_everything(state_data) {
realm_logo.initialize();
message_lists.initialize();
message_list.initialize();
recent_view_ui.initialize({
on_click_participant(avatar_element, participant_user_id) {
const user = people.get_by_user_id(participant_user_id);
@ -736,7 +735,7 @@ export function initialize_everything(state_data) {
on_mark_topic_as_read: unread_ops.mark_topic_as_read,
maybe_load_older_messages() {
message_fetch.maybe_load_older_messages({
msg_list: message_lists.home,
msg_list_data: all_messages_data,
recent_view: true,
});
},

View File

@ -47,10 +47,11 @@ export function handle_topic_updates(user_topic_event) {
}
setTimeout(0, () => {
/* Rerender the combined feed view if necessary, but defer until after
* the browser has rendered the DOM updates scheduled above. */
if (message_lists.current !== message_lists.home) {
message_lists.home.update_muting_and_rerender();
// Defer updates for any background-rendered messages lists until the visible one has been updated.
for (const list of message_lists.all_rendered_message_lists()) {
if (list.preserve_rendered_state && message_lists.current !== list) {
list.update_muting_and_rerender();
}
}
});
}

View File

@ -72,9 +72,7 @@ export function show(opts: {
complete_rerender: () => void;
is_recent_view?: boolean;
}): void {
if (narrow_state.has_shown_message_list_view) {
message_lists.save_pre_narrow_offset_for_reload();
}
if (opts.is_visible()) {
// If we're already visible, E.g. because the user hit Esc

View File

@ -92,6 +92,7 @@ const user_groups = mock_esm("../src/user_groups");
const user_group_edit = mock_esm("../src/user_group_edit");
const overlays = mock_esm("../src/overlays");
mock_esm("../src/giphy");
const {Filter} = zrequire("filter");
const electron_bridge = set_global("electron_bridge", {});
@ -101,19 +102,17 @@ message_lists.current = {
rerender_view: noop,
data: {
get_messages_sent_by_user: () => [],
filter: {
is_in_home: () => true,
},
filter: new Filter([]),
},
};
message_lists.home = {
const cached_message_list = {
get_row: noop,
rerender_view: noop,
data: {
get_messages_sent_by_user: () => [],
},
};
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
message_lists.all_rendered_message_lists = () => [cached_message_list, message_lists.current];
// page_params is highly coupled to dispatching now
page_params.test_suite = false;
@ -783,7 +782,6 @@ run_test("web_reload_client", ({override}) => {
dispatch(event);
assert.equal(stub.num_calls, 1);
const args = stub.get_args("options");
assert.equal(args.options.save_pointer, true);
assert.equal(args.options.immediate, true);
});
@ -890,13 +888,16 @@ run_test("user_settings", ({override}) => {
message_lists.current.rerender = () => {
called = true;
};
override(message_lists.home, "rerender", noop);
let called_for_cached_msg_list = false;
cached_message_list.rerender = () => {
called_for_cached_msg_list = true;
};
event = event_fixtures.user_settings__twenty_four_hour_time;
user_settings.twenty_four_hour_time = false;
dispatch(event);
assert_same(user_settings.twenty_four_hour_time, true);
assert_same(called, true);
assert_same(called_for_cached_msg_list, true);
event = event_fixtures.user_settings__translate_emoticons;
user_settings.translate_emoticons = false;

View File

@ -41,14 +41,15 @@ message_lists.current = {
},
change_message_id: noop,
};
message_lists.home = {
const home_msg_list = {
view: {
rerender_messages: noop,
change_message_id: noop,
},
preserver_rendered_state: true,
change_message_id: noop,
};
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
message_lists.all_rendered_message_lists = () => [home_msg_list, message_lists.current];
const echo = zrequire("echo");
const people = zrequire("people");
@ -76,7 +77,7 @@ run_test("process_from_server for un-echoed messages", () => {
run_test("process_from_server for differently rendered messages", ({override}) => {
let messages_to_rerender = [];
override(message_lists.home.view, "rerender_messages", (msgs) => {
override(home_msg_list.view, "rerender_messages", (msgs) => {
messages_to_rerender = msgs;
});
@ -193,13 +194,13 @@ run_test("build_display_recipient", () => {
});
run_test("update_message_lists", () => {
message_lists.home.view = {};
home_msg_list.view = {};
const stub = make_stub();
const view_stub = make_stub();
message_lists.home.change_message_id = stub.f;
message_lists.home.view.change_message_id = view_stub.f;
home_msg_list.change_message_id = stub.f;
home_msg_list.view.change_message_id = view_stub.f;
echo.update_message_lists({old_id: 401, new_id: 402});

View File

@ -25,7 +25,6 @@ const message_lists = mock_esm("../src/message_lists");
const message_notifications = mock_esm("../src/message_notifications");
const message_util = mock_esm("../src/message_util");
const pm_list = mock_esm("../src/pm_list");
const recent_view_data = mock_esm("../src/recent_view_data");
const stream_list = mock_esm("../src/stream_list");
const unread_ops = mock_esm("../src/unread_ops");
const unread_ui = mock_esm("../src/unread_ui");
@ -39,8 +38,7 @@ message_lists.current = {
},
},
};
message_lists.home = message_lists.current;
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
message_lists.all_rendered_message_lists = () => [message_lists.current];
// And we will also test some real code, of course.
const message_events = zrequire("message_events");
@ -101,7 +99,6 @@ run_test("insert_message", ({override}) => {
helper.redirect(message_notifications, "received_messages");
helper.redirect(message_util, "add_new_messages_data");
helper.redirect(message_util, "add_new_messages");
helper.redirect(recent_view_data, "process_message");
helper.redirect(stream_list, "update_streams_sidebar");
helper.redirect(unread_ops, "process_visible");
helper.redirect(unread_ui, "update_unread_counts");
@ -116,12 +113,10 @@ run_test("insert_message", ({override}) => {
[huddle_data, "process_loaded_messages"],
[message_util, "add_new_messages_data"],
[message_util, "add_new_messages"],
[message_util, "add_new_messages"],
[unread_ui, "update_unread_counts"],
[unread_ops, "process_visible"],
[message_notifications, "received_messages"],
[stream_list, "update_streams_sidebar"],
[recent_view_data, "process_message"],
]);
// Despite all of our stubbing/mocking, the call to

View File

@ -59,8 +59,7 @@ const message_viewport = mock_esm("../src/message_viewport");
const unread_ui = mock_esm("../src/unread_ui");
message_lists.current = {view: {}};
message_lists.home = {view: {}};
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
message_lists.all_rendered_message_lists = () => [message_lists.current];
const message_store = zrequire("message_store");
const stream_data = zrequire("stream_data");
@ -107,7 +106,6 @@ run_test("unread_ops", ({override}) => {
// Ignore these interactions for now:
override(message_lists.current.view, "show_message_as_read", noop);
override(message_lists.home.view, "show_message_as_read", noop);
override(desktop_notifications, "close_notification", noop);
override(unread_ui, "update_unread_counts", noop);
override(unread_ui, "notify_messages_remain_unread", noop);

View File

@ -14,7 +14,7 @@ const pm_list = mock_esm("../src/pm_list");
const stream_list = mock_esm("../src/stream_list");
const unread_ui = mock_esm("../src/unread_ui");
message_lists.current = {};
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
message_lists.all_rendered_message_lists = () => [message_lists.current];
const people = zrequire("people");
const message_events = zrequire("message_events");
@ -95,7 +95,6 @@ run_test("update_messages", () => {
rendered_mgs = msgs_to_rerender;
assert.equal(message_content_edited, true);
};
message_lists.home = message_lists.current;
const side_effects = [
[message_edit, "end_message_edit"],

View File

@ -1,490 +0,0 @@
"use strict";
const {strict: assert} = require("assert");
const _ = require("lodash");
const {mock_esm, set_global, zrequire} = require("./lib/namespace");
const {run_test, noop} = require("./lib/test");
const $ = require("./lib/zjquery");
const {page_params} = require("./lib/zpage_params");
set_global("document", "document-stub");
function MessageListView() {
return {
maybe_rerender: noop,
append: noop,
prepend: noop,
};
}
mock_esm("../src/message_list_view", {
MessageListView,
});
mock_esm("../src/recent_view_ui", {
process_messages: noop,
show_loading_indicator: noop,
hide_loading_indicator: noop,
set_oldest_message_date: noop,
});
mock_esm("../src/ui_report", {
hide_error: noop,
});
const channel = mock_esm("../src/channel");
const message_helper = mock_esm("../src/message_helper");
const message_lists = mock_esm("../src/message_lists");
const message_util = mock_esm("../src/message_util");
const stream_list = mock_esm("../src/stream_list", {
maybe_scroll_narrow_into_view() {},
});
mock_esm("../src/message_feed_top_notices", {
update_top_of_narrow_notices() {},
});
mock_esm("../src/message_feed_loading", {
show_loading_older: noop,
hide_loading_older: noop,
show_loading_newer: noop,
hide_loading_newer: noop,
});
set_global("document", "document-stub");
const message_fetch = zrequire("message_fetch");
const {all_messages_data} = zrequire("all_messages_data");
const {Filter} = zrequire("../src/filter");
const message_list = zrequire("message_list");
const people = zrequire("people");
const alice = {
email: "alice@example.com",
user_id: 7,
full_name: "Alice",
};
people.add_active_user(alice);
function make_home_msg_list() {
const filter = new Filter([]);
const list = new message_list.MessageList({
filter,
});
return list;
}
function reset_lists() {
message_lists.home = make_home_msg_list();
message_lists.current = message_lists.home;
all_messages_data.clear();
}
function config_fake_channel(conf) {
const self = {};
let called;
let called_with_newest_flag = false;
channel.get = (opts) => {
assert.equal(opts.url, "/json/messages");
// There's a separate call with anchor="newest" that happens
// unconditionally; do basic verification of that call.
if (opts.data.anchor === "newest") {
assert.ok(!called_with_newest_flag, "Only one 'newest' call allowed");
called_with_newest_flag = true;
assert.equal(opts.data.num_after, 0);
return;
}
assert.ok(!called || conf.can_call_again, "only use this for one call");
if (!conf.can_call_again) {
assert.equal(self.success, undefined);
}
assert.deepEqual(opts.data, conf.expected_opts_data);
self.success = opts.success;
called = true;
};
return self;
}
function config_process_results(messages) {
const self = {};
const messages_processed_for_new = [];
message_helper.process_new_message = (message) => {
messages_processed_for_new.push(message);
return message;
};
message_util.do_unread_count_updates = (arg) => {
assert.deepEqual(arg, messages);
};
message_util.add_old_messages = (new_messages, msg_list) => {
assert.deepEqual(new_messages, messages);
msg_list.add_messages(new_messages);
};
stream_list.update_streams_sidebar = noop;
self.verify = () => {
assert.deepEqual(messages_processed_for_new, messages);
};
return self;
}
function message_range(start, end) {
return _.range(start, end).map((idx) => ({
id: idx,
}));
}
const initialize_data = {
initial_fetch: {
req: {
anchor: "first_unread",
num_before: 200,
num_after: 200,
client_gravatar: true,
// Same as message_lists.home.data.public_terms() after `reset_lists` is called.
narrow: JSON.stringify([]),
},
resp: {
messages: message_range(201, 801),
found_newest: false,
anchor: 444,
},
},
forward_fill: {
req: {
anchor: "800",
num_before: 0,
num_after: 1000,
client_gravatar: true,
narrow: JSON.stringify([]),
},
resp: {
messages: message_range(800, 1000),
found_newest: true,
},
},
back_fill: {
req: {
anchor: "201",
num_before: 1000,
num_after: 0,
client_gravatar: true,
narrow: JSON.stringify([]),
},
resp: {
messages: message_range(100, 200),
found_oldest: true,
},
},
};
function test_fetch_success(opts) {
const response = opts.response;
const messages = response.messages;
const process_results = config_process_results(messages);
opts.fetch.success(response);
process_results.verify();
}
function initial_fetch_step(finished_initial_fetch) {
const self = {};
let fetch;
const response = initialize_data.initial_fetch.resp;
self.prep = () => {
fetch = config_fake_channel({
expected_opts_data: initialize_data.initial_fetch.req,
});
message_fetch.initialize(finished_initial_fetch);
};
self.finish = () => {
test_fetch_success({
fetch,
response,
});
};
return self;
}
function forward_fill_step() {
const self = {};
let fetch;
self.prep = () => {
/* Don't wait for the timeout before recursively calling `load_messages`. */
const expected_delay = 150;
set_global("setTimeout", (f, delay) => {
assert.equal(delay, expected_delay);
f();
});
fetch = config_fake_channel({
expected_opts_data: initialize_data.forward_fill.req,
});
};
self.finish = () => {
const response = initialize_data.forward_fill.resp;
let idle_config;
$("document-stub").idle = (config) => {
idle_config = config;
};
test_fetch_success({
fetch,
response,
});
assert.equal(idle_config.idle, 10000);
return idle_config;
};
return self;
}
function test_backfill_idle(idle_config) {
const fetch = config_fake_channel({
expected_opts_data: initialize_data.back_fill.req,
});
const response = initialize_data.back_fill.resp;
idle_config.onIdle();
test_fetch_success({
fetch,
response,
});
}
run_test("initialize", () => {
reset_lists();
let home_loaded = false;
page_params.unread_msgs = {
old_unreads_missing: false,
};
function finished_initial_fetch() {
home_loaded = true;
}
const step1 = initial_fetch_step(finished_initial_fetch);
step1.prep();
const step2 = forward_fill_step();
step2.prep();
step1.finish();
assert.ok(!home_loaded);
const idle_config = step2.finish();
assert.ok(home_loaded);
test_backfill_idle(idle_config);
});
function simulate_narrow() {
const filter = new Filter([{operator: "dm", operand: alice.email}]);
const msg_list = new message_list.MessageList({
filter,
});
message_lists.current = msg_list;
return msg_list;
}
run_test("loading_newer", () => {
function test_dup_new_fetch(msg_list) {
assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), false);
message_fetch.maybe_load_newer_messages({
msg_list,
});
}
function test_happy_path(opts) {
const msg_list = opts.msg_list;
const data = opts.data;
const fetch = config_fake_channel({
expected_opts_data: data.req,
can_call_again: true,
});
message_fetch.maybe_load_newer_messages({
msg_list,
show_loading: noop,
hide_loading: noop,
});
test_dup_new_fetch(msg_list);
test_fetch_success({
fetch,
response: data.resp,
});
}
(function test_narrow() {
let msg_list = simulate_narrow();
page_params.unread_msgs = {
old_unreads_missing: true,
};
// Test what happens when an empty list is returned with found_newest false.
const empty_list_data = {
req: {
anchor: "oldest",
num_before: 0,
num_after: 100,
narrow: `[{"negated":false,"operator":"dm","operand":[${alice.user_id}]}]`,
client_gravatar: true,
},
resp: {
messages: message_range(500, 600),
found_newest: false,
},
};
test_happy_path({
msg_list,
data: empty_list_data,
});
msg_list = simulate_narrow();
msg_list.append_to_view = noop;
// Instead of using 444 as page_param.pointer, we
// should have a message with that id in the message_list.
msg_list.append(message_range(444, 445), false);
const data = {
req: {
anchor: "444",
num_before: 0,
num_after: 100,
narrow: `[{"negated":false,"operator":"dm","operand":[${alice.user_id}]}]`,
client_gravatar: true,
},
resp: {
messages: message_range(500, 600),
found_newest: false,
},
};
test_happy_path({
msg_list,
data,
});
assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), true);
// The server successfully responded with messages having id's from 500-599.
// We test for the case that this was the last batch of messages for the narrow
// so no more fetching should occur.
// And also while fetching for the above condition the server received a new message
// event, updating the last message's id for that narrow to 600 from 599.
data.resp.found_newest = true;
msg_list.data.fetch_status.update_expected_max_message_id([{id: 600}]);
test_happy_path({
msg_list,
data,
});
// To handle this special case we should allow another fetch to occur,
// since the last message event's data had been discarded.
// This fetch goes on until the newest message has been found.
assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), false);
})();
(function test_home() {
reset_lists();
let msg_list = message_lists.home;
// Test what happens when an empty list is returned with found_newest false.
const empty_list_data = {
req: {
anchor: "oldest",
num_before: 0,
num_after: 100,
client_gravatar: true,
narrow: JSON.stringify([]),
},
resp: {
messages: message_range(500, 600),
found_newest: false,
},
};
test_happy_path({
msg_list,
data: empty_list_data,
});
reset_lists();
const data = [
{
req: {
anchor: "444",
num_before: 0,
num_after: 100,
client_gravatar: true,
narrow: JSON.stringify([]),
},
resp: {
messages: message_range(500, 600),
found_newest: false,
},
},
{
req: {
anchor: "599",
num_before: 0,
num_after: 100,
client_gravatar: true,
narrow: JSON.stringify([]),
},
resp: {
messages: message_range(700, 800),
found_newest: true,
},
},
];
msg_list = message_lists.home;
all_messages_data.append(message_range(444, 445), false);
test_happy_path({
msg_list,
data: data[0],
});
assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), true);
test_happy_path({
msg_list,
data: data[1],
});
assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), false);
})();
});

View File

@ -21,14 +21,6 @@ const compose_recipient = mock_esm("../src/compose_recipient");
const message_fetch = mock_esm("../src/message_fetch");
const message_list = mock_esm("../src/message_list");
const message_lists = mock_esm("../src/message_lists", {
home: {
view: {
$list: {
removeClass: noop,
addClass: noop,
},
},
},
current: {
view: {
$list: {
@ -44,6 +36,9 @@ const message_lists = mock_esm("../src/message_lists", {
update_current_message_list(msg_list) {
message_lists.current = msg_list;
},
all_rendered_message_lists() {
return [message_lists.current];
},
});
const message_feed_top_notices = mock_esm("../src/message_feed_top_notices");
const message_feed_loading = mock_esm("../src/message_feed_loading");

View File

@ -10,18 +10,11 @@ const {page_params} = require("./lib/zpage_params");
set_global("addEventListener", noop);
const channel = mock_esm("../src/channel");
const message_lists = mock_esm("../src/message_lists");
mock_esm("../src/reload_state", {
is_in_progress() {
return false;
},
});
message_lists.home = {
select_id: noop,
selected_id() {
return 1;
},
};
page_params.test_suite = false;
// we also directly write to pointer

View File

@ -80,7 +80,6 @@ run_test("transmit_message_ajax_reload_pending", () => {
reload_initiated = true;
assert.deepEqual(opts, {
immediate: true,
save_pointer: true,
save_compose: true,
send_after_reload: true,
});