ts: Migrate `user_status` module to TypeScript.

Also removed an defensive if check from `emoji.ts` since it is not
needed now that we have `emoji.ts` converted to TypeScript.
This commit is contained in:
Lalit Kumar Singh 2023-09-19 11:43:40 +05:30 committed by Tim Abbott
parent 951e31a154
commit dcf45da09c
8 changed files with 178 additions and 104 deletions

View File

@ -270,7 +270,7 @@ EXEMPT_FILES = make_set(
"web/src/user_profile.js", "web/src/user_profile.js",
"web/src/user_settings.ts", "web/src/user_settings.ts",
"web/src/user_sort.js", "web/src/user_sort.js",
"web/src/user_status.js", "web/src/user_status.ts",
"web/src/user_status_ui.js", "web/src/user_status_ui.js",
"web/src/user_topic_popover.js", "web/src/user_topic_popover.js",
"web/src/user_topics.ts", "web/src/user_topics.ts",

View File

@ -316,13 +316,9 @@ export function get_emoji_details_by_name(emoji_name: string): EmojiRenderingDet
export function get_emoji_details_for_rendering(opts: { export function get_emoji_details_for_rendering(opts: {
emoji_name: string; emoji_name: string;
emoji_code: string | number; emoji_code: string;
reaction_type: string; reaction_type: string;
}): EmojiRenderingDetails { }): EmojiRenderingDetails {
if (!opts.emoji_name || !opts.emoji_code || !opts.reaction_type) {
throw new Error("Invalid params.");
}
if (opts.reaction_type !== "unicode_emoji") { if (opts.reaction_type !== "unicode_emoji") {
const realm_emoji = all_realm_emojis.get(opts.emoji_code); const realm_emoji = all_realm_emojis.get(opts.emoji_code);
if (!realm_emoji) { if (!realm_emoji) {

View File

@ -1,94 +0,0 @@
import * as channel from "./channel";
import * as emoji from "./emoji";
import {user_settings} from "./user_settings";
const user_info = new Map();
const user_status_emoji_info = new Map();
export function server_update_status(opts) {
channel.post({
url: "/json/users/me/status",
data: {
status_text: opts.status_text,
emoji_name: opts.emoji_name,
emoji_code: opts.emoji_code,
reaction_type: opts.reaction_type,
},
success() {
if (opts.success) {
opts.success();
}
},
});
}
export function server_invisible_mode_on() {
channel.patch({
url: "/json/settings",
data: {
presence_enabled: false,
},
});
}
export function server_invisible_mode_off() {
channel.patch({
url: "/json/settings",
data: {
presence_enabled: true,
},
});
}
export function get_status_text(user_id) {
return user_info.get(user_id);
}
export function set_status_text(opts) {
if (!opts.status_text) {
user_info.delete(opts.user_id);
return;
}
user_info.set(opts.user_id, opts.status_text);
}
export function get_status_emoji(user_id) {
return user_status_emoji_info.get(user_id);
}
export function set_status_emoji(opts) {
if (!opts.emoji_name) {
user_status_emoji_info.delete(opts.user_id);
return;
}
user_status_emoji_info.set(opts.user_id, {
emoji_alt_code: user_settings.emojiset === "text",
...emoji.get_emoji_details_for_rendering({
emoji_name: opts.emoji_name,
emoji_code: opts.emoji_code,
reaction_type: opts.reaction_type,
}),
});
}
export function initialize(params) {
user_info.clear();
for (const [str_user_id, dct] of Object.entries(params.user_status)) {
// JSON does not allow integer keys, so we
// convert them here.
const user_id = Number.parseInt(str_user_id, 10);
if (dct.status_text) {
user_info.set(user_id, dct.status_text);
}
if (dct.emoji_name) {
user_status_emoji_info.set(user_id, {
...emoji.get_emoji_details_for_rendering(dct),
});
}
}
}

142
web/src/user_status.ts Normal file
View File

@ -0,0 +1,142 @@
import {z} from "zod";
import * as channel from "./channel";
import * as emoji from "./emoji";
import type {EmojiRenderingDetails} from "./emoji";
import {user_settings} from "./user_settings";
export type UserStatus = z.infer<typeof user_status_schema>;
export type UserStatusEmojiInfo = EmojiRenderingDetails & {
emoji_alt_code?: boolean;
};
export type UserStatusEvent = z.infer<typeof user_status_event_schema>;
const user_status_event_schema = z.object({
id: z.number(),
type: z.literal("user_status"),
user_id: z.number(),
away: z.boolean().optional(),
status_text: z.string(),
emoji_name: z.string(),
emoji_code: z.string(),
reaction_type: z.string(),
});
const user_status_schema = z.union([
z.object({
status_text: z.string().optional(),
emoji_name: z.string(),
emoji_code: z.string(),
reaction_type: z.string(),
away: z.boolean().optional(),
}),
z.object({
emoji_name: z.undefined(),
status_text: z.string().optional(),
away: z.boolean().optional(),
}),
]);
const user_status_param_schema = z.record(z.coerce.number(), user_status_schema);
const user_info = new Map<number, string>();
const user_status_emoji_info = new Map<number, UserStatusEmojiInfo>();
export function server_update_status(opts: {
status_text: string;
emoji_name: string;
emoji_code: string;
reaction_type?: string;
success?: () => void;
}): void {
void channel.post({
url: "/json/users/me/status",
data: {
status_text: opts.status_text,
emoji_name: opts.emoji_name,
emoji_code: opts.emoji_code,
reaction_type: opts.reaction_type,
},
success() {
if (opts.success) {
opts.success();
}
},
});
}
export function server_invisible_mode_on(): void {
void channel.patch({
url: "/json/settings",
data: {
presence_enabled: false,
},
});
}
export function server_invisible_mode_off(): void {
void channel.patch({
url: "/json/settings",
data: {
presence_enabled: true,
},
});
}
export function get_status_text(user_id: number): string | undefined {
return user_info.get(user_id);
}
export function set_status_text(opts: {user_id: number; status_text: string}): void {
if (!opts.status_text) {
user_info.delete(opts.user_id);
return;
}
user_info.set(opts.user_id, opts.status_text);
}
export function get_status_emoji(user_id: number): UserStatusEmojiInfo | undefined {
return user_status_emoji_info.get(user_id);
}
export function set_status_emoji(event: UserStatusEvent): void {
const opts = user_status_event_schema.parse(event);
if (!opts.emoji_name) {
user_status_emoji_info.delete(opts.user_id);
return;
}
user_status_emoji_info.set(opts.user_id, {
emoji_alt_code: user_settings.emojiset === "text",
...emoji.get_emoji_details_for_rendering({
emoji_name: opts.emoji_name,
emoji_code: opts.emoji_code,
reaction_type: opts.reaction_type,
}),
});
}
export function initialize(params: {user_status: object}): void {
user_info.clear();
const user_status = user_status_param_schema.parse(params.user_status);
for (const [str_user_id, dct] of Object.entries(user_status)) {
// JSON does not allow integer keys, so we
// convert them here.
const user_id = Number.parseInt(str_user_id, 10);
if (dct.status_text) {
user_info.set(user_id, dct.status_text);
}
if (dct.emoji_name) {
user_status_emoji_info.set(user_id, {
...emoji.get_emoji_details_for_rendering(dct),
});
}
}
}

View File

@ -489,6 +489,7 @@ test("get_items_for_users", () => {
set_presence(alice.user_id, "offline"); set_presence(alice.user_id, "offline");
user_settings.emojiset = "google"; user_settings.emojiset = "google";
user_settings.user_list_style = 2; user_settings.user_list_style = 2;
const status_emoji_info = { const status_emoji_info = {
emoji_alt_code: false, emoji_alt_code: false,
emoji_name: "car", emoji_name: "car",
@ -496,9 +497,16 @@ test("get_items_for_users", () => {
reaction_type: "unicode_emoji", reaction_type: "unicode_emoji",
}; };
const status_emoji_info_event = {
id: 1,
type: "user_status",
status_text: "",
...status_emoji_info,
};
const user_ids = [me.user_id, alice.user_id, fred.user_id]; const user_ids = [me.user_id, alice.user_id, fred.user_id];
for (const user_id of user_ids) { for (const user_id of user_ids) {
user_status.set_status_emoji({user_id, ...status_emoji_info}); user_status.set_status_emoji({user_id, ...status_emoji_info_event});
} }
const user_list_style = { const user_list_style = {

View File

@ -1152,9 +1152,10 @@ run_test("user_status", ({override}) => {
{ {
const stub = make_stub(); const stub = make_stub();
override(activity_ui, "redraw_user", stub.f); override(activity_ui, "redraw_user", stub.f);
override(compose_pm_pill, "get_user_ids", () => [event.user_id]);
override(pm_list, "update_private_messages", noop); override(pm_list, "update_private_messages", noop);
dispatch(event); dispatch(event);
assert.equal(stub.num_calls, 1); assert.equal(stub.num_calls, 2);
const args = stub.get_args("user_id"); const args = stub.get_args("user_id");
assert_same(args.user_id, test_user.user_id); assert_same(args.user_id, test_user.user_id);
const emoji_info = user_status.get_status_emoji(test_user.user_id); const emoji_info = user_status.get_status_emoji(test_user.user_id);

View File

@ -1068,11 +1068,13 @@ exports.fixtures = {
}, },
user_status__set_status_emoji: { user_status__set_status_emoji: {
id: 1,
type: "user_status", type: "user_status",
user_id: test_user.user_id, user_id: test_user.user_id,
emoji_name: "smiley", emoji_name: "smiley",
emoji_code: "1f603", emoji_code: "1f603",
reaction_type: "unicode_emoji", reaction_type: "unicode_emoji",
status_text: "",
}, },
user_status__set_status_text: { user_status__set_status_text: {

View File

@ -60,10 +60,13 @@ run_test("basics", () => {
}); });
user_status.set_status_emoji({ user_status.set_status_emoji({
id: 1,
user_id: 5, user_id: 5,
type: "user_status",
emoji_code: "991", emoji_code: "991",
emoji_name: "example_realm_emoji", emoji_name: "example_realm_emoji",
reaction_type: "realm_emoji", reaction_type: "realm_emoji",
status_text: "",
}); });
assert.deepEqual(user_status.get_status_emoji(5), { assert.deepEqual(user_status.get_status_emoji(5), {
@ -78,8 +81,13 @@ run_test("basics", () => {
assert.equal(user_status.get_status_text(1), "in a meeting"); assert.equal(user_status.get_status_text(1), "in a meeting");
user_status.set_status_text({ user_status.set_status_text({
id: 2,
user_id: 2, user_id: 2,
type: "user_status",
status_text: "out to lunch", status_text: "out to lunch",
emoji_name: "",
emoji_code: "",
reaction_type: "",
}); });
assert.equal(user_status.get_status_text(2), "out to lunch"); assert.equal(user_status.get_status_text(2), "out to lunch");
@ -90,10 +98,13 @@ run_test("basics", () => {
assert.equal(user_status.get_status_text(2), undefined); assert.equal(user_status.get_status_text(2), undefined);
user_status.set_status_emoji({ user_status.set_status_emoji({
id: 3,
user_id: 2, user_id: 2,
type: "user_status",
emoji_name: "smiley", emoji_name: "smiley",
emoji_code: "1f603", emoji_code: "1f603",
reaction_type: "unicode_emoji", reaction_type: "unicode_emoji",
status_text: "",
}); });
assert.deepEqual(user_status.get_status_emoji(2), { assert.deepEqual(user_status.get_status_emoji(2), {
emoji_name: "smiley", emoji_name: "smiley",
@ -103,10 +114,13 @@ run_test("basics", () => {
}); });
user_status.set_status_emoji({ user_status.set_status_emoji({
id: 4,
user_id: 2, user_id: 2,
type: "user_status",
emoji_name: "", emoji_name: "",
emoji_code: "", emoji_code: "",
reaction_type: "", reaction_type: "",
status_text: "",
}); });
assert.deepEqual(user_status.get_status_emoji(2), undefined); assert.deepEqual(user_status.get_status_emoji(2), undefined);
}); });
@ -142,23 +156,28 @@ run_test("defensive checks", () => {
assert.throws( assert.throws(
() => () =>
user_status.set_status_emoji({ user_status.set_status_emoji({
id: 1,
status_text: "",
type: "user_status",
user_id: 5, user_id: 5,
emoji_name: "emoji", emoji_name: "emoji",
// no status code or reaction type. // no status code or reaction type.
}), }),
{ {
name: "Error", name: "ZodError",
message: "Invalid params.",
}, },
); );
assert.throws( assert.throws(
() => () =>
user_status.set_status_emoji({ user_status.set_status_emoji({
id: 2,
type: "user_status",
user_id: 5, user_id: 5,
reaction_type: "realm_emoji", reaction_type: "realm_emoji",
emoji_name: "does_not_exist", emoji_name: "does_not_exist",
emoji_code: "fake_code", emoji_code: "fake_code",
status_text: "",
}), }),
{ {
name: "Error", name: "Error",