mirror of
https://github.com/zulip/zulip.git
synced 2026-06-03 21:01:43 +08:00
Previously, direct message conversations including a deactivated user were unconditionally hidden from the unzoomed left sidebar, even when they had unread messages. Their unread counts were silently rolled into the "More conversations" total, leaving a user with an unread DM from someone whose account was later deactivated no obvious path from the sidebar to that unread message. Only hide deactivated-user DMs that have no unread messages, so the decluttering benefit is preserved without stranding unread messages out of sight. Reported at: https://chat.zulip.org/#narrow/channel/9-issues/topic/Ordering.20of.20topics.20to.20include.20unread.20first/with/2462695 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
519 lines
17 KiB
JavaScript
519 lines
17 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("node:assert/strict");
|
|
|
|
const {make_realm} = require("./lib/example_realm.cjs");
|
|
const {make_bot, make_user} = require("./lib/example_user.cjs");
|
|
const {make_message_list} = require("./lib/message_list.cjs");
|
|
const {mock_esm, zrequire} = require("./lib/namespace.cjs");
|
|
const {run_test} = require("./lib/test.cjs");
|
|
const blueslip = require("./lib/zblueslip.cjs");
|
|
|
|
const unread = mock_esm("../src/unread", {
|
|
num_unread_mentions_for_user_ids_strings(user_ids_string) {
|
|
if (user_ids_string === "103") {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
});
|
|
|
|
mock_esm("../src/settings_data", {
|
|
user_can_access_all_other_users: () => true,
|
|
});
|
|
mock_esm("../src/user_status", {
|
|
get_status_emoji: () => ({
|
|
emoji_code: "20",
|
|
}),
|
|
});
|
|
|
|
const narrow_state = zrequire("narrow_state");
|
|
const people = zrequire("people");
|
|
const pm_conversations = zrequire("pm_conversations");
|
|
const pm_list_data = zrequire("pm_list_data");
|
|
const message_lists = zrequire("message_lists");
|
|
const {set_realm} = zrequire("state_data");
|
|
const {initialize_user_settings} = zrequire("user_settings");
|
|
|
|
set_realm(make_realm());
|
|
initialize_user_settings({user_settings: {}});
|
|
|
|
const alice = make_user({
|
|
email: "alice@zulip.com",
|
|
user_id: 101,
|
|
full_name: "Alice",
|
|
});
|
|
const bob = make_user({
|
|
email: "bob@zulip.com",
|
|
user_id: 102,
|
|
full_name: "Bob",
|
|
});
|
|
const me = make_user({
|
|
email: "me@zulip.com",
|
|
user_id: 103,
|
|
full_name: "Me Myself",
|
|
});
|
|
const zoe = make_user({
|
|
email: "zoe@zulip.com",
|
|
user_id: 104,
|
|
full_name: "Zoe",
|
|
});
|
|
const cardelio = make_user({
|
|
email: "cardelio@zulip.com",
|
|
user_id: 105,
|
|
full_name: "Cardelio",
|
|
});
|
|
const iago = make_user({
|
|
email: "iago@zulip.com",
|
|
user_id: 106,
|
|
full_name: "Iago",
|
|
});
|
|
const bot_test = make_bot({
|
|
email: "outgoingwebhook@zulip.com",
|
|
user_id: 314,
|
|
full_name: "Outgoing webhook",
|
|
});
|
|
// Add users to `valid_user_ids`.
|
|
const source = "server_events";
|
|
people.add_active_user(alice, source);
|
|
people.add_active_user(bob, source);
|
|
people.add_active_user(me, source);
|
|
people.add_active_user(zoe, source);
|
|
people.add_active_user(cardelio, source);
|
|
people.add_active_user(iago, source);
|
|
people.add_active_user(bot_test, source);
|
|
people.initialize_current_user(me.user_id);
|
|
|
|
function test(label, f) {
|
|
run_test(label, (helpers) => {
|
|
message_lists.set_current(undefined);
|
|
pm_conversations.clear_for_testing();
|
|
f(helpers);
|
|
});
|
|
}
|
|
|
|
function set_pm_with_filter(user_ids) {
|
|
message_lists.set_current(make_message_list([{operator: "dm", operand: user_ids}]));
|
|
}
|
|
|
|
function check_list_info(list, length, more_unread, recipients_array) {
|
|
assert.deepEqual(list.conversations_to_be_shown.length, length);
|
|
assert.deepEqual(list.more_conversations_unread_count, more_unread);
|
|
assert.deepEqual(
|
|
list.conversations_to_be_shown.map((conversation) => conversation.recipients),
|
|
recipients_array,
|
|
);
|
|
}
|
|
|
|
test("get_conversations", ({override}) => {
|
|
pm_conversations.recent.insert([alice.user_id, bob.user_id], 1);
|
|
pm_conversations.recent.insert([me.user_id], 2);
|
|
let num_unread_for_user_ids_string = 1;
|
|
override(unread, "num_unread_for_user_ids_string", () => num_unread_for_user_ids_string);
|
|
|
|
assert.equal(narrow_state.filter(), undefined);
|
|
|
|
const expected_data = [
|
|
{
|
|
is_bot: false,
|
|
is_current_user: true,
|
|
is_active: false,
|
|
includes_deactivated_user: false,
|
|
is_group: false,
|
|
is_zero: false,
|
|
recipients: "Me Myself",
|
|
unread: 1,
|
|
url: "#narrow/dm/103-Me-Myself",
|
|
user_circle_class: "user-circle-offline",
|
|
user_ids_string: "103",
|
|
status_emoji_info: {
|
|
emoji_code: "20",
|
|
},
|
|
has_unread_mention: true,
|
|
},
|
|
{
|
|
recipients: "Alice, Bob",
|
|
is_current_user: false,
|
|
user_ids_string: "101,102",
|
|
unread: 1,
|
|
is_zero: false,
|
|
is_active: false,
|
|
includes_deactivated_user: false,
|
|
url: "#narrow/dm/101,102-group",
|
|
user_circle_class: undefined,
|
|
is_group: true,
|
|
is_bot: false,
|
|
status_emoji_info: undefined,
|
|
has_unread_mention: false,
|
|
},
|
|
];
|
|
|
|
let pm_data = pm_list_data.get_conversations();
|
|
assert.deepEqual(pm_data, expected_data);
|
|
|
|
num_unread_for_user_ids_string = 0;
|
|
|
|
pm_data = pm_list_data.get_conversations();
|
|
expected_data[0].unread = 0;
|
|
expected_data[0].is_zero = true;
|
|
expected_data[1].unread = 0;
|
|
expected_data[1].is_zero = true;
|
|
assert.deepEqual(pm_data, expected_data);
|
|
|
|
pm_data = pm_list_data.get_conversations();
|
|
assert.deepEqual(pm_data, expected_data);
|
|
|
|
expected_data.unshift({
|
|
recipients: "Iago",
|
|
user_ids_string: "106",
|
|
unread: 0,
|
|
is_zero: true,
|
|
is_active: true,
|
|
includes_deactivated_user: false,
|
|
is_current_user: false,
|
|
url: "#narrow/dm/106-Iago",
|
|
status_emoji_info: {emoji_code: "20"},
|
|
user_circle_class: "user-circle-offline",
|
|
is_group: false,
|
|
is_bot: false,
|
|
has_unread_mention: false,
|
|
});
|
|
set_pm_with_filter([iago.user_id]);
|
|
pm_data = pm_list_data.get_conversations();
|
|
assert.deepEqual(pm_data, expected_data);
|
|
|
|
pm_data = pm_list_data.get_conversations("Ia");
|
|
assert.deepEqual(
|
|
pm_data,
|
|
expected_data.filter((item) => item.recipients === "Iago"),
|
|
);
|
|
|
|
// filter should work with email
|
|
pm_data = pm_list_data.get_conversations("me@zulip");
|
|
assert.deepEqual(
|
|
pm_data,
|
|
expected_data.filter((item) => item.recipients === "Me Myself"),
|
|
);
|
|
});
|
|
|
|
test("get_conversations bot", ({override}) => {
|
|
pm_conversations.recent.insert([alice.user_id, bob.user_id], 1);
|
|
pm_conversations.recent.insert([bot_test.user_id], 2);
|
|
|
|
override(unread, "num_unread_for_user_ids_string", () => 1);
|
|
|
|
assert.equal(narrow_state.filter(), undefined);
|
|
|
|
const expected_data = [
|
|
{
|
|
recipients: "Outgoing webhook",
|
|
user_ids_string: "314",
|
|
is_current_user: false,
|
|
unread: 1,
|
|
is_zero: false,
|
|
is_active: false,
|
|
includes_deactivated_user: false,
|
|
url: "#narrow/dm/314-Outgoing-webhook",
|
|
status_emoji_info: undefined,
|
|
user_circle_class: "user-circle-offline",
|
|
is_group: false,
|
|
is_bot: true,
|
|
has_unread_mention: false,
|
|
},
|
|
{
|
|
recipients: "Alice, Bob",
|
|
user_ids_string: "101,102",
|
|
is_current_user: false,
|
|
unread: 1,
|
|
is_zero: false,
|
|
is_active: false,
|
|
includes_deactivated_user: false,
|
|
url: "#narrow/dm/101,102-group",
|
|
user_circle_class: undefined,
|
|
status_emoji_info: undefined,
|
|
is_group: true,
|
|
is_bot: false,
|
|
has_unread_mention: false,
|
|
},
|
|
];
|
|
|
|
const pm_data = pm_list_data.get_conversations();
|
|
assert.deepEqual(pm_data, expected_data);
|
|
});
|
|
|
|
test("get_active_user_ids_string", () => {
|
|
assert.equal(pm_list_data.get_active_user_ids_string(), undefined);
|
|
|
|
message_lists.set_current(make_message_list([{operator: "stream", operand: "test"}]));
|
|
assert.equal(pm_list_data.get_active_user_ids_string(), undefined);
|
|
|
|
set_pm_with_filter([bob.user_id, alice.user_id]);
|
|
assert.equal(pm_list_data.get_active_user_ids_string(), "101,102");
|
|
|
|
blueslip.expect("warn", "Invalid user_ids");
|
|
set_pm_with_filter([-1]);
|
|
assert.equal(pm_list_data.get_active_user_ids_string(), undefined);
|
|
blueslip.reset();
|
|
|
|
set_pm_with_filter([alice.user_id, bob.user_id, me.user_id]);
|
|
assert.equal(pm_list_data.get_active_user_ids_string(), "101,102");
|
|
});
|
|
|
|
test("get_list_info_unread_messages", ({override}) => {
|
|
let list_info;
|
|
assert.equal(narrow_state.filter(), undefined);
|
|
|
|
// Initialize an empty list to start.
|
|
list_info = pm_list_data.get_list_info(false);
|
|
check_list_info(list_info, 0, 0, []);
|
|
|
|
// Mock to arrange that each user has exactly 1 unread.
|
|
override(unread, "num_unread_for_user_ids_string", () => 1);
|
|
|
|
// Initially, append 2 conversations and check for the
|
|
// `conversations_to_be_shown` returned in list_info.
|
|
pm_conversations.recent.insert([alice.user_id], 1);
|
|
pm_conversations.recent.insert([me.user_id], 2);
|
|
|
|
list_info = pm_list_data.get_list_info(false);
|
|
check_list_info(list_info, 2, 0, ["Me Myself", "Alice"]);
|
|
|
|
// Visible conversations are limited to value of
|
|
// `max_conversations_to_show_with_unreads`.
|
|
// Verify that the oldest conversations are not shown and
|
|
// their unreads are counted in more_conversations_unread_count.
|
|
pm_conversations.recent.insert([bob.user_id], 3);
|
|
pm_conversations.recent.insert([alice.user_id, bob.user_id], 4);
|
|
pm_conversations.recent.insert([zoe.user_id], 5);
|
|
pm_conversations.recent.insert([zoe.user_id, bob.user_id], 6);
|
|
pm_conversations.recent.insert([zoe.user_id, alice.user_id], 7);
|
|
pm_conversations.recent.insert([zoe.user_id, bob.user_id, alice.user_id], 8);
|
|
pm_conversations.recent.insert([cardelio.user_id, zoe.user_id], 9);
|
|
pm_conversations.recent.insert([cardelio.user_id, bob.user_id], 10);
|
|
pm_conversations.recent.insert([cardelio.user_id, alice.user_id], 11);
|
|
pm_conversations.recent.insert([cardelio.user_id, zoe.user_id, bob.user_id], 12);
|
|
pm_conversations.recent.insert([cardelio.user_id, zoe.user_id, alice.user_id], 13);
|
|
pm_conversations.recent.insert([cardelio.user_id, bob.user_id, alice.user_id], 14);
|
|
pm_conversations.recent.insert([cardelio.user_id, bob.user_id, alice.user_id, zoe.user_id], 15);
|
|
pm_conversations.recent.insert([cardelio.user_id], 16);
|
|
pm_conversations.recent.insert([iago.user_id], 17);
|
|
|
|
list_info = pm_list_data.get_list_info(false);
|
|
check_list_info(list_info, 15, 2, [
|
|
"Iago",
|
|
"Cardelio",
|
|
"Alice, Bob, Cardelio, Zoe",
|
|
"Alice, Bob, Cardelio",
|
|
"Alice, Cardelio, Zoe",
|
|
"Bob, Cardelio, Zoe",
|
|
"Alice, Cardelio",
|
|
"Bob, Cardelio",
|
|
"Cardelio, Zoe",
|
|
"Alice, Bob, Zoe",
|
|
"Alice, Zoe",
|
|
"Bob, Zoe",
|
|
"Zoe",
|
|
"Alice, Bob",
|
|
"Bob",
|
|
]);
|
|
|
|
// Narrowing to direct messages with Alice adds older
|
|
// one-on-one conversation with her to the list and one
|
|
// unread is removed from more_conversations_unread_count.
|
|
set_pm_with_filter([alice.user_id]);
|
|
list_info = pm_list_data.get_list_info(false);
|
|
check_list_info(list_info, 16, 1, [
|
|
"Iago",
|
|
"Cardelio",
|
|
"Alice, Bob, Cardelio, Zoe",
|
|
"Alice, Bob, Cardelio",
|
|
"Alice, Cardelio, Zoe",
|
|
"Bob, Cardelio, Zoe",
|
|
"Alice, Cardelio",
|
|
"Bob, Cardelio",
|
|
"Cardelio, Zoe",
|
|
"Alice, Bob, Zoe",
|
|
"Alice, Zoe",
|
|
"Bob, Zoe",
|
|
"Zoe",
|
|
"Alice, Bob",
|
|
"Bob",
|
|
"Alice",
|
|
]);
|
|
|
|
// Zooming will show all conversations and there will
|
|
// be no unreads in more_conversations_unread_count.
|
|
list_info = pm_list_data.get_list_info(true);
|
|
check_list_info(list_info, 17, 0, [
|
|
"Iago",
|
|
"Cardelio",
|
|
"Alice, Bob, Cardelio, Zoe",
|
|
"Alice, Bob, Cardelio",
|
|
"Alice, Cardelio, Zoe",
|
|
"Bob, Cardelio, Zoe",
|
|
"Alice, Cardelio",
|
|
"Bob, Cardelio",
|
|
"Cardelio, Zoe",
|
|
"Alice, Bob, Zoe",
|
|
"Alice, Zoe",
|
|
"Bob, Zoe",
|
|
"Zoe",
|
|
"Alice, Bob",
|
|
"Bob",
|
|
"Me Myself",
|
|
"Alice",
|
|
]);
|
|
});
|
|
|
|
test("get_list_info_no_unread_messages", ({override}) => {
|
|
let list_info;
|
|
override(unread, "num_unread_for_user_ids_string", () => 0);
|
|
|
|
pm_conversations.recent.insert([alice.user_id], 1);
|
|
pm_conversations.recent.insert([me.user_id], 2);
|
|
pm_conversations.recent.insert([bob.user_id], 3);
|
|
pm_conversations.recent.insert([zoe.user_id], 4);
|
|
pm_conversations.recent.insert([cardelio.user_id], 5);
|
|
pm_conversations.recent.insert([zoe.user_id, cardelio.user_id], 6);
|
|
pm_conversations.recent.insert([alice.user_id, bob.user_id], 7);
|
|
pm_conversations.recent.insert([zoe.user_id, bob.user_id], 8);
|
|
pm_conversations.recent.insert([alice.user_id, cardelio.user_id], 9);
|
|
pm_conversations.recent.insert([bob.user_id, cardelio.user_id], 10);
|
|
|
|
// Visible conversations are limited to value of
|
|
// `max_conversations_to_show`.
|
|
list_info = pm_list_data.get_list_info(false);
|
|
check_list_info(list_info, 8, 0, [
|
|
"Bob, Cardelio",
|
|
"Alice, Cardelio",
|
|
"Bob, Zoe",
|
|
"Alice, Bob",
|
|
"Cardelio, Zoe",
|
|
"Cardelio",
|
|
"Zoe",
|
|
"Bob",
|
|
]);
|
|
|
|
// Narrowing to direct messages with Alice adds older
|
|
// one-on-one conversation with her to the list.
|
|
set_pm_with_filter([alice.user_id]);
|
|
list_info = pm_list_data.get_list_info(false);
|
|
check_list_info(list_info, 9, 0, [
|
|
"Bob, Cardelio",
|
|
"Alice, Cardelio",
|
|
"Bob, Zoe",
|
|
"Alice, Bob",
|
|
"Cardelio, Zoe",
|
|
"Cardelio",
|
|
"Zoe",
|
|
"Bob",
|
|
"Alice",
|
|
]);
|
|
|
|
// Zooming will show all conversations.
|
|
list_info = pm_list_data.get_list_info(true);
|
|
check_list_info(list_info, 10, 0, [
|
|
"Bob, Cardelio",
|
|
"Alice, Cardelio",
|
|
"Bob, Zoe",
|
|
"Alice, Bob",
|
|
"Cardelio, Zoe",
|
|
"Cardelio",
|
|
"Zoe",
|
|
"Bob",
|
|
"Me Myself",
|
|
"Alice",
|
|
]);
|
|
});
|
|
|
|
test("get_list_info_deactivated_users", ({override}) => {
|
|
override(unread, "num_unread_for_user_ids_string", () => 0);
|
|
|
|
// Set up recent direct message conversations.
|
|
pm_conversations.recent.insert([alice.user_id], 1);
|
|
pm_conversations.recent.insert([me.user_id], 2);
|
|
pm_conversations.recent.insert([bob.user_id], 3);
|
|
pm_conversations.recent.insert([zoe.user_id], 4);
|
|
pm_conversations.recent.insert([cardelio.user_id], 5);
|
|
|
|
// Deactivate Bob.
|
|
const bob_from_people = people.get_by_user_id(bob.user_id);
|
|
people.deactivate(bob_from_people);
|
|
|
|
// When only 5 direct message conversations are present
|
|
// and Bob is deactivated, we should show only 4.
|
|
let list_info = pm_list_data.get_list_info(false);
|
|
// Verify that Bob (deactivated) is not included.
|
|
check_list_info(list_info, 4, 0, ["Cardelio", "Zoe", "Me Myself", "Alice"]);
|
|
|
|
// Set up more conversations than max_conversations_to_show
|
|
// (which is 8), including one recent group conversation that
|
|
// involves Bob who has been deactivated.
|
|
pm_conversations.recent.insert([zoe.user_id, cardelio.user_id], 6);
|
|
pm_conversations.recent.insert([bob.user_id, cardelio.user_id], 7);
|
|
pm_conversations.recent.insert([alice.user_id, iago.user_id], 8);
|
|
pm_conversations.recent.insert([alice.user_id, cardelio.user_id], 9);
|
|
pm_conversations.recent.insert([zoe.user_id, iago.user_id], 10);
|
|
pm_conversations.recent.insert([iago.user_id], 11);
|
|
pm_conversations.recent.insert([alice.user_id, zoe.user_id], 12);
|
|
pm_conversations.recent.insert([cardelio.user_id, iago.user_id], 13);
|
|
|
|
// There are 13 total conversations, 2 involve Bob and are excluded.
|
|
// From the remaining 11 conversantions latest 8 are included.
|
|
list_info = pm_list_data.get_list_info(false);
|
|
// Verify that Bob (deactivated) is not included.
|
|
check_list_info(list_info, 8, 0, [
|
|
"Cardelio, Iago",
|
|
"Alice, Zoe",
|
|
"Iago",
|
|
"Iago, Zoe",
|
|
"Alice, Cardelio",
|
|
"Alice, Iago",
|
|
"Cardelio, Zoe",
|
|
"Cardelio",
|
|
]);
|
|
|
|
// Zooming in should reveal all direct message conversations including
|
|
// the conversations with Bob.
|
|
list_info = pm_list_data.get_list_info(true);
|
|
check_list_info(list_info, 13, 0, [
|
|
"Cardelio, Iago",
|
|
"Alice, Zoe",
|
|
"Iago",
|
|
"Iago, Zoe",
|
|
"Alice, Cardelio",
|
|
"Alice, Iago",
|
|
"Bob, Cardelio",
|
|
"Cardelio, Zoe",
|
|
"Cardelio",
|
|
"Zoe",
|
|
"Bob",
|
|
"Me Myself",
|
|
"Alice",
|
|
]);
|
|
|
|
override(unread, "num_unread_for_user_ids_string", () => 1);
|
|
|
|
// Verify that with unread messages, conversations with Bob
|
|
// (deactivated) are shown in the unzoomed case.
|
|
list_info = pm_list_data.get_list_info(false);
|
|
check_list_info(list_info, 13, 0, [
|
|
"Cardelio, Iago",
|
|
"Alice, Zoe",
|
|
"Iago",
|
|
"Iago, Zoe",
|
|
"Alice, Cardelio",
|
|
"Alice, Iago",
|
|
"Bob, Cardelio",
|
|
"Cardelio, Zoe",
|
|
"Cardelio",
|
|
"Zoe",
|
|
"Bob",
|
|
"Me Myself",
|
|
"Alice",
|
|
]);
|
|
|
|
// Reactivate Bob to not affect other tests.
|
|
people.add_active_user(bob);
|
|
});
|