mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	This commit adds support for empty string as a valid topic name in syntax for linking to channel messages. The server stores it after empty string is replaced with `realm_empty_topic_display_name` and wrapped with an <em> tag. The web client parses the rendered_content and updates the topic_name part in the HTML with topic_name in user's language + wraps it in a <span> tag with 'empty-topic-display' css class.
		
			
				
	
	
		
			807 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			807 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
 | 
						|
const assert = require("node:assert/strict");
 | 
						|
 | 
						|
const {$t} = require("./lib/i18n.cjs");
 | 
						|
const {mock_cjs, 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");
 | 
						|
 | 
						|
let clipboard_args;
 | 
						|
class Clipboard {
 | 
						|
    constructor(...args) {
 | 
						|
        clipboard_args = args;
 | 
						|
    }
 | 
						|
    on(_success, show_copied_confirmation) {
 | 
						|
        show_copied_confirmation();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
mock_cjs("clipboard", Clipboard);
 | 
						|
 | 
						|
const realm_playground = mock_esm("../src/realm_playground");
 | 
						|
const copied_tooltip = mock_esm("../src/copied_tooltip");
 | 
						|
 | 
						|
const rm = zrequire("rendered_markdown");
 | 
						|
const people = zrequire("people");
 | 
						|
const user_groups = zrequire("user_groups");
 | 
						|
const stream_data = zrequire("stream_data");
 | 
						|
const rows = mock_esm("../src/rows");
 | 
						|
const message_store = mock_esm("../src/message_store");
 | 
						|
mock_esm("../src/settings_data", {
 | 
						|
    user_can_access_all_other_users: () => false,
 | 
						|
});
 | 
						|
const {set_realm} = zrequire("state_data");
 | 
						|
const {initialize_user_settings} = zrequire("user_settings");
 | 
						|
 | 
						|
const REALM_EMPTY_TOPIC_DISPLAY_NAME = "general chat";
 | 
						|
const realm = {realm_empty_topic_display_name: REALM_EMPTY_TOPIC_DISPLAY_NAME};
 | 
						|
set_realm(realm);
 | 
						|
const user_settings = {};
 | 
						|
initialize_user_settings({user_settings});
 | 
						|
 | 
						|
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",
 | 
						|
};
 | 
						|
 | 
						|
const polonius = {
 | 
						|
    email: "polonius@zulip.com",
 | 
						|
    user_id: 32,
 | 
						|
    full_name: "Polonius",
 | 
						|
    is_guest: true,
 | 
						|
};
 | 
						|
const inaccessible_user_id = 33;
 | 
						|
const inaccessible_user = people.add_inaccessible_user(inaccessible_user_id);
 | 
						|
people.init();
 | 
						|
people.add_active_user(iago);
 | 
						|
people.add_active_user(cordelia);
 | 
						|
people.add_active_user(polonius);
 | 
						|
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],
 | 
						|
};
 | 
						|
const group_me_via_subgroup = {
 | 
						|
    name: "I am part of this group via a subgroup",
 | 
						|
    id: 3,
 | 
						|
    members: [],
 | 
						|
    direct_subgroup_ids: [group_me.id],
 | 
						|
};
 | 
						|
user_groups.initialize({
 | 
						|
    realm_user_groups: [group_me, group_other, group_me_via_subgroup],
 | 
						|
});
 | 
						|
 | 
						|
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};
 | 
						|
};
 | 
						|
 | 
						|
