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:
608
web/tests/popover_menus_data.test.cjs
Normal file
608
web/tests/popover_menus_data.test.cjs
Normal file
@@ -0,0 +1,608 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {make_user_group} = require("./lib/example_group.cjs");
|
||||
const {make_realm} = require("./lib/example_realm.cjs");
|
||||
const {make_user} = require("./lib/example_user.cjs");
|
||||
const {mock_esm, zrequire} = require("./lib/namespace.cjs");
|
||||
const {run_test} = require("./lib/test.cjs");
|
||||
const {$} = require("./lib/zjquery.cjs");
|
||||
const {page_params} = require("./lib/zpage_params.cjs");
|
||||
|
||||
const {Filter} = zrequire("filter");
|
||||
const {MessageList} = zrequire("message_list");
|
||||
const message_lists = zrequire("message_lists");
|
||||
|
||||
const message_parser = mock_esm("../src/message_parser", {
|
||||
message_has_link_preview: () => false,
|
||||
});
|
||||
|
||||
const popover_menus_data = zrequire("popover_menus_data");
|
||||
const people = zrequire("people");
|
||||
const user_groups = zrequire("user_groups");
|
||||
const {MessageListData} = zrequire("message_list_data");
|
||||
const {set_current_user, set_realm} = zrequire("state_data");
|
||||
const settings_config = zrequire("settings_config");
|
||||
|
||||
const noop = function () {};
|
||||
|
||||
// Define MessageList stuff
|
||||
function MessageListView() {
|
||||
return {
|
||||
maybe_rerender: noop,
|
||||
append: noop,
|
||||
prepend: noop,
|
||||
clear_rendering_state: noop,
|
||||
get_row: () => ({
|
||||
find(selector) {
|
||||
if (selector === ".message_content") {
|
||||
return {
|
||||
hasClass(class_name) {
|
||||
assert.equal(class_name, "condensed");
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
assert.equal(selector, ".message_controls .reaction_button");
|
||||
return {
|
||||
length: 1,
|
||||
css(property) {
|
||||
assert.equal(property, "display");
|
||||
return "none";
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
message_containers: new Map(),
|
||||
};
|
||||
}
|
||||
mock_esm("../src/message_list_view", {
|
||||
MessageListView,
|
||||
});
|
||||
mock_esm("../src/ui_util", {
|
||||
listener_for_preferred_color_scheme_change: noop,
|
||||
});
|
||||
mock_esm("../src/hash_util", {
|
||||
by_conversation_and_time_url: () => "conversation_and_time_url",
|
||||
});
|
||||
mock_esm("../src/stream_data", {
|
||||
is_subscribed: () => true,
|
||||
is_stream_archived_by_id: () => false,
|
||||
get_sub_by_id: () => noop,
|
||||
user_can_move_messages_within_channel: () => true,
|
||||
is_empty_topic_only_channel: () => false,
|
||||
});
|
||||
mock_esm("../src/group_permission_settings", {
|
||||
get_group_permission_setting_config() {
|
||||
return {
|
||||
allow_everyone_group: false,
|
||||
};
|
||||
},
|
||||
});
|
||||
mock_esm("../src/timerender", {
|
||||
display_time_zone: "UTC",
|
||||
get_localized_date_or_time_for_format(date) {
|
||||
// Return a simple date string for testing.
|
||||
const d = new Date(date);
|
||||
const months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
return `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
|
||||
},
|
||||
});
|
||||
|
||||
const current_user = {};
|
||||
set_current_user(current_user);
|
||||
const realm = make_realm();
|
||||
set_realm(realm);
|
||||
|
||||
// Define test users
|
||||
const mike = make_user({
|
||||
user_id: 1000,
|
||||
full_name: "Test Mike",
|
||||
email: "mike@example.com",
|
||||
});
|
||||
|
||||
const bob = make_user({
|
||||
user_id: 2000,
|
||||
full_name: "Test Bob",
|
||||
email: "bob@example.com",
|
||||
});
|
||||
|
||||
const me = make_user({
|
||||
user_id: 999,
|
||||
full_name: "Test Myself",
|
||||
email: "me@example.com",
|
||||
});
|
||||
|
||||
const everyone = make_user_group({
|
||||
name: "role:everyone",
|
||||
id: 2,
|
||||
members: new Set([999, 1000, 2000]),
|
||||
is_system_group: true,
|
||||
direct_subgroup_ids: new Set(),
|
||||
});
|
||||
user_groups.initialize({realm_user_groups: [everyone]});
|
||||
|
||||
// Helper functions:
|
||||
function add_initialize_users() {
|
||||
// Initialize people
|
||||
people.init();
|
||||
|
||||
// Add users
|
||||
people.add_active_user(mike);
|
||||
people.add_active_user(bob);
|
||||
people.add_active_user(me);
|
||||
|
||||
// Initialize current user
|
||||
people.initialize_current_user(me.user_id);
|
||||
}
|
||||
|
||||
function init_message_list() {
|
||||
const filter = new Filter([]);
|
||||
const list = new MessageList({
|
||||
data: new MessageListData({
|
||||
excludes_muted_topics: false,
|
||||
filter,
|
||||
}),
|
||||
});
|
||||
|
||||
assert.equal(list.empty(), true);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// Append message to message_list, also add container to message_lists
|
||||
function add_message_with_view(list, messages) {
|
||||
list.append(messages, true);
|
||||
for (const message of messages) {
|
||||
message_lists.current.view.message_containers.set(message.id, {
|
||||
is_hidden: message.is_hidden,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Function sets page parameters with no time constraints on editing the message.
|
||||
// User is assumed to not be an admin.
|
||||
function set_page_params_no_edit_restrictions({override}) {
|
||||
page_params.is_spectator = false;
|
||||
override(realm, "realm_allow_message_editing", true);
|
||||
override(realm, "realm_message_content_edit_limit_seconds", null);
|
||||
override(
|
||||
realm,
|
||||
"realm_message_edit_history_visibility_policy",
|
||||
settings_config.message_edit_history_visibility_policy_values.always.code,
|
||||
);
|
||||
override(realm, "realm_message_content_delete_limit_seconds", null);
|
||||
override(realm, "realm_enable_read_receipts", true);
|
||||
override(realm, "realm_move_messages_within_stream_limit_seconds", null);
|
||||
}
|
||||
|
||||
// Test init function
|
||||
function test(label, f) {
|
||||
run_test(label, (helpers) => {
|
||||
// Stubs for calculate_timestamp_widths()
|
||||
$("<div>")[0].remove = noop;
|
||||
|
||||
// Clear stuff for testing environment
|
||||
add_initialize_users();
|
||||
message_lists.initialize();
|
||||
f(helpers);
|
||||
});
|
||||
}
|
||||
|
||||
// Test functions
|
||||
test("my_message_all_actions", ({override}) => {
|
||||
// Set page parameters.
|
||||
set_page_params_no_edit_restrictions({override});
|
||||
override(realm, "realm_can_delete_any_message_group", everyone.id);
|
||||
override(realm, "realm_can_delete_own_message_group", everyone.id);
|
||||
override(realm, "realm_can_move_messages_between_topics_group", everyone.id);
|
||||
override(current_user, "user_id", me.user_id);
|
||||
// Get message with maximum permissions available
|
||||
// Initialize message list
|
||||
const list = init_message_list();
|
||||
message_lists.set_current(list);
|
||||
|
||||
// Assume message has been previously edited.
|
||||
// Message is sent by me, and is a stream. I should have all permissions to this message.
|
||||
const messages = [
|
||||
{
|
||||
id: 1,
|
||||
type: "stream",
|
||||
sender_id: me.user_id,
|
||||
is_hidden: false,
|
||||
sent_by_me: true,
|
||||
locally_echoed: false,
|
||||
is_stream: true,
|
||||
stream_id: 1,
|
||||
unread: false,
|
||||
collapsed: false,
|
||||
hide_link_previews: false,
|
||||
not_spectator: true,
|
||||
submessages: [],
|
||||
edit_history: [
|
||||
{
|
||||
prev_content: "Previous content",
|
||||
prev_stream: 0,
|
||||
prev_topic: "Previous topic",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
add_message_with_view(list, messages);
|
||||
const response = popover_menus_data.get_actions_popover_content_context(1, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: true,
|
||||
});
|
||||
assert.equal(response.message_id, 1);
|
||||
assert.equal(response.stream_id, 1);
|
||||
assert.equal(response.editability_menu_item, "translated: Edit message");
|
||||
assert.equal(response.move_message_menu_item, "translated: Move messages");
|
||||
assert.equal(response.should_display_mark_as_unread, true);
|
||||
assert.equal(response.view_source_menu_item, undefined);
|
||||
assert.equal(response.should_display_collapse, true);
|
||||
assert.equal(response.should_display_uncollapse, false);
|
||||
assert.equal(response.should_display_hide_link_previews, false);
|
||||
assert.equal(response.should_display_show_link_previews, false);
|
||||
assert.equal(response.should_display_add_reaction_option, true);
|
||||
assert.equal(response.conversation_time_url, "conversation_and_time_url");
|
||||
assert.equal(response.should_display_delete_option, true);
|
||||
assert.equal(response.should_display_read_receipts_option, true);
|
||||
assert.equal(response.should_display_quote_message, true);
|
||||
assert.equal(response.show_quote_and_forward_hotkey_hints, true);
|
||||
|
||||
// A menu opened on a message the selection does not cover quotes just
|
||||
// that message, which is not what > and < would do.
|
||||
const diverging_response = popover_menus_data.get_actions_popover_content_context(1, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: false,
|
||||
});
|
||||
assert.equal(diverging_response.show_quote_and_forward_hotkey_hints, false);
|
||||
assert.equal(response.quote_message_menu_item, "translated: Quote message");
|
||||
assert.equal(response.forward_message_menu_item, "translated: Forward message");
|
||||
|
||||
// The Quote/Forward labels spell out what a text selection will act on.
|
||||
const selection_response = popover_menus_data.get_actions_popover_content_context(1, {
|
||||
kind: "message_selection",
|
||||
quote_content: "selected text",
|
||||
});
|
||||
assert.equal(selection_response.quote_message_menu_item, "translated: Quote selection");
|
||||
assert.equal(selection_response.forward_message_menu_item, "translated: Forward selection");
|
||||
|
||||
const messages_response = popover_menus_data.get_actions_popover_content_context(1, {
|
||||
kind: "selected_messages",
|
||||
message_ids: [1, 2],
|
||||
});
|
||||
assert.equal(messages_response.quote_message_menu_item, "translated: Quote selected messages");
|
||||
assert.equal(
|
||||
messages_response.forward_message_menu_item,
|
||||
"translated: Forward selected messages",
|
||||
);
|
||||
});
|
||||
|
||||
test("link_preview_actions", ({override}) => {
|
||||
set_page_params_no_edit_restrictions({override});
|
||||
override(realm, "realm_can_delete_any_message_group", everyone.id);
|
||||
override(realm, "realm_can_delete_own_message_group", everyone.id);
|
||||
override(realm, "realm_can_move_messages_between_topics_group", everyone.id);
|
||||
override(current_user, "user_id", me.user_id);
|
||||
override(message_parser, "message_has_link_preview", () => true);
|
||||
|
||||
const list = init_message_list();
|
||||
message_lists.set_current(list);
|
||||
|
||||
function preview_message(id, {collapsed, hide_link_previews, is_hidden = false}) {
|
||||
return {
|
||||
id,
|
||||
type: "stream",
|
||||
sender_id: me.user_id,
|
||||
is_hidden,
|
||||
sent_by_me: true,
|
||||
locally_echoed: false,
|
||||
is_stream: true,
|
||||
stream_id: 1,
|
||||
unread: false,
|
||||
collapsed,
|
||||
hide_link_previews,
|
||||
submessages: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Exercise every combination of the collapsed and hide_link_previews
|
||||
// flags on a message that has a website preview, plus one hidden
|
||||
// for a muted sender.
|
||||
add_message_with_view(list, [
|
||||
preview_message(1, {collapsed: false, hide_link_previews: false}),
|
||||
preview_message(2, {collapsed: false, hide_link_previews: true}),
|
||||
preview_message(3, {collapsed: true, hide_link_previews: false}),
|
||||
preview_message(4, {collapsed: true, hide_link_previews: true}),
|
||||
preview_message(5, {collapsed: false, hide_link_previews: false, is_hidden: true}),
|
||||
]);
|
||||
|
||||
let response = popover_menus_data.get_actions_popover_content_context(1, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: true,
|
||||
});
|
||||
assert.equal(response.should_display_hide_link_previews, true);
|
||||
assert.equal(response.should_display_show_link_previews, false);
|
||||
|
||||
response = popover_menus_data.get_actions_popover_content_context(2, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: true,
|
||||
});
|
||||
assert.equal(response.should_display_hide_link_previews, false);
|
||||
assert.equal(response.should_display_show_link_previews, true);
|
||||
|
||||
// Collapsed message: neither toggle is offered, since the preview is
|
||||
// already hidden along with the rest of the body.
|
||||
response = popover_menus_data.get_actions_popover_content_context(3, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: true,
|
||||
});
|
||||
assert.equal(response.should_display_hide_link_previews, false);
|
||||
assert.equal(response.should_display_show_link_previews, false);
|
||||
|
||||
response = popover_menus_data.get_actions_popover_content_context(4, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: true,
|
||||
});
|
||||
assert.equal(response.should_display_hide_link_previews, false);
|
||||
assert.equal(response.should_display_show_link_previews, false);
|
||||
|
||||
// A muted sender's hidden message shows a placeholder, not a preview.
|
||||
response = popover_menus_data.get_actions_popover_content_context(5, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: true,
|
||||
});
|
||||
assert.equal(response.should_display_hide_link_previews, false);
|
||||
assert.equal(response.should_display_show_link_previews, false);
|
||||
|
||||
// A message with no website preview offers neither toggle.
|
||||
override(message_parser, "message_has_link_preview", () => false);
|
||||
response = popover_menus_data.get_actions_popover_content_context(1, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: true,
|
||||
});
|
||||
assert.equal(response.should_display_hide_link_previews, false);
|
||||
assert.equal(response.should_display_show_link_previews, false);
|
||||
});
|
||||
|
||||
test("not_my_message_view_actions", ({override}) => {
|
||||
set_page_params_no_edit_restrictions({override});
|
||||
// Get message that is only viewable
|
||||
override(realm, "realm_can_delete_any_message_group", everyone.id);
|
||||
const list = init_message_list();
|
||||
message_lists.set_current(list);
|
||||
|
||||
// Message is sent by somebody else and is a stream with previous history.
|
||||
// I should only be able to view this message with no edit/move permissions.
|
||||
const messages = [
|
||||
{
|
||||
id: 1,
|
||||
sender_id: mike.user_id,
|
||||
is_hidden: false,
|
||||
sent_by_me: false,
|
||||
locally_echoed: false,
|
||||
is_stream: true,
|
||||
stream_id: 1,
|
||||
collapsed: false,
|
||||
unread: false,
|
||||
edit_history: [
|
||||
{
|
||||
prev_content: "Previous content",
|
||||
prev_stream: 0,
|
||||
prev_topic: "Previous topic",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
add_message_with_view(list, messages);
|
||||
|
||||
const response = popover_menus_data.get_actions_popover_content_context(1, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: true,
|
||||
});
|
||||
|
||||
assert.equal(response.view_source_menu_item, "translated: View original message");
|
||||
assert.equal(response.editability_menu_item, undefined);
|
||||
assert.equal(response.move_message_menu_item, undefined);
|
||||
});
|
||||
|
||||
test("not_my_message_view_source_and_move", ({override}) => {
|
||||
set_page_params_no_edit_restrictions({override});
|
||||
override(realm, "realm_can_delete_any_message_group", everyone.id);
|
||||
override(realm, "realm_can_move_messages_between_topics_group", everyone.id);
|
||||
override(current_user, "user_id", me.user_id);
|
||||
// Get message that is movable with viewable source
|
||||
|
||||
const list = init_message_list();
|
||||
message_lists.set_current(list);
|
||||
|
||||
// Message tests edge case where message it sent by someone else.
|
||||
// Message is movable, however--I should have only view permissions with the exception of moving the message.
|
||||
const messages = [
|
||||
{
|
||||
id: 1,
|
||||
sender_id: mike.user_id,
|
||||
is_hidden: false,
|
||||
sent_by_me: false,
|
||||
locally_echoed: false,
|
||||
is_stream: true,
|
||||
stream_id: 1,
|
||||
type: "stream",
|
||||
unread: false,
|
||||
collapsed: false,
|
||||
topic: "New topic",
|
||||
edit_history: [
|
||||
{
|
||||
prev_content: "Previous content",
|
||||
prev_stream: 0,
|
||||
prev_topic: "Previous topic",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
add_message_with_view(list, messages);
|
||||
|
||||
const response = popover_menus_data.get_actions_popover_content_context(1, {
|
||||
kind: "full_message",
|
||||
hotkeys_agree: true,
|
||||
});
|
||||
assert.equal(response.view_source_menu_item, "translated: View original message");
|
||||
assert.equal(response.editability_menu_item, undefined);
|
||||
assert.equal(response.move_message_menu_item, "translated: Move messages");
|
||||
});
|
||||
|
||||
// Helper to create a minimal message object with a given timestamp.
|
||||
function make_message(id, timestamp) {
|
||||
return {id, timestamp};
|
||||
}
|
||||
|
||||
function date_timestamp(date_string) {
|
||||
return new Date(date_string + "T12:00:00").getTime() / 1000;
|
||||
}
|
||||
|
||||
function make_messages_from_date_counts(date_counts) {
|
||||
const messages = [];
|
||||
let id = 1;
|
||||
for (const {date, count} of date_counts) {
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
messages.push(make_message(id, date_timestamp(date)));
|
||||
id += 1;
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
// Simulate a conversation with bursts separated by gaps.
|
||||
// Burst 1: Jan 5-7 (9 messages)
|
||||
// Gap: 20 days
|
||||
// Burst 2: Jan 27-28 (8 messages)
|
||||
// Gap: 15 days
|
||||
// Burst 3: Feb 12-14 (10 messages)
|
||||
// Gap: 25 days
|
||||
// Burst 4: Mar 11-12 (8 messages)
|
||||
const FOUR_MESSAGE_BURSTS = [
|
||||
// Burst 1
|
||||
{date: "2025-01-05", count: 3},
|
||||
{date: "2025-01-06", count: 3},
|
||||
{date: "2025-01-07", count: 3},
|
||||
// Burst 2
|
||||
{date: "2025-01-27", count: 4},
|
||||
{date: "2025-01-28", count: 4},
|
||||
// Burst 3
|
||||
{date: "2025-02-12", count: 4},
|
||||
{date: "2025-02-13", count: 3},
|
||||
{date: "2025-02-14", count: 3},
|
||||
// Burst 4
|
||||
{date: "2025-03-11", count: 4},
|
||||
{date: "2025-03-12", count: 4},
|
||||
];
|
||||
|
||||
run_test("get_scroll_to_date_suggestions - empty messages", () => {
|
||||
const result = popover_menus_data.get_scroll_to_date_suggestions([]);
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
|
||||
run_test("get_scroll_to_date_suggestions - all messages today", () => {
|
||||
const today = new Date();
|
||||
const today_ts = today.getTime() / 1000;
|
||||
const messages = [make_message(1, today_ts), make_message(2, today_ts + 60)];
|
||||
const result = popover_menus_data.get_scroll_to_date_suggestions(messages);
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
|
||||
run_test("get_scroll_to_date_suggestions - few unique dates", () => {
|
||||
// With 3 unique dates (all in the past), all should be suggested.
|
||||
const messages = [
|
||||
make_message(1, date_timestamp("2025-01-10")),
|
||||
make_message(2, date_timestamp("2025-01-10")),
|
||||
make_message(3, date_timestamp("2025-02-15")),
|
||||
make_message(4, date_timestamp("2025-03-20")),
|
||||
];
|
||||
const result = popover_menus_data.get_scroll_to_date_suggestions(messages);
|
||||
assert.equal(result.length, 3);
|
||||
// Verify chronological order.
|
||||
assert.ok(result[0].iso_date_string < result[1].iso_date_string);
|
||||
assert.ok(result[1].iso_date_string < result[2].iso_date_string);
|
||||
});
|
||||
|
||||
run_test("get_scroll_to_date_suggestions - many dates with gaps", () => {
|
||||
const messages = make_messages_from_date_counts(FOUR_MESSAGE_BURSTS);
|
||||
|
||||
const result = popover_menus_data.get_scroll_to_date_suggestions(messages);
|
||||
// We have 10 unique dates across 4 bursts; expect 3-4 suggestions.
|
||||
assert.ok(result.length >= 3 && result.length <= 4);
|
||||
// Verify chronological order.
|
||||
for (let i = 1; i < result.length; i += 1) {
|
||||
assert.ok(result[i - 1].iso_date_string < result[i].iso_date_string);
|
||||
}
|
||||
// The algorithm should prefer dates at the start of bursts (after gaps).
|
||||
// Jan 27 has a 20-day gap before it, Feb 12 has a 15-day gap,
|
||||
// Mar 11 has a 25-day gap — these should appear as suggestions.
|
||||
const iso_dates = result.map((s) => s.iso_date_string.slice(0, 10));
|
||||
// Burst-start dates (Jan 27, Feb 12, Mar 11) should be well represented.
|
||||
const burst_starts = new Set(["2025-01-27", "2025-02-12", "2025-03-11"]);
|
||||
const burst_start_count = iso_dates.filter((d) => burst_starts.has(d)).length;
|
||||
assert.ok(burst_start_count >= 2, `Expected >=2 burst starts, got ${burst_start_count}`);
|
||||
});
|
||||
|
||||
run_test("get_scroll_to_date_suggestions - single date in the past", () => {
|
||||
const messages = [
|
||||
make_message(1, date_timestamp("2025-06-15")),
|
||||
make_message(2, date_timestamp("2025-06-15")),
|
||||
];
|
||||
const result = popover_menus_data.get_scroll_to_date_suggestions(messages);
|
||||
assert.equal(result.length, 1);
|
||||
});
|
||||
|
||||
run_test("get_scroll_to_date_suggestions - excludes the clicked date", () => {
|
||||
const messages = [
|
||||
make_message(1, date_timestamp("2025-01-10")),
|
||||
make_message(2, date_timestamp("2025-02-15")),
|
||||
make_message(3, date_timestamp("2025-03-20")),
|
||||
];
|
||||
const result = popover_menus_data.get_scroll_to_date_suggestions(
|
||||
messages,
|
||||
date_timestamp("2025-02-15"),
|
||||
);
|
||||
assert.equal(result.length, 2);
|
||||
const dates = result.map((date) => date.iso_date_string.slice(0, 10));
|
||||
assert.ok(!dates.includes("2025-02-15"));
|
||||
});
|
||||
|
||||
run_test("get_scroll_to_date_suggestions - clicked slot is replaced not dropped", () => {
|
||||
const messages = make_messages_from_date_counts(FOUR_MESSAGE_BURSTS);
|
||||
const clicked_date = "2025-02-12";
|
||||
|
||||
const baseline = popover_menus_data.get_scroll_to_date_suggestions(messages);
|
||||
const baseline_dates = baseline.map((s) => s.iso_date_string.slice(0, 10));
|
||||
// The clicked date must be one the slicer would otherwise
|
||||
// pick, or this test proves nothing.
|
||||
assert.ok(baseline_dates.includes(clicked_date));
|
||||
|
||||
const filtered = popover_menus_data.get_scroll_to_date_suggestions(
|
||||
messages,
|
||||
date_timestamp(clicked_date),
|
||||
);
|
||||
const filtered_dates = filtered.map((date) => date.iso_date_string.slice(0, 10));
|
||||
assert.equal(filtered.length, baseline.length);
|
||||
assert.ok(!filtered_dates.includes(clicked_date));
|
||||
});
|
||||
Reference in New Issue
Block a user