/
githubmirror
/
zulip
Обзор
Документация
Войти
/
githubmirror
/
zulip
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
web/tests/compose_validate.test.cjs
1 011 строк
37 KB
Sairam Bisoyi
compose: Clarify warning when linking to a private channel.
04 авг 2026, 10:46
04 авг 2026, 10:46
b466f0c
Код
Авторство
О чём код?
"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, () => "<div>pill-html</div>"); 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 "<banner-stub>"; }); $("#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 "<banner-stub>"; }); 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 "<banner-stub>"; }); 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 "<banner-stub>"; }); $("#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 "<banner-stub>"; }); 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 "<banner-stub>"; }); $.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 "<banner-stub>"; }); 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 "<banner-stub>"; }); 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 "<banner-stub>"; }); 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 "<banner-stub>"; }); $("#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 = $("<textarea>").attr("id", "compose-textarea"); stub_message_row($textarea); const test_sub = { name: compose_state.stream_name(), stream_id: 99, }; stream_data.add_sub_for_tests(test_sub); peer_data.set_subscribers(test_sub.stream_id, [1, 2]); const denmark = { stream_id: 100, name: "Denmark", }; stream_data.add_sub_for_tests(denmark); peer_data.set_subscribers(denmark.stream_id, [1, 2, 3]); let banner_rendered = false; mock_template("compose_banner/private_stream_warning.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.private_stream_warning); assert.equal(data.channel_name, "secret"); assert.equal(data.audience_channel_name, "Denmark"); banner_rendered = true; return "<banner-stub>"; }); async function test_noop_case(invite_only) { banner_rendered = false; compose_state.set_message_type("stream"); denmark.invite_only = invite_only; await compose_validate.warn_if_private_stream_is_linked(denmark, $textarea); assert.ok(!banner_rendered); } compose_state.set_selected_recipient_id(undefined); await test_noop_case(false); // invite_only=true and current compose stream subscribers are a subset // of mentioned_stream subscribers. await test_noop_case(true); $("#compose_private").hide(); compose_state.set_message_type("stream"); // Not everyone is subscribed to secret_stream in denmark, so the // warning is rendered. compose_state.set_selected_recipient_id(denmark.stream_id); const secret_stream = { invite_only: true, name: "secret", stream_id: 22, }; stream_data.add_sub_for_tests(secret_stream); peer_data.set_subscribers(secret_stream.stream_id, []); banner_rendered = false; const $banner_container = $("#compose_banners"); $banner_container.set_find_results(".private_stream_warning", []); await compose_validate.warn_if_private_stream_is_linked(secret_stream, $textarea); assert.ok(banner_rendered); // Simulate that the row was added to the DOM. const $warning_row = $("#compose_banners .private_stream_warning"); $warning_row.attr("data-stream-id", "22"); // Now try to mention the same stream again. The template should // not render. banner_rendered = false; $banner_container.set_find_results(".private_stream_warning", $warning_row); await compose_validate.warn_if_private_stream_is_linked(secret_stream, $textarea); assert.ok(!banner_rendered); }); test_ui("warn_if_mentioning_unsubscribed_user", async ({override, mock_template}) => { mock_banners(); const $textarea = $("<textarea>").attr("id", "compose-textarea"); stub_message_row($textarea); compose_state.set_stream_id(""); override(realm, "realm_can_add_subscribers_group", everyone.id); let mentioned_details = { user: { email: "foo@bar.com", }, }; let new_banner_rendered = false; mock_template("compose_banner/not_subscribed_warning.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.recipient_not_subscribed); assert.equal(data.user_id, 34); assert.equal(data.stream_id, 111); assert.equal(data.name, "Foo Barson"); new_banner_rendered = true; return "<banner-stub>"; }); async function test_noop_case(is_private, type) { new_banner_rendered = false; const msg_type = is_private ? "private" : "stream"; compose_state.set_message_type(msg_type); mentioned_details.type = type; await compose_validate.warn_if_mentioning_unsubscribed_user(mentioned_details, $textarea); assert.ok(!new_banner_rendered); } await test_noop_case(true, "user"); await test_noop_case(false, "broadcast"); $("#compose_invite_users").hide(); compose_state.set_message_type("stream"); // Test with empty stream name in compose box. It should return noop. new_banner_rendered = false; assert.equal(compose_state.stream_name(), ""); await compose_validate.warn_if_mentioning_unsubscribed_user(mentioned_details, $textarea); assert.ok(!new_banner_rendered); const sub = { stream_id: 111, name: "random", can_add_subscribers_group: admin.id, can_administer_channel_group: admin.id, can_subscribe_group: admin.id, }; stream_data.add_sub_for_tests(sub); compose_state.set_stream_id(sub.stream_id); // Test with invalid stream in compose box. It should return noop. new_banner_rendered = false; await compose_validate.warn_if_mentioning_unsubscribed_user(mentioned_details, $textarea); assert.ok(!new_banner_rendered); // Test mentioning a user that should get a warning. mentioned_details = { type: "user", user: { email: "foo@bar.com", user_id: 34, full_name: "Foo Barson", }, }; people.add_active_user(mentioned_details.user); new_banner_rendered = false; const $banner_container = $("#compose_banners"); $banner_container.set_find_results(".recipient_not_subscribed", []); mock_channel_get(channel, (opts) => { assert.equal(opts.url, `/json/streams/${sub.stream_id}/members`); return opts.success({ subscribers: [], }); }); await compose_validate.warn_if_mentioning_unsubscribed_user(mentioned_details, $textarea); assert.ok(new_banner_rendered); // Simulate that the row was added to the DOM. const $warning_row = $("#compose_banners .recipient_not_subscribed"); $warning_row.attr("data-user-id", "34"); $warning_row.attr("data-stream-id", "111"); // Now try to mention the same person again. The template should // not render. new_banner_rendered = false; $banner_container.set_find_results(".recipient_not_subscribed", $warning_row); await compose_validate.warn_if_mentioning_unsubscribed_user(mentioned_details, $textarea); assert.ok(!new_banner_rendered); }); test_ui("maybe_clear_stale_recipient_not_subscribed_warnings", () => { const $textarea = $("<textarea>").attr("id", "compose-textarea"); stub_message_row($textarea); const $banner_container = $("#compose_banners"); // Using a shared factory so the removal callback has a single // source location — coverage is satisfied as long as any banner // is actually removed across all test cases. const removed_banners = new Set(); function make_banner(name, user_id_str) { removed_banners.delete(name); const $banner = $.create(name); if (user_id_str !== undefined) { $banner.attr("data-user-id", user_id_str); } $banner[0].remove = () => { removed_banners.add(name); }; return $banner; } // No banners present: function is a no-op. $banner_container.set_find_results(".recipient_not_subscribed", []); $textarea.val("text without any mention"); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); // Banner preserved when canonical @**Name** mention is present. { const $banner = make_banner("canonical", String(alice.user_id)); const mention = people.get_mention_syntax(alice.full_name, alice.user_id, false); $banner_container.set_find_results(".recipient_not_subscribed", $banner); $textarea.val(`Hello ${mention} here.`); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); assert.ok(!removed_banners.has("canonical")); } // Banner removed when mention is deleted from compose text. { const $banner = make_banner("deleted", String(alice.user_id)); $banner_container.set_find_results(".recipient_not_subscribed", $banner); $textarea.val("Hello, how are you?"); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); assert.ok(removed_banners.has("deleted")); } // Banner preserved for @**|user_id** form. { const $banner = make_banner("id-only", String(alice.user_id)); $banner_container.set_find_results(".recipient_not_subscribed", $banner); $textarea.val(`Hello @**|${alice.user_id}** how are you?`); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); assert.ok(!removed_banners.has("id-only")); } // Banner preserved for @**Name|user_id** form. { const $banner = make_banner("name-and-id", String(alice.user_id)); $banner_container.set_find_results(".recipient_not_subscribed", $banner); $textarea.val(`Hello @**${alice.full_name}|${alice.user_id}** how are you?`); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); assert.ok(!removed_banners.has("name-and-id")); } // Banner removed when only a silent mention is present, since // the banner is not displayed for silent mentions. { const silent_mention = people.get_mention_syntax(alice.full_name, alice.user_id, true); const $banner = make_banner("silent-only", String(alice.user_id)); $banner_container.set_find_results(".recipient_not_subscribed", $banner); $textarea.val(`Hello ${silent_mention} here.`); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); assert.ok(removed_banners.has("silent-only")); } // Banner removed when compose text is empty. { const $banner = make_banner("empty-text", String(alice.user_id)); $banner_container.set_find_results(".recipient_not_subscribed", $banner); $textarea.val(""); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); assert.ok(removed_banners.has("empty-text")); } // Banner removed when mention syntax is incomplete. { const $banner = make_banner("incomplete", String(alice.user_id)); $banner_container.set_find_results(".recipient_not_subscribed", $banner); $textarea.val(`Hello @**${alice.full_name} here.`); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); assert.ok(removed_banners.has("incomplete")); } // Banner removed for unknown user_id. { const $banner = make_banner("unknown-user", "99999"); $banner_container.set_find_results(".recipient_not_subscribed", $banner); $textarea.val("No mention here."); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); assert.ok(removed_banners.has("unknown-user")); } // Banner removed when data-user-id attribute is missing. { const $banner = make_banner("no-user-id", undefined); $banner_container.set_find_results(".recipient_not_subscribed", $banner); $textarea.val("Some text."); compose_validate.maybe_clear_stale_recipient_not_subscribed_warnings($textarea); assert.ok(removed_banners.has("no-user-id")); } }); test_ui("test warn_if_topic_resolved", ({override, mock_template}) => { mock_banners(); $.reset_selector("#compose_banners .topic_resolved"); $.set_results("#compose_banners .topic_resolved", []); override(realm, "realm_can_resolve_topics_group", everyone.id); let error_shown; mock_template("compose_banner/compose_banner.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.topic_resolved); assert.equal( data.banner_text, $t({ defaultMessage: "You are sending a message to a resolved topic. You can send as-is or unresolve the topic first.", }), ); error_shown = true; return "<banner-stub>"; }); const sub = { stream_id: 111, name: "random", can_administer_channel_group: nobody.id, can_move_messages_out_of_channel_group: nobody.id, can_move_messages_within_channel_group: nobody.id, can_resolve_topics_group: nobody.id, }; stream_data.add_sub_for_tests(sub); compose_state.set_message_type("stream"); compose_state.set_stream_id(""); compose_state.topic(resolved_topic.resolve_name("hello")); compose_state.message_content("content"); error_shown = false; compose_validate.warn_if_topic_resolved(true); assert.ok(!error_shown); compose_state.set_stream_id(sub.stream_id); // Show the warning now as stream also exists error_shown = false; compose_validate.warn_if_topic_resolved(true); assert.ok(error_shown); // We reset the state to be able to show the banner again compose_state.set_recipient_viewed_topic_resolved_banner(false); // Call it again with false; this should do the same thing. error_shown = false; compose_validate.warn_if_topic_resolved(false); assert.ok(error_shown); // Call the func again. This should not show the error because // we have already shown the error once for this topic. error_shown = false; compose_validate.warn_if_topic_resolved(false); assert.ok(!error_shown); compose_state.topic("hello"); // The warning will be cleared now error_shown = false; compose_validate.warn_if_topic_resolved(true); assert.ok(!error_shown); // Calling with false won't do anything. error_shown = false; compose_validate.warn_if_topic_resolved(false); assert.ok(!error_shown); }); test_ui("test_warn_if_guest_in_dm_recipient", ({mock_template, override}) => { let is_active = false; override(realm, "realm_can_access_all_users_group", everyone.id); mock_template("compose_banner/guest_in_dm_recipient_warning.hbs", false, (data) => { assert.equal(data.classname, compose_banner.CLASSNAMES.guest_in_dm_recipient_warning); assert.equal( data.banner_text, $t({defaultMessage: "Guest is a guest in this organization."}), ); is_active = true; return "<banner-stub>"; }); compose_state.set_message_type("private"); initialize_pm_pill(mock_template); compose_state.set_private_message_recipient_ids([guest.user_id]); const classname = compose_banner.CLASSNAMES.guest_in_dm_recipient_warning; // if setting is disabled, remove warning if exists realm.realm_enable_guest_user_dm_warning = false; compose_validate.warn_if_guest_in_dm_recipient(); assert.ok(!is_active); // to show warning for guest emails, banner should be created realm.realm_enable_guest_user_dm_warning = true; $.reset_selector(`#compose_banners .${CSS.escape(classname)}`); $.set_results(`#compose_banners .${CSS.escape(classname)}`, []); compose_validate.warn_if_guest_in_dm_recipient(); assert.ok(is_active); assert.deepEqual(compose_state.get_recipient_guest_ids_for_dm_warning(), [33]); // don't show warning for same guests if user closed the banner. is_active = false; compose_validate.warn_if_guest_in_dm_recipient(); assert.ok(!is_active); // on modifying the guest recipient, update banner if already shown. const new_guest = { email: "new_guest@example.com", user_id: 34, full_name: "New Guest", is_guest: true, }; people.add_active_user(new_guest); initialize_pm_pill(mock_template); compose_state.set_private_message_recipient_ids([guest.user_id, new_guest.user_id]); $.reset_selector(`#compose_banners .${CSS.escape(classname)}`); const $banner = $(`#compose_banners .${CSS.escape(classname)}`); const $banner_content = $(`#compose_banners .${CSS.escape(classname)} .banner_content`); $banner.set_find_results(".banner_content", $banner_content); compose_validate.warn_if_guest_in_dm_recipient(); assert.equal( $banner_content.text(), $t({defaultMessage: "Guest and New Guest are guests in this organization."}), ); assert.deepEqual(compose_state.get_recipient_guest_ids_for_dm_warning(), [33, 34]); });