Show & clear your own on-a-call status (#34613)
* Show & clear your own on-a-call status The user menu / settings now reflects your own on-a-call status and pressing the 'clear' button will clear both m.status and m.call, whichever are set. * Update function name * Fix tests And make the clear status function simpler by just throwing if either fails. * Fix more tests
This commit is contained in:
@@ -25,7 +25,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import { _t } from "../languageHandler";
|
||||
import { mediaFromMxc } from "../customisations/Media";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { userStatusFromProfile } from "../utils/userStatus";
|
||||
import { fetchUserStatus } from "../utils/userStatus";
|
||||
|
||||
interface IState {
|
||||
displayName?: string;
|
||||
@@ -202,12 +202,8 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
|
||||
if (!this.matrixClient) return;
|
||||
if (!SettingsStore.getValue("feature_user_status")) return;
|
||||
|
||||
const rawUserStatus = await this.matrixClient.getExtendedProfileProperty(
|
||||
this.matrixClient.getSafeUserId(),
|
||||
"org.matrix.msc4426.status",
|
||||
);
|
||||
// We don't show our own "on a call" status so we pass undefined for the call status.
|
||||
await this.updateState({ userStatus: userStatusFromProfile(rawUserStatus, undefined) });
|
||||
const userStatus = await fetchUserStatus(this.matrixClient, this.matrixClient.getSafeUserId());
|
||||
await this.updateState({ userStatus });
|
||||
};
|
||||
|
||||
private onStateEvents = async (ev: MatrixEvent): Promise<void> => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import { stubClient } from "test-utils";
|
||||
|
||||
import {
|
||||
clearUserStatus,
|
||||
clearAllUserStatus,
|
||||
fetchUserStatus,
|
||||
setUserOnCall,
|
||||
setUserStatus,
|
||||
@@ -143,7 +143,7 @@ describe("userStatus utils", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearUserStatus", () => {
|
||||
describe("clearAllUserStatus", () => {
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -151,10 +151,20 @@ describe("userStatus utils", () => {
|
||||
});
|
||||
|
||||
it("clears the user status", async () => {
|
||||
clearUserStatus(client);
|
||||
vi.mocked(client.getExtendedProfileProperty).mockResolvedValue({ emoji: "🍩", text: "Arbitrary Status" });
|
||||
|
||||
await clearAllUserStatus(client);
|
||||
|
||||
expect(client.setExtendedProfileProperty).toHaveBeenCalledWith("org.matrix.msc4426.status", null);
|
||||
});
|
||||
|
||||
it("clears the call status", async () => {
|
||||
vi.mocked(client.getExtendedProfileProperty).mockResolvedValue({ call_joined_ts: 12345 });
|
||||
|
||||
await clearAllUserStatus(client);
|
||||
|
||||
expect(client.setExtendedProfileProperty).toHaveBeenCalledWith("org.matrix.msc4426.call", null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setUserOnCall", () => {
|
||||
|
||||
@@ -139,12 +139,22 @@ export function setUserStatus(client: MatrixClient, userStatus: UserStatus): Pro
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the MSC4426 user status for the given user.
|
||||
* Clears all MSC4426 user status for the given user, including their m.status and m.call status,
|
||||
* if anything is set in those fields.
|
||||
*
|
||||
* @param client The Matrix client to use.
|
||||
* @throws If either request fails, in which case the other may also not have been cleared.
|
||||
*/
|
||||
export function clearUserStatus(client: MatrixClient): Promise<void> {
|
||||
return client.setExtendedProfileProperty("org.matrix.msc4426.status", null);
|
||||
export async function clearAllUserStatus(client: MatrixClient): Promise<void> {
|
||||
const rawUserStatus = await client.getExtendedProfileProperty(client.getSafeUserId(), "org.matrix.msc4426.status");
|
||||
if (rawUserStatus) {
|
||||
await client.setExtendedProfileProperty("org.matrix.msc4426.status", null);
|
||||
}
|
||||
|
||||
const rawCallStatus = await client.getExtendedProfileProperty(client.getSafeUserId(), "org.matrix.msc4426.call");
|
||||
if (rawCallStatus) {
|
||||
await setUserOnCall(client, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ import { shouldShowFeedback } from "../../utils/Feedback";
|
||||
import { getHomePageUrl } from "../../utils/pages";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { clearUserStatus } from "../../utils/userStatus";
|
||||
import { clearAllUserStatus } from "../../utils/userStatus";
|
||||
import { type SetStatusViewModel, UserMenuSetStatusViewModel } from "../status/SetStatusViewModel";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
|
||||
@@ -153,7 +153,7 @@ export class UserMenuViewModel
|
||||
|
||||
public readonly clearStatus = (): void => {
|
||||
this.setOpen(false);
|
||||
clearUserStatus(this.client).catch((err) => {
|
||||
clearAllUserStatus(this.client).catch((err) => {
|
||||
logger.warn("Failed to clear user status", err);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ describe("SetStatusViewModel", () => {
|
||||
client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(),
|
||||
...mockClientMethodsServer(),
|
||||
getExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
||||
setExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
||||
});
|
||||
vi.mocked(mockOwnProfileStoreInstance).userStatus = undefined;
|
||||
@@ -127,6 +128,7 @@ describe("SetStatusViewModel", () => {
|
||||
});
|
||||
|
||||
it("calls setExtendedProfileProperty with null", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue(STATUS);
|
||||
const vm = new SetStatusViewModel({ client, ownProfileStore: mockOwnProfileStoreInstance });
|
||||
vm.clearStatus();
|
||||
await waitFor(() =>
|
||||
@@ -145,6 +147,7 @@ describe("SetStatusViewModel", () => {
|
||||
|
||||
it("rolls back the snapshot on failure", async () => {
|
||||
vi.mocked(mockOwnProfileStoreInstance).userStatus = STATUS;
|
||||
client.getExtendedProfileProperty.mockResolvedValue(STATUS);
|
||||
client.setExtendedProfileProperty.mockRejectedValue(new Error("network error"));
|
||||
const vm = new SetStatusViewModel({ client, ownProfileStore: mockOwnProfileStoreInstance });
|
||||
vm.clearStatus();
|
||||
@@ -169,6 +172,7 @@ describe("UserMenuSetStatusViewModel", () => {
|
||||
client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(),
|
||||
...mockClientMethodsServer(),
|
||||
getExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
||||
setExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
||||
});
|
||||
vi.mocked(mockOwnProfileStoreInstance).userStatus = undefined;
|
||||
@@ -203,6 +207,7 @@ describe("UserMenuSetStatusViewModel", () => {
|
||||
});
|
||||
|
||||
it("inherits clearStatus from SetStatusViewModel", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue(STATUS);
|
||||
const vm = new UserMenuSetStatusViewModel({ client, ownProfileStore: mockOwnProfileStoreInstance });
|
||||
vm.clearStatus();
|
||||
await waitFor(() =>
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
type UserStatus,
|
||||
} from "@element-hq/web-shared-components";
|
||||
|
||||
import { clearUserStatus, setUserStatus } from "../../utils/userStatus";
|
||||
import { clearAllUserStatus, setUserStatus } from "../../utils/userStatus";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { UserTab } from "../../components/views/dialogs/UserTab";
|
||||
@@ -59,7 +59,7 @@ export class SetStatusViewModel
|
||||
const oldStatus = this.snapshot.current.userStatus;
|
||||
|
||||
this.snapshot.merge({ userStatus: undefined });
|
||||
clearUserStatus(this.props.client).catch((err) => {
|
||||
clearAllUserStatus(this.props.client).catch((err) => {
|
||||
this.snapshot.merge({ userStatus: oldStatus });
|
||||
logger.warn("Failed to clear user status", err);
|
||||
});
|
||||
|
||||
@@ -110,6 +110,20 @@ describe("OwnProfileStore", () => {
|
||||
expect(ownProfileStore.userStatus).toEqual({ emoji: "🏝️", text: "On a tropical holiday" });
|
||||
});
|
||||
|
||||
it("should reflect our own on-a-call status if no other status is set", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
||||
client.getExtendedProfileProperty.mockImplementation(async (_userId, key) =>
|
||||
key === "org.matrix.msc4426.call" ? { call_joined_ts: 12345 } : undefined,
|
||||
);
|
||||
await ownProfileStore.start();
|
||||
|
||||
expect(client.getExtendedProfileProperty).toHaveBeenCalledWith(
|
||||
client.getSafeUserId(),
|
||||
"org.matrix.msc4426.call",
|
||||
);
|
||||
expect(ownProfileStore.userStatus).toEqual({ emoji: "📞", text: "On a call" });
|
||||
});
|
||||
|
||||
it("should update the user status when a UserProfileUpdate event is received for the current user", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
||||
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🏝️", text: "On a tropical holiday" });
|
||||
|
||||
@@ -32,6 +32,7 @@ describe("UserMenuViewModel", () => {
|
||||
...mockClientMethodsUser(),
|
||||
...mockClientMethodsServer(),
|
||||
getAuthMetadata: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_UNRECOGNIZED" }, 404)),
|
||||
getExtendedProfileProperty: jest.fn().mockResolvedValue(undefined),
|
||||
setExtendedProfileProperty: jest.fn().mockResolvedValue(undefined),
|
||||
});
|
||||
sdkContext = new TestSDKContext();
|
||||
@@ -178,6 +179,7 @@ describe("UserMenuViewModel", () => {
|
||||
});
|
||||
|
||||
it("can clear a user status", async () => {
|
||||
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🧪", text: "Testing" });
|
||||
const vm = new UserMenuViewModel({ ownProfileStore: mockOwnProfileStore }, dispatcher, client, true);
|
||||
vm.setOpen(true);
|
||||
vm.clearStatus();
|
||||
|
||||
Reference in New Issue
Block a user