"use strict";
const {strict: assert} = require("assert");
const {mock_cjs, mock_esm, with_field, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const blueslip = require("../zjsunit/zblueslip");
const $ = require("../zjsunit/zjquery");
const {user_settings} = require("../zjsunit/zpage_params");
let clipboard_args;
class Clipboard {
constructor(...args) {
clipboard_args = args;
}
}
mock_cjs("clipboard", Clipboard);
const realm_playground = mock_esm("../../static/js/realm_playground");
user_settings.emojiset = "apple";
const rm = zrequire("rendered_markdown");
const people = zrequire("people");
const user_groups = zrequire("user_groups");
const stream_data = zrequire("stream_data");
const iago = {
email: "iago@zulip.com",
user_id: 30,
full_name: "Iago",
};
const cordelia = {
email: "cordelia@zulip.com",
user_id: 31,
full_name: "Cordelia Lear",
};
people.init();
people.add_active_user(iago);
people.add_active_user(cordelia);
people.initialize_current_user(iago.user_id);
const group_me = {
name: "my user group",
id: 1,
members: [iago.user_id, cordelia.user_id],
};
const group_other = {
name: "other user group",
id: 2,
members: [cordelia.user_id],
};
user_groups.initialize({
realm_user_groups: [group_me, group_other],
});
const stream = {
subscribed: true,
color: "yellow",
name: "test",
stream_id: 3,
is_muted: true,
invite_only: false,
};
stream_data.add_sub(stream);
const $array = (array) => {
const each = (func) => {
for (const e of array) {
func.call(e);
}
};
return {each};
};
const get_content_element = () => {
const $content = $.create("content-stub");
$content.set_find_results(".user-mention", $array([]));
$content.set_find_results(".user-group-mention", $array([]));
$content.set_find_results("a.stream", $array([]));
$content.set_find_results("a.stream-topic", $array([]));
$content.set_find_results("time", $array([]));
$content.set_find_results("span.timestamp-error", $array([]));
$content.set_find_results(".emoji", $array([]));
$content.set_find_results("div.spoiler-header", $array([]));
$content.set_find_results("div.codehilite", $array([]));
// Fend off dumb security bugs by forcing devs to be
// intentional about HTML manipulation.
function security_violation() {
throw new Error(`
Be super careful about HTML manipulation.
Make sure your test objects set up their own
functions to validate that calls to html/prepend/append
use trusted values.
`);
}
$content.html = security_violation;
$content.prepend = security_violation;
$content.append = security_violation;
return $content;
};
run_test("misc_helpers", () => {
const elem = $.create("user-mention");
rm.set_name_in_mention_element(elem, "Aaron");
assert.equal(elem.text(), "@Aaron");
elem.addClass("silent");
rm.set_name_in_mention_element(elem, "Aaron, but silent");
assert.equal(elem.text(), "Aaron, but silent");
});
run_test("user-mention", () => {
// Setup
const $content = get_content_element();
const $iago = $.create("user-mention(iago)");
$iago.set_find_results(".highlight", false);
$iago.attr("data-user-id", iago.user_id);
const $cordelia = $.create("user-mention(cordelia)");
$cordelia.set_find_results(".highlight", false);
$cordelia.attr("data-user-id", cordelia.user_id);
$content.set_find_results(".user-mention", $array([$iago, $cordelia]));
// Initial asserts
assert.ok(!$iago.hasClass("user-mention-me"));
assert.equal($iago.text(), "never-been-set");
assert.equal($cordelia.text(), "never-been-set");
rm.update_elements($content);
// Final asserts
assert.ok($iago.hasClass("user-mention-me"));
assert.equal($iago.text(), `@${iago.full_name}`);
assert.equal($cordelia.text(), `@${cordelia.full_name}`);
});
run_test("user-mention (wildcard)", () => {
// Setup
const $content = get_content_element();
const $mention = $.create("mention");
$mention.attr("data-user-id", "*");
$content.set_find_results(".user-mention", $array([$mention]));
assert.ok(!$mention.hasClass("user-mention-me"));
rm.update_elements($content);
assert.ok($mention.hasClass("user-mention-me"));
});
run_test("user-mention (email)", () => {
// Setup
const $content = get_content_element();
const $mention = $.create("mention");
$mention.attr("data-user-email", cordelia.email);
$mention.set_find_results(".highlight", false);
$content.set_find_results(".user-mention", $array([$mention]));
rm.update_elements($content);
assert.ok(!$mention.hasClass("user-mention-me"));
assert.equal($mention.text(), "@Cordelia Lear");
});
run_test("user-mention (missing)", () => {
const $content = get_content_element();
const $mention = $.create("mention");
$content.set_find_results(".user-mention", $array([$mention]));
rm.update_elements($content);
assert.ok(!$mention.hasClass("user-mention-me"));
});
run_test("user-group-mention", () => {
// Setup
const $content = get_content_element();
const $group_me = $.create("user-group-mention(me)");
$group_me.set_find_results(".highlight", false);
$group_me.attr("data-user-group-id", group_me.id);
const $group_other = $.create("user-group-mention(other)");
$group_other.set_find_results(".highlight", false);
$group_other.attr("data-user-group-id", group_other.id);
$content.set_find_results(".user-group-mention", $array([$group_me, $group_other]));
// Initial asserts
assert.ok(!$group_me.hasClass("user-mention-me"));
assert.equal($group_me.text(), "never-been-set");
assert.equal($group_other.text(), "never-been-set");
rm.update_elements($content);
// Final asserts
assert.ok($group_me.hasClass("user-mention-me"));
assert.equal($group_me.text(), `@${group_me.name}`);
assert.equal($group_other.text(), `@${group_other.name}`);
});
run_test("user-group-mention (error)", () => {
const $content = get_content_element();
const $group = $.create("user-group-mention(bogus)");
$group.attr("data-user-group-id", "not-even-a-number");
$content.set_find_results(".user-group-mention", $array([$group]));
rm.update_elements($content);
assert.ok(!$group.hasClass("user-mention-me"));
});
run_test("user-group-mention (missing)", () => {
const $content = get_content_element();
const $group = $.create("whatever");
$content.set_find_results(".user-group-mention", $array([$group]));
rm.update_elements($content);
assert.ok(!$group.hasClass("user-mention-me"));
});
run_test("stream-links", () => {
// Setup
const $content = get_content_element();
const $stream = $.create("a.stream");
$stream.set_find_results(".highlight", false);
$stream.attr("data-stream-id", stream.stream_id);
const $stream_topic = $.create("a.stream-topic");
$stream_topic.set_find_results(".highlight", false);
$stream_topic.attr("data-stream-id", stream.stream_id);
$stream_topic.text("#random > topic name > still the topic name");
$content.set_find_results("a.stream", $array([$stream]));
$content.set_find_results("a.stream-topic", $array([$stream_topic]));
// Initial asserts
assert.equal($stream.text(), "never-been-set");
assert.equal($stream_topic.text(), "#random > topic name > still the topic name");
rm.update_elements($content);
// Final asserts
assert.equal($stream.text(), `#${stream.name}`);
assert.equal($stream_topic.text(), `#${stream.name} > topic name > still the topic name`);
});
run_test("timestamp without time", () => {
const $content = get_content_element();
const $timestamp = $.create("timestampe without actual time");
$content.set_find_results("time", $array([$timestamp]));
rm.update_elements($content);
assert.equal($timestamp.text(), "never-been-set");
});
run_test("timestamp", ({mock_template}) => {
mock_template("markdown_timestamp.hbs", true, (data, html) => {
assert.deepEqual(data, {text: "Thu, Jan 1 1970, 12:00 AM"});
return html;
});
mock_template("markdown_time_tooltip.hbs", true, (data, html) => {
assert.deepEqual(data, {tz_offset_str: "UTC"});
return html;
});
// Setup
const $content = get_content_element();
const $timestamp = $.create("timestamp(valid)");
$timestamp.attr("datetime", "1970-01-01T00:00:01Z");
const $timestamp_invalid = $.create("timestamp(invalid)");
$timestamp_invalid.attr("datetime", "invalid");
$content.set_find_results("time", $array([$timestamp, $timestamp_invalid]));
blueslip.expect("error", "Could not parse datetime supplied by backend: invalid");
// Initial asserts
assert.equal($timestamp.text(), "never-been-set");
assert.equal($timestamp_invalid.text(), "never-been-set");
rm.update_elements($content);
// Final asserts
assert.equal($timestamp.html(), '\nThu, Jan 1 1970, 12:00 AM\n');
assert.equal(
$timestamp.attr("data-tippy-content"),
"Everyone sees this in their own time zone.\n
\nYour time zone: UTC\n",
);
assert.equal($timestamp_invalid.text(), "never-been-set");
});
run_test("timestamp-twenty-four-hour-time", ({mock_template}) => {
mock_template("markdown_timestamp.hbs", true, (data, html) => {
// sanity check incoming data
assert.ok(data.text.startsWith("Wed, Jul 15 2020, "));
return html;
});
mock_template("markdown_time_tooltip.hbs", false, (data) => {
assert.deepEqual(data, {tz_offset_str: "UTC"});
});
const $content = get_content_element();
const $timestamp = $.create("timestamp");
$timestamp.attr("datetime", "2020-07-15T20:40:00Z");
$content.set_find_results("time", $array([$timestamp]));
// We will temporarily change the 24h setting for this test.
with_field(user_settings, "twenty_four_hour_time", true, () => {
rm.update_elements($content);
assert.equal($timestamp.html(), '\nWed, Jul 15 2020, 20:40\n');
});
with_field(user_settings, "twenty_four_hour_time", false, () => {
rm.update_elements($content);
assert.equal(
$timestamp.html(),
'\nWed, Jul 15 2020, 8:40 PM\n',
);
});
});
run_test("timestamp-error", () => {
// Setup
const $content = get_content_element();
const $timestamp_error = $.create("timestamp-error");
$timestamp_error.text("Invalid time format: the-time-format");
$content.set_find_results("span.timestamp-error", $array([$timestamp_error]));
// Initial assert
assert.equal($timestamp_error.text(), "Invalid time format: the-time-format");
rm.update_elements($content);
// Final assert
assert.equal($timestamp_error.text(), "translated: Invalid time format: the-time-format");
});
run_test("emoji", () => {
// Setup
const $content = get_content_element();
const $emoji = $.create("emoji-stub");
$emoji.attr("title", "tada");
let called = false;
$emoji.replaceWith = (f) => {
const text = f.call($emoji);
assert.equal(":tada:", text);
called = true;
};
$content.set_find_results(".emoji", $emoji);
user_settings.emojiset = "text";
rm.update_elements($content);
assert.ok(called);
// Set page parameters back so that test run order is independent
user_settings.emojiset = "apple";
});
run_test("spoiler-header", () => {
// Setup
const $content = get_content_element();
const $header = $.create("div.spoiler-header");
$content.set_find_results("div.spoiler-header", $array([$header]));
// Test that the show/hide button gets added to a spoiler header.
const label = "My spoiler header";
const toggle_button_html =
' ';
$header.html(label);
rm.update_elements($content);
assert.equal(toggle_button_html + label, $header.html());
});
run_test("spoiler-header-empty-fill", () => {
// Setup
const $content = get_content_element();
const $header = $.create("div.spoiler-header");
$content.set_find_results("div.spoiler-header", $array([$header]));
// Test that an empty header gets the default text applied (through i18n filter).
const toggle_button_html =
' ';
$header.html("");
rm.update_elements($content);
assert.equal(toggle_button_html + "
translated HTML: Spoiler
", $header.html()); }); function assert_clipboard_setup() { assert.equal(clipboard_args[0], "copy-code-stub"); const text = clipboard_args[1].text({ to_$: () => ({ siblings: (arg) => { assert.equal(arg, "code"); return { text: () => "text", }; }, }), }); assert.equal(text, "text"); } function test_code_playground(mock_template, viewing_code) { const $content = get_content_element(); const $hilite = $.create("div.codehilite"); const $pre = $.create("hilite-pre"); $content.set_find_results("div.codehilite", $array([$hilite])); $hilite.set_find_results("pre", $pre); $hilite.data("code-language", "javascript"); const $copy_code_button = $.create("copy_code_button", {children: ["copy-code-stub"]}); const $view_code_in_playground = $.create("view_code_in_playground"); // The code playground code prepends a few buttons // to thesection of a highlighted piece of code. // The args to prepend should be jQuery objects (or in // our case "fake" zjquery objects). const prepends = []; $pre.prepend = (arg) => { if (!arg.__zjquery) { throw new Error("We should only prepend jQuery objects."); } prepends.push(arg); }; mock_template("copy_code_button.hbs", false, (data) => { assert.equal(data, undefined); return {to_$: () => $copy_code_button}; }); if (viewing_code) { mock_template("view_code_in_playground.hbs", false, (data) => { assert.equal(data, undefined); return {to_$: () => $view_code_in_playground}; }); } rm.update_elements($content); return { prepends, copy_code: $copy_code_button, view_code: $view_code_in_playground, }; } run_test("code playground none", ({override, mock_template}) => { override(realm_playground, "get_playground_info_for_languages", (language) => { assert.equal(language, "javascript"); return undefined; }); const {prepends, copy_code, view_code} = test_code_playground(mock_template, false); assert.deepEqual(prepends, [copy_code]); assert_clipboard_setup(); assert.equal(view_code.attr("data-tippy-content"), undefined); assert.equal(view_code.attr("aria-label"), undefined); }); run_test("code playground single", ({override, mock_template}) => { override(realm_playground, "get_playground_info_for_languages", (language) => { assert.equal(language, "javascript"); return [{name: "Some Javascript Playground"}]; }); const {prepends, copy_code, view_code} = test_code_playground(mock_template, true); assert.deepEqual(prepends, [view_code, copy_code]); assert_clipboard_setup(); assert.equal( view_code.attr("data-tippy-content"), "translated: View in Some Javascript Playground", ); assert.equal(view_code.attr("aria-label"), "translated: View in Some Javascript Playground"); assert.equal(view_code.attr("aria-haspopup"), undefined); }); run_test("code playground multiple", ({override, mock_template}) => { override(realm_playground, "get_playground_info_for_languages", (language) => { assert.equal(language, "javascript"); return ["whatever", "whatever"]; }); const {prepends, copy_code, view_code} = test_code_playground(mock_template, true); assert.deepEqual(prepends, [view_code, copy_code]); assert_clipboard_setup(); assert.equal(view_code.attr("data-tippy-content"), "translated: View in playground"); assert.equal(view_code.attr("aria-label"), "translated: View in playground"); assert.equal(view_code.attr("aria-haspopup"), "true"); }); run_test("rtl", () => { const $content = get_content_element(); $content.text("مرحبا"); assert.ok(!$content.hasClass("rtl")); rm.update_elements($content); assert.ok($content.hasClass("rtl")); });