narrow: Use message list id to track message lists in DOM.

This removes use of zfilt and zhome from codebase.
This commit is contained in:
Aman Agrawal 2024-01-17 06:53:40 +00:00 committed by Tim Abbott
parent 91ee0bf676
commit 633f64a79e
31 changed files with 297 additions and 179 deletions

View File

@ -255,12 +255,7 @@
<div id="loading_older_messages_indicator"></div> <div id="loading_older_messages_indicator"></div>
<div id="page_loading_indicator"></div> <div id="page_loading_indicator"></div>
<div id="message_feed_errors_container"></div> <div id="message_feed_errors_container"></div>
<div id="message-lists-container"> <div id="message-lists-container"></div>
<div class="message-list focused-message-list" id="zhome" role="list" aria-live="polite" aria-label="{{ _('Messages') }}">
</div>
<div class="message-list" id="zfilt" role="list" aria-live="polite" aria-label="{{ _('Messages') }}">
</div>
</div>
<div id="scheduled_message_indicator"> <div id="scheduled_message_indicator">
</div> </div>
<div id="typing_notifications"> <div id="typing_notifications">

View File

@ -57,10 +57,14 @@ async function run() {
await page.goto(`${options.realmUri}/#narrow/id/${options.messageId}`, { await page.goto(`${options.realmUri}/#narrow/id/${options.messageId}`, {
waitUntil: "networkidle2", waitUntil: "networkidle2",
}); });
const messageSelector = `#zfilt${CSS.escape(options.messageId)}`; // eslint-disable-next-line no-undef
const message_list_id = await page.evaluate(() => zulip_test.current_msg_list.id);
const messageSelector = `#message-row-${message_list_id}-${CSS.escape(options.messageId)}`;
await page.waitForSelector(messageSelector); await page.waitForSelector(messageSelector);
// remove unread marker and don't select message // remove unread marker and don't select message
const marker = `#zfilt${CSS.escape(options.messageId)} .unread_marker`; const marker = `#message-row-${message_list_id}-${CSS.escape(
options.messageId,
)} .unread_marker`;
await page.evaluate((sel) => $(sel).remove(), marker); await page.evaluate((sel) => $(sel).remove(), marker);
const messageBox = await page.$(messageSelector); const messageBox = await page.$(messageSelector);
await page.evaluate((msg) => $(msg).removeClass("selected_message"), messageSelector); await page.evaluate((msg) => $(msg).removeClass("selected_message"), messageSelector);

View File

@ -28,14 +28,14 @@ function get_message_selector(text: string): string {
} }
async function test_send_messages(page: Page): Promise<void> { async function test_send_messages(page: Page): Promise<void> {
const initial_msgs_count = (await page.$$("#zhome .message_row")).length; const initial_msgs_count = (await page.$$(".message-list .message_row")).length;
await common.send_multiple_messages(page, [ await common.send_multiple_messages(page, [
{stream_name: "Verona", topic: "Reply test", content: "Compose stream reply test"}, {stream_name: "Verona", topic: "Reply test", content: "Compose stream reply test"},
{recipient: "cordelia@zulip.com", content: "Compose direct message reply test"}, {recipient: "cordelia@zulip.com", content: "Compose direct message reply test"},
]); ]);
assert.equal((await page.$$("#zhome .message_row")).length, initial_msgs_count + 2); assert.equal((await page.$$(".message-list .message_row")).length, initial_msgs_count + 2);
} }
async function test_stream_compose_keyboard_shortcut(page: Page): Promise<void> { async function test_stream_compose_keyboard_shortcut(page: Page): Promise<void> {
@ -138,7 +138,9 @@ async function test_send_multirecipient_pm_from_cordelia_pm_narrow(page: Page):
// Go back to all messages view and make sure all messages are loaded. // Go back to all messages view and make sure all messages are loaded.
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); 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);
const pm = await page.waitForSelector( const pm = await page.waitForSelector(
`xpath/(//*[${common.has_class_x( `xpath/(//*[${common.has_class_x(
"messagebox", "messagebox",
@ -217,7 +219,7 @@ async function test_markdown_preview(page: Page): Promise<void> {
async function compose_tests(page: Page): Promise<void> { async function compose_tests(page: Page): Promise<void> {
await common.log_in(page); await common.log_in(page);
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); await page.waitForSelector(".message-list .message_row", {visible: true});
await test_send_messages(page); await test_send_messages(page);
await test_keyboard_shortcuts(page); await test_keyboard_shortcuts(page);
await test_reply_by_click_prepopulates_stream_topic_names(page); await test_reply_by_click_prepopulates_stream_topic_names(page);

View File

@ -12,7 +12,7 @@ async function copy_messages(
return await page.evaluate( return await page.evaluate(
(start_message: string, end_message: string) => { (start_message: string, end_message: string) => {
function get_message_node(message: string): Element { function get_message_node(message: string): Element {
return [...document.querySelectorAll("#zhome .message_content")].find( return [...document.querySelectorAll(".message-list .message_content")].find(
(node) => node.textContent?.trim() === message, (node) => node.textContent?.trim() === message,
)!; )!;
} }
@ -130,7 +130,9 @@ async function test_copying_messages_from_several_topics(page: Page): Promise<vo
async function copy_paste_test(page: Page): Promise<void> { async function copy_paste_test(page: Page): Promise<void> {
await common.log_in(page); await common.log_in(page);
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); 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);
await common.send_multiple_messages(page, [ await common.send_multiple_messages(page, [
{stream_name: "Verona", topic: "copy-paste-topic #1", content: "copy paste test A"}, {stream_name: "Verona", topic: "copy-paste-topic #1", content: "copy paste test A"},
@ -148,7 +150,8 @@ async function copy_paste_test(page: Page): Promise<void> {
{stream_name: "Verona", topic: "copy-paste-topic #3", content: "copy paste test G"}, {stream_name: "Verona", topic: "copy-paste-topic #3", content: "copy paste test G"},
]); ]);
await common.check_messages_sent(page, "zhome", [ const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, message_list_id, [
["Verona > copy-paste-topic #1", ["copy paste test A", "copy paste test B"]], ["Verona > copy-paste-topic #1", ["copy paste test A", "copy paste test B"]],
[ [
"Verona > copy-paste-topic #2", "Verona > copy-paste-topic #2",

View File

@ -5,7 +5,7 @@ import type {Page} from "puppeteer";
import * as common from "./lib/common"; import * as common from "./lib/common";
async function click_delete_and_return_last_msg_id(page: Page): Promise<string> { async function click_delete_and_return_last_msg_id(page: Page): Promise<string> {
const msg = (await page.$$("#zhome .message_row")).at(-1); const msg = (await page.$$(".message-list .message_row")).at(-1);
assert.ok(msg !== undefined); assert.ok(msg !== undefined);
const id = await (await msg.getProperty("id")).jsonValue(); const id = await (await msg.getProperty("id")).jsonValue();
await msg.hover(); await msg.hover();
@ -23,8 +23,14 @@ async function click_delete_and_return_last_msg_id(page: Page): Promise<string>
async function delete_message_test(page: Page): Promise<void> { async function delete_message_test(page: Page): Promise<void> {
await common.log_in(page); await common.log_in(page);
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); const message_list_id = await common.get_current_msg_list_id(page, true);
const messages_quantity = (await page.$$("#zhome .message_row")).length; await page.waitForSelector(
`.message-list[data-message-list-id='${message_list_id}'] .message_row`,
{visible: true},
);
// Assert that there is only one message list.
assert.equal((await page.$$(".message-list")).length, 1);
const messages_quantity = (await page.$$(".message-list .message_row")).length;
const last_message_id = await click_delete_and_return_last_msg_id(page); const last_message_id = await click_delete_and_return_last_msg_id(page);
await common.wait_for_micromodal_to_open(page); await common.wait_for_micromodal_to_open(page);
@ -34,7 +40,7 @@ async function delete_message_test(page: Page): Promise<void> {
await common.wait_for_micromodal_to_close(page); await common.wait_for_micromodal_to_close(page);
await page.waitForSelector(`#${CSS.escape(last_message_id)}`, {hidden: true}); await page.waitForSelector(`#${CSS.escape(last_message_id)}`, {hidden: true});
assert.equal((await page.$$("#zhome .message_row")).length, messages_quantity - 1); assert.equal((await page.$$(".message-list .message_row")).length, messages_quantity - 1);
} }
common.run_test(delete_message_test); common.run_test(delete_message_test);

View File

@ -246,7 +246,9 @@ async function test_save_draft_by_reloading(page: Page): Promise<void> {
async function drafts_test(page: Page): Promise<void> { async function drafts_test(page: Page): Promise<void> {
await common.log_in(page); await common.log_in(page);
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); 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);
await test_empty_drafts(page); await test_empty_drafts(page);

View File

@ -5,7 +5,7 @@ import type {Page} from "puppeteer";
import * as common from "./lib/common"; import * as common from "./lib/common";
async function trigger_edit_last_message(page: Page): Promise<void> { async function trigger_edit_last_message(page: Page): Promise<void> {
const msg = (await page.$$("#zhome .message_row")).at(-1); const msg = (await page.$$(".message-list .message_row")).at(-1);
assert.ok(msg !== undefined); assert.ok(msg !== undefined);
const id = await (await msg.getProperty("id")).jsonValue(); const id = await (await msg.getProperty("id")).jsonValue();
await msg.hover(); await msg.hover();
@ -29,7 +29,7 @@ async function edit_stream_message(page: Page, content: string): Promise<void> {
await common.wait_for_fully_processed_message(page, content); await common.wait_for_fully_processed_message(page, content);
} }
async function test_stream_message_edit(page: Page): Promise<void> { async function test_stream_message_edit(page: Page, message_list_id: number): Promise<void> {
await common.send_message(page, "stream", { await common.send_message(page, "stream", {
stream_name: "Verona", stream_name: "Verona",
topic: "edits", topic: "edits",
@ -38,11 +38,13 @@ async function test_stream_message_edit(page: Page): Promise<void> {
await edit_stream_message(page, "test edited"); await edit_stream_message(page, "test edited");
await common.check_messages_sent(page, "zhome", [["Verona > edits", ["test edited"]]]); await common.check_messages_sent(page, message_list_id, [["Verona > edits", ["test edited"]]]);
} }
async function test_edit_message_with_slash_me(page: Page): Promise<void> { async function test_edit_message_with_slash_me(page: Page): Promise<void> {
const last_message_xpath = `(//*[@id="zhome"]//*[${common.has_class_x("messagebox")}])[last()]`; const last_message_xpath = `(//*[${common.has_class_x("message-list")}]//*[${common.has_class_x(
"messagebox",
)}])[last()]`;
await common.send_message(page, "stream", { await common.send_message(page, "stream", {
stream_name: "Verona", stream_name: "Verona",
@ -74,7 +76,7 @@ async function test_edit_message_with_slash_me(page: Page): Promise<void> {
); );
} }
async function test_edit_private_message(page: Page): Promise<void> { async function test_edit_private_message(page: Page, message_list_id: number): Promise<void> {
await common.send_message(page, "private", { await common.send_message(page, "private", {
recipient: "cordelia@zulip.com", recipient: "cordelia@zulip.com",
content: "test editing pm", content: "test editing pm",
@ -85,7 +87,7 @@ async function test_edit_private_message(page: Page): Promise<void> {
await page.click(".message_edit_save"); await page.click(".message_edit_save");
await common.wait_for_fully_processed_message(page, "test edited pm"); await common.wait_for_fully_processed_message(page, "test edited pm");
await common.check_messages_sent(page, "zhome", [ await common.check_messages_sent(page, message_list_id, [
["You and Cordelia, Lear's daughter", ["test edited pm"]], ["You and Cordelia, Lear's daughter", ["test edited pm"]],
]); ]);
} }
@ -93,11 +95,12 @@ async function test_edit_private_message(page: Page): Promise<void> {
async function edit_tests(page: Page): Promise<void> { async function edit_tests(page: Page): Promise<void> {
await common.log_in(page); await common.log_in(page);
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); await page.waitForSelector(".message-list .message_row", {visible: true});
const message_list_id = await common.get_current_msg_list_id(page, true);
await test_stream_message_edit(page); await test_stream_message_edit(page, message_list_id);
await test_edit_message_with_slash_me(page); await test_edit_message_with_slash_me(page);
await test_edit_private_message(page); await test_edit_private_message(page, message_list_id);
} }
common.run_test(edit_tests); common.run_test(edit_tests);

View File

@ -26,6 +26,8 @@ export const is_firefox = process.env.PUPPETEER_PRODUCT === "firefox";
let realm_url = "http://zulip.zulipdev.com:9981/"; let realm_url = "http://zulip.zulipdev.com:9981/";
const gps = new StackTraceGPS({ajax: async (url) => (await fetch(url)).text()}); const gps = new StackTraceGPS({ajax: async (url) => (await fetch(url)).text()});
let last_current_msg_list_id: number | null = null;
export const pm_recipient = { export const pm_recipient = {
async set(page: Page, recipient: string): Promise<void> { async set(page: Page, recipient: string): Promise<void> {
// Without using the delay option here there seems to be // Without using the delay option here there seems to be
@ -483,9 +485,11 @@ export async function send_multiple_messages(page: Page, msgs: Message[]): Promi
*/ */
export async function get_rendered_messages( export async function get_rendered_messages(
page: Page, page: Page,
table = "zhome", message_list_id: number,
): Promise<[string, string[]][]> { ): Promise<[string, string[]][]> {
const recipient_rows = await page.$$(`#${CSS.escape(table)} .recipient_row`); const recipient_rows = await page.$$(
`.message-list[data-message-list-id='${message_list_id}'] .recipient_row`,
);
return Promise.all( return Promise.all(
recipient_rows.map(async (element): Promise<[string, string[]]> => { recipient_rows.map(async (element): Promise<[string, string[]]> => {
const stream_label = await element.$(".stream_label"); const stream_label = await element.$(".stream_label");
@ -519,11 +523,13 @@ export async function get_rendered_messages(
// messages array passed exist in the order they are passed. // messages array passed exist in the order they are passed.
export async function check_messages_sent( export async function check_messages_sent(
page: Page, page: Page,
table: string, message_list_id: number,
messages: [string, string[]][], messages: [string, string[]][],
): Promise<void> { ): Promise<void> {
await page.waitForSelector(`#${CSS.escape(table)}`, {visible: true}); await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
const rendered_messages = await get_rendered_messages(page, table); visible: true,
});
const rendered_messages = await get_rendered_messages(page, message_list_id);
// We only check the last n messages because if we run // We only check the last n messages because if we run
// the test with --interactive there will be duplicates. // the test with --interactive there will be duplicates.
@ -714,3 +720,23 @@ export function run_test(test_function: (page: Page) => Promise<void>): void {
process.exit(1); process.exit(1);
}); });
} }
export async function get_current_msg_list_id(
page: Page,
wait_for_change = false,
): Promise<number> {
if (wait_for_change) {
// Wait for the current_msg_list to change if the in the middle of switching narrows.
// Also works as a way to verify that the current message list did change.
// NOTE: This only checks if the current message list id changed from the last call to this function,
// so, make sure to have a call to this function before changing to the narrow that you want to check.
await page.waitForFunction(
(last_current_msg_list_id) =>
zulip_test.current_msg_list.id !== last_current_msg_list_id,
{},
last_current_msg_list_id,
);
}
last_current_msg_list_id = await page.evaluate(() => zulip_test.current_msg_list.id);
return last_current_msg_list_id!;
}

View File

@ -7,7 +7,7 @@ import * as common from "./lib/common";
async function test_mention(page: Page): Promise<void> { async function test_mention(page: Page): Promise<void> {
await common.log_in(page); await common.log_in(page);
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); await page.waitForSelector(".message-list .message_row", {visible: true});
await page.keyboard.press("KeyC"); await page.keyboard.press("KeyC");
await page.waitForSelector("#compose", {visible: true}); await page.waitForSelector("#compose", {visible: true});
@ -36,7 +36,10 @@ async function test_mention(page: Page): Promise<void> {
await page.click("#compose_banners .wildcard_warning .main-view-banner-action-button"); await page.click("#compose_banners .wildcard_warning .main-view-banner-action-button");
await page.waitForSelector(".wildcard_warning", {hidden: true}); await page.waitForSelector(".wildcard_warning", {hidden: true});
await common.check_messages_sent(page, "zhome", [["Verona > Test mention all", ["@all"]]]); const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, message_list_id, [
["Verona > Test mention all", ["@all"]],
]);
} }
common.run_test(test_mention); common.run_test(test_mention);

View File

@ -10,7 +10,8 @@ async function get_stream_li(page: Page, stream_name: string): Promise<string> {
} }
async function expect_home(page: Page): Promise<void> { async function expect_home(page: Page): Promise<void> {
await common.check_messages_sent(page, "zhome", [ const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, message_list_id, [
["Verona > test", ["verona test a", "verona test b"]], ["Verona > test", ["verona test a", "verona test b"]],
["Verona > other topic", ["verona other topic c"]], ["Verona > other topic", ["verona other topic c"]],
["Denmark > test", ["denmark message"]], ["Denmark > test", ["denmark message"]],
@ -26,8 +27,11 @@ async function expect_home(page: Page): Promise<void> {
} }
async function expect_verona_stream(page: Page): Promise<void> { async function expect_verona_stream(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true}); const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, "zfilt", [ await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
visible: true,
});
await common.check_messages_sent(page, message_list_id, [
["Verona > test", ["verona test a", "verona test b"]], ["Verona > test", ["verona test a", "verona test b"]],
["Verona > other topic", ["verona other topic c"]], ["Verona > other topic", ["verona other topic c"]],
["Verona > test", ["verona test d"]], ["Verona > test", ["verona test d"]],
@ -36,8 +40,11 @@ async function expect_verona_stream(page: Page): Promise<void> {
} }
async function expect_verona_stream_test_topic(page: Page): Promise<void> { async function expect_verona_stream_test_topic(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true}); const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, "zfilt", [ await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
visible: true,
});
await common.check_messages_sent(page, message_list_id, [
["Verona > test", ["verona test a", "verona test b", "verona test d"]], ["Verona > test", ["verona test a", "verona test b", "verona test d"]],
]); ]);
assert.strictEqual( assert.strictEqual(
@ -47,15 +54,21 @@ async function expect_verona_stream_test_topic(page: Page): Promise<void> {
} }
async function expect_verona_other_topic(page: Page): Promise<void> { async function expect_verona_other_topic(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true}); const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, "zfilt", [ await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
visible: true,
});
await common.check_messages_sent(page, message_list_id, [
["Verona > other topic", ["verona other topic c"]], ["Verona > other topic", ["verona other topic c"]],
]); ]);
} }
async function expect_test_topic(page: Page): Promise<void> { async function expect_test_topic(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true}); const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, "zfilt", [ await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
visible: true,
});
await common.check_messages_sent(page, message_list_id, [
["Verona > test", ["verona test a", "verona test b"]], ["Verona > test", ["verona test a", "verona test b"]],
["Denmark > test", ["denmark message"]], ["Denmark > test", ["denmark message"]],
["Verona > test", ["verona test d"]], ["Verona > test", ["verona test d"]],
@ -63,8 +76,11 @@ async function expect_test_topic(page: Page): Promise<void> {
} }
async function expect_group_direct_messages(page: Page): Promise<void> { async function expect_group_direct_messages(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true}); const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, "zfilt", [ await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
visible: true,
});
await common.check_messages_sent(page, message_list_id, [
[ [
"You and Cordelia, Lear's daughter, King Hamlet", "You and Cordelia, Lear's daughter, King Hamlet",
["group direct message a", "group direct message b", "group direct message d"], ["group direct message a", "group direct message b", "group direct message d"],
@ -77,8 +93,11 @@ async function expect_group_direct_messages(page: Page): Promise<void> {
} }
async function expect_cordelia_direct_messages(page: Page): Promise<void> { async function expect_cordelia_direct_messages(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true}); const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, "zfilt", [ await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
visible: true,
});
await common.check_messages_sent(page, message_list_id, [
["You and Cordelia, Lear's daughter", ["direct message c", "direct message e"]], ["You and Cordelia, Lear's daughter", ["direct message c", "direct message e"]],
]); ]);
} }
@ -88,7 +107,9 @@ async function un_narrow(page: Page): Promise<void> {
await page.keyboard.press("Escape"); await page.keyboard.press("Escape");
} }
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); 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(), "All messages - Zulip Dev - Zulip"); assert.strictEqual(await page.title(), "All messages - Zulip Dev - Zulip");
} }
@ -104,7 +125,7 @@ async function expect_recent_view(page: Page): Promise<void> {
async function test_navigations_from_home(page: Page): Promise<void> { async function test_navigations_from_home(page: Page): Promise<void> {
return; // No idea why this is broken. return; // No idea why this is broken.
console.log("Narrowing by clicking stream"); console.log("Narrowing by clicking stream");
await page.click(`#zhome [title='Narrow to stream "Verona"']`); await page.click(`.focused-message-list [title='Narrow to stream "Verona"']`);
await expect_verona_stream(page); await expect_verona_stream(page);
assert.strictEqual(await page.title(), "#Verona - Zulip Dev - Zulip"); assert.strictEqual(await page.title(), "#Verona - Zulip Dev - Zulip");
@ -112,7 +133,7 @@ async function test_navigations_from_home(page: Page): Promise<void> {
await expect_home(page); await expect_home(page);
console.log("Narrowing by clicking topic"); console.log("Narrowing by clicking topic");
await page.click(`#zhome [title='Narrow to stream "Verona", topic "test"']`); await page.click(`.focused-message-list [title='Narrow to stream "Verona", topic "test"']`);
await expect_verona_stream_test_topic(page); await expect_verona_stream_test_topic(page);
await un_narrow(page); await un_narrow(page);
@ -121,7 +142,7 @@ async function test_navigations_from_home(page: Page): Promise<void> {
return; // TODO: rest of this test seems nondeterministically broken return; // TODO: rest of this test seems nondeterministically broken
console.log("Narrowing by clicking group personal header"); console.log("Narrowing by clicking group personal header");
await page.click( await page.click(
`#zhome [title="Narrow to your direct messages with Cordelia, Lear's daughter, King Hamlet"]`, `.focused-message-list [title="Narrow to your direct messages with Cordelia, Lear's daughter, King Hamlet"]`,
); );
await expect_group_direct_messages(page); await expect_group_direct_messages(page);
@ -129,7 +150,7 @@ async function test_navigations_from_home(page: Page): Promise<void> {
await expect_home(page); await expect_home(page);
await page.click( await page.click(
`#zhome [title="Narrow to your direct messages with Cordelia, Lear's daughter, King Hamlet"]`, `.focused-message-list [title="Narrow to your direct messages with Cordelia, Lear's daughter, King Hamlet"]`,
); );
await un_narrow_by_clicking_org_icon(page); await un_narrow_by_clicking_org_icon(page);
await expect_recent_view(page); await expect_recent_view(page);
@ -161,11 +182,13 @@ async function search_silent_user(page: Page, str: string, item: string): Promis
await common.get_text_from_selector(page, ".empty_feed_notice"), await common.get_text_from_selector(page, ".empty_feed_notice"),
expect_message, expect_message,
); );
await common.get_current_msg_list_id(page, true);
await un_narrow(page); await un_narrow(page);
await expect_home(page); await expect_home(page);
} }
async function expect_non_existing_user(page: Page): Promise<void> { async function expect_non_existing_user(page: Page): Promise<void> {
await common.get_current_msg_list_id(page, true);
await page.waitForSelector(".empty_feed_notice", {visible: true}); await page.waitForSelector(".empty_feed_notice", {visible: true});
const expected_message = "This user does not exist!"; const expected_message = "This user does not exist!";
assert.strictEqual( assert.strictEqual(
@ -175,6 +198,7 @@ async function expect_non_existing_user(page: Page): Promise<void> {
} }
async function expect_non_existing_users(page: Page): Promise<void> { async function expect_non_existing_users(page: Page): Promise<void> {
await common.get_current_msg_list_id(page, true);
await page.waitForSelector(".empty_feed_notice", {visible: true}); await page.waitForSelector(".empty_feed_notice", {visible: true});
const expected_message = "One or more of these users do not exist!"; const expected_message = "One or more of these users do not exist!";
assert.strictEqual( assert.strictEqual(
@ -263,8 +287,11 @@ async function search_tests(page: Page): Promise<void> {
} }
async function expect_all_direct_messages(page: Page): Promise<void> { async function expect_all_direct_messages(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true}); const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, "zfilt", [ await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
visible: true,
});
await common.check_messages_sent(page, message_list_id, [
[ [
"You and Cordelia, Lear's daughter, King Hamlet", "You and Cordelia, Lear's daughter, King Hamlet",
["group direct message a", "group direct message b"], ["group direct message a", "group direct message b"],
@ -477,18 +504,34 @@ async function test_narrow_public_streams(page: Page): Promise<void> {
await page.click(".subscriptions-header .exit-sign"); await page.click(".subscriptions-header .exit-sign");
await page.waitForSelector("#subscription_overlay", {hidden: true}); await page.waitForSelector("#subscription_overlay", {hidden: true});
await page.goto(`http://zulip.zulipdev.com:9981/#narrow/stream/${stream_id}-Denmark`); await page.goto(`http://zulip.zulipdev.com:9981/#narrow/stream/${stream_id}-Denmark`);
await page.waitForSelector("#zfilt .recipient_row ~ .recipient_row ~ .recipient_row"); let message_list_id = await common.get_current_msg_list_id(page, true);
assert.ok((await page.$("#zfilt .stream-status")) !== null); await page.waitForSelector(
`.message-list[data-message-list-id='${message_list_id}'] .recipient_row ~ .recipient_row ~ .recipient_row`,
);
assert.ok(
(await page.$(
`.message-list[data-message-list-id='${message_list_id}'] .stream-status`,
)) !== null,
);
await page.goto("http://zulip.zulipdev.com:9981/#narrow/streams/public"); await page.goto("http://zulip.zulipdev.com:9981/#narrow/streams/public");
await page.waitForSelector("#zfilt .recipient_row ~ .recipient_row ~ .recipient_row"); message_list_id = await common.get_current_msg_list_id(page, true);
assert.ok((await page.$("#zfilt .stream-status")) === null); await page.waitForSelector(
`.message-list[data-message-list-id='${message_list_id}'] .recipient_row ~ .recipient_row ~ .recipient_row`,
);
assert.ok(
(await page.$(
`.message-list[data-message-list-id='${message_list_id}'] .stream-status`,
)) === null,
);
} }
async function message_basic_tests(page: Page): Promise<void> { async function message_basic_tests(page: Page): Promise<void> {
await common.log_in(page); await common.log_in(page);
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); 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);
console.log("Sending messages"); console.log("Sending messages");
await common.send_multiple_messages(page, [ await common.send_multiple_messages(page, [

View File

@ -71,7 +71,10 @@ async function test_reload_hash(page: Page): Promise<void> {
await page.evaluate(() => zulip_test.initiate_reload({immediate: true})); await page.evaluate(() => zulip_test.initiate_reload({immediate: true}));
await page.waitForNavigation(); await page.waitForNavigation();
await page.waitForSelector("#zfilt", {visible: true}); 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,
});
const page_load_time = await page.evaluate(() => zulip_test.page_load_time); const page_load_time = await page.evaluate(() => zulip_test.page_load_time);
assert.ok(page_load_time > initial_page_load_time, "Page not reloaded."); assert.ok(page_load_time > initial_page_load_time, "Page not reloaded.");

View File

@ -7,12 +7,12 @@ import * as common from "./lib/common";
const message = "test star"; const message = "test star";
async function stars_count(page: Page): Promise<number> { async function stars_count(page: Page): Promise<number> {
return (await page.$$("#zhome .zulip-icon-star-filled:not(.empty-star)")).length; return (await page.$$(".message-list .zulip-icon-star-filled:not(.empty-star)")).length;
} }
async function toggle_test_star_message(page: Page): Promise<void> { async function toggle_test_star_message(page: Page): Promise<void> {
const messagebox = await page.waitForSelector( const messagebox = await page.waitForSelector(
`xpath/(//*[@id="zhome"]//*[${common.has_class_x( `xpath/(//*[${common.has_class_x("message-list")}]//*[${common.has_class_x(
"message_content", "message_content",
)} and normalize-space()="${message}"])[last()]/ancestor::*[${common.has_class_x( )} and normalize-space()="${message}"])[last()]/ancestor::*[${common.has_class_x(
"messagebox", "messagebox",
@ -29,17 +29,24 @@ async function toggle_test_star_message(page: Page): Promise<void> {
async function test_narrow_to_starred_messages(page: Page): Promise<void> { async function test_narrow_to_starred_messages(page: Page): Promise<void> {
await page.click('#left-sidebar-navigation-list a[href^="#narrow/is/starred"]'); await page.click('#left-sidebar-navigation-list a[href^="#narrow/is/starred"]');
await common.check_messages_sent(page, "zfilt", [["Verona > stars", [message]]]); const message_list_id = await common.get_current_msg_list_id(page, true);
await common.check_messages_sent(page, message_list_id, [["Verona > stars", [message]]]);
// Go back to all messages narrow. // Go back to all messages narrow.
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); await page.waitForSelector(".message-list .message_row", {visible: true});
} }
async function stars_test(page: Page): Promise<void> { async function stars_test(page: Page): Promise<void> {
await common.log_in(page); await common.log_in(page);
await page.click("#left-sidebar-navigation-list .top_left_all_messages"); await page.click("#left-sidebar-navigation-list .top_left_all_messages");
await page.waitForSelector("#zhome .message_row", {visible: true}); 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}'] .message_row`,
{visible: true},
);
// Assert that there is only one message list.
assert.equal((await page.$$(".message-list")).length, 1);
await common.send_message(page, "stream", { await common.send_message(page, "stream", {
stream_name: "Verona", stream_name: "Verona",
topic: "stars", topic: "stars",
@ -49,7 +56,10 @@ async function stars_test(page: Page): Promise<void> {
assert.strictEqual(await stars_count(page), 0, "Unexpected already starred message(s)."); assert.strictEqual(await stars_count(page), 0, "Unexpected already starred message(s).");
await toggle_test_star_message(page); await toggle_test_star_message(page);
await page.waitForSelector("#zhome .zulip-icon-star-filled", {visible: true}); await page.waitForSelector(
`.message-list[data-message-list-id='${message_list_id}'] .zulip-icon-star-filled`,
{visible: true},
);
assert.strictEqual( assert.strictEqual(
await stars_count(page), await stars_count(page),
1, 1,

View File

@ -42,8 +42,7 @@ export function set_focused_recipient(msg_type) {
} }
function display_messages_normally() { function display_messages_normally() {
const $table = rows.get_table(message_lists.current.table_name); message_lists.current.view.$list.find(".recipient_row").removeClass("message-fade");
$table.find(".recipient_row").removeClass("message-fade");
normal_display = true; normal_display = true;
} }
@ -77,9 +76,7 @@ function fade_messages() {
// Defer updating all message groups so that the compose box can open sooner // Defer updating all message groups so that the compose box can open sooner
setTimeout( setTimeout(
(expected_msg_list, expected_recipient) => { (expected_msg_list, expected_recipient) => {
const all_groups = rows const $all_groups = message_lists.current.view.$list.find(".recipient_row");
.get_table(message_lists.current.table_name)
.find(".recipient_row");
if ( if (
message_lists.current !== expected_msg_list || message_lists.current !== expected_msg_list ||
@ -93,8 +90,8 @@ function fade_messages() {
// Note: The below algorithm relies on the fact that all_elts is // Note: The below algorithm relies on the fact that all_elts is
// sorted as it would be displayed in the message view // sorted as it would be displayed in the message view
for (i = 0; i < all_groups.length; i += 1) { for (i = 0; i < $all_groups.length; i += 1) {
const $group_elt = $(all_groups[i]); const $group_elt = $($all_groups[i]);
should_fade_group = compose_fade_helper.should_fade_message( should_fade_group = compose_fade_helper.should_fade_message(
rows.recipient_from_group($group_elt), rows.recipient_from_group($group_elt),
); );

View File

@ -17,6 +17,7 @@ import * as stream_data from "./stream_data";
import {user_settings} from "./user_settings"; import {user_settings} from "./user_settings";
export class MessageList { export class MessageList {
static id_counter = 0;
// A MessageList is the main interface for a message feed that is // A MessageList is the main interface for a message feed that is
// rendered in the DOM. Code outside the message feed rendering // rendered in the DOM. Code outside the message feed rendering
// internals will directly call this module in order to manipulate // internals will directly call this module in order to manipulate
@ -30,6 +31,8 @@ export class MessageList {
// is not particularly well-defined; it could be nice to figure // is not particularly well-defined; it could be nice to figure
// out a good rule. // out a good rule.
constructor(opts) { constructor(opts) {
MessageList.id_counter += 1;
this.id = MessageList.id_counter;
// The MessageListData keeps track of the actual sequence of // The MessageListData keeps track of the actual sequence of
// messages displayed by this MessageList. Most // messages displayed by this MessageList. Most
// configuration/logic questions in this module will be // configuration/logic questions in this module will be
@ -46,11 +49,6 @@ export class MessageList {
}); });
} }
// The table_name is the outer HTML element for this message
// list in the DOM.
const table_name = opts.table_name;
this.table_name = table_name;
// TODO: This property should likely just be inlined into // TODO: This property should likely just be inlined into
// having the MessageListView code that needs to access it // having the MessageListView code that needs to access it
// query .data.filter directly. // query .data.filter directly.
@ -59,7 +57,7 @@ export class MessageList {
// The MessageListView object that is responsible for // The MessageListView object that is responsible for
// maintaining this message feed's HTML representation in the // maintaining this message feed's HTML representation in the
// DOM. // DOM.
this.view = new MessageListView(this, table_name, collapse_messages); this.view = new MessageListView(this, collapse_messages, opts.is_node_test);
// Whether this is a narrowed message list. The only message // Whether this is a narrowed message list. The only message
// list that is not is the home_msg_list global. // list that is not is the home_msg_list global.
@ -266,7 +264,7 @@ export class MessageList {
// false. // false.
if (!opts.use_closest && closest_id !== id) { if (!opts.use_closest && closest_id !== id) {
error_data = { error_data = {
table_name: this.table_name, filter_terms: this.filter.terms(),
id, id,
closest_id, closest_id,
}; };
@ -275,7 +273,7 @@ export class MessageList {
if (closest_id === -1 && !opts.empty_ok) { if (closest_id === -1 && !opts.empty_ok) {
error_data = { error_data = {
table_name: this.table_name, filter_terms: this.filter.terms(),
id, id,
items_length: this.data.num_items(), items_length: this.data.num_items(),
}; };
@ -526,7 +524,6 @@ export class MessageList {
export function initialize() { export function initialize() {
/* Create home_msg_list and register it. */ /* Create home_msg_list and register it. */
const home_msg_list = new MessageList({ const home_msg_list = new MessageList({
table_name: "zhome",
filter: new Filter([{operator: "in", operand: "home"}]), filter: new Filter([{operator: "in", operand: "home"}]),
excludes_muted_topics: true, excludes_muted_topics: true,
}); });

View File

@ -5,6 +5,7 @@ import * as resolved_topic from "../shared/src/resolved_topic";
import render_bookend from "../templates/bookend.hbs"; import render_bookend from "../templates/bookend.hbs";
import render_login_to_view_image_button from "../templates/login_to_view_image_button.hbs"; import render_login_to_view_image_button from "../templates/login_to_view_image_button.hbs";
import render_message_group from "../templates/message_group.hbs"; import render_message_group from "../templates/message_group.hbs";
import render_message_list from "../templates/message_list.hbs";
import render_recipient_row from "../templates/recipient_row.hbs"; import render_recipient_row from "../templates/recipient_row.hbs";
import render_single_message from "../templates/single_message.hbs"; import render_single_message from "../templates/single_message.hbs";
@ -260,9 +261,12 @@ export class MessageListView {
// Logic to compute context, render templates, insert them into // Logic to compute context, render templates, insert them into
// the DOM, and generally // the DOM, and generally
constructor(list, table_name, collapse_messages) { constructor(list, collapse_messages, is_node_test = false) {
// The MessageList that this MessageListView is responsible for rendering. // The MessageList that this MessageListView is responsible for rendering.
this.list = list; this.list = list;
this._add_message_list_to_DOM();
// The jQuery element for the rendered list element.
this.$list = $(`.message-list[data-message-list-id="${this.list.id}"]`);
// TODO: Access this via .list.data. // TODO: Access this via .list.data.
this.collapse_messages = collapse_messages; this.collapse_messages = collapse_messages;
@ -285,12 +289,10 @@ export class MessageListView {
this.message_containers = new Map(); this.message_containers = new Map();
this._message_groups = []; this._message_groups = [];
// TODO: Should this be just accessing .list.table_name? if (!is_node_test) {
this.table_name = table_name; // Skip running this in node tests.
if (this.table_name) {
this.clear_table(); this.clear_table();
} }
// For performance reasons, this module renders at most // For performance reasons, this module renders at most
// _RENDER_WINDOW_SIZE messages into the DOM at a time, and // _RENDER_WINDOW_SIZE messages into the DOM at a time, and
// will transparently adjust which messages are rendered // will transparently adjust which messages are rendered
@ -312,6 +314,10 @@ export class MessageListView {
// trigger a re-render // trigger a re-render
_RENDER_THRESHOLD = 50; _RENDER_THRESHOLD = 50;
_add_message_list_to_DOM() {
$("#message-lists-container").append(render_message_list({message_list_id: this.list.id}));
}
_get_msg_timestring(message_container) { _get_msg_timestring(message_container) {
let last_edit_timestamp; let last_edit_timestamp;
if (message_container.msg.local_edit_timestamp !== undefined) { if (message_container.msg.local_edit_timestamp !== undefined) {
@ -782,7 +788,7 @@ export class MessageListView {
message_container.msg.message_reactions = msg_reactions; message_container.msg.message_reactions = msg_reactions;
const msg_to_render = { const msg_to_render = {
...message_container, ...message_container,
table_name: this.table_name, message_list_id: this.list.id,
}; };
return render_single_message(msg_to_render); return render_single_message(msg_to_render);
} }
@ -790,13 +796,12 @@ export class MessageListView {
_render_group(opts) { _render_group(opts) {
const message_groups = opts.message_groups; const message_groups = opts.message_groups;
const use_match_properties = opts.use_match_properties; const use_match_properties = opts.use_match_properties;
const table_name = opts.table_name;
return $( return $(
render_message_group({ render_message_group({
message_groups, message_groups,
use_match_properties, use_match_properties,
table_name, message_list_id: this.list.id,
}), }),
); );
} }
@ -810,8 +815,6 @@ export class MessageListView {
} }
const list = this.list; // for convenience const list = this.list; // for convenience
const table_name = this.table_name;
const $table = rows.get_table(table_name);
let orig_scrolltop_offset; let orig_scrolltop_offset;
// If we start with the message feed scrolled up (i.e. // If we start with the message feed scrolled up (i.e.
@ -857,7 +860,7 @@ export class MessageListView {
return undefined; return undefined;
} }
const new_message_groups = this.build_message_groups(message_containers, this.table_name); const new_message_groups = this.build_message_groups(message_containers);
const message_actions = this.merge_message_groups(new_message_groups, where); const message_actions = this.merge_message_groups(new_message_groups, where);
const new_dom_elements = []; const new_dom_elements = [];
let $rendered_groups; let $rendered_groups;
@ -876,7 +879,6 @@ export class MessageListView {
$rendered_groups = this._render_group({ $rendered_groups = this._render_group({
message_groups: message_actions.prepend_groups, message_groups: message_actions.prepend_groups,
use_match_properties: this.list.is_keyword_search(), use_match_properties: this.list.is_keyword_search(),
table_name: this.table_name,
}); });
$dom_messages = $rendered_groups.find(".message_row"); $dom_messages = $rendered_groups.find(".message_row");
@ -886,8 +888,8 @@ export class MessageListView {
// The date row will be included in the message groups or will be // The date row will be included in the message groups or will be
// added in a rerendered in the group below // added in a rerendered in the group below
$table.find(".recipient_row").first().prev(".date_row").remove(); this.$list.find(".recipient_row").first().prev(".date_row").remove();
$table.prepend($rendered_groups); this.$list.prepend($rendered_groups);
condense.condense_and_collapse($dom_messages); condense.condense_and_collapse($dom_messages);
} }
@ -903,7 +905,6 @@ export class MessageListView {
$rendered_groups = this._render_group({ $rendered_groups = this._render_group({
message_groups: [message_group], message_groups: [message_group],
use_match_properties: this.list.is_keyword_search(), use_match_properties: this.list.is_keyword_search(),
table_name: this.table_name,
}); });
$dom_messages = $rendered_groups.find(".message_row"); $dom_messages = $rendered_groups.find(".message_row");
@ -917,7 +918,7 @@ export class MessageListView {
// Insert new messages in to the last message group // Insert new messages in to the last message group
if (message_actions.append_messages.length > 0) { if (message_actions.append_messages.length > 0) {
$last_message_row = $table.find(".message_row").last().expectOne(); $last_message_row = this.$list.find(".message_row").last().expectOne();
$last_group_row = rows.get_message_recipient_row($last_message_row); $last_group_row = rows.get_message_recipient_row($last_message_row);
$dom_messages = $( $dom_messages = $(
message_actions.append_messages message_actions.append_messages
@ -940,7 +941,6 @@ export class MessageListView {
$rendered_groups = this._render_group({ $rendered_groups = this._render_group({
message_groups: message_actions.append_groups, message_groups: message_actions.append_groups,
use_match_properties: this.list.is_keyword_search(), use_match_properties: this.list.is_keyword_search(),
table_name: this.table_name,
}); });
$dom_messages = $rendered_groups.find(".message_row"); $dom_messages = $rendered_groups.find(".message_row");
@ -962,7 +962,7 @@ export class MessageListView {
// this next line seems to prevent the Chrome bug from firing. // this next line seems to prevent the Chrome bug from firing.
message_viewport.scrollTop(); message_viewport.scrollTop();
$table.append($rendered_groups); this.$list.append($rendered_groups);
condense.condense_and_collapse($dom_messages); condense.condense_and_collapse($dom_messages);
} }
@ -1436,7 +1436,7 @@ export class MessageListView {
// We do not want to call .empty() because that also clears // We do not want to call .empty() because that also clears
// jQuery data. This does mean, however, that we need to be // jQuery data. This does mean, however, that we need to be
// mindful of memory leaks. // mindful of memory leaks.
rows.get_table(this.table_name).children().detach(); this.$list.children().detach();
this._rows.clear(); this._rows.clear();
this._message_groups = []; this._message_groups = [];
this.message_containers.clear(); this.message_containers.clear();
@ -1455,7 +1455,7 @@ export class MessageListView {
} }
clear_trailing_bookend() { clear_trailing_bookend() {
const $trailing_bookend = rows.get_table(this.table_name).find(".trailing_bookend"); const $trailing_bookend = this.$list.find(".trailing_bookend");
$trailing_bookend.remove(); $trailing_bookend.remove();
} }
@ -1484,7 +1484,7 @@ export class MessageListView {
is_web_public, is_web_public,
}), }),
); );
rows.get_table(this.table_name).append($rendered_trailing_bookend); this.$list.append($rendered_trailing_bookend);
} }
selected_row() { selected_row() {
@ -1501,7 +1501,7 @@ export class MessageListView {
this._rows.delete(old_id); this._rows.delete(old_id);
$row.attr("zid", new_id); $row.attr("zid", new_id);
$row.attr("id", this.table_name + new_id); $row.attr("id", `message-row-${this.list.id}-` + new_id);
$row.removeClass("local"); $row.removeClass("local");
this._rows.set(new_id, $row); this._rows.set(new_id, $row);
} }
@ -1570,8 +1570,7 @@ export class MessageListView {
return 0; return 0;
} }
const $table = rows.get_table(this.table_name); const $headers = this.$list.find(".message_header");
const $headers = $table.find(".message_header");
const iterable_headers = $headers.toArray(); const iterable_headers = $headers.toArray();
let start = 0; let start = 0;
let end = iterable_headers.length - 1; let end = iterable_headers.length - 1;
@ -1670,8 +1669,7 @@ export class MessageListView {
} }
update_recipient_bar_background_color() { update_recipient_bar_background_color() {
const $table = rows.get_table(this.table_name); const $stream_headers = this.$list.find(".message_header_stream");
const $stream_headers = $table.find(".message_header_stream");
for (const stream_header of $stream_headers) { for (const stream_header of $stream_headers) {
const $stream_header = $(stream_header); const $stream_header = $(stream_header);
stream_color.update_stream_recipient_color($stream_header); stream_color.update_stream_recipient_color($stream_header);
@ -1689,8 +1687,7 @@ export class MessageListView {
} }
show_messages_as_unread(message_ids) { show_messages_as_unread(message_ids) {
const $table = rows.get_table(this.table_name); const $rows_to_show_as_unread = this.$list.find(".message_row").filter((_index, $row) => {
const $rows_to_show_as_unread = $table.find(".message_row").filter((_index, $row) => {
const message_id = Number.parseFloat($row.getAttribute("zid")); const message_id = Number.parseFloat($row.getAttribute("zid"));
return message_ids.includes(message_id); return message_ids.includes(message_id);
}); });

View File

@ -1,4 +1,3 @@
import $ from "jquery";
import assert from "minimalistic-assert"; import assert from "minimalistic-assert";
import * as blueslip from "./blueslip"; import * as blueslip from "./blueslip";
@ -15,11 +14,12 @@ type MessageListView = {
_render_win_start: number; _render_win_start: number;
_render_win_end: number; _render_win_end: number;
sticky_recipient_message_id: number | undefined; sticky_recipient_message_id: number | undefined;
$list: JQuery;
}; };
export type RenderInfo = {need_user_to_scroll: boolean}; export type RenderInfo = {need_user_to_scroll: boolean};
export type MessageList = { export type MessageList = {
table_name: string; id: number;
view: MessageListView; view: MessageListView;
selected_id: () => number; selected_id: () => number;
selected_row: () => JQuery; selected_row: () => JQuery;
@ -61,7 +61,7 @@ export function all_rendered_message_lists(): MessageList[] {
export function all_current_message_rows(): JQuery { export function all_current_message_rows(): JQuery {
assert(current !== undefined); assert(current !== undefined);
return $(`#${CSS.escape(current.table_name)}.message-list .message_row`); return current.view.$list.find(".message_row");
} }
export function update_recipient_bar_background_color(): void { export function update_recipient_bar_background_color(): void {

View File

@ -420,16 +420,19 @@ export function activate(raw_terms, opts) {
const msg_list = new message_list.MessageList({ const msg_list = new message_list.MessageList({
data: msg_data, data: msg_data,
table_name: "zfilt",
}); });
// Show the new set of messages. It is important to set message_lists.current to // 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 // the view right as it's being shown, because we rely on message_lists.current
// being shown for deciding when to condense messages. // being shown for deciding when to condense messages.
$("body").addClass("narrowed_view"); $("body").addClass("narrowed_view");
$("#zfilt").addClass("focused-message-list"); msg_list.view.$list.addClass("focused-message-list");
$("#zhome").removeClass("focused-message-list"); message_lists.home.view.$list.removeClass("focused-message-list");
// Remove old message list from DOM.
if (message_lists.current !== message_lists.home) {
message_lists.current?.view.$list.remove();
}
message_lists.set_current(msg_list); message_lists.set_current(msg_list);
let then_select_offset; let then_select_offset;
@ -1003,7 +1006,7 @@ function handle_post_narrow_deactivate_processes(msg_list) {
message_feed_top_notices.update_top_of_narrow_notices(msg_list); message_feed_top_notices.update_top_of_narrow_notices(msg_list);
// We may need to scroll to the selected message after swapping // We may need to scroll to the selected message after swapping
// the currently displayed center panel to zhome. // the currently displayed center panel to All messages.
message_viewport.maybe_scroll_to_selected(); message_viewport.maybe_scroll_to_selected();
} }
@ -1076,11 +1079,14 @@ export function deactivate() {
narrow_state.set_has_shown_message_list_view(); narrow_state.set_has_shown_message_list_view();
$("body").removeClass("narrowed_view"); $("body").removeClass("narrowed_view");
$("#zfilt").removeClass("focused-message-list"); message_lists.home.view.$list.addClass("focused-message-list");
$("#zhome").addClass("focused-message-list"); // Remove old message list from DOM.
if (message_lists.current !== message_lists.home) {
message_lists.current?.view.$list.remove();
}
message_lists.set_current(message_lists.home); message_lists.set_current(message_lists.home);
message_lists.current.resume_reading(); message_lists.current.resume_reading();
condense.condense_and_collapse($("#zhome div.message_row")); condense.condense_and_collapse(message_lists.home.view.$list.find(".message_row"));
reset_ui_state(); reset_ui_state();
compose_recipient.handle_middle_pane_transition(); compose_recipient.handle_middle_pane_transition();

View File

@ -1,6 +1,5 @@
import * as message_lists from "./message_lists"; import * as message_lists from "./message_lists";
import * as message_viewport from "./message_viewport"; import * as message_viewport from "./message_viewport";
import * as rows from "./rows";
import * as unread_ops from "./unread_ops"; import * as unread_ops from "./unread_ops";
function go_to_row(msg_id) { function go_to_row(msg_id) {
@ -23,9 +22,9 @@ export function down(with_centering) {
if (with_centering) { if (with_centering) {
// At the last message, scroll to the bottom so we have // At the last message, scroll to the bottom so we have
// lots of nice whitespace for new messages coming in. // lots of nice whitespace for new messages coming in.
const $current_msg_table = rows.get_table(message_lists.current.table_name); const $current_msg_list = message_lists.current.view.$list;
message_viewport.scrollTop( message_viewport.scrollTop(
($current_msg_table.outerHeight(true) ?? 0) - message_viewport.height() * 0.1, ($current_msg_list.outerHeight(true) ?? 0) - message_viewport.height() * 0.1,
); );
unread_ops.process_scrolled_to_bottom(); unread_ops.process_scrolled_to_bottom();
} }

View File

@ -112,16 +112,6 @@ export function local_echo_id($message_row: JQuery): string | undefined {
return zid; return zid;
} }
const valid_table_names = new Set(["zhome", "zfilt"]);
export function get_table(table_name: string): JQuery {
if (!valid_table_names.has(table_name)) {
return $();
}
return $(`#${CSS.escape(table_name)}`);
}
export function get_message_id(elem: string): number | undefined { export function get_message_id(elem: string): number | undefined {
// Gets the message_id for elem, where elem is a DOM // Gets the message_id for elem, where elem is a DOM
// element inside a message. This is typically used // element inside a message. This is typically used

View File

@ -4,7 +4,6 @@ import * as inbox_util from "./inbox_util";
import * as message_lists from "./message_lists"; import * as message_lists from "./message_lists";
import * as message_view_header from "./message_view_header"; import * as message_view_header from "./message_view_header";
import * as overlays from "./overlays"; import * as overlays from "./overlays";
import * as rows from "./rows";
import * as stream_color from "./stream_color"; import * as stream_color from "./stream_color";
import * as stream_data from "./stream_data"; import * as stream_data from "./stream_data";
@ -30,8 +29,11 @@ function update_stream_privacy_color(id, color) {
function update_message_recipient_color(stream_name, color) { function update_message_recipient_color(stream_name, color) {
const recipient_color = stream_color.get_recipient_bar_color(color); const recipient_color = stream_color.get_recipient_bar_color(color);
for (const msg_list of message_lists.all_rendered_message_lists()) { for (const msg_list of message_lists.all_rendered_message_lists()) {
const $table = rows.get_table(msg_list.table_name); update_table_message_recipient_stream_color(
update_table_message_recipient_stream_color($table, stream_name, recipient_color); msg_list.view.$list,
stream_name,
recipient_color,
);
} }
// Update color for drafts if open. // Update color for drafts if open.

View File

@ -404,8 +404,8 @@ export function update_timestamps(): void {
const className = entry.className; const className = entry.className;
const $elements = $(`.${CSS.escape(className)}`); const $elements = $(`.${CSS.escape(className)}`);
// The element might not exist any more (because it // The element might not exist any more (because it
// was in the zfilt table, or because we added // was in the narrowed message list which was removed,
// messages above it and re-collapsed). // or because we added messages above it and re-collapsed).
if ($elements.length > 0) { if ($elements.length > 0) {
const time = entry.time; const time = entry.time;
const rendered_time = render_now(time, today); const rendered_time = render_now(time, today);

View File

@ -118,7 +118,7 @@ export function get_item(key, config, file_id) {
return "message-edit-file-input"; return "message-edit-file-input";
case "drag_drop_container": case "drag_drop_container":
return $( return $(
`#${message_lists.current.table_name}${CSS.escape( `#message-row-${message_lists.current.id}-${CSS.escape(
config.row, config.row,
)} .message_edit_form`, )} .message_edit_form`,
); );

View File

@ -49,7 +49,10 @@ export function activate(in_opts) {
}); });
}; };
if ($row.attr("id").startsWith("zhome") && narrow_state.active()) { if (
$row.attr("id").startsWith(`message-row-${message_lists.home.id}-`) &&
narrow_state.active()
) {
// Don't place widget in a home message row if we are narrowed // Don't place widget in a home message row if we are narrowed
// to active state // to active state
return; return;

View File

@ -10,7 +10,7 @@
{{> recipient_row use_match_properties=../use_match_properties}} {{> recipient_row use_match_properties=../use_match_properties}}
{{#each message_containers}} {{#each message_containers}}
{{#with this}} {{#with this}}
{{> single_message use_match_properties=../../use_match_properties table_name=../../table_name}} {{> single_message use_match_properties=../../use_match_properties message_list_id=../../message_list_id}}
{{/with}} {{/with}}
{{/each}} {{/each}}
</div> </div>

View File

@ -0,0 +1 @@
<div class="message-list" data-message-list-id="{{ message_list_id }}" role="list" aria-live="polite" aria-label="{{t 'Messages' }}"></div>

View File

@ -1,4 +1,4 @@
<div zid="{{msg/id}}" id="{{table_name}}{{msg/id}}" <div zid="{{msg/id}}" id="message-row-{{message_list_id}}-{{msg/id}}"
class="message_row{{#unless msg/is_stream}} private-message{{/unless}}{{#include_sender}} include-sender{{/include_sender}}{{#if mention_classname}} {{mention_classname}}{{/if}}{{#msg.unread}} unread{{/msg.unread}} {{#if msg.locally_echoed}}locally-echoed{{/if}} selectable_row" class="message_row{{#unless msg/is_stream}} private-message{{/unless}}{{#include_sender}} include-sender{{/include_sender}}{{#if mention_classname}} {{mention_classname}}{{/if}}{{#msg.unread}} unread{{/msg.unread}} {{#if msg.locally_echoed}}locally-echoed{{/if}} selectable_row"
role="listitem"> role="listitem">
{{#if want_date_divider}} {{#if want_date_divider}}

View File

@ -65,11 +65,9 @@ const alice = {
people.add_active_user(alice); people.add_active_user(alice);
function make_home_msg_list() { function make_home_msg_list() {
const table_name = "whatever";
const filter = new Filter([]); const filter = new Filter([]);
const list = new message_list.MessageList({ const list = new message_list.MessageList({
table_name,
filter, filter,
}); });
return list; return list;
@ -297,7 +295,6 @@ function simulate_narrow() {
const filter = new Filter([{operator: "dm", operand: alice.email}]); const filter = new Filter([{operator: "dm", operand: alice.email}]);
const msg_list = new message_list.MessageList({ const msg_list = new message_list.MessageList({
table_name: "zfilt",
filter, filter,
}); });
message_lists.current = msg_list; message_lists.current = msg_list;

View File

@ -20,18 +20,6 @@ mock_esm("../src/timerender", {
}, },
}); });
mock_esm("../src/rows", {
get_table() {
return {
children() {
return {
detach: noop,
};
},
};
},
});
mock_esm("../src/people", { mock_esm("../src/people", {
sender_is_bot: () => false, sender_is_bot: () => false,
sender_is_guest: () => false, sender_is_guest: () => false,
@ -47,9 +35,10 @@ const muted_users = zrequire("muted_users");
let next_timestamp = 1500000000; let next_timestamp = 1500000000;
function test(label, f) { function test(label, f) {
run_test(label, ({override}) => { run_test(label, ({override, mock_template}) => {
muted_users.set_muted_users([]); muted_users.set_muted_users([]);
f({override}); mock_template("message_list.hbs", false, noop);
f({override, mock_template});
}); });
} }
@ -80,7 +69,13 @@ test("msg_moved_var", () => {
} }
function build_list(message_groups) { function build_list(message_groups) {
const list = new MessageListView(undefined, undefined, true); const list = new MessageListView(
{
id: 1,
},
true,
true,
);
list._message_groups = message_groups; list._message_groups = message_groups;
return list; return list;
} }
@ -229,7 +224,13 @@ test("msg_edited_vars", () => {
} }
function build_list(message_groups) { function build_list(message_groups) {
const list = new MessageListView(undefined, undefined, true); const list = new MessageListView(
{
id: 1,
},
true,
true,
);
list._message_groups = message_groups; list._message_groups = message_groups;
return list; return list;
} }
@ -293,7 +294,13 @@ test("muted_message_vars", () => {
} }
function build_list(message_groups) { function build_list(message_groups) {
const list = new MessageListView(undefined, undefined, true); const list = new MessageListView(
{
id: 1,
},
true,
true,
);
list._message_groups = message_groups; list._message_groups = message_groups;
return list; return list;
} }
@ -403,7 +410,8 @@ test("muted_message_vars", () => {
})(); })();
}); });
test("merge_message_groups", () => { test("merge_message_groups", ({mock_template}) => {
mock_template("message_list.hbs", false, noop);
// MessageListView has lots of DOM code, so we are going to test the message // MessageListView has lots of DOM code, so we are going to test the message
// group merging logic on its own. // group merging logic on its own.
@ -433,15 +441,14 @@ test("merge_message_groups", () => {
} }
function build_list(message_groups) { function build_list(message_groups) {
const table_name = "zfilt";
const filter = new Filter([{operator: "stream", operand: "foo"}]); const filter = new Filter([{operator: "stream", operand: "foo"}]);
const list = new message_list.MessageList({ const list = new message_list.MessageList({
table_name,
filter, filter,
is_node_test: true,
}); });
const view = new MessageListView(list, table_name, true); const view = new MessageListView(list, true, true);
view._message_groups = message_groups; view._message_groups = message_groups;
view.list.unsubscribed_bookend_content = noop; view.list.unsubscribed_bookend_content = noop;
view.list.subscribed_bookend_content = noop; view.list.subscribed_bookend_content = noop;
@ -687,19 +694,19 @@ test("merge_message_groups", () => {
})(); })();
}); });
test("render_windows", () => { test("render_windows", ({mock_template}) => {
mock_template("message_list.hbs", false, noop);
// We only render up to 400 messages at a time in our message list, // We only render up to 400 messages at a time in our message list,
// and we only change the window (which is a range, really, with // and we only change the window (which is a range, really, with
// start/end) when the pointer moves outside of the window or close // start/end) when the pointer moves outside of the window or close
// to the edges. // to the edges.
const view = (function make_view() { const view = (function make_view() {
const table_name = "zfilt";
const filter = new Filter([]); const filter = new Filter([]);
const list = new message_list.MessageList({ const list = new message_list.MessageList({
table_name,
filter, filter,
is_node_test: true,
}); });
const view = list.view; const view = list.view;
@ -731,6 +738,7 @@ test("render_windows", () => {
id: i, id: i,
})); }));
list.selected_idx = () => 0; list.selected_idx = () => 0;
list.view.clear_table = noop;
list.clear(); list.clear();
list.add_messages(messages, {}); list.add_messages(messages, {});

View File

@ -21,8 +21,23 @@ const compose_recipient = mock_esm("../src/compose_recipient");
const message_fetch = mock_esm("../src/message_fetch"); const message_fetch = mock_esm("../src/message_fetch");
const message_list = mock_esm("../src/message_list"); const message_list = mock_esm("../src/message_list");
const message_lists = mock_esm("../src/message_lists", { const message_lists = mock_esm("../src/message_lists", {
home: {}, home: {
current: {}, view: {
$list: {
removeClass: noop,
addClass: noop,
},
},
},
current: {
view: {
$list: {
remove: noop,
removeClass: noop,
addClass: noop,
},
},
},
set_current(msg_list) { set_current(msg_list) {
message_lists.current = msg_list; message_lists.current = msg_list;
}, },
@ -117,6 +132,12 @@ function stub_message_list() {
set_message_offset(offset) { set_message_offset(offset) {
this.offset = offset; this.offset = offset;
}, },
$list: {
remove: noop,
removeClass: noop,
addClass: noop,
},
}; };
get(msg_id) { get(msg_id) {

View File

@ -30,7 +30,7 @@ const compose_ui = zrequire("compose_ui");
const upload = zrequire("upload"); const upload = zrequire("upload");
const message_lists = zrequire("message_lists"); const message_lists = zrequire("message_lists");
message_lists.current = { message_lists.current = {
table_name: "zfilt", id: "1",
}; };
function test(label, f) { function test(label, f) {
run_test(label, (helpers) => { run_test(label, (helpers) => {
@ -129,7 +129,7 @@ test("get_item", () => {
assert.equal(upload.get_item("source", {mode: "edit", row: 123}), "message-edit-file-input"); assert.equal(upload.get_item("source", {mode: "edit", row: 123}), "message-edit-file-input");
assert.equal( assert.equal(
upload.get_item("drag_drop_container", {mode: "edit", row: 1}), upload.get_item("drag_drop_container", {mode: "edit", row: 1}),
$(`#zfilt${CSS.escape(1)} .message_edit_form`), $(`#message-row-1-${CSS.escape(1)} .message_edit_form`),
); );
assert.equal( assert.equal(
upload.get_item("markdown_preview_hide_button", {mode: "edit", row: 65}), upload.get_item("markdown_preview_hide_button", {mode: "edit", row: 65}),
@ -769,7 +769,7 @@ test("main_file_drop_edit_mode", ({override, override_rewire}) => {
prevent_default_counter += 1; prevent_default_counter += 1;
}, },
}; };
const $drag_drop_container = $(`#zfilt${CSS.escape(40)} .message_edit_form`); const $drag_drop_container = $(`#message-row-1-${CSS.escape(40)} .message_edit_form`);
// Dragover event test // Dragover event test
const dragover_handler = $(".app, #navbar-fixed-container").get_on_handler("dragover"); const dragover_handler = $(".app, #navbar-fixed-container").get_on_handler("dragover");

View File

@ -54,7 +54,7 @@ const fake_poll_widget = {
}, },
}; };
const message_lists = mock_esm("../src/message_lists", {current: {}}); const message_lists = mock_esm("../src/message_lists", {current: {}, home: {id: 1}});
const narrow_state = mock_esm("../src/narrow_state"); const narrow_state = mock_esm("../src/narrow_state");
mock_esm("../src/poll_widget", fake_poll_widget); mock_esm("../src/poll_widget", fake_poll_widget);
@ -80,8 +80,8 @@ test("activate", ({override}) => {
// Both widgetize.activate and widgetize.handle_event are tested // Both widgetize.activate and widgetize.handle_event are tested
// here to use the "caching" of widgets // here to use the "caching" of widgets
const $row = $.create("<stub message row>"); const $row = $.create("<stub message row>");
$row.attr("id", "zhome2909"); $row.attr("id", "message-row-1-2909");
const $message_content = $.create("#zhome2909"); const $message_content = $.create("#message-row-1-2909");
$row.set_find_results(".message_content", $message_content); $row.set_find_results(".message_content", $message_content);
let narrow_active; let narrow_active;