snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
390
web/tests/hash_util.test.cjs
Normal file
390
web/tests/hash_util.test.cjs
Normal file
@@ -0,0 +1,390 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const message_link_test_cases = require("../../zerver/tests/fixtures/message_link_test_cases.json");
|
||||
|
||||
const {make_stream} = require("./lib/example_stream.cjs");
|
||||
const {make_user} = require("./lib/example_user.cjs");
|
||||
const {zrequire} = require("./lib/namespace.cjs");
|
||||
const {run_test} = require("./lib/test.cjs");
|
||||
|
||||
const hash_parser = zrequire("hash_parser");
|
||||
const hash_util = zrequire("hash_util");
|
||||
const stream_data = zrequire("stream_data");
|
||||
const people = zrequire("people");
|
||||
const spectators = zrequire("spectators");
|
||||
|
||||
const hamlet = make_user({
|
||||
user_id: 15,
|
||||
email: "hamlet@example.com",
|
||||
full_name: "Hamlet",
|
||||
});
|
||||
|
||||
people.add_active_user(hamlet, "server_events");
|
||||
|
||||
const frontend = make_stream({
|
||||
stream_id: 99,
|
||||
name: "frontend",
|
||||
});
|
||||
|
||||
stream_data.add_sub_for_tests(frontend);
|
||||
|
||||
run_test("hash_util", () => {
|
||||
// Test encode_operand and decode_operand
|
||||
function encode_decode_operand(operator, operand, expected_val) {
|
||||
const encode_result = hash_util.encode_operand({operator, operand});
|
||||
assert.equal(encode_result, expected_val);
|
||||
const new_operand = encode_result;
|
||||
const decode_result = hash_util.decode_operand(operator, new_operand);
|
||||
assert.equal(decode_result, operand);
|
||||
}
|
||||
|
||||
let operator = "sender";
|
||||
let operand = hamlet.user_id;
|
||||
|
||||
encode_decode_operand(operator, operand, "15-Hamlet");
|
||||
|
||||
operator = "channel";
|
||||
operand = frontend.stream_id.toString();
|
||||
|
||||
encode_decode_operand(operator, operand, "99-frontend");
|
||||
|
||||
operator = "topic";
|
||||
operand = "testing 123";
|
||||
|
||||
encode_decode_operand(operator, operand, "testing.20123");
|
||||
});
|
||||
|
||||
run_test("test_get_hash_category", () => {
|
||||
assert.deepEqual(hash_parser.get_hash_category("channels/subscribed"), "channels");
|
||||
assert.deepEqual(hash_parser.get_hash_category("#settings/preferences"), "settings");
|
||||
assert.deepEqual(hash_parser.get_hash_category("#drafts"), "drafts");
|
||||
assert.deepEqual(hash_parser.get_hash_category("invites"), "invites");
|
||||
|
||||
window.location.hash = "#settings/profile";
|
||||
assert.deepEqual(hash_parser.get_current_hash_category(), "settings");
|
||||
});
|
||||
|
||||
run_test("test_get_hash_section", () => {
|
||||
assert.equal(hash_parser.get_hash_section("channels/subscribed"), "subscribed");
|
||||
assert.equal(hash_parser.get_hash_section("#settings/profile"), "profile");
|
||||
|
||||
assert.equal(hash_parser.get_hash_section("settings/10/general/"), "10");
|
||||
|
||||
assert.equal(hash_parser.get_hash_section("#drafts"), "");
|
||||
assert.equal(hash_parser.get_hash_section(""), "");
|
||||
|
||||
window.location.hash = "#settings/profile";
|
||||
assert.deepEqual(hash_parser.get_current_hash_section(), "profile");
|
||||
});
|
||||
|
||||
run_test("get_current_nth_hash_section", () => {
|
||||
window.location.hash = "#settings/profile";
|
||||
assert.equal(hash_parser.get_current_nth_hash_section(0), "#settings");
|
||||
assert.equal(hash_parser.get_current_nth_hash_section(1), "profile");
|
||||
|
||||
window.location.hash = "#settings/10/general";
|
||||
assert.equal(hash_parser.get_current_nth_hash_section(0), "#settings");
|
||||
assert.equal(hash_parser.get_current_nth_hash_section(1), "10");
|
||||
assert.equal(hash_parser.get_current_nth_hash_section(2), "general");
|
||||
assert.equal(hash_parser.get_current_nth_hash_section(3), "");
|
||||
});
|
||||
|
||||
run_test("test_is_same_server_message_link", () => {
|
||||
for (const message_link_test_case of message_link_test_cases) {
|
||||
assert.equal(
|
||||
hash_parser.is_same_server_message_link(message_link_test_case.message_link),
|
||||
message_link_test_case.expected_output,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
run_test("get_reload_hash", () => {
|
||||
window.location.hash = "#settings/profile";
|
||||
assert.equal(hash_util.get_reload_hash(), "settings/profile");
|
||||
|
||||
window.location.hash = "#test";
|
||||
assert.equal(hash_util.get_reload_hash(), "test");
|
||||
|
||||
window.location.hash = "#";
|
||||
assert.equal(hash_util.get_reload_hash(), "");
|
||||
|
||||
window.location.hash = "";
|
||||
assert.equal(hash_util.get_reload_hash(), "");
|
||||
});
|
||||
|
||||
run_test("test is_editing_stream", () => {
|
||||
window.location.hash = "#channels/1/announce";
|
||||
assert.equal(hash_parser.is_editing_stream(1), true);
|
||||
assert.equal(hash_parser.is_editing_stream(2), false);
|
||||
|
||||
// url is missing name at end
|
||||
window.location.hash = "#channels/1";
|
||||
assert.equal(hash_parser.is_editing_stream(1), false);
|
||||
|
||||
window.location.hash = "#channels/bogus/bogus";
|
||||
assert.equal(hash_parser.is_editing_stream(1), false);
|
||||
|
||||
window.location.hash = "#test/narrow";
|
||||
assert.equal(hash_parser.is_editing_stream(1), false);
|
||||
});
|
||||
|
||||
run_test("test_is_create_new_stream_narrow", () => {
|
||||
window.location.hash = "#channels/new";
|
||||
assert.equal(hash_parser.is_create_new_stream_narrow(), true);
|
||||
|
||||
window.location.hash = "#some/random/hash";
|
||||
assert.equal(hash_parser.is_create_new_stream_narrow(), false);
|
||||
});
|
||||
|
||||
run_test("test_is_subscribers_section_opened_for_stream", () => {
|
||||
window.location.hash = "#channels/1/Design/subscribers";
|
||||
assert.equal(hash_parser.is_subscribers_section_opened_for_stream(), true);
|
||||
|
||||
window.location.hash = "#channels/99/.EC.A1.B0.EB.A6.AC.EB.B2.95.20.F0.9F.98.8E/subscribers";
|
||||
assert.equal(hash_parser.is_subscribers_section_opened_for_stream(), true);
|
||||
|
||||
window.location.hash = "#channels/random/subscribers";
|
||||
assert.equal(hash_parser.is_subscribers_section_opened_for_stream(), false);
|
||||
|
||||
window.location.hash = "#some/random/place/subscribers";
|
||||
assert.equal(hash_parser.is_subscribers_section_opened_for_stream(), false);
|
||||
|
||||
window.location.hash = "#";
|
||||
assert.equal(hash_parser.is_subscribers_section_opened_for_stream(), false);
|
||||
});
|
||||
|
||||
run_test("test_is_in_specified_hash_category", () => {
|
||||
window.location.hash = "#channels/1/Design/subscribers";
|
||||
assert.equal(hash_parser.is_in_specified_hash_category(["channels", "channel"]), true);
|
||||
|
||||
window.location.hash = "#channels/99/.EC.A1.B0.EB.A6.AC.EB.B2.95.20.F0.9F.98.8E/subscribers";
|
||||
assert.equal(hash_parser.is_in_specified_hash_category(["channels", "channel"]), true);
|
||||
|
||||
window.location.hash = "#gro/channels/channel";
|
||||
assert.equal(hash_parser.is_in_specified_hash_category(["stream", "channel", "group"]), false);
|
||||
|
||||
window.location.hash = "#some/stream/channel/group";
|
||||
assert.equal(hash_parser.is_in_specified_hash_category(["stream", "channel", "group"]), false);
|
||||
|
||||
window.location.hash = "#some/stream/channel/group";
|
||||
assert.equal(hash_parser.is_in_specified_hash_category([""]), false);
|
||||
|
||||
window.location.hash = "#some/stream/channel/group";
|
||||
assert.equal(hash_parser.is_in_specified_hash_category([]), false);
|
||||
});
|
||||
|
||||
run_test("test_parse_narrow", () => {
|
||||
assert.deepEqual(hash_util.parse_narrow(["narrow", "stream", "99-frontend"]), [
|
||||
{negated: false, operator: "channel", operand: frontend.stream_id.toString()},
|
||||
]);
|
||||
|
||||
assert.deepEqual(hash_util.parse_narrow(["narrow", "-stream", "99-frontend"]), [
|
||||
{negated: true, operator: "channel", operand: frontend.stream_id.toString()},
|
||||
]);
|
||||
|
||||
assert.equal(hash_util.parse_narrow(["narrow", "BOGUS"]), undefined);
|
||||
|
||||
// For unknown channel IDs, we still parse it; it could be a valid channel we do
|
||||
// not have access to. We'll end up showing "Invalid stream" in the navbar.
|
||||
assert.deepEqual(hash_util.parse_narrow(["narrow", "stream", "42-bogus"]), [
|
||||
{negated: false, operator: "channel", operand: "42"},
|
||||
]);
|
||||
|
||||
// Empty string as a topic name is valid.
|
||||
assert.deepEqual(hash_util.parse_narrow(["narrow", "stream", "99-frontend", "topic", ""]), [
|
||||
{negated: false, operator: "channel", operand: frontend.stream_id.toString()},
|
||||
{negated: false, operator: "topic", operand: ""},
|
||||
]);
|
||||
|
||||
// Empty string used as an operand in other cases is invalid.
|
||||
assert.deepEqual(
|
||||
hash_util.parse_narrow(["narrow", "stream", "99-frontend", "topic", "", "near", ""]),
|
||||
undefined,
|
||||
);
|
||||
|
||||
// mentions:me in URL is converted to is:mentioned.
|
||||
assert.deepEqual(hash_util.parse_narrow(["narrow", "mentions", "me"]), [
|
||||
{negated: false, operator: "is", operand: "mentioned"},
|
||||
]);
|
||||
|
||||
assert.deepEqual(hash_util.parse_narrow(["narrow", "-mentions", "me"]), [
|
||||
{negated: true, operator: "is", operand: "mentioned"},
|
||||
]);
|
||||
});
|
||||
|
||||
run_test("test_channels_settings_edit_url", () => {
|
||||
const sub = {
|
||||
name: "research & development",
|
||||
stream_id: 42,
|
||||
};
|
||||
assert.equal(
|
||||
hash_util.channels_settings_edit_url(sub, "general"),
|
||||
"#channels/42/research.20.26.20development/general",
|
||||
);
|
||||
});
|
||||
|
||||
run_test("test_by_conversation_and_time_url", () => {
|
||||
let message = {
|
||||
type: "stream",
|
||||
stream_id: frontend.stream_id,
|
||||
topic: "testing",
|
||||
id: 42,
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
hash_util.by_conversation_and_time_url(message),
|
||||
"http://zulip.zulipdev.com/#narrow/channel/99-frontend/topic/testing/near/42",
|
||||
);
|
||||
|
||||
message = {
|
||||
type: "private",
|
||||
display_recipient: [
|
||||
{
|
||||
id: hamlet.user_id,
|
||||
},
|
||||
],
|
||||
id: 43,
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
hash_util.by_conversation_and_time_url(message),
|
||||
"http://zulip.zulipdev.com/#narrow/dm/15-dm/near/43",
|
||||
);
|
||||
});
|
||||
|
||||
run_test("test_search_public_streams_notice_url", () => {
|
||||
function get_terms(url) {
|
||||
return hash_util.parse_narrow(url.split("/"));
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
hash_util.search_public_streams_notice_url(get_terms("#narrow/search/abc")),
|
||||
"#narrow/channels/public/search/abc",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
hash_util.search_public_streams_notice_url(
|
||||
get_terms("#narrow/has/link/has/image/has/attachment"),
|
||||
),
|
||||
"#narrow/channels/public/has/link/has/image/has/attachment",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
hash_util.search_public_streams_notice_url(get_terms("#narrow/sender/15")),
|
||||
"#narrow/channels/public/sender/15-Hamlet",
|
||||
);
|
||||
});
|
||||
|
||||
run_test("test_current_hash_as_next", () => {
|
||||
window.location.hash = "#foo";
|
||||
assert.equal(spectators.current_hash_as_next(), "next=/%23foo");
|
||||
});
|
||||
|
||||
run_test("get_link_hash", () => {
|
||||
assert.equal(
|
||||
hash_util.get_link_hash("/#narrow/channel/9-announce"),
|
||||
"#narrow/channel/9-announce",
|
||||
);
|
||||
assert.equal(
|
||||
hash_util.get_link_hash("https://chat.zulip.org/#narrow/channel/127-integrations/"),
|
||||
"#narrow/channel/127-integrations/",
|
||||
);
|
||||
assert.equal(hash_util.get_link_hash("bad-url"), "");
|
||||
});
|
||||
|
||||
run_test("test_is_overlay_hash", () => {
|
||||
// All overlay categories should return true.
|
||||
const overlay_hashes = [
|
||||
"#streams/all",
|
||||
"#channels/subscribed",
|
||||
"#drafts",
|
||||
"#groups",
|
||||
"#settings/profile",
|
||||
"#organization/profile",
|
||||
"#invite",
|
||||
"#keyboard-shortcuts",
|
||||
"#message-formatting",
|
||||
"#search-operators",
|
||||
"#about-zulip",
|
||||
"#scheduled",
|
||||
"#reminders",
|
||||
"#user/12345",
|
||||
];
|
||||
for (const hash of overlay_hashes) {
|
||||
assert.equal(hash_parser.is_overlay_hash(hash), true);
|
||||
}
|
||||
|
||||
// Extra path segments still match on the category.
|
||||
assert.equal(hash_parser.is_overlay_hash("#channels/1/general"), true);
|
||||
|
||||
// Non-overlay hashes should return false.
|
||||
assert.equal(hash_parser.is_overlay_hash("#narrow/channel/99-frontend"), false);
|
||||
assert.equal(hash_parser.is_overlay_hash("#recent"), false);
|
||||
assert.equal(hash_parser.is_overlay_hash("#feed"), false);
|
||||
assert.equal(hash_parser.is_overlay_hash(""), false);
|
||||
assert.equal(hash_parser.is_overlay_hash(undefined), false);
|
||||
});
|
||||
|
||||
run_test("test_is_an_allowed_web_public_narrow", () => {
|
||||
// The special case: "is" operator with "resolved" operand.
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("is", "resolved"), true);
|
||||
|
||||
// "is" with other operands is not allowed.
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("is", "starred"), false);
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("is", "mentioned"), false);
|
||||
|
||||
// Allowed operators return true regardless of operand.
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("channel", "anything"), true);
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("topic", "anything"), true);
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("sender", "anything"), true);
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("has", "anything"), true);
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("search", "anything"), true);
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("near", "anything"), true);
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("id", "anything"), true);
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("with", "anything"), true);
|
||||
|
||||
// Disallowed operators should return false.
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("dm", "someone"), false);
|
||||
assert.equal(hash_parser.is_an_allowed_web_public_narrow("dm-including", "someone"), false);
|
||||
});
|
||||
|
||||
run_test("test_is_spectator_compatible", () => {
|
||||
// Non-narrow allowed hashes.
|
||||
assert.equal(hash_parser.is_spectator_compatible(""), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#recent"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#recent_topics"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#feed"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#all_messages"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#keyboard-shortcuts"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#message-formatting"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#search-operators"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#about-zulip"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#topics"), true);
|
||||
|
||||
// Narrow hashes with allowed operators.
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/channel/99-frontend"), true);
|
||||
assert.equal(
|
||||
hash_parser.is_spectator_compatible("#narrow/channel/99-frontend/topic/testing"),
|
||||
true,
|
||||
);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/sender/15"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/has/link"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/search/hello"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/id/42"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/near/100"), true);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/with/5"), true);
|
||||
|
||||
// Narrow hashes with disallowed operators.
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/dm/15-dm"), false);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/is/starred"), false);
|
||||
|
||||
// Narrow with "is/resolved" is allowed via is_an_allowed_web_public_narrow.
|
||||
assert.equal(hash_parser.is_spectator_compatible("#narrow/is/resolved"), true);
|
||||
|
||||
// Hashes that are neither narrow nor in the allowed list.
|
||||
assert.equal(hash_parser.is_spectator_compatible("#settings/profile"), false);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#drafts"), false);
|
||||
assert.equal(hash_parser.is_spectator_compatible("#channels/subscribed"), false);
|
||||
});
|
||||
Reference in New Issue
Block a user