function set_message_for_message_content($content, value) {
 | 
						|
    // no message row found
 | 
						|
    if (value === undefined) {
 | 
						|
        $content.closest = (closest_opts) => {
 | 
						|
            assert.equal(closest_opts, ".message_row");
 | 
						|
            return [];
 | 
						|
        };
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    // message row found
 | 
						|
    const $message_row = $.create(".message-row");
 | 
						|
    $content.closest = (closest_opts) => {
 | 
						|
        assert.equal(closest_opts, ".message_row");
 | 
						|
        return $message_row;
 | 
						|
    };
 | 
						|
    $message_row.length = 1;
 | 
						|
    $message_row.closest = (closest_opts) => {
 | 
						|
        assert.equal(closest_opts, ".overlay-message-row");
 | 
						|
        return [];
 | 
						|
    };
 | 
						|
    const message_id = 100;
 | 
						|
    rows.id = (message_row) => {
 | 
						|
        assert.equal(message_row, $message_row);
 | 
						|
        return message_id;
 | 
						|
    };
 | 
						|
    message_store.get = (message_id_opt) => {
 | 
						|
        assert.equal(message_id_opt, message_id);
 | 
						|
        return value;
 | 
						|
    };
 | 
						|
}
 | 
						|
 | 
						|
const get_content_element = () => {
 | 
						|
    const $content = $.create("content-stub");
 | 
						|
    $content.set_find_results(".user-mention", $array([]));
 | 
						|
    $content.set_find_results(".topic-mention", $array([]));
 | 
						|
    $content.set_find_results(".user-group-mention", $array([]));
 | 
						|
    $content.set_find_results("a.stream", $array([]));
 | 
						|
    $content.set_find_results("a.stream-topic, a.message-link", $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([]));
 | 
						|
    $content.set_find_results(".message_inline_video video", $array([]));
 | 
						|
 | 
						|
    set_message_for_message_content($content, undefined);
 | 
						|
 | 
						|
    // Fend off dumb security bugs by forcing devs to be
 | 
						|
    // intentional about HTML manipulation.
 | 
						|
    /* istanbul ignore next */
 | 
						|
    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", ({override}) => {
 | 
						|
    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");
 | 
						|
 | 
						|
    override(realm, "realm_enable_guest_user_indicator", true);
 | 
						|
    rm.set_name_in_mention_element($elem, "Polonius", polonius.user_id);
 | 
						|
    assert.equal($elem.text(), "translated: Polonius (guest)");
 | 
						|
 | 
						|
    override(realm, "realm_enable_guest_user_indicator", false);
 | 
						|
    rm.set_name_in_mention_element($elem, "Polonius", polonius.user_id);
 | 
						|
    assert.equal($elem.text(), "Polonius");
 | 
						|
});
 | 
						|
 | 
						|
run_test("message_inline_video", () => {
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $elem = $.create("message_inline_video");
 | 
						|
 | 
						|
    let load_called = false;
 | 
						|
    $elem.load = () => {
 | 
						|
        load_called = true;
 | 
						|
    };
 | 
						|
 | 
						|
    $content.set_find_results(".message_inline_video video", $array([$elem]));
 | 
						|
    window.GestureEvent = true;
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.equal(load_called, true);
 | 
						|
    window.GestureEvent = false;
 | 
						|
});
 | 
						|
 | 
						|
run_test("user-mention", ({override}) => {
 | 
						|
    // 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);
 | 
						|
    const $polonius = $.create("user-mention(polonius)");
 | 
						|
    $polonius.set_find_results(".highlight", false);
 | 
						|
    $polonius.attr("data-user-id", polonius.user_id);
 | 
						|
    $content.set_find_results(".user-mention", $array([$iago, $cordelia, $polonius]));
 | 
						|
    override(realm, "realm_enable_guest_user_indicator", true);
 | 
						|
    // Initial asserts
 | 
						|
    assert.ok(!$iago.hasClass("user-mention-me"));
 | 
						|
    assert.equal($iago.text(), "never-been-set");
 | 
						|
    assert.equal($cordelia.text(), "never-been-set");
 | 
						|
    assert.equal($polonius.text(), "never-been-set");
 | 
						|
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.ok(!$iago.hasClass("user-mention-me"));
 | 
						|
    assert.equal($iago.text(), `@${iago.full_name}`);
 | 
						|
    assert.equal($cordelia.text(), `@${cordelia.full_name}`);
 | 
						|
    assert.equal($polonius.text(), `translated: @${polonius.full_name} (guest)`);
 | 
						|
 | 
						|
    // message row found
 | 
						|
    const message = {mentioned_me_directly: true};
 | 
						|
    set_message_for_message_content($content, message);
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.ok($iago.hasClass("user-mention-me"));
 | 
						|
});
 | 
						|
 | 
						|
run_test("user-mention without guest indicator", ({override}) => {
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $polonius = $.create("user-mention(polonius-again)");
 | 
						|
    $polonius.set_find_results(".highlight", false);
 | 
						|
    $polonius.attr("data-user-id", polonius.user_id);
 | 
						|
    $content.set_find_results(".user-mention", $array([$polonius]));
 | 
						|
 | 
						|
    override(realm, "realm_enable_guest_user_indicator", false);
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.equal($polonius.text(), `@${polonius.full_name}`);
 | 
						|
});
 | 
						|
 | 
						|
