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 { _t } from "../languageHandler";
|
||||||
import { mediaFromMxc } from "../customisations/Media";
|
import { mediaFromMxc } from "../customisations/Media";
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
import { userStatusFromProfile } from "../utils/userStatus";
|
import { fetchUserStatus } from "../utils/userStatus";
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
@@ -202,12 +202,8 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
|
|||||||
if (!this.matrixClient) return;
|
if (!this.matrixClient) return;
|
||||||
if (!SettingsStore.getValue("feature_user_status")) return;
|
if (!SettingsStore.getValue("feature_user_status")) return;
|
||||||
|
|
||||||
const rawUserStatus = await this.matrixClient.getExtendedProfileProperty(
|
const userStatus = await fetchUserStatus(this.matrixClient, this.matrixClient.getSafeUserId());
|
||||||
this.matrixClient.getSafeUserId(),
|
await this.updateState({ userStatus });
|
||||||
"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) });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onStateEvents = async (ev: MatrixEvent): Promise<void> => {
|
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 { stubClient } from "test-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clearUserStatus,
|
clearAllUserStatus,
|
||||||
fetchUserStatus,
|
fetchUserStatus,
|
||||||
setUserOnCall,
|
setUserOnCall,
|
||||||
setUserStatus,
|
setUserStatus,
|
||||||
@@ -143,7 +143,7 @@ describe("userStatus utils", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("clearUserStatus", () => {
|
describe("clearAllUserStatus", () => {
|
||||||
let client: MatrixClient;
|
let client: MatrixClient;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -151,10 +151,20 @@ describe("userStatus utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("clears the user status", async () => {
|
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);
|
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", () => {
|
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.
|
* @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> {
|
export async function clearAllUserStatus(client: MatrixClient): Promise<void> {
|
||||||
return client.setExtendedProfileProperty("org.matrix.msc4426.status", null);
|
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 { getHomePageUrl } from "../../utils/pages";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
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 { type SetStatusViewModel, UserMenuSetStatusViewModel } from "../status/SetStatusViewModel";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ export class UserMenuViewModel
|
|||||||
|
|
||||||
public readonly clearStatus = (): void => {
|
public readonly clearStatus = (): void => {
|
||||||
this.setOpen(false);
|
this.setOpen(false);
|
||||||
clearUserStatus(this.client).catch((err) => {
|
clearAllUserStatus(this.client).catch((err) => {
|
||||||
logger.warn("Failed to clear user status", err);
|
logger.warn("Failed to clear user status", err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ describe("SetStatusViewModel", () => {
|
|||||||
client = getMockClientWithEventEmitter({
|
client = getMockClientWithEventEmitter({
|
||||||
...mockClientMethodsUser(),
|
...mockClientMethodsUser(),
|
||||||
...mockClientMethodsServer(),
|
...mockClientMethodsServer(),
|
||||||
|
getExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
||||||
setExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
setExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
||||||
});
|
});
|
||||||
vi.mocked(mockOwnProfileStoreInstance).userStatus = undefined;
|
vi.mocked(mockOwnProfileStoreInstance).userStatus = undefined;
|
||||||
@@ -127,6 +128,7 @@ describe("SetStatusViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("calls setExtendedProfileProperty with null", async () => {
|
it("calls setExtendedProfileProperty with null", async () => {
|
||||||
|
client.getExtendedProfileProperty.mockResolvedValue(STATUS);
|
||||||
const vm = new SetStatusViewModel({ client, ownProfileStore: mockOwnProfileStoreInstance });
|
const vm = new SetStatusViewModel({ client, ownProfileStore: mockOwnProfileStoreInstance });
|
||||||
vm.clearStatus();
|
vm.clearStatus();
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
@@ -145,6 +147,7 @@ describe("SetStatusViewModel", () => {
|
|||||||
|
|
||||||
it("rolls back the snapshot on failure", async () => {
|
it("rolls back the snapshot on failure", async () => {
|
||||||
vi.mocked(mockOwnProfileStoreInstance).userStatus = STATUS;
|
vi.mocked(mockOwnProfileStoreInstance).userStatus = STATUS;
|
||||||
|
client.getExtendedProfileProperty.mockResolvedValue(STATUS);
|
||||||
client.setExtendedProfileProperty.mockRejectedValue(new Error("network error"));
|
client.setExtendedProfileProperty.mockRejectedValue(new Error("network error"));
|
||||||
const vm = new SetStatusViewModel({ client, ownProfileStore: mockOwnProfileStoreInstance });
|
const vm = new SetStatusViewModel({ client, ownProfileStore: mockOwnProfileStoreInstance });
|
||||||
vm.clearStatus();
|
vm.clearStatus();
|
||||||
@@ -169,6 +172,7 @@ describe("UserMenuSetStatusViewModel", () => {
|
|||||||
client = getMockClientWithEventEmitter({
|
client = getMockClientWithEventEmitter({
|
||||||
...mockClientMethodsUser(),
|
...mockClientMethodsUser(),
|
||||||
...mockClientMethodsServer(),
|
...mockClientMethodsServer(),
|
||||||
|
getExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
||||||
setExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
setExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
||||||
});
|
});
|
||||||
vi.mocked(mockOwnProfileStoreInstance).userStatus = undefined;
|
vi.mocked(mockOwnProfileStoreInstance).userStatus = undefined;
|
||||||
@@ -203,6 +207,7 @@ describe("UserMenuSetStatusViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("inherits clearStatus from SetStatusViewModel", async () => {
|
it("inherits clearStatus from SetStatusViewModel", async () => {
|
||||||
|
client.getExtendedProfileProperty.mockResolvedValue(STATUS);
|
||||||
const vm = new UserMenuSetStatusViewModel({ client, ownProfileStore: mockOwnProfileStoreInstance });
|
const vm = new UserMenuSetStatusViewModel({ client, ownProfileStore: mockOwnProfileStoreInstance });
|
||||||
vm.clearStatus();
|
vm.clearStatus();
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
type UserStatus,
|
type UserStatus,
|
||||||
} from "@element-hq/web-shared-components";
|
} 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 { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import { UserTab } from "../../components/views/dialogs/UserTab";
|
import { UserTab } from "../../components/views/dialogs/UserTab";
|
||||||
@@ -59,7 +59,7 @@ export class SetStatusViewModel
|
|||||||
const oldStatus = this.snapshot.current.userStatus;
|
const oldStatus = this.snapshot.current.userStatus;
|
||||||
|
|
||||||
this.snapshot.merge({ userStatus: undefined });
|
this.snapshot.merge({ userStatus: undefined });
|
||||||
clearUserStatus(this.props.client).catch((err) => {
|
clearAllUserStatus(this.props.client).catch((err) => {
|
||||||
this.snapshot.merge({ userStatus: oldStatus });
|
this.snapshot.merge({ userStatus: oldStatus });
|
||||||
logger.warn("Failed to clear user status", err);
|
logger.warn("Failed to clear user status", err);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -110,6 +110,20 @@ describe("OwnProfileStore", () => {
|
|||||||
expect(ownProfileStore.userStatus).toEqual({ emoji: "🏝️", text: "On a tropical holiday" });
|
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 () => {
|
it("should update the user status when a UserProfileUpdate event is received for the current user", async () => {
|
||||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
||||||
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🏝️", text: "On a tropical holiday" });
|
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🏝️", text: "On a tropical holiday" });
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ describe("UserMenuViewModel", () => {
|
|||||||
...mockClientMethodsUser(),
|
...mockClientMethodsUser(),
|
||||||
...mockClientMethodsServer(),
|
...mockClientMethodsServer(),
|
||||||
getAuthMetadata: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_UNRECOGNIZED" }, 404)),
|
getAuthMetadata: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_UNRECOGNIZED" }, 404)),
|
||||||
|
getExtendedProfileProperty: jest.fn().mockResolvedValue(undefined),
|
||||||
setExtendedProfileProperty: jest.fn().mockResolvedValue(undefined),
|
setExtendedProfileProperty: jest.fn().mockResolvedValue(undefined),
|
||||||
});
|
});
|
||||||
sdkContext = new TestSDKContext();
|
sdkContext = new TestSDKContext();
|
||||||
@@ -178,6 +179,7 @@ describe("UserMenuViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("can clear a user status", async () => {
|
it("can clear a user status", async () => {
|
||||||
|
client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🧪", text: "Testing" });
|
||||||
const vm = new UserMenuViewModel({ ownProfileStore: mockOwnProfileStore }, dispatcher, client, true);
|
const vm = new UserMenuViewModel({ ownProfileStore: mockOwnProfileStore }, dispatcher, client, true);
|
||||||
vm.setOpen(true);
|
vm.setOpen(true);
|
||||||
vm.clearStatus();
|
vm.clearStatus();
|
||||||
|
|||||||
Reference in New Issue
Block a user