snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
336
web/tests/message_list_data.test.cjs
Normal file
336
web/tests/message_list_data.test.cjs
Normal file
@@ -0,0 +1,336 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {mock_esm, zrequire} = require("./lib/namespace.cjs");
|
||||
const {run_test, noop} = require("./lib/test.cjs");
|
||||
const blueslip = require("./lib/zblueslip.cjs");
|
||||
|
||||
const user_topics = zrequire("user_topics");
|
||||
const muted_users = zrequire("muted_users");
|
||||
const {MessageListData} = zrequire("../src/message_list_data");
|
||||
const {Filter} = zrequire("filter");
|
||||
|
||||
function make_msg(msg_id) {
|
||||
return {
|
||||
id: msg_id,
|
||||
type: "stream",
|
||||
unread: true,
|
||||
topic: "whatever",
|
||||
};
|
||||
}
|
||||
|
||||
function make_msgs(msg_ids) {
|
||||
return msg_ids.map((msg_id) => make_msg(msg_id));
|
||||
}
|
||||
|
||||
function assert_contents(mld, msg_ids) {
|
||||
const msgs = mld.all_messages_after_mute_filtering();
|
||||
assert.deepEqual(msgs, make_msgs(msg_ids));
|
||||
}
|
||||
|
||||
function assert_msg_ids(messages, msg_ids) {
|
||||
assert.deepEqual(
|
||||
msg_ids,
|
||||
messages.map((message) => message.id),
|
||||
);
|
||||
}
|
||||
|
||||
mock_esm("../src/people.ts", {
|
||||
maybe_get_user_by_id: noop,
|
||||
pm_with_user_ids(msg) {
|
||||
if (msg.type !== "private") {
|
||||
return undefined;
|
||||
}
|
||||
return msg.to_user_ids.split(",").map(Number);
|
||||
},
|
||||
});
|
||||
|
||||
run_test("basics", () => {
|
||||
const mld = new MessageListData({
|
||||
excludes_muted_topics: false,
|
||||
filter: new Filter([]),
|
||||
});
|
||||
|
||||
assert.equal(mld.is_keyword_search(), false);
|
||||
assert.ok(mld.can_mark_messages_read());
|
||||
mld.add_anywhere(make_msgs([35, 25, 15, 45]));
|
||||
|
||||
assert_contents(mld, [15, 25, 35, 45]);
|
||||
|
||||
const new_msgs = make_msgs([10, 20, 30, 40, 50, 60, 70]);
|
||||
const info = mld.add_messages(new_msgs, true);
|
||||
|
||||
assert.deepEqual(info, {
|
||||
top_messages: make_msgs([10]),
|
||||
interior_messages: make_msgs([20, 30, 40]),
|
||||
bottom_messages: make_msgs([50, 60, 70]),
|
||||
});
|
||||
|
||||
assert_contents(mld, [10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70]);
|
||||
|
||||
assert.equal(mld.selected_id(), -1);
|
||||
assert.equal(mld.closest_id(8), 10);
|
||||
assert.equal(mld.closest_id(27), 25);
|
||||
assert.equal(mld.closest_id(72), 70);
|
||||
|
||||
mld.set_selected_id(50);
|
||||
assert.equal(mld.selected_id(), 50);
|
||||
assert.equal(mld.selected_idx(), 8);
|
||||
|
||||
mld.remove([50]);
|
||||
assert_contents(mld, [10, 15, 20, 25, 30, 35, 40, 45, 60, 70]);
|
||||
|
||||
mld.update_items_for_muting();
|
||||
assert_contents(mld, [10, 15, 20, 25, 30, 35, 40, 45, 60, 70]);
|
||||
|
||||
mld.reset_select_to_closest();
|
||||
assert.equal(mld.selected_id(), 45);
|
||||
assert.equal(mld.selected_idx(), 7);
|
||||
|
||||
assert.equal(mld.first_unread_message_id(), 10);
|
||||
assert.equal(mld.has_unread_messages(), true);
|
||||
mld.get(10).unread = false;
|
||||
assert.equal(mld.first_unread_message_id(), 15);
|
||||
assert.equal(mld.has_unread_messages(), true);
|
||||
|
||||
mld.clear();
|
||||
assert_contents(mld, []);
|
||||
const msgs_sent_by_6 = [
|
||||
{id: 2, sender_id: 6, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
{id: 4, sender_id: 6, type: "private", to_user_ids: "6,9,10"},
|
||||
{id: 6, sender_id: 6, type: "private", to_user_ids: "6, 11"},
|
||||
];
|
||||
// A DM 6 received but did not send -- in get_messages_involving_user
|
||||
// but not get_messages_sent_by_user.
|
||||
const dm_received_by_6 = {id: 7, sender_id: 11, type: "private", to_user_ids: "11,6"};
|
||||
const msgs_involving_6 = [...msgs_sent_by_6, dm_received_by_6];
|
||||
const msgs_with_sender_ids = [
|
||||
{id: 1, sender_id: 1, type: "stream", stream_id: 1, topic: "random1"},
|
||||
{id: 3, sender_id: 4, type: "stream", stream_id: 1, topic: "random2"},
|
||||
{id: 5, sender_id: 2, type: "private", to_user_ids: "2,10,11"},
|
||||
{id: 8, sender_id: 11, type: "private", to_user_ids: "10"},
|
||||
{id: 9, sender_id: 11, type: "private", to_user_ids: "9"},
|
||||
...msgs_involving_6,
|
||||
];
|
||||
mld.add_messages(msgs_with_sender_ids, true);
|
||||
assert.deepEqual(mld.get_messages_sent_by_user(6), msgs_sent_by_6);
|
||||
assert.deepEqual(mld.get_messages_involving_user(6), msgs_involving_6);
|
||||
|
||||
mld.clear();
|
||||
assert_contents(mld, []);
|
||||
assert.equal(mld.closest_id(99), -1);
|
||||
assert.equal(mld.get_last_message_sent_by_me(), undefined);
|
||||
|
||||
mld.add_messages(make_msgs([120, 125.01, 130, 140]), true);
|
||||
assert_contents(mld, [120, 125.01, 130, 140]);
|
||||
mld.set_selected_id(125.01);
|
||||
assert.equal(mld.selected_id(), 125.01);
|
||||
|
||||
mld.get(125.01).id = 145;
|
||||
mld.change_message_id(125.01, 145);
|
||||
assert_contents(mld, [120, 130, 140, 145]);
|
||||
|
||||
for (const msg of mld.all_messages_after_mute_filtering()) {
|
||||
msg.unread = false;
|
||||
}
|
||||
|
||||
assert.equal(mld.first_unread_message_id(), 145);
|
||||
assert.equal(mld.has_unread_messages(), false);
|
||||
});
|
||||
|
||||
run_test("muting", () => {
|
||||
const dm_filter = new Filter([{operator: "dm", operand: [1]}]);
|
||||
let mld = new MessageListData({
|
||||
excludes_muted_topics: dm_filter.excludes_muted_topics(),
|
||||
excludes_muted_users: dm_filter.excludes_muted_users(),
|
||||
filter: dm_filter,
|
||||
});
|
||||
|
||||
const msgs = [
|
||||
{id: 1, type: "stream", stream_id: 1, topic: "muted"},
|
||||
{id: 2, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
// mentions override muting
|
||||
{id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true},
|
||||
|
||||
// 10,12 = muted users, 9 = non-muted user, 11 = you
|
||||
// muted to group direct message
|
||||
{id: 4, type: "private", to_user_ids: "9,10", sender_id: 10},
|
||||
// non-muted to group direct message
|
||||
{id: 5, type: "private", to_user_ids: "9,10", sender_id: 9},
|
||||
// muted to 1:1 direct message
|
||||
{id: 6, type: "private", to_user_ids: "10", sender_id: 10},
|
||||
// non-muted to 1:1 direct message
|
||||
{id: 7, type: "private", to_user_ids: "9", sender_id: 9},
|
||||
// 1:1 direct message to muted
|
||||
{id: 8, type: "private", to_user_ids: "10", sender_id: 11},
|
||||
// 1:1 direct message to non-muted
|
||||
{id: 9, type: "private", to_user_ids: "9", sender_id: 11},
|
||||
// group direct message with everyone muted
|
||||
{id: 10, type: "private", to_user_ids: "10,12", sender_id: 10},
|
||||
];
|
||||
|
||||
user_topics.update_user_topics(
|
||||
1,
|
||||
"random stream name",
|
||||
"muted",
|
||||
user_topics.all_visibility_policies.MUTED,
|
||||
);
|
||||
muted_users.add_muted_user(10);
|
||||
muted_users.add_muted_user(12);
|
||||
|
||||
// `messages_filtered_for_topic_mutes` should skip filtering
|
||||
// messages if `excludes_muted_topics` is false.
|
||||
assert.deepEqual(mld.messages_filtered_for_topic_mutes(msgs), msgs);
|
||||
|
||||
// If we are in a 1:1 direct message narrow, `messages_filtered_for_user_mutes`
|
||||
// should skip filtering messages.
|
||||
assert.deepEqual(mld.messages_filtered_for_user_mutes(msgs), msgs);
|
||||
|
||||
// Test actual behaviour of `messages_filtered_for_*` methods.
|
||||
mld.excludes_muted_topics = true;
|
||||
mld.filter = new Filter([{operator: "stream", operand: "general"}]);
|
||||
mld.excludes_muted_users = mld.filter.excludes_muted_users();
|
||||
const res = mld.messages_filtered_for_topic_mutes(msgs);
|
||||
assert.deepEqual(res, [
|
||||
{id: 2, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
{id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true}, // mentions override muting
|
||||
|
||||
// `messages_filtered_for_topic_mutes` does not affect direct messages
|
||||
{id: 4, type: "private", to_user_ids: "9,10", sender_id: 10},
|
||||
{id: 5, type: "private", to_user_ids: "9,10", sender_id: 9},
|
||||
{id: 6, type: "private", to_user_ids: "10", sender_id: 10},
|
||||
{id: 7, type: "private", to_user_ids: "9", sender_id: 9},
|
||||
{id: 8, type: "private", to_user_ids: "10", sender_id: 11},
|
||||
{id: 9, type: "private", to_user_ids: "9", sender_id: 11},
|
||||
{id: 10, type: "private", to_user_ids: "10,12", sender_id: 10},
|
||||
]);
|
||||
|
||||
const res_user = mld.messages_filtered_for_user_mutes(msgs);
|
||||
assert.deepEqual(res_user, [
|
||||
// `messages_filtered_for_user_mutes` does not affect stream messages
|
||||
{id: 1, type: "stream", stream_id: 1, topic: "muted"},
|
||||
{id: 2, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
{id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true},
|
||||
// muted to group direct message
|
||||
{id: 4, type: "private", to_user_ids: "9,10", sender_id: 10},
|
||||
// non-muted to group direct message
|
||||
{id: 5, type: "private", to_user_ids: "9,10", sender_id: 9},
|
||||
// non-muted to 1:1 direct message
|
||||
{id: 7, type: "private", to_user_ids: "9", sender_id: 9},
|
||||
// 1:1 direct message to non-muted
|
||||
{id: 9, type: "private", to_user_ids: "9", sender_id: 11},
|
||||
]);
|
||||
|
||||
// Output filtered based on both topic and user muting.
|
||||
mld._all_items = msgs;
|
||||
const filtered_messages = mld.unmuted_messages(mld._all_items);
|
||||
assert.deepEqual(filtered_messages, [
|
||||
{id: 2, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
{id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true},
|
||||
{id: 4, type: "private", to_user_ids: "9,10", sender_id: 10},
|
||||
{id: 5, type: "private", to_user_ids: "9,10", sender_id: 9},
|
||||
{id: 7, type: "private", to_user_ids: "9", sender_id: 9},
|
||||
{id: 9, type: "private", to_user_ids: "9", sender_id: 11},
|
||||
]);
|
||||
|
||||
// Also verify that, the correct set of messages is stored in `_items`
|
||||
// once we update the list for muting.
|
||||
mld.update_items_for_muting();
|
||||
assert.deepEqual(filtered_messages, mld._items);
|
||||
|
||||
// MessageListData methods should always attempt to filter messages,
|
||||
// and keep `_all_items` up-to-date.
|
||||
mld = new MessageListData({
|
||||
excludes_muted_topics: true,
|
||||
filter: new Filter([]),
|
||||
});
|
||||
assert.deepEqual(mld._all_items, []);
|
||||
|
||||
let unmuted_messages_calls = 0;
|
||||
mld.unmuted_messages = (messages) => {
|
||||
unmuted_messages_calls += 1;
|
||||
return messages;
|
||||
};
|
||||
|
||||
mld.add_anywhere([{id: 10}, {id: 20}]);
|
||||
assert.equal(unmuted_messages_calls, 1);
|
||||
assert_msg_ids(mld._all_items, [10, 20]);
|
||||
|
||||
mld.prepend([{id: 9}, {id: 19}]);
|
||||
assert.equal(unmuted_messages_calls, 2);
|
||||
assert_msg_ids(mld._all_items, [9, 19, 10, 20]);
|
||||
|
||||
mld.append([{id: 11}, {id: 21}]);
|
||||
assert.equal(unmuted_messages_calls, 3);
|
||||
assert_msg_ids(mld._all_items, [9, 19, 10, 20, 11, 21]);
|
||||
|
||||
mld.remove([9]);
|
||||
assert_msg_ids(mld._all_items, [19, 10, 20, 11, 21]);
|
||||
|
||||
mld.reorder_messages(20);
|
||||
assert_msg_ids(mld._all_items, [10, 11, 19, 20, 21]);
|
||||
|
||||
mld.clear();
|
||||
assert_msg_ids(mld._all_items, []);
|
||||
|
||||
// Test `add_messages` populates the `info` dict **after**
|
||||
// filtering the messages.
|
||||
mld = new MessageListData({
|
||||
excludes_muted_topics: true,
|
||||
filter: new Filter([]),
|
||||
});
|
||||
|
||||
const orig_messages = [
|
||||
{id: 3, type: "stream", stream_id: 1, topic: "muted"},
|
||||
{id: 4, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
{id: 7, type: "stream", stream_id: 1, topic: "muted"},
|
||||
{id: 8, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
];
|
||||
|
||||
const orig_info = mld.add_messages(orig_messages, true);
|
||||
assert.deepEqual(orig_info, {
|
||||
top_messages: [],
|
||||
interior_messages: [],
|
||||
bottom_messages: [
|
||||
{id: 4, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
{id: 8, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
],
|
||||
});
|
||||
|
||||
assert_msg_ids(mld._all_items, [3, 4, 7, 8]);
|
||||
assert_msg_ids(mld._items, [4, 8]);
|
||||
|
||||
const more_messages = [
|
||||
{id: 1, type: "stream", stream_id: 1, topic: "muted"},
|
||||
{id: 2, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
{id: 3, type: "stream", stream_id: 1, topic: "muted"}, // dup
|
||||
{id: 5, type: "stream", stream_id: 1, topic: "muted"},
|
||||
{id: 6, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
{id: 9, type: "stream", stream_id: 1, topic: "muted"},
|
||||
{id: 10, type: "stream", stream_id: 1, topic: "whatever"},
|
||||
];
|
||||
|
||||
const more_info = mld.add_messages(more_messages, true);
|
||||
|
||||
assert_msg_ids(mld._all_items, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
assert_msg_ids(mld._items, [2, 4, 6, 8, 10]);
|
||||
|
||||
assert.deepEqual(more_info, {
|
||||
top_messages: [{id: 2, type: "stream", stream_id: 1, topic: "whatever"}],
|
||||
interior_messages: [{id: 6, type: "stream", stream_id: 1, topic: "whatever"}],
|
||||
bottom_messages: [{id: 10, type: "stream", stream_id: 1, topic: "whatever"}],
|
||||
});
|
||||
});
|
||||
|
||||
run_test("errors", () => {
|
||||
const mld = new MessageListData({
|
||||
excludes_muted_topics: false,
|
||||
filter: new Filter([]),
|
||||
});
|
||||
assert.equal(mld.get("bogus-id"), undefined);
|
||||
|
||||
blueslip.expect("error", "Duplicate message added to MessageListData");
|
||||
mld._hash.set(1, "taken");
|
||||
mld._add_to_hash(make_msgs([1]));
|
||||
});
|
||||
Reference in New Issue
Block a user