run_test("user-mention of inaccessible users", () => {
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $othello = $.create("user-mention(othello)");
 | 
						|
    $othello.set_find_results(".highlight", false);
 | 
						|
    $othello.attr("data-user-id", inaccessible_user_id);
 | 
						|
    $othello.text("@Othello");
 | 
						|
    $content.set_find_results(".user-mention", $array([$othello]));
 | 
						|
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.equal($othello.text(), "@Othello");
 | 
						|
    assert.notEqual($othello.text(), `@${inaccessible_user.full_name}`);
 | 
						|
 | 
						|
    // Test inaccessible user id with no user object.
 | 
						|
    const $cordelia = $.create("user-mention(cordelia)");
 | 
						|
    $cordelia.set_find_results(".highlight", false);
 | 
						|
    $cordelia.attr("data-user-id", 40);
 | 
						|
    $cordelia.text("@Cordelia");
 | 
						|
    $content.set_find_results(".user-mention", $array([$cordelia]));
 | 
						|
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.equal($cordelia.text(), "@Cordelia");
 | 
						|
});
 | 
						|
 | 
						|
run_test("user-mention (stream wildcard)", () => {
 | 
						|
    // Setup
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $mention = $.create("mention");
 | 
						|
    $mention.attr("data-user-id", "*");
 | 
						|
    $content.set_find_results(".user-mention", $array([$mention]));
 | 
						|
    const message = {stream_wildcard_mentioned: true};
 | 
						|
    set_message_for_message_content($content, message);
 | 
						|
 | 
						|
    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("topic-mention", () => {
 | 
						|
    // Setup
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $mention = $.create("mention");
 | 
						|
    $content.set_find_results(".topic-mention", $array([$mention]));
 | 
						|
 | 
						|
    // when no message row found
 | 
						|
    assert.ok(!$mention.hasClass("user-mention-me"));
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.ok(!$mention.hasClass("user-mention-me"));
 | 
						|
 | 
						|
    // message row found
 | 
						|
    const message = {
 | 
						|
        topic_wildcard_mentioned: true,
 | 
						|
    };
 | 
						|
    set_message_for_message_content($content, message);
 | 
						|
 | 
						|
    assert.ok(!$mention.hasClass("user-mention-me"));
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.ok($mention.hasClass("user-mention-me"));
 | 
						|
});
 | 
						|
 | 
						|
run_test("topic-mention not topic participant", () => {
 | 
						|
    // Setup
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $mention = $.create("mention");
 | 
						|
    $content.set_find_results(".topic-mention", $array([$mention]));
 | 
						|
 | 
						|
    const message = {
 | 
						|
        topic_wildcard_mentioned: false,
 | 
						|
    };
 | 
						|
    set_message_for_message_content($content, message);
 | 
						|
 | 
						|
    assert.ok(!$mention.hasClass("user-mention-me"));
 | 
						|
    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", () => {
 | 
						|
    // Setup
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $group_me_via_subgroup = $.create("user-group-mention(me_via_subgroup)");
 | 
						|
    $group_me_via_subgroup.set_find_results(".highlight", false);
 | 
						|
    $group_me_via_subgroup.attr("data-user-group-id", group_me_via_subgroup.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_via_subgroup, $group_other]),
 | 
						|
    );
 | 
						|
 | 
						|
    // Initial asserts
 | 
						|
    assert.ok(!$group_me_via_subgroup.hasClass("user-mention-me"));
 | 
						|
    assert.equal($group_me_via_subgroup.text(), "never-been-set");
 | 
						|
    assert.equal($group_other.text(), "never-been-set");
 | 
						|
 | 
						|
    rm.update_elements($content);
 | 
						|
 | 
						|
    // Final asserts
 | 
						|
    assert.ok($group_me_via_subgroup.hasClass("user-mention-me"));
 | 
						|
    assert.equal($group_me_via_subgroup.text(), `@${group_me_via_subgroup.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("stream-links", ({mock_template}) => {
 | 
						|
    // 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(
 | 
						|
        "href",
 | 
						|
        `/#narrow/channel/${stream.stream_id}-random/topic/topic.20name.20.3E.20still.20the.20topic.20name`,
 | 
						|
    );
 | 
						|
    $stream_topic.replaceWith = noop;
 | 
						|
    $stream_topic.hasClass = (class_name) => class_name === "stream-topic";
 | 
						|
    $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, a.message-link", $array([$stream_topic]));
 | 
						|
 | 
						|
    let topic_link_context;
 | 
						|
    let topic_link_rendered_html;
 | 
						|
    mock_template("topic_link.hbs", true, (data, html) => {
 | 
						|
        topic_link_context = data;
 | 
						|
        topic_link_rendered_html = html;
 | 
						|
        return html;
 | 
						|
    });
 | 
						|
 | 
						|
    // 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.deepEqual(topic_link_context, {
 | 
						|
        channel_id: stream.stream_id,
 | 
						|
        channel_name: stream.name,
 | 
						|
        topic_display_name: "topic name > still the topic name",
 | 
						|
        is_empty_string_topic: false,
 | 
						|
        href: `/#narrow/channel/${stream.stream_id}-random/topic/topic.20name.20.3E.20still.20the.20topic.20name`,
 | 
						|
    });
 | 
						|
    assert.ok(!topic_link_rendered_html.includes("empty-topic-display"));
 | 
						|
});
 | 
						|
 | 
						|
