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

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

View File

@@ -0,0 +1,429 @@
"use strict";
const assert = require("node:assert/strict");
const {zrequire} = require("./lib/namespace.cjs");
const {run_test} = require("./lib/test.cjs");
const typeahead = zrequire("typeahead");
const unicode_emojis = [
["1f43c", "panda_face"],
["1f642", "slight_smile"],
["1f604", "smile"],
["1f368", "ice_cream"],
["1f366", "soft_ice_cream"],
["1f6a5", "horizontal_traffic_light"],
["1f6a6", "traffic_light"],
["1f537", "large_blue_diamond"],
["1f539", "small_blue_diamond"],
];
const emojis = [
{emoji_name: "japanese_post_office", reaction_type: "realm_emoji", url: "TBD"},
{emoji_name: "tada", reaction_type: "realm_emoji", random_field: "whatever"},
...unicode_emojis.map(([emoji_code, emoji_name]) => ({
emoji_name,
emoji_code,
reaction_type: "unicode_emoji",
})),
];
function emoji_matches(query) {
const matcher = typeahead.get_emoji_matcher(query);
return emojis.filter((emoji) => matcher(emoji));
}
function assert_emoji_matches(query, expected) {
const names = emoji_matches(query).map((emoji) => emoji.emoji_name);
assert.deepEqual(names.toSorted(), expected);
}
run_test("get_emoji_matcher: nonmatches", () => {
assert_emoji_matches("notaemoji", []);
assert_emoji_matches("da_", []);
});
run_test("get_emoji_matcher: misc matches", () => {
assert_emoji_matches("da", ["panda_face", "tada"]);
assert_emoji_matches("smil", ["slight_smile", "smile"]);
assert_emoji_matches("mile", ["slight_smile", "smile"]);
assert_emoji_matches("japanese_post_", ["japanese_post_office"]);
});
run_test("matches starting at non-first word, too", () => {
assert_emoji_matches("ice_cream", ["ice_cream", "soft_ice_cream"]);
assert_emoji_matches("blue_dia", ["large_blue_diamond", "small_blue_diamond"]);
assert_emoji_matches("traffic_", ["horizontal_traffic_light", "traffic_light"]);
});
run_test("matches literal unicode emoji", () => {
assert_emoji_matches("🐼", ["panda_face"]);
});
run_test("get_emoji_matcher: spaces equivalent to underscores", () => {
function assert_equivalent(query) {
assert.deepEqual(emoji_matches(query), emoji_matches(query.replace(" ", "_")));
}
assert_equivalent("da ");
assert_equivalent("panda ");
assert_equivalent("japanese post ");
assert_equivalent("ice ");
assert_equivalent("ice cream");
assert_equivalent("blue dia");
assert_equivalent("traffic ");
assert_equivalent("traffic l");
});
run_test("query_matches_string_in_order", () => {
function assert_matches(query, source, split_char) {
const should_remove_diacritics = !typeahead.contains_diacritics(query);
assert.equal(
typeahead.query_matches_string_in_order(
query,
source,
split_char,
should_remove_diacritics,
),
true,
);
}
function assert_no_match(query, source, split_char) {
const should_remove_diacritics = !typeahead.contains_diacritics(query);
assert.equal(
typeahead.query_matches_string_in_order(
query,
source,
split_char,
should_remove_diacritics,
),
false,
);
}
// Query without diacritics should match source with diacritics.
assert_matches("jose mar", "José María", " ");
assert_matches("maria gon", "María González", " ");
assert_matches("jose", "José María", " ");
// Query with diacritics does diacritic-sensitive matching
assert_matches("josé", "José María", " ");
assert_no_match("josé", "Jose Maria", " ");
assert_matches("josé mar", "José María", " ");
assert_no_match("josé mar", "Jose Maria", " ");
});
run_test("triage", () => {
const alice = {name: "alice"};
const alicia = {name: "Alicia"};
const joan = {name: "Joan"};
const jo = {name: "Jo"};
const steve = {name: "steve"};
const stephanie = {name: "Stephanie"};
const names = [alice, alicia, joan, jo, steve, stephanie];
assert.deepEqual(
typeahead.triage("a", names, (r) => r.name),
{
matches: [alice, alicia],
rest: [joan, jo, steve, stephanie],
},
);
assert.deepEqual(
typeahead.triage("A", names, (r) => r.name),
{
matches: [alicia, alice],
rest: [joan, jo, steve, stephanie],
},
);
assert.deepEqual(
typeahead.triage("S", names, (r) => r.name),
{
matches: [stephanie, steve],
rest: [alice, alicia, joan, jo],
},
);
assert.deepEqual(
typeahead.triage("fred", names, (r) => r.name),
{
matches: [],
rest: [alice, alicia, joan, jo, steve, stephanie],
},
);
assert.deepEqual(
typeahead.triage("Jo", names, (r) => r.name),
{
matches: [jo, joan],
rest: [alice, alicia, steve, stephanie],
},
);
assert.deepEqual(
typeahead.triage("jo", names, (r) => r.name),
{
matches: [jo, joan],
rest: [alice, alicia, steve, stephanie],
},
);
assert.deepEqual(
typeahead.triage(" ", names, (r) => r.name),
{
matches: [],
rest: [alice, alicia, joan, jo, steve, stephanie],
},
);
assert.deepEqual(
typeahead.triage(";", names, (r) => r.name),
{
matches: [],
rest: [alice, alicia, joan, jo, steve, stephanie],
},
);
});
run_test("triage: prioritise word boundary matches to arbitrary substring matches", () => {
const book = {name: "book"};
const hyphen_ok = {name: "hyphen_ok"};
const space_ok = {name: "space ok"};
const no_space_ok = {name: "nospaceok"};
const number_ok = {name: "number1ok"};
const okay = {name: "okay"};
const ok = {name: "ok"};
const emojis = [book, hyphen_ok, space_ok, no_space_ok, number_ok, okay, ok];
assert.deepEqual(
typeahead.triage("ok", emojis, (r) => r.name),
{
matches: [ok, okay, hyphen_ok, space_ok],
rest: [book, no_space_ok, number_ok],
},
);
});
run_test("triage: prioritize exact diacritic matches over diacritic-stripped matches", () => {
const exact_a = {name: "a"};
const exact_diacritic = {name: "ą"};
const aaron = {name: "aaron"};
const aa = {name: "Aa"};
const adam_diacritic = {name: "Ądam"};
const john_a = {name: "John a"};
const zoe = {name: "Zoe"};
const users = [exact_a, exact_diacritic, aaron, aa, adam_diacritic, john_a, zoe];
assert.deepEqual(
typeahead.triage("a", users, (r) => r.name),
{
matches: [exact_a, aaron, aa, john_a],
rest: [exact_diacritic, adam_diacritic, zoe],
},
);
assert.deepEqual(
typeahead.triage("A", users, (r) => r.name),
{
matches: [exact_a, aa, aaron, john_a],
rest: [exact_diacritic, adam_diacritic, zoe],
},
);
assert.deepEqual(
typeahead.triage("ą", users, (r) => r.name),
{
matches: [exact_diacritic, adam_diacritic, exact_a, aaron, aa, john_a],
rest: [zoe],
},
);
assert.deepEqual(
typeahead.triage("Ą", users, (r) => r.name),
{
matches: [exact_diacritic, adam_diacritic, exact_a, aaron, aa, john_a],
rest: [zoe],
},
);
});
run_test("triage: ASCII query matches diacritic item via word boundary", () => {
const luke_adam = {name: "Łuke adam"};
const zoe = {name: "Zoe"};
const users = [luke_adam, zoe];
assert.deepEqual(
typeahead.triage("a", users, (r) => r.name),
{
matches: [luke_adam],
rest: [zoe],
},
);
});
run_test("triage_raw: diacritic-prefix and diacritic-stripped fallback", () => {
const adam_diacritic = {names: ["Ądam"]};
const joe_ascii = {names: ["Joe Maria"]};
const alex_joe = {names: ["Alex joe"]};
const zoe = {names: ["Zoe"]};
const items = [adam_diacritic, joe_ascii, alex_joe, zoe];
const diacritic_result = typeahead.triage_raw("ą", items, (item) => item.names);
assert.deepEqual(diacritic_result.begins_with_case_insensitive_diacritic_matches, [
adam_diacritic,
]);
// The query has diacritics, so the diacritic-stripped fallback still lets an
// ASCII candidate ("Alex joe") land in the case-insensitive bucket.
assert.deepEqual(diacritic_result.begins_with_case_insensitive_matches, [alex_joe]);
const fallback_result = typeahead.triage_raw("joé", items, (item) => item.names);
assert.deepEqual(fallback_result.begins_with_case_insensitive_matches, [joe_ascii]);
assert.deepEqual(fallback_result.word_boundary_matches, [alex_joe]);
});
function sort_emojis(emojis, query) {
return typeahead.sort_emojis(emojis, query).map((emoji) => emoji.emoji_name);
}
run_test("sort_emojis: th", () => {
const emoji_list = [
{emoji_name: "mother_nature", is_realm_emoji: true},
{emoji_name: "thermometer", is_realm_emoji: true},
{emoji_name: "thumbs_down", is_realm_emoji: true},
{emoji_name: "thumbs_up", is_realm_emoji: false, emoji_code: "1f44d"},
];
typeahead.set_frequently_used_emojis(typeahead.get_popular_emojis());
assert.deepEqual(sort_emojis(emoji_list, "th"), [
"thumbs_up",
"thermometer",
"thumbs_down",
"mother_nature",
]);
});
run_test("sort_emojis: sm", () => {
const emoji_list = [
{emoji_name: "smile", is_realm_emoji: true},
{emoji_name: "slight_smile", is_realm_emoji: false, emoji_code: "1f642"},
{emoji_name: "small_airplane", is_realm_emoji: true},
];
assert.deepEqual(sort_emojis(emoji_list, "sm"), ["slight_smile", "smile", "small_airplane"]);
});
run_test("sort_emojis: SM", () => {
const emoji_list = [
{emoji_name: "smile", is_realm_emoji: true},
{emoji_name: "slight_smile", is_realm_emoji: false, emoji_code: "1f642"},
{emoji_name: "small_airplane", is_realm_emoji: true},
];
assert.deepEqual(sort_emojis(emoji_list, "SM"), ["slight_smile", "smile", "small_airplane"]);
});
run_test("sort_emojis: prefix before midphrase, with underscore (traffic_li)", () => {
const emoji_list = [
{emoji_name: "horizontal_traffic_light", is_realm_emoji: true},
{emoji_name: "traffic_light", is_realm_emoji: true},
];
assert.deepEqual(sort_emojis(emoji_list, "traffic_li"), [
"traffic_light",
"horizontal_traffic_light",
]);
});
run_test("sort_emojis: prefix before midphrase, with space (traffic li)", () => {
const emoji_list = [
{emoji_name: "horizontal_traffic_light", is_realm_emoji: true},
{emoji_name: "traffic_light", is_realm_emoji: true},
];
assert.deepEqual(sort_emojis(emoji_list, "traffic li"), [
"traffic_light",
"horizontal_traffic_light",
]);
});
run_test("sort_emojis: remove duplicates", () => {
// notice the last 2 are aliases of the same emoji (same emoji code)
const emoji_list = [
{emoji_name: "laughter_tears", emoji_code: "1f602", is_realm_emoji: false},
{emoji_name: "tear", emoji_code: "1f972", is_realm_emoji: false},
{emoji_name: "smile_with_tear", emoji_code: "1f972", is_realm_emoji: false},
];
assert.deepEqual(typeahead.sort_emojis(emoji_list, "tear"), [emoji_list[1], emoji_list[0]]);
});
run_test("sort_emojis: prioritise realm emojis", () => {
const emoji_list = [
{emoji_name: "thank_you", emoji_code: "1f64f", is_realm_emoji: false},
{
emoji_name: "thank_you_custom",
url: "something",
is_realm_emoji: true,
},
];
assert.deepEqual(typeahead.sort_emojis(emoji_list, "thank"), [emoji_list[1], emoji_list[0]]);
});
run_test("sort_emojis: prioritise perfect matches", () => {
const emoji_list = [
{emoji_name: "thank_you", emoji_code: "1f64f", is_realm_emoji: false},
{
emoji_name: "thank_you_custom",
url: "something",
is_realm_emoji: true,
},
];
assert.deepEqual(typeahead.sort_emojis(emoji_list, "thank you"), emoji_list);
});
run_test("last_prefix_match", () => {
let words = [
"apple",
"banana",
"cantaloupe",
"cherry",
"kiwi",
"melon",
"pear",
"plum",
"raspberry",
"watermelon",
];
let prefix = "p";
assert.equal(typeahead.last_prefix_match(prefix, words), 7);
prefix = "ch";
assert.equal(typeahead.last_prefix_match(prefix, words), 3);
prefix = "pom";
assert.equal(typeahead.last_prefix_match(prefix, words), null);
prefix = "aa";
assert.equal(typeahead.last_prefix_match(prefix, words), null);
prefix = "zu";
assert.equal(typeahead.last_prefix_match(prefix, words), null);
prefix = "";
assert.equal(typeahead.last_prefix_match(prefix, words), 9);
words = ["one"];
prefix = "one";
assert.equal(typeahead.last_prefix_match(prefix, words), 0);
words = ["aa", "pr", "pra", "pre", "pri", "pro", "pru", "zz"];
prefix = "pr";
assert.equal(typeahead.last_prefix_match(prefix, words), 6);
words = ["same", "same", "same", "same", "same"];
prefix = "same";
assert.equal(typeahead.last_prefix_match(prefix, words), 4);
words = [];
prefix = "empty";
assert.equal(typeahead.last_prefix_match(prefix, words), null);
});