"use strict"; const assert = require("node:assert/strict"); const {mock_banners} = require("./lib/compose_banner.cjs"); const {FakeComposeBox} = require("./lib/compose_helpers.cjs"); const {make_user_group} = require("./lib/example_group.cjs"); const {make_realm} = require("./lib/example_realm.cjs"); const {make_stream} = require("./lib/example_stream.cjs"); const {make_cross_realm_bot, make_user, Role} = require("./lib/example_user.cjs"); const {$t} = require("./lib/i18n.cjs"); const {mock_channel_get} = require("./lib/mock_channel.cjs"); const {mock_esm, zrequire} = require("./lib/namespace.cjs"); const {run_test, noop} = require("./lib/test.cjs"); const blueslip = require("./lib/zblueslip.cjs"); const {$} = require("./lib/zjquery.cjs"); const channel = mock_esm("../src/channel"); const compose_banner = zrequire("compose_banner"); const compose_pm_pill = zrequire("compose_pm_pill"); const compose_state = zrequire("compose_state"); const compose_validate = zrequire("compose_validate"); const peer_data = zrequire("peer_data"); const people = zrequire("people"); const resolved_topic = zrequire("resolved_topic"); const {set_current_user, set_realm} = zrequire("state_data"); const stream_data = zrequire("stream_data"); const compose_recipient = zrequire("/compose_recipient"); const user_groups = zrequire("user_groups"); const {initialize_user_settings} = zrequire("user_settings"); mock_esm("../src/group_permission_settings", { get_group_permission_setting_config: () => ({ allow_everyone_group: true, }), }); const REALM_EMPTY_TOPIC_DISPLAY_NAME = "general chat"; const realm = make_realm({ realm_empty_topic_display_name: REALM_EMPTY_TOPIC_DISPLAY_NAME, realm_topics_policy: "allow_empty_topic", }); set_realm(realm); const current_user = {}; set_current_user(current_user); const user_settings = {default_language: "en"}; initialize_user_settings({user_settings}); const me = make_user({ email: "me@example.com", user_id: 30, full_name: "Me Myself", date_joined: new Date(), }); const alice = make_user({ email: "alice@example.com", user_id: 31, full_name: "Alice", }); const bob = make_user({ email: "bob@example.com", user_id: 32, full_name: "Bob", role: Role.ADMINISTRATOR, }); const guest = make_user({ email: "guest@example.com", user_id: 33, full_name: "Guest", role: Role.GUEST, }); const moderator = make_user({ email: "moderator@example.com", user_id: 34, full_name: "Charlie", role: Role.MODERATOR, }); const social_sub = make_stream({ stream_id: 101, name: "social", subscribed: true, }); stream_data.add_sub_for_tests(social_sub); people.add_active_user(me); people.initialize_current_user(me.user_id); people.add_active_user(alice); people.add_active_user(bob); people.add_active_user(guest); const welcome_bot = make_cross_realm_bot({ email: "welcome-bot@example.com", user_id: 4, full_name: "Welcome Bot", }); people.add_cross_realm_user(welcome_bot); const nobody = make_user_group({ name: "role:nobody", id: 1, members: new Set(), is_system_group: true, direct_subgroup_ids: new Set(), }); const everyone = make_user_group({ name: "role:everyone", id: 2, members: new Set([30, 33]), is_system_group: true, direct_subgroup_ids: new Set([5]), }); const admin = make_user_group({ name: "role:administrators", id: 3, members: new Set([32]), is_system_group: true, direct_subgroup_ids: new Set(), }); const moderators = make_user_group({ name: "role:moderators", id: 4, members: new Set([34]), is_system_group: true, direct_subgroup_ids: new Set([3]), }); const members = make_user_group({ name: "role:members", id: 5, members: new Set([31]), is_system_group: true, direct_subgroup_ids: new Set([4]), }); user_groups.initialize({realm_user_groups: [nobody, everyone, admin, moderators, members]}); function test_ui(label, f) { run_test(label, (helpers) => { $("textarea#compose-textarea").val("some message"); return f(helpers); }); } function stub_message_row($textarea) { const $stub = $.set_results("message_row_stub", []); $textarea.set_closest_results(".message_row", $stub); } function initialize_pm_pill(mock_template) { $.clear_all_elements(); $("#compose-send-button").trigger("focus"); $("#compose-send-button .loader").hide(); const $pm_pill_container = $.create("fake-pm-pill-container"); $("#private_message_recipient").set_parent($pm_pill_container); $pm_pill_container.set_find_results(".input", $("#private_message_recipient")); $("#private_message_recipient")[0].before = noop; compose_pm_pill.initialize({ on_pill_create_or_remove: compose_recipient.update_compose_area_placeholder_text, }); mock_template("input_pill.hbs", false, () => "
pill-html
"); mock_banners(); } test_ui("validate", ({mock_template, override}) => { function add_content_to_compose_box() { $("textarea#compose-textarea").val("foobarfoobar"); } override(realm, "realm_can_access_all_users_group", everyone.id); // test validating direct messages compose_state.set_message_type("private"); initialize_pm_pill(mock_template); add_content_to_compose_box(); let pm_recipient_error_rendered = false; override(realm, "realm_direct_message_permission_group", everyone.id); override(realm, "realm_direct_message_initiator_group", everyone.id); mock_template("compose_banner/compose_banner.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.missing_private_message_recipient); assert.equal(data.banner_text, compose_validate.NO_PRIVATE_RECIPIENT_ERROR_MESSAGE); pm_recipient_error_rendered = true; return ""; }); $("#send_message_form").set_find_results(".message-textarea", $("textarea#compose-textarea")); assert.ok(!compose_validate.validate()); assert.ok(pm_recipient_error_rendered); // Textarea should not be disabled for missing recipient. assert.ok(!$("textarea#compose-textarea").prop("disabled")); pm_recipient_error_rendered = false; people.add_active_user(bob); compose_state.set_private_message_recipient_ids([bob.user_id]); assert.ok(compose_validate.validate()); assert.ok(!pm_recipient_error_rendered); assert.ok(!$("textarea#compose-textarea").prop("disabled")); override(realm, "realm_direct_message_initiator_group", admin.id); assert.ok(compose_validate.validate()); assert.ok(!pm_recipient_error_rendered); assert.ok(!$("textarea#compose-textarea").prop("disabled")); override(realm, "realm_direct_message_permission_group", admin.id); assert.ok(compose_validate.validate()); assert.ok(!pm_recipient_error_rendered); assert.ok(!$("textarea#compose-textarea").prop("disabled")); override(realm, "realm_direct_message_initiator_group", everyone.id); override(realm, "realm_direct_message_permission_group", everyone.id); people.deactivate(bob); let deactivated_user_error_rendered = false; mock_template("compose_banner/compose_banner.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.deactivated_user); assert.equal( data.banner_text, $t({defaultMessage: "You cannot send messages to deactivated users."}), ); deactivated_user_error_rendered = true; return ""; }); assert.ok(!compose_validate.validate()); assert.ok(deactivated_user_error_rendered); // Textarea should be disabled for deactivated user. assert.ok($("textarea#compose-textarea").prop("disabled")); bob.is_deleted = true; let deleted_user_error_rendered = false; mock_template("compose_banner/compose_banner.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.deactivated_user); assert.equal( data.banner_text, $t({defaultMessage: "You cannot send messages to deleted users."}), ); deleted_user_error_rendered = true; return ""; }); assert.ok(!compose_validate.validate()); assert.ok(deleted_user_error_rendered); bob.is_deleted = false; initialize_pm_pill(mock_template); add_content_to_compose_box(); compose_state.set_private_message_recipient_ids([welcome_bot.user_id]); $("#send_message_form").set_find_results(".message-textarea", $("textarea#compose-textarea")); assert.ok(compose_validate.validate()); assert.ok(!$("textarea#compose-textarea").prop("disabled")); // For this first block, we should fail due to empty compose. initialize_pm_pill(mock_template); compose_state.set_private_message_recipient_ids([welcome_bot.user_id]); $("textarea#compose-textarea").removeClass("invalid"); assert.ok(!compose_validate.validate()); assert.ok(!$("#compose-send-button .loader").visible()); assert.ok($("textarea#compose-textarea").hasClass("invalid")); assert.ok(!$("textarea#compose-textarea").prop("disabled")); // Now add content to compose. add_content_to_compose_box(); $("#send_message_form").set_find_results(".message-textarea", $("textarea#compose-textarea")); $("textarea#compose-textarea").addClass("invalid"); assert.ok(compose_validate.validate()); assert.ok(!$("textarea#compose-textarea").hasClass("invalid")); assert.ok(!$("textarea#compose-textarea").prop("disabled")); initialize_pm_pill(mock_template); add_content_to_compose_box(); // test validating stream messages compose_state.set_message_type("stream"); compose_state.set_stream_id(""); let empty_stream_error_rendered = false; mock_template("compose_banner/compose_banner.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.missing_stream); assert.equal(data.banner_text, compose_validate.NO_CHANNEL_SELECTED_ERROR_MESSAGE); empty_stream_error_rendered = true; return ""; }); $("#send_message_form").set_find_results(".message-textarea", $("textarea#compose-textarea")); assert.ok(!compose_validate.validate()); assert.ok(empty_stream_error_rendered); const denmark = { stream_id: 100, name: "Denmark", topics_policy: "inherit", }; stream_data.add_sub_for_tests(denmark); compose_state.set_stream_id(denmark.stream_id); override(realm, "realm_topics_policy", "disable_empty_topic"); let missing_topic_error_rendered; mock_template("compose_banner/compose_banner.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.topic_missing); missing_topic_error_rendered = true; return ""; }); for (const topic_name of ["", "(no topic)", `translated: ${REALM_EMPTY_TOPIC_DISPLAY_NAME}`]) { compose_state.topic(topic_name); missing_topic_error_rendered = false; assert.ok(!compose_validate.validate()); assert.ok(missing_topic_error_rendered); assert.ok(!$("textarea#compose-textarea").prop("disabled")); } // Test textarea disabled when DMs are disabled in the org. compose_state.set_message_type("private"); compose_state.set_private_message_recipient_ids([bob.user_id]); override(realm, "realm_direct_message_permission_group", nobody.id); let dm_disabled_error_rendered = false; mock_template("compose_banner/cannot_send_direct_message_error.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.cannot_send_direct_message); dm_disabled_error_rendered = true; return ""; }); $.reset_selector(`#compose_banners .cannot_send_direct_message`); $.set_results(`#compose_banners .cannot_send_direct_message`, []); assert.ok(!compose_validate.validate()); assert.ok(dm_disabled_error_rendered); assert.ok($("textarea#compose-textarea").prop("disabled")); // Switching to stream message should re-enable textarea. compose_state.set_message_type("stream"); assert.ok(!compose_validate.validate()); assert.ok(!$("textarea#compose-textarea").prop("disabled")); }); test_ui("test_stream_wildcard_mention_allowed", ({override}) => { override(current_user, "user_id", me.user_id); // First, check for large streams (>15 subscribers) where the wildcard mention // policy matters. Set 16 subscribers so is_recipient_large_stream() returns true. const large_stream = {stream_id: 100, name: "Denmark"}; stream_data.add_sub_for_tests(large_stream); compose_state.set_stream_id(large_stream.stream_id); peer_data.set_subscribers( large_stream.stream_id, Array.from({length: 16}, (_, i) => i + 1), ); override(realm, "realm_can_mention_many_users_group", everyone.id); override(current_user, "user_id", guest.user_id); assert.ok(compose_validate.stream_wildcard_mention_allowed()); override(realm, "realm_can_mention_many_users_group", nobody.id); override(current_user, "user_id", bob.user_id); assert.ok(!compose_validate.stream_wildcard_mention_allowed()); override(realm, "realm_can_mention_many_users_group", members.id); override(current_user, "user_id", guest.user_id); assert.ok(!compose_validate.stream_wildcard_mention_allowed()); override(current_user, "user_id", alice.user_id); assert.ok(compose_validate.stream_wildcard_mention_allowed()); override(realm, "realm_can_mention_many_users_group", moderators.id); assert.ok(!compose_validate.stream_wildcard_mention_allowed()); override(current_user, "user_id", moderator.user_id); assert.ok(compose_validate.stream_wildcard_mention_allowed()); override(realm, "realm_can_mention_many_users_group", admin.id); override(current_user, "user_id", moderator.user_id); assert.ok(!compose_validate.stream_wildcard_mention_allowed()); // TODO: Add a by_admins_only case when we implement stream-level administrators. override(current_user, "user_id", bob.user_id); assert.ok(compose_validate.stream_wildcard_mention_allowed()); }); test_ui("validate_stream_message", ({override, mock_template}) => { // This test is in kind of continuation to test_validate but since it is // primarily used to get coverage over functions called from validate() // we are separating it up in different test. Though their relative position // of execution should not be changed. mock_banners(); override(current_user, "user_id", me.user_id); override(realm, "realm_topics_policy", "allow_empty_topic"); const special_sub = { stream_id: 101, name: "special", subscribed: true, can_send_message_group: everyone.id, topics_policy: "inherit", can_create_topic_group: everyone.id, }; stream_data.add_sub_for_tests(special_sub); compose_state.set_stream_id(special_sub.stream_id); $("#send_message_form").set_find_results(".message-textarea", $("textarea#compose-textarea")); assert.ok(compose_validate.validate()); assert.ok(!$("#compose-all-everyone").visible()); peer_data.set_subscribers( special_sub.stream_id, Array.from({length: 16}, (_, i) => i + 1), ); let stream_wildcard_warning_rendered = false; $.set_results("#compose_banner_area .wildcard_warning", []); mock_template("compose_banner/stream_wildcard_warning.hbs", false, (data) => { stream_wildcard_warning_rendered = true; assert.equal(data.subscriber_count, 16); return ""; }); override(realm, "realm_can_mention_many_users_group", everyone.id); compose_state.message_content("Hey @**all**"); assert.ok(!compose_validate.validate()); assert.ok(stream_wildcard_warning_rendered); let wildcards_not_allowed_rendered = false; mock_template("compose_banner/wildcard_mention_not_allowed_error.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.wildcards_not_allowed); assert.equal(data.wildcard_mention_string, "all"); wildcards_not_allowed_rendered = true; return ""; }); override(realm, "realm_can_mention_many_users_group", admin.id); assert.ok(!compose_validate.validate()); assert.ok(wildcards_not_allowed_rendered); }); test_ui("test_stream_posting_permission", ({mock_template, override}) => { mock_banners(); override(current_user, "user_id", 30); const sub_stream_102 = { stream_id: 102, name: "stream102", subscribed: true, can_send_message_group: admin.id, can_create_topic_group: everyone.id, can_add_subscribers_group: everyone.id, }; stream_data.add_sub_for_tests(sub_stream_102); compose_state.topic("topic102"); compose_state.set_stream_id(sub_stream_102.stream_id); sub_stream_102.subscribed = false; let user_not_subscribed_rendered = false; mock_template("compose_banner/compose_banner.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.user_not_subscribed); assert.equal( data.banner_text, $t({ defaultMessage: "You're not subscribed to this channel. You will not be notified if other users reply to your message.", }), ); user_not_subscribed_rendered = true; return ""; }); assert.ok(!compose_validate.validate()); assert.ok(user_not_subscribed_rendered); sub_stream_102.subscribed = true; let banner_rendered = false; mock_template("compose_banner/compose_banner.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.no_post_permissions); assert.equal( data.banner_text, $t({ defaultMessage: "You do not have permission to post in this channel.", }), ); banner_rendered = true; return ""; }); $("#send_message_form").set_find_results(".message-textarea", $("textarea#compose-textarea")); assert.ok(!compose_validate.validate()); assert.ok(banner_rendered); override(current_user, "user_id", 32); banner_rendered = false; assert.ok(compose_validate.validate()); assert.ok(!banner_rendered); sub_stream_102.can_send_message_group = everyone.id; override(current_user, "user_id", 30); banner_rendered = false; assert.ok(compose_validate.validate()); assert.ok(!banner_rendered); // Reset error message. compose_state.set_stream_id(social_sub.stream_id); const anonymous_setting_group = { direct_subgroups: [admin.id], direct_members: [31], }; sub_stream_102.can_send_message_group = anonymous_setting_group; compose_state.topic("topic102"); compose_state.set_stream_id(sub_stream_102.stream_id); override(current_user, "user_id", 30); banner_rendered = false; assert.ok(!compose_validate.validate()); assert.ok(banner_rendered); override(current_user, "user_id", 31); banner_rendered = false; assert.ok(compose_validate.validate()); assert.ok(!banner_rendered); override(current_user, "user_id", 32); banner_rendered = false; assert.ok(compose_validate.validate()); assert.ok(!banner_rendered); }); test_ui("test_check_overflow_text", ({override, override_rewire}) => { const fake_compose_box = new FakeComposeBox(); override_rewire(compose_validate, "validate_and_update_send_button_status", noop); override(realm, "max_message_length", 10000); // RED { fake_compose_box.set_textarea_val("a".repeat(10005)); compose_validate.check_overflow_text(fake_compose_box.$send_message_form); fake_compose_box.assert_message_size_is_over_the_limit("-5\n"); } // ORANGE { fake_compose_box.set_textarea_val("a".repeat(9100)); compose_validate.check_overflow_text(fake_compose_box.$send_message_form); fake_compose_box.assert_message_size_is_under_the_limit("900\n"); } // ALL CLEAR { fake_compose_box.set_textarea_val("a".repeat(9100 - 1)); compose_validate.check_overflow_text(fake_compose_box.$send_message_form); fake_compose_box.assert_message_size_is_under_the_limit(); } }); test_ui("needs_subscribe_warning", async () => { const invalid_user_id = 999; const test_bot = { full_name: "Test Bot", email: "test-bot@example.com", user_id: 135, is_bot: true, }; people.add_active_user(test_bot); const sub = { stream_id: 110, name: "stream", }; stream_data.add_sub_for_tests(sub); peer_data.set_subscribers(sub.stream_id, [bob.user_id, me.user_id]); blueslip.expect("error", "Unknown user_id in maybe_get_user_by_id"); // Test with an invalid user id. assert.equal( await compose_validate.needs_subscribe_warning(invalid_user_id, sub.stream_id), false, ); // Test with bot user. assert.equal( await compose_validate.needs_subscribe_warning(test_bot.user_id, sub.stream_id), false, ); // Test when user is subscribed to the stream. assert.equal(await compose_validate.needs_subscribe_warning(bob.user_id, sub.stream_id), false); peer_data.remove_subscriber(sub.stream_id, bob.user_id); // Test when the user is not subscribed. assert.equal(await compose_validate.needs_subscribe_warning(bob.user_id, sub.stream_id), true); }); test_ui("warn_if_private_stream_is_linked", async ({mock_template}) => { mock_banners(); const $textarea = $("