run_test("topic-link (empty string topic)", ({mock_template}) => {
 | 
						|
    // Setup
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $channel_topic = $.create("a.stream-topic(empty-string-topic)");
 | 
						|
    $channel_topic.set_find_results(".highlight", false);
 | 
						|
    $channel_topic.attr("href", `/#narrow/channel/${stream.stream_id}-random/topic/`);
 | 
						|
    $channel_topic.replaceWith = noop;
 | 
						|
    $channel_topic.hasClass = (class_name) => class_name === "stream-topic";
 | 
						|
    $channel_topic.html(`#random > <em>${REALM_EMPTY_TOPIC_DISPLAY_NAME}</em>`);
 | 
						|
    $content.set_find_results("a.stream-topic, a.message-link", $array([$channel_topic]));
 | 
						|
 | 
						|
    let topic_link_context;
 | 
						|
    let topic_link_rendered_html;
 | 
						|
    mock_template("topic_link.hbs", true, (data, html) => {
 | 
						|
        topic_link_context = data;
 | 
						|
        topic_link_rendered_html = html;
 | 
						|
        return html;
 | 
						|
    });
 | 
						|
 | 
						|
    // Initial assert
 | 
						|
    assert.equal($channel_topic.html(), "#random > <em>general chat</em>");
 | 
						|
 | 
						|
    rm.update_elements($content);
 | 
						|
 | 
						|
    // Final assert
 | 
						|
    assert.deepEqual(topic_link_context, {
 | 
						|
        channel_id: stream.stream_id,
 | 
						|
        channel_name: stream.name,
 | 
						|
        topic_display_name: `translated: ${REALM_EMPTY_TOPIC_DISPLAY_NAME}`,
 | 
						|
        is_empty_string_topic: true,
 | 
						|
        href: `/#narrow/channel/${stream.stream_id}-random/topic/`,
 | 
						|
    });
 | 
						|
    assert.ok(topic_link_rendered_html.includes("empty-topic-display"));
 | 
						|
});
 | 
						|
 | 
						|
