zulip/web/tests/recent_senders.test.js

364 lines
10 KiB
JavaScript
Raw Normal View History

"use strict";
const assert = require("node:assert/strict");
const {mock_esm, zrequire} = require("./lib/namespace");
const {run_test} = require("./lib/test");
let next_id = 0;
const messages = new Map();
function make_stream_message({stream_id, topic, sender_id}) {
next_id += 1;
const message = {
type: "stream",
stream_id,
id: next_id,
topic,
sender_id,
};
messages.set(message.id, message);
return message;
}
mock_esm("../src/message_store", {
get: (message_id) => messages.get(message_id),
});
const people = zrequire("people");
people.initialize_current_user(1);
const rs = zrequire("recent_senders");
zrequire("message_util.ts");
function test(label, f) {
run_test(label, ({override}) => {
messages.clear();
next_id = 0;
rs.clear_for_testing();
f({override});
});
}
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
test("IdTracker", () => {
const id_tracker = new rs.IdTracker();
function test_add(id, expected_max_id) {
id_tracker.add(id);
assert.equal(id_tracker.max_id(), expected_max_id);
}
test_add(5, 5);
test_add(7, 7);
test_add(3, 7);
test_add(10, 10);
test_add(12, 12);
test_add(11, 12);
function test_remove(id, expected_max_id) {
id_tracker.remove(id);
assert.equal(id_tracker.max_id(), expected_max_id);
}
test_remove(10, 12);
test_remove(999999, 12); // bogus id has no effect
test_remove(3, 12);
test_remove(12, 11);
test_add(3, 11);
test_add(7, 11);
test_add(13, 13);
test_remove(3, 13);
test_remove(13, 11);
});
test("noop process_topic_edit", () => {
// Just get line coverage on defensive code.
const bogus_ids = [333, 444];
rs.process_topic_edit({message_ids: bogus_ids});
});
test("update_topics_of_deleted_message_ids", () => {
// Just get line coverage on defensive code.
const stream_id = 555;
const topic = "whatever";
const sender_id = 999;
const message = make_stream_message({
stream_id,
topic,
sender_id,
});
rs.update_topics_of_deleted_message_ids([message.id]);
assert.deepEqual(rs.get_topic_recent_senders(stream_id, topic), []);
rs.process_stream_message(message);
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
assert.deepEqual(rs.get_topic_recent_senders(stream_id, topic), [sender_id]);
});
test("process_stream_message", () => {
const stream1 = 1;
const stream2 = 2;
const stream3 = 3;
const topic1 = "topic-1";
const topic2 = "topic-2";
const topic3 = "topic-3";
const topic4 = "topic-4";
const sender1 = 1;
const sender2 = 2;
const sender3 = 3;
const stream4 = 4;
const stream5 = 5;
// New stream
const message1 = make_stream_message({
stream_id: stream1,
topic: topic1,
sender_id: sender1,
});
const message2 = make_stream_message({
stream_id: stream2,
topic: topic1,
sender_id: sender2,
});
rs.process_stream_message(message1);
rs.process_stream_message(message2);
// Users have posted in only one of the streams
assert.equal(
rs.compare_by_recency({user_id: sender1}, {user_id: sender2}, stream1, topic1) < 0,
true,
);
assert.equal(
rs.compare_by_recency({user_id: sender1}, {user_id: sender2}, stream2, topic1) > 0,
true,
);
// Users haven't posted in this stream, return zero
assert.equal(
rs.compare_by_recency({user_id: sender1}, {user_id: sender2}, stream3, undefined) === 0,
true,
);
// New topic
const message3 = make_stream_message({
stream_id: stream1,
topic: topic2,
sender_id: sender3,
});
rs.process_stream_message(message3);
assert.equal(
rs.compare_by_recency({user_id: sender3}, {user_id: sender2}, stream1, topic2) < 0,
true,
);
// New sender
const message4 = make_stream_message({
stream_id: stream1,
topic: topic1,
sender_id: sender2,
});
rs.process_stream_message(message4);
assert.equal(
rs.compare_by_recency({user_id: sender1}, {user_id: sender2}, stream1, topic1) > 0,
true,
);
// More recent message
const message5 = make_stream_message({
stream_id: stream1,
topic: topic1,
sender_id: sender1,
});
rs.process_stream_message(message5);
assert.equal(
rs.compare_by_recency({user_id: sender1}, {user_id: sender2}, stream1, topic1) < 0,
true,
);
// Messages sent by sender1 in stream1 > topic1
assert.equal(rs.get_topic_message_ids_for_sender(stream1, topic1, sender1).size, 2);
// Messages sent by sender1 in stream1 > topic2
assert.equal(rs.get_topic_message_ids_for_sender(stream1, topic2, sender1).size, 0);
// Same stream, but different topics
const message6 = make_stream_message({
stream_id: stream3,
topic: topic1,
sender_id: sender1,
});
const message7 = make_stream_message({
stream_id: stream3,
topic: topic2,
sender_id: sender2,
});
const message8 = make_stream_message({
stream_id: stream3,
topic: topic3,
sender_id: sender3,
});
rs.process_stream_message(message6);
rs.process_stream_message(message7);
rs.process_stream_message(message8);
// topic3 has a message in it, but sender1 nor sender2 have participated, so sort by stream
assert.equal(
rs.compare_by_recency({user_id: sender1}, {user_id: sender2}, stream3, topic3) > 0,
true,
);
assert.equal(
rs.compare_by_recency({user_id: sender2}, {user_id: sender1}, stream3, topic3) < 0,
true,
);
assert.equal(rs.compare_by_recency({}, {}, (next_id += 1), ""), 0);
// new message in topic2
const message9 = make_stream_message({
stream_id: stream3,
topic: topic2,
sender_id: sender3,
});
rs.process_stream_message(message9);
// Test topic change
assert.equal(rs.get_topic_recent_senders(stream3, topic3).toString(), "3");
assert.equal(rs.get_topic_recent_senders(stream3, topic2).toString(), "3,2");
// message7's topic was changed by user
message7.topic = topic3;
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
rs.process_topic_edit({
message_ids: [message7.id],
old_stream_id: stream3,
new_stream_id: stream3,
old_topic: topic2,
new_topic: topic3,
});
assert.equal(rs.get_topic_recent_senders(stream3, topic3).toString(), "3,2");
assert.equal(rs.get_topic_recent_senders(stream3, topic2).toString(), "3");
// Test stream change
assert.equal(rs.get_topic_recent_senders(stream3, topic3).toString(), "3,2");
assert.equal(rs.get_topic_recent_senders(stream4, topic3).toString(), "");
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
message7.stream_id = stream4;
message8.stream_id = stream4;
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
rs.process_topic_edit({
message_ids: [message7.id, message8.id],
old_stream_id: stream3,
new_stream_id: stream4,
old_topic: topic3,
new_topic: topic3,
});
assert.equal(rs.get_topic_recent_senders(stream3, topic3).toString(), "");
assert.equal(rs.get_topic_recent_senders(stream4, topic3).toString(), "3,2");
// Test stream & topic change
assert.equal(rs.get_topic_recent_senders(stream4, topic3).toString(), "3,2");
assert.equal(rs.get_topic_recent_senders(stream5, topic4).toString(), "");
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
message7.stream_id = stream5;
message7.topic = topic4;
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
message8.stream_id = stream5;
message8.topic = topic4;
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
rs.process_topic_edit({
message_ids: [message7.id, message8.id],
old_stream_id: stream4,
new_stream_id: stream5,
old_topic: topic3,
new_topic: topic4,
});
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
assert.equal(rs.get_topic_recent_senders(stream4, topic3).toString(), "");
assert.equal(rs.get_topic_recent_senders(stream5, topic4).toString(), "3,2");
assert.equal(rs.get_topic_recent_senders(stream1, topic1).toString(), "1,2");
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
// delete message1 and message5 sent by sender1
rs.update_topics_of_deleted_message_ids([message1.id, message5.id]);
assert.equal(rs.get_topic_recent_senders(stream1, topic1).toString(), "2");
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
// test that we can remove again, harmlessly
rs.update_topics_of_deleted_message_ids([message1.id, message5.id]);
assert.equal(rs.get_topic_recent_senders(stream1, topic1).toString(), "2");
// remove some more senders
rs.update_topics_of_deleted_message_ids([message2.id, message3.id, message4.id, message5.id]);
assert.equal(rs.get_topic_recent_senders(stream1, topic1).toString(), "");
rs.update_topics_of_deleted_message_ids([message6.id, message7.id, message8.id, message9.id]);
assert.equal(rs.get_topic_recent_senders(stream1, topic1).toString(), "");
assert.equal(rs.get_topic_recent_senders(stream2, topic2).toString(), "");
assert.equal(rs.get_topic_recent_senders(stream3, topic3).toString(), "");
// deleting an old message which isn't locally stored.
// We are just testing that it doesn't raise an error;
// no changes should take place in this case.
rs.update_topics_of_deleted_message_ids([-1]);
refactor: Simplify recent_senders code. This reduces our dependency on message_list code (via message_util), and it makes moving streams/topics and deleting messages more performant. For every single message that was being updated or deleted, the previous code was basically re-computing lots of things, including having to iterate through every message in memory to find the messages matching your topic. Now everything basically happens in O(1) time. The only O(N) computation is that we now lazily re-compute the max message id every time you need it for typeahead logic, and then we cache it for subsequent use. The N here is the number of messages that the particular sender has sent to the particular stream/topic combination, so it should always be quite small, except for certain spammy bots. Once the max has been calculated, the common operation of adding a message doesn't invalidate our cached value. We only invalidate the cache on deletes. The main change that we make here from a data standpoint is that we just keep track of all message_ids for all senders. The storage overhead here should be negligible. By keeping track of our own messages, we don't have to punt to other code for update/delete situations. There is similar code in recent_topics that I think can be improved in similar ways, and it would allow us to eliminate functions like this one: export function get_messages_in_topic(stream_id, topic) { return message_list.all .all_messages() .filter( (x) => x.type === "stream" && x.stream_id === stream_id && x.topic.toLowerCase() === topic.toLowerCase(), ); }
2021-03-29 19:42:44 +02:00
// Comparing on a non-existent topic doesn't crash.
assert.equal(
rs.compare_by_recency({user_id: sender2}, {user_id: sender1}, stream3, "bogus") < 0,
true,
);
});
test("process_pms", () => {
const sender1 = 1; // Current user id
const sender2 = 2;
const sender3 = 3;
const user_ids_string = "2,3,4";
rs.process_private_message({
to_user_ids: user_ids_string,
sender_id: sender2,
id: 1,
});
rs.process_private_message({
to_user_ids: user_ids_string,
sender_id: sender3,
id: 2,
});
rs.process_private_message({
to_user_ids: user_ids_string,
sender_id: sender1,
id: 3,
});
// Recent Conversations displays avatars in the opposite order to this since
// that was simpler to implement in HTML.
assert.deepEqual(rs.get_pm_recent_senders(user_ids_string), {
participants: [1, 3, 2],
non_participants: [4],
});
// Direct message doesn't exist.
assert.deepEqual(rs.get_pm_recent_senders("1000,2000"), {
participants: [],
non_participants: [],
});
rs.process_private_message({
to_user_ids: "1",
sender_id: sender1,
id: 4,
});
assert.deepEqual(rs.get_pm_recent_senders("1"), {
participants: [1],
non_participants: [],
});
});