Files
zulip/frontend_tests/node_tests/compose_actions.js
Priyansh Garg 3294e8dbe0 compose-box: Insert quoted content at the cursor position.
Right now, on clicking `quote and reply` on any message, the quoted
message is always inserted at the top of compose-box irrespective of
the current cursor position. Also, after insertion of the quoted
message, the cursor is shifted at the end of the compose box.

This commit changes this behaviour to insertion of quoted message
at the current cursor position with a newline at the end of quote
and moving the cursor position to that newline after insertion.
A newline is added at the beginning of quoted message only if there
was some content already present in compose box before the previous
cursor position.

Tested on Google Chrome and Firefox browsers on Ubuntu dev environment.

Fixes: #16836
2021-07-06 16:38:17 -07:00

526 lines
15 KiB
JavaScript

"use strict";
const {strict: assert} = require("assert");
const {mock_esm, set_global, with_field, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const $ = require("../zjsunit/zjquery");
const noop = () => {};
set_global("document", {
location: {}, // we need this to load compose.js
to_$: () => $("document-stub"),
});
const channel = mock_esm("../../static/js/channel");
const compose_fade = mock_esm("../../static/js/compose_fade", {
clear_compose: noop,
});
const compose_pm_pill = mock_esm("../../static/js/compose_pm_pill");
const hash_util = mock_esm("../../static/js/hash_util");
const narrow_state = mock_esm("../../static/js/narrow_state", {
set_compose_defaults: noop,
});
mock_esm("../../static/js/notifications", {
clear_compose_notifications: noop,
});
mock_esm("../../static/js/reload_state", {
is_in_progress: () => false,
});
mock_esm("../../static/js/recent_topics_util", {
is_visible: noop,
});
mock_esm("../../static/js/drafts", {
update_draft: noop,
});
mock_esm("../../static/js/common", {
status_classes: "status_classes",
});
mock_esm("../../static/js/unread_ops", {
notify_server_message_read: noop,
});
mock_esm("../../static/js/message_lists", {
current: {
can_mark_messages_read: () => true,
},
});
mock_esm("../../static/js/resize", {
reset_compose_textarea_max_height: noop,
});
const people = zrequire("people");
const compose_ui = zrequire("compose_ui");
const compose = zrequire("compose");
const compose_state = zrequire("compose_state");
const compose_actions = zrequire("compose_actions");
const message_lists = zrequire("message_lists");
const stream_data = zrequire("stream_data");
const start = compose_actions.start;
const cancel = compose_actions.cancel;
const get_focus_area = compose_actions._get_focus_area;
const respond_to_message = compose_actions.respond_to_message;
const reply_with_mention = compose_actions.reply_with_mention;
const quote_and_reply = compose_actions.quote_and_reply;
function assert_visible(sel) {
assert.ok($(sel).visible());
}
function assert_hidden(sel) {
assert.ok(!$(sel).visible());
}
function override_private_message_recipient({override}) {
override(
compose_state,
"private_message_recipient",
(function () {
let recipient;
return function (arg) {
if (arg === undefined) {
return recipient;
}
recipient = arg;
return undefined;
};
})(),
);
}
function test(label, f) {
run_test(label, ({override}) => {
// We don't test the css calls; we just skip over them.
$("#compose").css = () => {};
$(".new_message_textarea").css = () => {};
people.init();
compose_state.set_message_type(false);
f({override});
});
}
test("initial_state", () => {
assert.equal(compose_state.composing(), false);
assert.equal(compose_state.get_message_type(), false);
assert.equal(compose_state.has_message_content(), false);
});
test("start", ({override}) => {
override_private_message_recipient({override});
override(compose_actions, "autosize_message_content", () => {});
override(compose_actions, "expand_compose_box", () => {});
override(compose_actions, "set_focus", () => {});
override(compose_actions, "complete_starting_tasks", () => {});
override(compose_actions, "blur_compose_inputs", () => {});
override(compose_actions, "clear_textarea", () => {});
let compose_defaults;
override(narrow_state, "set_compose_defaults", () => compose_defaults);
// Start stream message
compose_defaults = {
stream: "stream1",
topic: "topic1",
};
let opts = {};
start("stream", opts);
assert_visible("#stream-message");
assert_hidden("#private-message");
assert.equal($("#stream_message_recipient_stream").val(), "stream1");
assert.equal($("#stream_message_recipient_topic").val(), "topic1");
assert.equal(compose_state.get_message_type(), "stream");
assert.ok(compose_state.composing());
// Autofill stream field for single subscription
const denmark = {
subscribed: true,
color: "blue",
name: "Denmark",
stream_id: 1,
};
stream_data.add_sub(denmark);
compose_defaults = {
trigger: "new topic button",
};
opts = {};
start("stream", opts);
assert.equal($("#stream_message_recipient_stream").val(), "Denmark");
assert.equal($("#stream_message_recipient_topic").val(), "");
compose_defaults = {
trigger: "compose_hotkey",
};
opts = {};
start("stream", opts);
assert.equal($("#stream_message_recipient_stream").val(), "Denmark");
assert.equal($("#stream_message_recipient_topic").val(), "");
const social = {
subscribed: true,
color: "red",
name: "social",
stream_id: 2,
};
stream_data.add_sub(social);
// More than 1 subscription, do not autofill
opts = {};
start("stream", opts);
assert.equal($("#stream_message_recipient_stream").val(), "");
assert.equal($("#stream_message_recipient_topic").val(), "");
stream_data.clear_subscriptions();
// Start PM
compose_defaults = {
private_message_recipient: "foo@example.com",
};
opts = {
content: "hello",
};
start("private", opts);
assert_hidden("#stream-message");
assert_visible("#private-message");
assert.equal(compose_state.private_message_recipient(), "foo@example.com");
assert.equal($("#compose-textarea").val(), "hello");
assert.equal(compose_state.get_message_type(), "private");
assert.ok(compose_state.composing());
// Triggered by new private message
opts = {
trigger: "new private message",
};
start("private", opts);
assert.equal(compose_state.private_message_recipient(), "");
assert.equal(compose_state.get_message_type(), "private");
assert.ok(compose_state.composing());
// Cancel compose.
let pill_cleared;
compose_pm_pill.clear = () => {
pill_cleared = true;
};
let abort_xhr_called = false;
override(compose, "abort_xhr", () => {
abort_xhr_called = true;
});
$("#compose-textarea").set_height(50);
assert_hidden("#compose_controls");
cancel();
assert.ok(abort_xhr_called);
assert.ok(pill_cleared);
assert_visible("#compose_controls");
assert_hidden("#private-message");
assert.ok(!compose_state.composing());
});
test("respond_to_message", ({override}) => {
override(compose_actions, "set_focus", () => {});
override(compose_actions, "complete_starting_tasks", () => {});
override(compose_actions, "clear_textarea", () => {});
override_private_message_recipient({override});
// Test PM
const person = {
user_id: 22,
email: "alice@example.com",
full_name: "Alice",
};
people.add_active_user(person);
let msg = {
type: "private",
sender_id: person.user_id,
};
override(message_lists.current, "selected_message", () => msg);
let opts = {
reply_type: "personal",
};
respond_to_message(opts);
assert.equal(compose_state.private_message_recipient(), "alice@example.com");
// Test stream
msg = {
type: "stream",
stream: "devel",
topic: "python",
};
opts = {};
respond_to_message(opts);
assert.equal($("#stream_message_recipient_stream").val(), "devel");
});
test("reply_with_mention", ({override}) => {
compose_state.set_message_type("stream");
override(compose_actions, "set_focus", () => {});
override(compose_actions, "complete_starting_tasks", () => {});
override(compose_actions, "clear_textarea", () => {});
override_private_message_recipient({override});
const msg = {
type: "stream",
stream: "devel",
topic: "python",
sender_full_name: "Bob Roberts",
sender_id: 40,
};
override(message_lists.current, "selected_message", () => msg);
let syntax_to_insert;
override(compose_ui, "insert_syntax_and_focus", (syntax) => {
syntax_to_insert = syntax;
});
const opts = {};
reply_with_mention(opts);
assert.equal($("#stream_message_recipient_stream").val(), "devel");
assert.equal(syntax_to_insert, "@**Bob Roberts**");
// Test for extended mention syntax
const bob_1 = {
user_id: 30,
email: "bob1@example.com",
full_name: "Bob Roberts",
};
people.add_active_user(bob_1);
const bob_2 = {
user_id: 40,
email: "bob2@example.com",
full_name: "Bob Roberts",
};
people.add_active_user(bob_2);
reply_with_mention(opts);
assert.equal($("#stream_message_recipient_stream").val(), "devel");
assert.equal(syntax_to_insert, "@**Bob Roberts|40**");
});
test("quote_and_reply", ({override}) => {
compose_state.set_message_type("stream");
const steve = {
user_id: 90,
email: "steve@example.com",
full_name: "Steve Stephenson",
};
people.add_active_user(steve);
override(compose_actions, "set_focus", () => {});
override(compose_actions, "complete_starting_tasks", () => {});
override(compose_actions, "clear_textarea", () => {});
override_private_message_recipient({override});
let selected_message;
override(message_lists.current, "selected_message", () => selected_message);
let expected_replacement;
let replaced;
override(compose_ui, "replace_syntax", (syntax, replacement) => {
assert.equal(syntax, "[Quoting…]");
assert.equal(replacement, expected_replacement);
replaced = true;
});
selected_message = {
type: "stream",
stream: "devel",
topic: "python",
sender_full_name: "Steve Stephenson",
sender_id: 90,
};
hash_util.by_conversation_and_time_uri = () =>
"https://chat.zulip.org/#narrow/stream/92-learning/topic/Tornado";
let success_function;
override(channel, "get", (opts) => {
success_function = opts.success;
});
override(message_lists.current, "selected_id", () => 100);
override(compose_ui, "insert_syntax_and_focus", (syntax) => {
assert.equal(syntax, "[Quoting…]\n");
});
const opts = {
reply_type: "personal",
};
$("#compose-textarea").caret = noop;
replaced = false;
expected_replacement =
"translated: @_**Steve Stephenson|90** [said](https://chat.zulip.org/#narrow/stream/92-learning/topic/Tornado):\n```quote\nTesting.\n```";
quote_and_reply(opts);
success_function({
raw_content: "Testing.",
});
assert.ok(replaced);
selected_message = {
type: "stream",
stream: "devel",
topic: "test",
sender_full_name: "Steve Stephenson",
sender_id: 90,
raw_content: "Testing.",
};
function whiny_get() {
assert.fail("channel.get should not be used if raw_content is present");
}
replaced = false;
with_field(channel, "get", whiny_get, () => {
quote_and_reply(opts);
});
assert.ok(replaced);
selected_message = {
type: "stream",
stream: "devel",
topic: "test",
sender_full_name: "Steve Stephenson",
sender_id: 90,
raw_content: "```\nmultiline code block\nshoudln't mess with quotes\n```",
};
replaced = false;
expected_replacement =
"translated: @_**Steve Stephenson|90** [said](https://chat.zulip.org/#narrow/stream/92-learning/topic/Tornado):\n````quote\n```\nmultiline code block\nshoudln't mess with quotes\n```\n````";
quote_and_reply(opts);
assert.ok(replaced);
});
test("get_focus_area", () => {
assert.equal(get_focus_area("private", {}), "#private_message_recipient");
assert.equal(
get_focus_area("private", {
private_message_recipient: "bob@example.com",
}),
"#compose-textarea",
);
assert.equal(get_focus_area("stream", {}), "#stream_message_recipient_stream");
assert.equal(get_focus_area("stream", {stream: "fun"}), "#stream_message_recipient_topic");
assert.equal(get_focus_area("stream", {stream: "fun", topic: "more"}), "#compose-textarea");
assert.equal(
get_focus_area("stream", {stream: "fun", topic: "more", trigger: "new topic button"}),
"#stream_message_recipient_topic",
);
});
test("focus_in_empty_compose", ({override}) => {
$("#compose-textarea").is = (attr) => {
assert.equal(attr, ":focus");
return $("#compose-textarea").is_focused;
};
override(compose_state, "composing", () => true);
$("#compose-textarea").val("");
$("#compose-textarea").trigger("focus");
assert.ok(compose_state.focus_in_empty_compose());
override(compose_state, "composing", () => false);
assert.ok(!compose_state.focus_in_empty_compose());
$("#compose-textarea").val("foo");
assert.ok(!compose_state.focus_in_empty_compose());
$("#compose-textarea").trigger("blur");
assert.ok(!compose_state.focus_in_empty_compose());
});
test("on_narrow", ({override}) => {
let narrowed_by_topic_reply;
override(narrow_state, "narrowed_by_topic_reply", () => narrowed_by_topic_reply);
let narrowed_by_pm_reply;
override(narrow_state, "narrowed_by_pm_reply", () => narrowed_by_pm_reply);
let has_message_content;
override(compose_state, "has_message_content", () => has_message_content);
let cancel_called = false;
override(compose_actions, "cancel", () => {
cancel_called = true;
});
compose_actions.on_narrow({
force_close: true,
});
assert.ok(cancel_called);
let on_topic_narrow_called = false;
override(compose_actions, "on_topic_narrow", () => {
on_topic_narrow_called = true;
});
narrowed_by_topic_reply = true;
compose_actions.on_narrow({
force_close: false,
});
assert.ok(on_topic_narrow_called);
let update_message_list_called = false;
narrowed_by_topic_reply = false;
compose_fade.update_message_list = () => {
update_message_list_called = true;
};
has_message_content = true;
compose_actions.on_narrow({
force_close: false,
});
assert.ok(update_message_list_called);
has_message_content = false;
let start_called = false;
override(compose_actions, "start", () => {
start_called = true;
});
narrowed_by_pm_reply = true;
compose_actions.on_narrow({
force_close: false,
trigger: "not-search",
private_message_recipient: "not@empty.com",
});
assert.ok(start_called);
start_called = false;
compose_actions.on_narrow({
force_close: false,
trigger: "search",
private_message_recipient: "",
});
assert.ok(!start_called);
narrowed_by_pm_reply = false;
cancel_called = false;
compose_actions.on_narrow({
force_close: false,
});
assert.ok(cancel_called);
});