run_test("message-links", ({mock_template}) => {
 | 
						|
    // Setup
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $channel_topic_message = $.create("a.message-link");
 | 
						|
    $channel_topic_message.set_find_results(".highlight", false);
 | 
						|
    $channel_topic_message.attr(
 | 
						|
        "href",
 | 
						|
        `/#narrow/channel/${stream.stream_id}-${stream.name}/topic//near/123`,
 | 
						|
    );
 | 
						|
    $channel_topic_message.replaceWith = noop;
 | 
						|
    $channel_topic_message.hasClass = (class_name) => class_name === "message-link";
 | 
						|
    $channel_topic_message.html(
 | 
						|
        `#${stream.name} > <em>${REALM_EMPTY_TOPIC_DISPLAY_NAME}</em> @ 💬`,
 | 
						|
    );
 | 
						|
    $content.set_find_results("a.stream-topic, a.message-link", $array([$channel_topic_message]));
 | 
						|
 | 
						|
    let channel_message_link_context;
 | 
						|
    let channel_message_link_rendered_html;
 | 
						|
    mock_template("channel_message_link.hbs", true, (data, html) => {
 | 
						|
        channel_message_link_context = data;
 | 
						|
        channel_message_link_rendered_html = html;
 | 
						|
        return html;
 | 
						|
    });
 | 
						|
 | 
						|
    // Initial assert
 | 
						|
    assert.equal($channel_topic_message.html(), "#test > <em>general chat</em> @ 💬");
 | 
						|
 | 
						|
    rm.update_elements($content);
 | 
						|
 | 
						|
    // Final asserts
 | 
						|
    assert.deepEqual(channel_message_link_context, {
 | 
						|
        channel_name: stream.name,
 | 
						|
        topic_display_name: `translated: ${REALM_EMPTY_TOPIC_DISPLAY_NAME}`,
 | 
						|
        is_empty_string_topic: true,
 | 
						|
        href: `/#narrow/channel/${stream.stream_id}-test/topic//near/123`,
 | 
						|
    });
 | 
						|
    assert.ok(channel_message_link_rendered_html.includes("empty-topic-display"));
 | 
						|
});
 | 
						|
 | 
						|
run_test("timestamp without time", () => {
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $timestamp = $.create("timestamp 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;
 | 
						|
    });
 | 
						|
 | 
						|
    // 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");
 | 
						|
 | 
						|
    // 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(),
 | 
						|
        '<span class="timestamp-content-wrapper">\n    <i class="zulip-icon zulip-icon-clock markdown-timestamp-icon"></i>Thu, Jan 1, 1970, 12:00 AM</span>',
 | 
						|
    );
 | 
						|
    assert.equal($timestamp_invalid.text(), "never-been-set");
 | 
						|
});
 | 
						|
 | 
						|
run_test("timestamp-twenty-four-hour-time", ({mock_template, override}) => {
 | 
						|
    mock_template("markdown_timestamp.hbs", true, (data, html) => {
 | 
						|
        // sanity check incoming data
 | 
						|
        assert.ok(data.text.startsWith("Wed, Jul 15, 2020, "));
 | 
						|
        return html;
 | 
						|
    });
 | 
						|
 | 
						|
    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.
 | 
						|
    override(user_settings, "twenty_four_hour_time", true);
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.equal(
 | 
						|
        $timestamp.html(),
 | 
						|
        '<span class="timestamp-content-wrapper">\n    <i class="zulip-icon zulip-icon-clock markdown-timestamp-icon"></i>Wed, Jul 15, 2020, 20:40</span>',
 | 
						|
    );
 | 
						|
 | 
						|
    override(user_settings, "twenty_four_hour_time", false);
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.equal(
 | 
						|
        $timestamp.html(),
 | 
						|
        '<span class="timestamp-content-wrapper">\n    <i class="zulip-icon zulip-icon-clock markdown-timestamp-icon"></i>Wed, Jul 15, 2020, 8:40 PM</span>',
 | 
						|
    );
 | 
						|
});
 | 
						|
 | 
						|
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", ({override}) => {
 | 
						|
    // Setup
 | 
						|
    const $content = get_content_element();
 | 
						|
    const $emoji = $.create("emoji-stub");
 | 
						|
    $emoji.attr("title", "tada");
 | 
						|
    let called = false;
 | 
						|
    $emoji.text = (f) => {
 | 
						|
        const text = f.call($emoji);
 | 
						|
        assert.equal(":tada:", text);
 | 
						|
        called = true;
 | 
						|
        return {contents: () => ({unwrap() {}})};
 | 
						|
    };
 | 
						|
    $content.set_find_results(".emoji", $emoji);
 | 
						|
    override(user_settings, "emojiset", "text");
 | 
						|
 | 
						|
    rm.update_elements($content);
 | 
						|
 | 
						|
    assert.ok(called);
 | 
						|
 | 
						|
    // Set page parameters back so that test run order is independent
 | 
						|
    override(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]));
 | 
						|
    let $appended;
 | 
						|
    $header.append = ($element) => {
 | 
						|
        $appended = $element;
 | 
						|
    };
 | 
						|
 | 
						|
    // Test that the show/hide button gets added to a spoiler header.
 | 
						|
    const label = "My spoiler header";
 | 
						|
    const toggle_button_html =
 | 
						|
        '<span class="spoiler-button" aria-expanded="false"><span class="spoiler-arrow"></span></span>';
 | 
						|
    $header.html(label);
 | 
						|
    $header.set_find_results("p", $.create("p"));
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.equal(label, $header.html());
 | 
						|
    assert.equal($appended.selector, toggle_button_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]));
 | 
						|
    const $appended = [];
 | 
						|
    $header.append = ($element) => {
 | 
						|
        $appended.push($element);
 | 
						|
    };
 | 
						|
 | 
						|
    // Test that an empty header gets the default text applied (through i18n filter).
 | 
						|
    const toggle_button_html =
 | 
						|
        '<span class="spoiler-button" aria-expanded="false"><span class="spoiler-arrow"></span></span>';
 | 
						|
    $header.empty();
 | 
						|
    $header.set_find_results("p", $.create("p"));
 | 
						|
    rm.update_elements($content);
 | 
						|
    assert.equal($appended[0].selector, "<p>");
 | 
						|
    assert.equal($appended[0].text(), $t({defaultMessage: "Spoiler"}));
 | 
						|
    assert.equal($appended[1].selector, toggle_button_html);
 | 
						|
});
 | 
						|
 | 
						|
