zulip/frontend_tests/node_tests/compose.js

1430 lines
47 KiB
JavaScript
Raw Normal View History

"use strict";
const {strict: assert} = require("assert");
const MockDate = require("mockdate");
const {$t, $t_html} = require("../zjsunit/i18n");
const {mock_esm, set_global, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const blueslip = require("../zjsunit/zblueslip");
const $ = require("../zjsunit/zjquery");
const {page_params} = require("../zjsunit/zpage_params");
const noop = () => {};
2021-05-19 21:15:26 +02:00
set_global("document", {});
set_global("navigator", {});
// Setting these up so that we can test that links to uploads within messages are
// automatically converted to server relative links.
2021-05-19 21:15:26 +02:00
document.location = {
protocol: "https:",
host: "foo.com",
};
const fake_now = 555;
const channel = mock_esm("../../static/js/channel");
const compose_actions = mock_esm("../../static/js/compose_actions");
const drafts = mock_esm("../../static/js/drafts");
const giphy = mock_esm("../../static/js/giphy");
const loading = mock_esm("../../static/js/loading");
const markdown = mock_esm("../../static/js/markdown");
const notifications = mock_esm("../../static/js/notifications");
const reminder = mock_esm("../../static/js/reminder");
const rendered_markdown = mock_esm("../../static/js/rendered_markdown");
const resize = mock_esm("../../static/js/resize");
const sent_messages = mock_esm("../../static/js/sent_messages");
const server_events = mock_esm("../../static/js/server_events");
const settings_data = mock_esm("../../static/js/settings_data");
const stream_edit = mock_esm("../../static/js/stream_edit");
const subs = mock_esm("../../static/js/subs");
const transmit = mock_esm("../../static/js/transmit");
const compose_closed_ui = zrequire("compose_closed_ui");
const compose_fade = zrequire("compose_fade");
const compose_pm_pill = zrequire("compose_pm_pill");
2021-05-19 19:34:42 +02:00
const compose_state = zrequire("compose_state");
const compose = zrequire("compose");
const echo = zrequire("echo");
const peer_data = zrequire("peer_data");
2021-05-19 19:34:42 +02:00
const people = zrequire("people");
const settings_config = zrequire("settings_config");
const stream_data = zrequire("stream_data");
const upload = zrequire("upload");
2017-02-24 16:18:56 +01:00
function reset_jquery() {
// Avoid leaks.
$.clear_all_elements();
}
const new_user = {
email: "new_user@example.com",
user_id: 101,
full_name: "New User",
date_joined: new Date(),
};
const me = {
email: "me@example.com",
user_id: 30,
full_name: "Me Myself",
date_joined: new Date(),
};
const alice = {
email: "alice@example.com",
user_id: 31,
full_name: "Alice",
};
const bob = {
email: "bob@example.com",
user_id: 32,
full_name: "Bob",
};
people.add_active_user(new_user);
people.add_active_user(me);
people.initialize_current_user(me.user_id);
people.add_active_user(alice);
people.add_active_user(bob);
const social = {
stream_id: 101,
name: "social",
subscribed: true,
};
stream_data.add_sub(social);
function test_ui(label, f) {
// TODO: initialize data more aggressively.
run_test(label, f);
}
function initialize_handlers({override}) {
override(compose, "compute_show_video_chat_button", () => false);
override(compose, "render_compose_box", () => {});
override(upload, "setup_upload", () => undefined);
override(resize, "watch_manual_resize", () => {});
compose.initialize();
}
test_ui("test_wildcard_mention_allowed", () => {
page_params.user_id = me.user_id;
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.by_everyone.code;
page_params.is_guest = true;
page_params.is_admin = false;
assert.ok(compose.wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.nobody.code;
page_params.is_admin = true;
assert.ok(!compose.wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.by_members.code;
page_params.is_guest = true;
page_params.is_admin = false;
assert.ok(!compose.wildcard_mention_allowed());
page_params.is_guest = false;
assert.ok(compose.wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.by_moderators_only.code;
page_params.is_moderator = false;
assert.ok(!compose.wildcard_mention_allowed());
page_params.is_moderator = true;
assert.ok(compose.wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.by_stream_admins_only.code;
page_params.is_admin = false;
assert.ok(!compose.wildcard_mention_allowed());
// TODO: Add a by_admins_only case when we implement stream-level administrators.
page_params.is_admin = true;
assert.ok(compose.wildcard_mention_allowed());
page_params.realm_wildcard_mention_policy =
settings_config.wildcard_mention_policy_values.by_full_members.code;
const person = people.get_by_user_id(page_params.user_id);
person.date_joined = new Date(Date.now());
page_params.realm_waiting_period_threshold = 10;
assert.ok(compose.wildcard_mention_allowed());
page_params.is_admin = false;
assert.ok(!compose.wildcard_mention_allowed());
});
2021-05-19 19:40:12 +02:00
test_ui("right-to-left", () => {
const textarea = $("#compose-textarea");
const event = {
key: "A",
};
assert.equal(textarea.hasClass("rtl"), false);
2021-05-19 19:40:12 +02:00
textarea.val("```quote\nمرحبا");
compose.handle_keyup(event, $("#compose-textarea"));
assert.equal(textarea.hasClass("rtl"), true);
textarea.val("```quote foo");
compose.handle_keyup(event, textarea);
assert.equal(textarea.hasClass("rtl"), false);
});
test_ui("markdown_shortcuts", ({override}) => {
let queryCommandEnabled = true;
const event = {
key: "b",
target: {
id: "compose-textarea",
},
stopPropagation: noop,
preventDefault: noop,
};
let input_text = "";
let range_start = 0;
let range_length = 0;
let compose_value = $("#compose_textarea").val();
let selected_word = "";
override(document, "queryCommandEnabled", () => queryCommandEnabled);
override(document, "execCommand", (cmd, bool, markdown) => {
const compose_textarea = $("#compose-textarea");
const value = compose_textarea.val();
$("#compose-textarea").val(
value.slice(0, compose_textarea.range().start) +
markdown +
value.slice(compose_textarea.range().end),
);
});
$("#compose-textarea")[0] = {};
$("#compose-textarea").range = () => ({
start: range_start,
end: range_start + range_length,
length: range_length,
range: noop,
text: $("#compose-textarea")
.val()
.slice(range_start, range_length + range_start),
});
$("#compose-textarea").caret = noop;
function test_i_typed(isCtrl, isCmd) {
// Test 'i' is typed correctly.
$("#compose-textarea").val("i");
event.key = "i";
event.metaKey = isCmd;
event.ctrlKey = isCtrl;
compose.handle_keydown(event, $("#compose-textarea"));
assert.equal("i", $("#compose-textarea").val());
}
function all_markdown_test(isCtrl, isCmd) {
input_text = "Any text.";
$("#compose-textarea").val(input_text);
compose_value = $("#compose-textarea").val();
// Select "text" word in compose box.
selected_word = "text";
range_start = compose_value.search(selected_word);
range_length = selected_word.length;
// Test bold:
// Mac env = Cmd+b
// Windows/Linux = Ctrl+b
event.key = "b";
event.ctrlKey = isCtrl;
event.metaKey = isCmd;
compose.handle_keydown(event, $("#compose-textarea"));
assert.equal("Any **text**.", $("#compose-textarea").val());
// Test if no text is selected.
range_start = 0;
// Change cursor to first position.
range_length = 0;
compose.handle_keydown(event, $("#compose-textarea"));
assert.equal("****Any **text**.", $("#compose-textarea").val());
// Test italic:
// Mac = Cmd+I
// Windows/Linux = Ctrl+I
// We use event.key = "I" to emulate user using Caps Lock key.
$("#compose-textarea").val(input_text);
range_start = compose_value.search(selected_word);
range_length = selected_word.length;
event.key = "I";
event.shiftKey = false;
compose.handle_keydown(event, $("#compose-textarea"));
assert.equal("Any *text*.", $("#compose-textarea").val());
// Test if no text is selected.
range_length = 0;
// Change cursor to first position.
range_start = 0;
compose.handle_keydown(event, $("#compose-textarea"));
assert.equal("**Any *text*.", $("#compose-textarea").val());
// Test link insertion:
// Mac = Cmd+Shift+L
// Windows/Linux = Ctrl+Shift+L
$("#compose-textarea").val(input_text);
range_start = compose_value.search(selected_word);
range_length = selected_word.length;
event.key = "l";
event.shiftKey = true;
compose.handle_keydown(event, $("#compose-textarea"));
assert.equal("Any [text](url).", $("#compose-textarea").val());
// Test if exec command is not enabled in browser.
queryCommandEnabled = false;
compose.handle_keydown(event, $("#compose-textarea"));
}
// This function cross tests the Cmd/Ctrl + Markdown shortcuts in
// Mac and Linux/Windows environments. So in short, this tests
// that e.g. Cmd+B should be ignored on Linux/Windows and Ctrl+B
// should be ignored on Mac.
function os_specific_markdown_test(isCtrl, isCmd) {
input_text = "Any text.";
$("#compose-textarea").val(input_text);
compose_value = $("#compose-textarea").val();
selected_word = "text";
range_start = compose_value.search(selected_word);
range_length = selected_word.length;
event.metaKey = isCmd;
event.ctrlKey = isCtrl;
event.key = "b";
compose.handle_keydown(event, $("#compose-textarea"));
assert.equal(input_text, $("#compose-textarea").val());
event.key = "i";
event.shiftKey = false;
compose.handle_keydown(event, $("#compose-textarea"));
assert.equal(input_text, $("#compose-textarea").val());
event.key = "l";
event.shiftKey = true;
compose.handle_keydown(event, $("#compose-textarea"));
assert.equal(input_text, $("#compose-textarea").val());
}
// These keyboard shortcuts differ as to what key one should use
// on MacOS vs. other platforms: Cmd (Mac) vs. Ctrl (non-Mac).
// Default (Linux/Windows) userAgent tests:
navigator.platform = "";
test_i_typed(false, false);
// Check all the Ctrl + Markdown shortcuts work correctly
all_markdown_test(true, false);
// The Cmd + Markdown shortcuts should do nothing on Linux/Windows
os_specific_markdown_test(false, true);
// Setting following platform to test in mac env
navigator.platform = "MacIntel";
// Mac userAgent tests:
test_i_typed(false, false);
// The Ctrl + Markdown shortcuts should do nothing on mac
os_specific_markdown_test(true, false);
// Check all the Cmd + Markdown shortcuts work correctly
all_markdown_test(false, true);
// Reset userAgent
navigator.userAgent = "";
});
test_ui("send_message_success", ({override}) => {
override(drafts, "delete_active_draft", () => {});
$("#compose-textarea").val("foobarfoobar");
$("#compose-textarea").trigger("blur");
$("#compose-send-status").show();
$("#compose-send-button").prop("disabled", true);
$("#sending-indicator").show();
sending messages: Extract sent_messages.js. This commit extract send_messages.js to clean up code related to the following things: * sending data to /json/report_send_time * restarting the event loop if events don't arrive on time The code related to /json/report changes the following ways: * We track the state almost completely in the new send_messages.js module, with other modules just making one-line calls. * We no longer send "displayed" times to the servers, since we were kind of lying about them anyway. * We now explicitly track the state of each single sent message in its own object. * We now look up data related to the messages by local_id, instead of message_id. The problem with message_id was that is was mutable. Now we use local_id, and we extend the local_id concept to messages that don't get rendered client side. We no longer need to react to the 'message_id_changed' event to change our hash key. * The code used to live in many places: * various big chunks were scattered among compose.js, and those were all moved or reduced to one-line calls into the new module * echo.js continues to make basically one-line calls, but it no longer calls compose.report_as_received(), nor does it set the "start" time. * message_util.js used to report received events, but only when they finally got drawn in the home view; this code is gone now The code related to restarting the event loop if events don't arrive changes as follows: * The timer now gets set up from within send_messages.message_state.report_server_ack, where we can easily inspect the current state of the possibly-still-in-flight message. * The code to confirm that an event was received happens now in server_events.js, rather than later, so that we don't falsely blame the event loop for a downstream bug. (Plus it's easier to just do it one place.) This change removes a fair amount of code from our node tests. Some of the removal is good stuff related to us completing killing off unnecessary code. Other removals are more expediency-driven, and we should make another sweep at ramping up our coverage on compose.js, with possibly a little more mocking of the new `send_messages` code layer, since it's now abstracted better. There is also some minor cleanup to echo.resend_message() in this commit. See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
let reify_message_id_checked;
override(echo, "reify_message_id", (local_id, message_id) => {
assert.equal(local_id, "1001");
assert.equal(message_id, 12);
reify_message_id_checked = true;
});
sending messages: Extract sent_messages.js. This commit extract send_messages.js to clean up code related to the following things: * sending data to /json/report_send_time * restarting the event loop if events don't arrive on time The code related to /json/report changes the following ways: * We track the state almost completely in the new send_messages.js module, with other modules just making one-line calls. * We no longer send "displayed" times to the servers, since we were kind of lying about them anyway. * We now explicitly track the state of each single sent message in its own object. * We now look up data related to the messages by local_id, instead of message_id. The problem with message_id was that is was mutable. Now we use local_id, and we extend the local_id concept to messages that don't get rendered client side. We no longer need to react to the 'message_id_changed' event to change our hash key. * The code used to live in many places: * various big chunks were scattered among compose.js, and those were all moved or reduced to one-line calls into the new module * echo.js continues to make basically one-line calls, but it no longer calls compose.report_as_received(), nor does it set the "start" time. * message_util.js used to report received events, but only when they finally got drawn in the home view; this code is gone now The code related to restarting the event loop if events don't arrive changes as follows: * The timer now gets set up from within send_messages.message_state.report_server_ack, where we can easily inspect the current state of the possibly-still-in-flight message. * The code to confirm that an event was received happens now in server_events.js, rather than later, so that we don't falsely blame the event loop for a downstream bug. (Plus it's easier to just do it one place.) This change removes a fair amount of code from our node tests. Some of the removal is good stuff related to us completing killing off unnecessary code. Other removals are more expediency-driven, and we should make another sweep at ramping up our coverage on compose.js, with possibly a little more mocking of the new `send_messages` code layer, since it's now abstracted better. There is also some minor cleanup to echo.resend_message() in this commit. See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
compose.send_message_success("1001", 12, false);
sending messages: Extract sent_messages.js. This commit extract send_messages.js to clean up code related to the following things: * sending data to /json/report_send_time * restarting the event loop if events don't arrive on time The code related to /json/report changes the following ways: * We track the state almost completely in the new send_messages.js module, with other modules just making one-line calls. * We no longer send "displayed" times to the servers, since we were kind of lying about them anyway. * We now explicitly track the state of each single sent message in its own object. * We now look up data related to the messages by local_id, instead of message_id. The problem with message_id was that is was mutable. Now we use local_id, and we extend the local_id concept to messages that don't get rendered client side. We no longer need to react to the 'message_id_changed' event to change our hash key. * The code used to live in many places: * various big chunks were scattered among compose.js, and those were all moved or reduced to one-line calls into the new module * echo.js continues to make basically one-line calls, but it no longer calls compose.report_as_received(), nor does it set the "start" time. * message_util.js used to report received events, but only when they finally got drawn in the home view; this code is gone now The code related to restarting the event loop if events don't arrive changes as follows: * The timer now gets set up from within send_messages.message_state.report_server_ack, where we can easily inspect the current state of the possibly-still-in-flight message. * The code to confirm that an event was received happens now in server_events.js, rather than later, so that we don't falsely blame the event loop for a downstream bug. (Plus it's easier to just do it one place.) This change removes a fair amount of code from our node tests. Some of the removal is good stuff related to us completing killing off unnecessary code. Other removals are more expediency-driven, and we should make another sweep at ramping up our coverage on compose.js, with possibly a little more mocking of the new `send_messages` code layer, since it's now abstracted better. There is also some minor cleanup to echo.resend_message() in this commit. See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
assert.equal($("#compose-textarea").val(), "");
assert.ok($("#compose-textarea").is_focused());
assert.ok(!$("#compose-send-status").visible());
assert.equal($("#compose-send-button").prop("disabled"), false);
assert.ok(!$("#sending-indicator").visible());
assert.ok(reify_message_id_checked);
});
test_ui("send_message", ({override}) => {
2021-05-19 20:19:45 +02:00
MockDate.set(new Date(fake_now * 1000));
override(drafts, "delete_active_draft", () => {});
override(sent_messages, "start_tracking_message", () => {});
// This is the common setup stuff for all of the four tests.
let stub_state;
function initialize_state_stub_dict() {
stub_state = {};
stub_state.send_msg_called = 0;
stub_state.get_events_running_called = 0;
stub_state.reify_message_id_checked = 0;
return stub_state;
}
set_global("setTimeout", (func) => {
func();
});
override(server_events, "assert_get_events_running", () => {
stub_state.get_events_running_called += 1;
});
// Tests start here.
(function test_message_send_success_codepath() {
stub_state = initialize_state_stub_dict();
compose_state.topic("");
compose_state.set_message_type("private");
page_params.user_id = new_user.user_id;
override(compose_state, "private_message_recipient", () => "alice@example.com");
const server_message_id = 127;
override(echo, "insert_message", (message) => {
assert.equal(message.timestamp, fake_now);
});
override(markdown, "apply_markdown", () => {});
override(markdown, "add_topic_links", () => {});
override(echo, "try_deliver_locally", (message_request) => {
const local_id_float = 123.04;
return echo.insert_local_message(message_request, local_id_float);
});
override(transmit, "send_message", (payload, success) => {
const single_msg = {
type: "private",
content: "[foobar](/user_uploads/123456)",
sender_id: new_user.user_id,
queue_id: undefined,
stream: "",
topic: "",
to: `[${alice.user_id}]`,
reply_to: "alice@example.com",
private_message_recipient: "alice@example.com",
to_user_ids: "31",
local_id: "123.04",
locally_echoed: true,
};
assert.deepEqual(payload, single_msg);
payload.id = server_message_id;
success(payload);
stub_state.send_msg_called += 1;
});
override(echo, "reify_message_id", (local_id, message_id) => {
assert.equal(typeof local_id, "string");
assert.equal(typeof message_id, "number");
assert.equal(message_id, server_message_id);
stub_state.reify_message_id_checked += 1;
});
sending messages: Extract sent_messages.js. This commit extract send_messages.js to clean up code related to the following things: * sending data to /json/report_send_time * restarting the event loop if events don't arrive on time The code related to /json/report changes the following ways: * We track the state almost completely in the new send_messages.js module, with other modules just making one-line calls. * We no longer send "displayed" times to the servers, since we were kind of lying about them anyway. * We now explicitly track the state of each single sent message in its own object. * We now look up data related to the messages by local_id, instead of message_id. The problem with message_id was that is was mutable. Now we use local_id, and we extend the local_id concept to messages that don't get rendered client side. We no longer need to react to the 'message_id_changed' event to change our hash key. * The code used to live in many places: * various big chunks were scattered among compose.js, and those were all moved or reduced to one-line calls into the new module * echo.js continues to make basically one-line calls, but it no longer calls compose.report_as_received(), nor does it set the "start" time. * message_util.js used to report received events, but only when they finally got drawn in the home view; this code is gone now The code related to restarting the event loop if events don't arrive changes as follows: * The timer now gets set up from within send_messages.message_state.report_server_ack, where we can easily inspect the current state of the possibly-still-in-flight message. * The code to confirm that an event was received happens now in server_events.js, rather than later, so that we don't falsely blame the event loop for a downstream bug. (Plus it's easier to just do it one place.) This change removes a fair amount of code from our node tests. Some of the removal is good stuff related to us completing killing off unnecessary code. Other removals are more expediency-driven, and we should make another sweep at ramping up our coverage on compose.js, with possibly a little more mocking of the new `send_messages` code layer, since it's now abstracted better. There is also some minor cleanup to echo.resend_message() in this commit. See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
// Setting message content with a host server link and we will assert
// later that this has been converted to a relative link.
$("#compose-textarea").val("[foobar](https://foo.com/user_uploads/123456)");
$("#compose-textarea").trigger("blur");
$("#compose-send-status").show();
$("#compose-send-button").prop("disabled", true);
$("#sending-indicator").show();
compose.send_message();
const state = {
get_events_running_called: 1,
reify_message_id_checked: 1,
send_msg_called: 1,
};
assert.deepEqual(stub_state, state);
assert.equal($("#compose-textarea").val(), "");
assert.ok($("#compose-textarea").is_focused());
assert.ok(!$("#compose-send-status").visible());
assert.equal($("#compose-send-button").prop("disabled"), false);
assert.ok(!$("#sending-indicator").visible());
})();
// This is the additional setup which is common to both the tests below.
override(transmit, "send_message", (payload, success, error) => {
stub_state.send_msg_called += 1;
error("Error sending message: Server says 408");
});
let echo_error_msg_checked;
override(echo, "message_send_error", (local_id, error_response) => {
assert.equal(local_id, 123.04);
assert.equal(error_response, "Error sending message: Server says 408");
echo_error_msg_checked = true;
});
// Tests start here.
(function test_param_error_function_passed_from_send_message() {
stub_state = initialize_state_stub_dict();
compose.send_message();
const state = {
get_events_running_called: 1,
reify_message_id_checked: 0,
send_msg_called: 1,
};
assert.deepEqual(stub_state, state);
assert.ok(echo_error_msg_checked);
})();
(function test_error_codepath_local_id_undefined() {
stub_state = initialize_state_stub_dict();
$("#compose-textarea").val("foobarfoobar");
$("#compose-textarea").trigger("blur");
$("#compose-send-status").show();
$("#compose-send-button").prop("disabled", true);
$("#sending-indicator").show();
$("#compose-textarea").off("select");
echo_error_msg_checked = false;
override(echo, "try_deliver_locally", () => {});
override(sent_messages, "get_new_local_id", () => "loc-55");
sending messages: Extract sent_messages.js. This commit extract send_messages.js to clean up code related to the following things: * sending data to /json/report_send_time * restarting the event loop if events don't arrive on time The code related to /json/report changes the following ways: * We track the state almost completely in the new send_messages.js module, with other modules just making one-line calls. * We no longer send "displayed" times to the servers, since we were kind of lying about them anyway. * We now explicitly track the state of each single sent message in its own object. * We now look up data related to the messages by local_id, instead of message_id. The problem with message_id was that is was mutable. Now we use local_id, and we extend the local_id concept to messages that don't get rendered client side. We no longer need to react to the 'message_id_changed' event to change our hash key. * The code used to live in many places: * various big chunks were scattered among compose.js, and those were all moved or reduced to one-line calls into the new module * echo.js continues to make basically one-line calls, but it no longer calls compose.report_as_received(), nor does it set the "start" time. * message_util.js used to report received events, but only when they finally got drawn in the home view; this code is gone now The code related to restarting the event loop if events don't arrive changes as follows: * The timer now gets set up from within send_messages.message_state.report_server_ack, where we can easily inspect the current state of the possibly-still-in-flight message. * The code to confirm that an event was received happens now in server_events.js, rather than later, so that we don't falsely blame the event loop for a downstream bug. (Plus it's easier to just do it one place.) This change removes a fair amount of code from our node tests. Some of the removal is good stuff related to us completing killing off unnecessary code. Other removals are more expediency-driven, and we should make another sweep at ramping up our coverage on compose.js, with possibly a little more mocking of the new `send_messages` code layer, since it's now abstracted better. There is also some minor cleanup to echo.resend_message() in this commit. See #5968 for a detailed breakdown of the changes.
2017-07-30 12:56:46 +02:00
compose.send_message();
const state = {
get_events_running_called: 1,
reify_message_id_checked: 0,
send_msg_called: 1,
};
assert.deepEqual(stub_state, state);
assert.ok(!echo_error_msg_checked);
assert.equal($("#compose-send-button").prop("disabled"), false);
assert.equal($("#compose-error-msg").html(), "Error sending message: Server says 408");
assert.equal($("#compose-textarea").val(), "foobarfoobar");
assert.ok($("#compose-textarea").is_focused());
assert.ok($("#compose-send-status").visible());
assert.equal($("#compose-send-button").prop("disabled"), false);
assert.ok(!$("#sending-indicator").visible());
})();
});
test_ui("enter_with_preview_open", ({override}) => {
override(notifications, "clear_compose_notifications", () => {});
override(reminder, "is_deferred_delivery", () => false);
2021-05-19 21:15:26 +02:00
override(document, "to_$", () => $("document-stub"));
page_params.user_id = new_user.user_id;
// Test sending a message with content.
compose_state.set_message_type("stream");
compose_state.stream_name("social");
$("#compose-textarea").val("message me");
$("#compose-textarea").hide();
$("#compose .undo_markdown_preview").show();
$("#compose .preview_message_area").show();
$("#compose .markdown_preview").hide();
page_params.enter_sends = true;
let send_message_called = false;
override(compose, "send_message", () => {
send_message_called = true;
});
compose.enter_with_preview_open();
assert.ok($("#compose-textarea").visible());
assert.ok(!$("#compose .undo_markdown_preview").visible());
assert.ok(!$("#compose .preview_message_area").visible());
assert.ok($("#compose .markdown_preview").visible());
assert.ok(send_message_called);
page_params.enter_sends = false;
$("#compose-textarea").trigger("blur");
compose.enter_with_preview_open();
assert.ok($("#compose-textarea").is_focused());
// Test sending a message without content.
$("#compose-textarea").val("");
$("#compose .preview_message_area").show();
$("#enter_sends").prop("checked", true);
page_params.enter_sends = true;
compose.enter_with_preview_open();
assert.ok($("#enter_sends").prop("checked"));
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "You have nothing to send!"}),
);
});
test_ui("finish", ({override}) => {
override(notifications, "clear_compose_notifications", () => {});
override(reminder, "is_deferred_delivery", () => false);
2021-05-19 21:15:26 +02:00
override(document, "to_$", () => $("document-stub"));
(function test_when_compose_validation_fails() {
$("#compose_invite_users").show();
$("#compose-send-button").prop("disabled", false);
$("#compose-send-button").trigger("focus");
$("#sending-indicator").hide();
$("#compose-textarea").off("select");
$("#compose-textarea").val("");
const res = compose.finish();
assert.equal(res, false);
assert.ok(!$("#compose_invite_users").visible());
assert.ok(!$("#sending-indicator").visible());
assert.ok(!$("#compose-send-button").is_focused());
assert.equal($("#compose-send-button").prop("disabled"), false);
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "You have nothing to send!"}),
);
})();
(function test_when_compose_validation_succeed() {
$("#compose-textarea").hide();
$("#compose .undo_markdown_preview").show();
$("#compose .preview_message_area").show();
$("#compose .markdown_preview").hide();
$("#compose-textarea").val("foobarfoobar");
compose_state.set_message_type("private");
override(compose_state, "private_message_recipient", () => "bob@example.com");
override(compose_pm_pill, "get_user_ids", () => []);
let compose_finished_event_checked = false;
$(document).on("compose_finished.zulip", () => {
compose_finished_event_checked = true;
});
let send_message_called = false;
override(compose, "send_message", () => {
send_message_called = true;
});
assert.ok(compose.finish());
assert.ok($("#compose-textarea").visible());
assert.ok(!$("#compose .undo_markdown_preview").visible());
assert.ok(!$("#compose .preview_message_area").visible());
assert.ok($("#compose .markdown_preview").visible());
assert.ok(send_message_called);
assert.ok(compose_finished_event_checked);
})();
});
test_ui("warn_if_private_stream_is_linked", ({mock_template}) => {
const test_sub = {
name: compose_state.stream_name(),
stream_id: 99,
};
stream_data.add_sub(test_sub);
peer_data.set_subscribers(test_sub.stream_id, [1, 2]);
let denmark = {
stream_id: 100,
name: "Denmark",
};
stream_data.add_sub(denmark);
peer_data.set_subscribers(denmark.stream_id, [1, 2, 3]);
function test_noop_case(invite_only) {
compose_state.set_message_type("stream");
denmark.invite_only = invite_only;
compose.warn_if_private_stream_is_linked(denmark);
assert.equal($("#compose_private_stream_alert").visible(), false);
}
test_noop_case(false);
// invite_only=true and current compose stream subscribers are a subset
// of mentioned_stream subscribers.
test_noop_case(true);
$("#compose_private").hide();
compose_state.set_message_type("stream");
const checks = [
(function () {
let called;
mock_template("compose_private_stream_alert.hbs", false, (context) => {
called = true;
assert.equal(context.stream_name, "Denmark");
return "fake-compose_private_stream_alert-template";
});
return function () {
assert.ok(called);
};
})(),
(function () {
let called;
$("#compose_private_stream_alert").append = (html) => {
called = true;
assert.equal(html, "fake-compose_private_stream_alert-template");
};
return function () {
assert.ok(called);
};
})(),
];
denmark = {
invite_only: true,
name: "Denmark",
stream_id: 22,
};
stream_data.add_sub(denmark);
compose.warn_if_private_stream_is_linked(denmark);
assert.equal($("#compose_private_stream_alert").visible(), true);
for (const f of checks) {
f();
}
});
test_ui("initialize", ({override, mock_template}) => {
override(giphy, "is_giphy_enabled", () => true);
let compose_actions_expected_opts;
let compose_actions_start_checked;
override(compose_actions, "start", (msg_type, opts) => {
assert.equal(msg_type, "stream");
assert.deepEqual(opts, compose_actions_expected_opts);
compose_actions_start_checked = true;
});
// In this test we mostly do the setup stuff in addition to testing the
// normal workflow of the function. All the tests for the on functions are
// done in subsequent tests directly below this test.
override(compose, "compute_show_video_chat_button", () => false);
let resize_watch_manual_resize_checked = false;
override(resize, "watch_manual_resize", (elem) => {
assert.equal("#compose-textarea", elem);
resize_watch_manual_resize_checked = true;
});
let xmlhttprequest_checked = false;
set_global("XMLHttpRequest", function () {
this.upload = true;
xmlhttprequest_checked = true;
});
$("#compose .compose_upload_file").addClass("notdisplayed");
page_params.max_file_upload_size_mib = 512;
let setup_upload_called = false;
let uppy_cancel_all_called = false;
override(upload, "setup_upload", (config) => {
assert.equal(config.mode, "compose");
setup_upload_called = true;
return {
cancelAll: () => {
uppy_cancel_all_called = true;
},
};
});
mock_template("compose.hbs", false, (context) => {
assert.equal(context.embedded, false);
assert.equal(context.file_upload_enabled, true);
assert.equal(context.giphy_enabled, true);
return "fake-compose-template";
});
compose.initialize();
assert.ok(resize_watch_manual_resize_checked);
assert.ok(xmlhttprequest_checked);
assert.ok(!$("#compose .compose_upload_file").hasClass("notdisplayed"));
assert.ok(setup_upload_called);
function set_up_compose_start_mock(expected_opts) {
compose_actions_start_checked = false;
compose_actions_expected_opts = expected_opts;
}
(function test_page_params_narrow_path() {
page_params.narrow = true;
reset_jquery();
set_up_compose_start_mock({});
compose.initialize();
assert.ok(compose_actions_start_checked);
})();
(function test_page_params_narrow_topic() {
page_params.narrow_topic = "testing";
reset_jquery();
set_up_compose_start_mock({topic: "testing"});
compose.initialize();
assert.ok(compose_actions_start_checked);
})();
(function test_abort_xhr() {
$("#compose-send-button").prop("disabled", true);
reset_jquery();
compose.initialize();
compose.abort_xhr();
assert.equal($("#compose-send-button").attr(), undefined);
assert.ok(uppy_cancel_all_called);
})();
});
test_ui("update_fade", ({override}) => {
initialize_handlers({override});
const selector =
"#stream_message_recipient_stream,#stream_message_recipient_topic,#private_message_recipient";
const keyup_handler_func = $(selector).get_on_handler("keyup");
let set_focused_recipient_checked = false;
let update_all_called = false;
override(compose_fade, "set_focused_recipient", (msg_type) => {
assert.equal(msg_type, "private");
set_focused_recipient_checked = true;
});
override(compose_fade, "update_all", () => {
update_all_called = true;
});
compose_state.set_message_type(false);
keyup_handler_func();
assert.ok(!set_focused_recipient_checked);
assert.ok(!update_all_called);
compose_state.set_message_type("private");
keyup_handler_func();
assert.ok(set_focused_recipient_checked);
assert.ok(update_all_called);
});
test_ui("trigger_submit_compose_form", ({override}) => {
initialize_handlers({override});
let prevent_default_checked = false;
let compose_finish_checked = false;
const e = {
preventDefault() {
prevent_default_checked = true;
},
};
override(compose, "finish", () => {
compose_finish_checked = true;
});
const submit_handler = $("#compose form").get_on_handler("submit");
submit_handler(e);
assert.ok(prevent_default_checked);
assert.ok(compose_finish_checked);
});
test_ui("needs_subscribe_warning", () => {
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(sub);
peer_data.set_subscribers(sub.stream_id, [bob.user_id, me.user_id]);
blueslip.expect("error", "Unknown user_id in get_by_user_id: 999");
// Test with an invalid user id.
assert.equal(compose.needs_subscribe_warning(invalid_user_id, sub.stream_id), false);
// Test with bot user.
assert.equal(compose.needs_subscribe_warning(test_bot.user_id, sub.stream_id), false);
// Test when user is subscribed to the stream.
assert.equal(compose.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(compose.needs_subscribe_warning(bob.user_id, sub.stream_id), true);
});
test_ui("warn_if_mentioning_unsubscribed_user", ({override, mock_template}) => {
override(settings_data, "user_can_subscribe_other_users", () => true);
let mentioned = {
email: "foo@bar.com",
};
$("#compose_invite_users .compose_invite_user").length = 0;
function test_noop_case(is_private, is_zephyr_mirror, is_broadcast) {
const msg_type = is_private ? "private" : "stream";
compose_state.set_message_type(msg_type);
page_params.realm_is_zephyr_mirror_realm = is_zephyr_mirror;
mentioned.is_broadcast = is_broadcast;
compose.warn_if_mentioning_unsubscribed_user(mentioned);
assert.equal($("#compose_invite_users").visible(), false);
}
test_noop_case(true, false, false);
test_noop_case(false, true, false);
test_noop_case(false, false, true);
$("#compose_invite_users").hide();
compose_state.set_message_type("stream");
page_params.realm_is_zephyr_mirror_realm = false;
// Test with empty stream name in compose box. It should return noop.
assert.equal(compose_state.stream_name(), "");
compose.warn_if_mentioning_unsubscribed_user(mentioned);
assert.equal($("#compose_invite_users").visible(), false);
compose_state.stream_name("random");
const sub = {
stream_id: 111,
name: "random",
};
// Test with invalid stream in compose box. It should return noop.
compose.warn_if_mentioning_unsubscribed_user(mentioned);
assert.equal($("#compose_invite_users").visible(), false);
// Test mentioning a user that should gets a warning.
const checks = [
(function () {
let called;
override(compose, "needs_subscribe_warning", (user_id, stream_id) => {
called = true;
assert.equal(user_id, 34);
assert.equal(stream_id, 111);
return true;
});
return function () {
assert.ok(called);
};
})(),
(function () {
let called;
mock_template("compose_invite_users.hbs", false, (context) => {
called = true;
assert.equal(context.user_id, 34);
assert.equal(context.stream_id, 111);
assert.equal(context.name, "Foo Barson");
return "fake-compose-invite-user-template";
});
return function () {
assert.ok(called);
};
})(),
(function () {
let called;
$("#compose_invite_users").append = (html) => {
called = true;
assert.equal(html, "fake-compose-invite-user-template");
};
return function () {
assert.ok(called);
};
})(),
];
mentioned = {
email: "foo@bar.com",
user_id: 34,
full_name: "Foo Barson",
};
stream_data.add_sub(sub);
compose.warn_if_mentioning_unsubscribed_user(mentioned);
assert.equal($("#compose_invite_users").visible(), true);
for (const f of checks) {
f();
}
// Simulate that the row was added to the DOM.
const warning_row = $("<warning row>");
let looked_for_existing;
warning_row.data = (field) => {
if (field === "user-id") {
looked_for_existing = true;
return "34";
}
if (field === "stream-id") {
return "111";
}
throw new Error(`Unknown field ${field}`);
};
const previous_users = $("#compose_invite_users .compose_invite_user");
previous_users.length = 1;
previous_users[0] = warning_row;
$("#compose_invite_users").hide();
// Now try to mention the same person again. The template should
// not render.
compose.warn_if_mentioning_unsubscribed_user(mentioned);
assert.equal($("#compose_invite_users").visible(), true);
assert.ok(looked_for_existing);
});
test_ui("on_events", ({override}) => {
initialize_handlers({override});
override(rendered_markdown, "update_elements", () => {});
function setup_parents_and_mock_remove(container_sel, target_sel, parent) {
const container = $.create("fake " + container_sel);
let container_removed = false;
container.remove = () => {
container_removed = true;
};
const target = $.create("fake click target (" + target_sel + ")");
target.set_parents_result(parent, container);
const event = {
preventDefault: noop,
target,
};
const helper = {
event,
container,
target,
container_was_removed: () => container_removed,
};
return helper;
}
(function test_compose_all_everyone_confirm_clicked() {
const handler = $("#compose-all-everyone").get_on_handler(
"click",
".compose-all-everyone-confirm",
);
const helper = setup_parents_and_mock_remove(
"compose-all-everyone",
"compose-all-everyone",
".compose-all-everyone",
);
$("#compose-all-everyone").show();
$("#compose-send-status").show();
let compose_finish_checked = false;
override(compose, "finish", () => {
compose_finish_checked = true;
});
handler(helper.event);
assert.ok(helper.container_was_removed());
assert.ok(compose_finish_checked);
assert.ok(!$("#compose-all-everyone").visible());
assert.ok(!$("#compose-send-status").visible());
})();
(function test_compose_invite_users_clicked() {
const handler = $("#compose_invite_users").get_on_handler("click", ".compose_invite_link");
const subscription = {
stream_id: 102,
name: "test",
subscribed: true,
};
const mentioned = {
full_name: "Foo Barson",
email: "foo@bar.com",
user_id: 34,
};
people.add_active_user(mentioned);
let invite_user_to_stream_called = false;
override(stream_edit, "invite_user_to_stream", (user_ids, sub, success) => {
invite_user_to_stream_called = true;
assert.deepEqual(user_ids, [mentioned.user_id]);
assert.equal(sub, subscription);
success(); // This will check success callback path.
});
const helper = setup_parents_and_mock_remove(
"compose_invite_users",
"compose_invite_link",
".compose_invite_user",
);
helper.container.data = (field) => {
if (field === "user-id") {
return "34";
}
if (field === "stream-id") {
return "102";
}
throw new Error(`Unknown field ${field}`);
};
helper.target.prop("disabled", false);
// !sub will result in true here and we check the success code path.
stream_data.add_sub(subscription);
$("#stream_message_recipient_stream").val("test");
let all_invite_children_called = false;
$("#compose_invite_users").children = () => {
all_invite_children_called = true;
return [];
};
$("#compose_invite_users").show();
handler(helper.event);
assert.ok(helper.container_was_removed());
assert.ok(!$("#compose_invite_users").visible());
assert.ok(invite_user_to_stream_called);
assert.ok(all_invite_children_called);
})();
(function test_compose_invite_close_clicked() {
const handler = $("#compose_invite_users").get_on_handler("click", ".compose_invite_close");
const helper = setup_parents_and_mock_remove(
"compose_invite_users_close",
"compose_invite_close",
".compose_invite_user",
);
let all_invite_children_called = false;
$("#compose_invite_users").children = () => {
all_invite_children_called = true;
return [];
};
$("#compose_invite_users").show();
handler(helper.event);
assert.ok(helper.container_was_removed());
assert.ok(all_invite_children_called);
assert.ok(!$("#compose_invite_users").visible());
})();
(function test_compose_not_subscribed_clicked() {
const handler = $("#compose-send-status").get_on_handler("click", ".sub_unsub_button");
const subscription = {
stream_id: 102,
name: "test",
subscribed: false,
};
let compose_not_subscribed_called = false;
subs.sub_or_unsub = () => {
compose_not_subscribed_called = true;
};
const helper = setup_parents_and_mock_remove(
"compose-send-status",
"sub_unsub_button",
".compose_not_subscribed",
);
handler(helper.event);
assert.ok(compose_not_subscribed_called);
stream_data.add_sub(subscription);
$("#stream_message_recipient_stream").val("test");
$("#compose-send-status").show();
handler(helper.event);
assert.ok(!$("#compose-send-status").visible());
})();
(function test_compose_not_subscribed_close_clicked() {
const handler = $("#compose-send-status").get_on_handler(
"click",
"#compose_not_subscribed_close",
);
const helper = setup_parents_and_mock_remove(
"compose_user_not_subscribed_close",
"compose_not_subscribed_close",
".compose_not_subscribed",
);
$("#compose-send-status").show();
handler(helper.event);
assert.ok(!$("#compose-send-status").visible());
})();
(function test_attach_files_compose_clicked() {
const handler = $("#compose").get_on_handler("click", ".compose_upload_file");
$("#compose .file_input").clone = (param) => {
assert.ok(param);
};
let compose_file_input_clicked = false;
$("#compose .file_input").on("click", () => {
compose_file_input_clicked = true;
});
const event = {
preventDefault: noop,
};
handler(event);
assert.ok(compose_file_input_clicked);
})();
(function test_markdown_preview_compose_clicked() {
// Tests setup
function setup_visibilities() {
$("#compose-textarea").show();
$("#compose .markdown_preview").show();
$("#compose .undo_markdown_preview").hide();
$("#compose .preview_message_area").hide();
}
function assert_visibilities() {
assert.ok(!$("#compose-textarea").visible());
assert.ok(!$("#compose .markdown_preview").visible());
assert.ok($("#compose .undo_markdown_preview").visible());
assert.ok($("#compose .preview_message_area").visible());
}
function setup_mock_markdown_contains_backend_only_syntax(msg_content, return_val) {
override(markdown, "contains_backend_only_syntax", (msg) => {
assert.equal(msg, msg_content);
return return_val;
});
}
function setup_mock_markdown_is_status_message(msg_content, return_val) {
override(markdown, "is_status_message", (content) => {
assert.equal(content, msg_content);
return return_val;
});
}
function test_post_success(success_callback) {
const resp = {
rendered: "Server: foobarfoobar",
};
success_callback(resp);
assert.equal($("#compose .preview_content").html(), "Server: foobarfoobar");
}
function test_post_error(error_callback) {
error_callback();
assert.equal(
$("#compose .preview_content").html(),
"translated HTML: Failed to generate preview",
);
}
let current_message;
override(channel, "post", (payload) => {
assert.equal(payload.url, "/json/messages/render");
assert.ok(payload.idempotent);
assert.ok(payload.data);
assert.deepEqual(payload.data.content, current_message);
function test(func, param) {
let destroy_indicator_called = false;
override(loading, "destroy_indicator", (spinner) => {
assert.equal(spinner, $("#compose .markdown_preview_spinner"));
destroy_indicator_called = true;
});
setup_mock_markdown_contains_backend_only_syntax(current_message, true);
func(param);
assert.ok(destroy_indicator_called);
}
test(test_post_error, payload.error);
test(test_post_success, payload.success);
});
const handler = $("#compose").get_on_handler("click", ".markdown_preview");
// Tests start here
$("#compose-textarea").val("");
setup_visibilities();
const event = {
preventDefault: noop,
};
handler(event);
assert.equal($("#compose .preview_content").html(), "translated HTML: Nothing to preview");
assert_visibilities();
let make_indicator_called = false;
$("#compose-textarea").val("```foobarfoobar```");
setup_visibilities();
setup_mock_markdown_contains_backend_only_syntax("```foobarfoobar```", true);
setup_mock_markdown_is_status_message("```foobarfoobar```", false);
override(loading, "make_indicator", (spinner) => {
assert.equal(spinner.selector, "#compose .markdown_preview_spinner");
make_indicator_called = true;
});
current_message = "```foobarfoobar```";
handler(event);
assert.ok(make_indicator_called);
assert_visibilities();
let apply_markdown_called = false;
$("#compose-textarea").val("foobarfoobar");
setup_visibilities();
setup_mock_markdown_contains_backend_only_syntax("foobarfoobar", false);
setup_mock_markdown_is_status_message("foobarfoobar", false);
current_message = "foobarfoobar";
override(markdown, "apply_markdown", (msg) => {
assert.equal(msg.raw_content, "foobarfoobar");
apply_markdown_called = true;
return msg;
});
handler(event);
assert.ok(apply_markdown_called);
assert_visibilities();
assert.equal($("#compose .preview_content").html(), "Server: foobarfoobar");
})();
(function test_undo_markdown_preview_clicked() {
const handler = $("#compose").get_on_handler("click", ".undo_markdown_preview");
$("#compose-textarea").hide();
$("#compose .undo_markdown_preview").show();
$("#compose .preview_message_area").show();
$("#compose .markdown_preview").hide();
const event = {
preventDefault: noop,
};
handler(event);
assert.ok($("#compose-textarea").visible());
assert.ok(!$("#compose .undo_markdown_preview").visible());
assert.ok(!$("#compose .preview_message_area").visible());
assert.ok($("#compose .markdown_preview").visible());
})();
});
test_ui("create_message_object", ({override}) => {
$("#stream_message_recipient_stream").val("social");
$("#stream_message_recipient_topic").val("lunch");
$("#compose-textarea").val("burrito");
2017-02-24 16:18:56 +01:00
override(compose_state, "get_message_type", () => "stream");
2017-02-24 16:18:56 +01:00
let message = compose.create_message_object();
assert.equal(message.to, social.stream_id);
assert.equal(message.topic, "lunch");
assert.equal(message.content, "burrito");
blueslip.expect("error", "Trying to send message with bad stream name: BOGUS STREAM");
$("#stream_message_recipient_stream").val("BOGUS STREAM");
message = compose.create_message_object();
assert.equal(message.to, "BOGUS STREAM");
assert.equal(message.topic, "lunch");
assert.equal(message.content, "burrito");
2017-02-24 16:18:56 +01:00
override(compose_state, "get_message_type", () => "private");
compose_state.__Rewire__(
"private_message_recipient",
() => "alice@example.com, bob@example.com",
);
message = compose.create_message_object();
assert.deepEqual(message.to, [alice.user_id, bob.user_id]);
assert.equal(message.to_user_ids, "31,32");
assert.equal(message.content, "burrito");
2017-02-24 16:18:56 +01:00
const {email_list_to_user_ids_string} = people;
override(people, "email_list_to_user_ids_string", () => undefined);
message = compose.create_message_object();
assert.deepEqual(message.to, [alice.email, bob.email]);
people.email_list_to_user_ids_string = email_list_to_user_ids_string;
});
test_ui("narrow_button_titles", () => {
compose_closed_ui.update_buttons_for_private();
assert.equal(
$("#left_bar_compose_stream_button_big").text(),
$t({defaultMessage: "New stream message"}),
);
assert.equal(
$("#left_bar_compose_private_button_big").text(),
$t({defaultMessage: "New private message"}),
);
compose_closed_ui.update_buttons_for_stream();
assert.equal(
$("#left_bar_compose_stream_button_big").text(),
$t({defaultMessage: "New topic"}),
);
assert.equal(
$("#left_bar_compose_private_button_big").text(),
$t({defaultMessage: "New private message"}),
);
});
MockDate.reset();