function assert_clipboard_setup() {
 | 
						|
    assert.equal(clipboard_args[0], "copy-code-stub");
 | 
						|
    const text = clipboard_args[1].text({
 | 
						|
        to_$: () => ({
 | 
						|
            parent: () => ({
 | 
						|
                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.attr("data-code-language", "javascript");
 | 
						|
 | 
						|
    const $code_buttons_container = $.create("code_buttons_container", {
 | 
						|
        children: ["copy-code-stub", "view-code-stub"],
 | 
						|
    });
 | 
						|
    const $copy_code_button = $.create("copy_code_button", {children: ["copy-code-stub"]});
 | 
						|
    const $view_code_in_playground = $.create("view_code_in_playground");
 | 
						|
 | 
						|
    $code_buttons_container.set_find_results(".copy_codeblock", $copy_code_button);
 | 
						|
    $code_buttons_container.set_find_results(".code_external_link", $view_code_in_playground);
 | 
						|
 | 
						|
    // The code playground code prepends a button container
 | 
						|
    // to the <pre> section 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) => {
 | 
						|
        assert.ok(arg.__zjquery, "We should only prepend jQuery objects.");
 | 
						|
        prepends.push(arg);
 | 
						|
    };
 | 
						|
 | 
						|
    if (viewing_code) {
 | 
						|
        mock_template("code_buttons_container.hbs", true, (data) => {
 | 
						|
            assert.equal(data.show_playground_button, true);
 | 
						|
            return {to_$: () => $code_buttons_container};
 | 
						|
        });
 | 
						|
    } else {
 | 
						|
        mock_template("code_buttons_container.hbs", true, (data) => {
 | 
						|
            assert.equal(data.show_playground_button, false);
 | 
						|
            return {to_$: () => $code_buttons_container};
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    rm.update_elements($content);
 | 
						|
 | 
						|
    return {
 | 
						|
        prepends,
 | 
						|
        $button_container: $code_buttons_container,
 | 
						|
        $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;
 | 
						|
    });
 | 
						|
 | 
						|
    override(copied_tooltip, "show_copied_confirmation", noop);
 | 
						|
 | 
						|
    const {prepends, $button_container, $view_code} = test_code_playground(mock_template, false);
 | 
						|
    assert.deepEqual(prepends, [$button_container]);
 | 
						|
    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"}];
 | 
						|
    });
 | 
						|
 | 
						|
    override(copied_tooltip, "show_copied_confirmation", noop);
 | 
						|
 | 
						|
    const {prepends, $button_container, $view_code} = test_code_playground(mock_template, true);
 | 
						|
    assert.deepEqual(prepends, [$button_container]);
 | 
						|
    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"];
 | 
						|
    });
 | 
						|
 | 
						|
    override(copied_tooltip, "show_copied_confirmation", noop);
 | 
						|
 | 
						|
    const {prepends, $button_container, $view_code} = test_code_playground(mock_template, true);
 | 
						|
    assert.deepEqual(prepends, [$button_container]);
 | 
						|
    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"));
 | 
						|
});
 |