User status in user menu (#33797)

* Add user status on user profile icon

Currently unstyled & no tests

* Style the user status icon

* Update snapshot

for avatar wrapper

* More snapshot updates

* add if braces

* Split out user status functions

to avoid circular dep which has the weird effect of just breaking
jest's mocking.

* type imports

* Update imports

* Update snapshot

* Tests

* baseline image

* Just snapshot the component itself

* User status in user menu

first pass, unstyled

* fix export

* superstylin'

* snapshots

* Add story & test

* Snapshots

and re-use the repeated rules

* Tests

* Fix jest lcov projectRoot config

* Change tooltip text to just 'clear'

* Add comment & fix console warn

* Remove all options screenshot

as it's not really particularly useful as a preset story

* remove stale screenshot

* fix imports

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
David Baker
2026-06-16 09:01:34 +00:00
committed by GitHub
co-authored by Michael Telatynski
parent 44c540eca0
commit 3ab233940c
23 changed files with 293 additions and 74 deletions
@@ -16,7 +16,6 @@ describe("/status", () => {
beforeEach(() => {
client = stubClient();
client.setExtendedProfileProperty = jest.fn().mockResolvedValue(undefined);
});
function run(args?: string) {
+1
View File
@@ -360,6 +360,7 @@ export function createTestClient(): MatrixClient {
deleteRoomTag: jest.fn().mockResolvedValue({}),
setRoomTag: jest.fn().mockResolvedValue({}),
getExtendedProfileProperty: jest.fn(),
setExtendedProfileProperty: jest.fn().mockResolvedValue(undefined),
} as unknown as MatrixClient;
client.reEmitter = new ReEmitter(client);
@@ -7,19 +7,19 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
class="mx_SpacePanel collapsed newUi"
>
<div
class="_wrapper_11z86_8 mx_UserMenu"
class="_wrapper_bh5eg_8 mx_UserMenu"
>
<button
aria-expanded="false"
aria-haspopup="menu"
aria-label="User menu"
class="_triggerButton_11z86_80"
class="_triggerButton_bh5eg_107"
data-state="closed"
id="radix-react-use-id-1"
type="button"
>
<div
class="_avatarWrapper_11z86_57"
class="_avatarWrapper_bh5eg_57"
>
<span
aria-label="@test:test"
@@ -0,0 +1,55 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { clearUserStatus, setUserStatus, userStatusTextWithinMaxLength } from "../../../src/utils/userStatus";
import { stubClient } from "../../test-utils";
describe("userStatus utils", () => {
describe("userStatusTextWithinMaxLength", () => {
it("returns true for text within the max length", () => {
const text = "a".repeat(256);
expect(userStatusTextWithinMaxLength(text)).toBe(true);
});
it("returns false for text exceeding the max length", () => {
const text = "a".repeat(257);
expect(userStatusTextWithinMaxLength(text)).toBe(false);
});
});
describe("setUserStatus", () => {
let client: MatrixClient;
beforeEach(() => {
client = stubClient();
});
it("sets the user status with valid input", async () => {
setUserStatus(client, { emoji: "🐳", text: "Feeling a little blue" });
expect(client.setExtendedProfileProperty).toHaveBeenCalledWith("org.matrix.msc4426.status", {
emoji: "🐳",
text: "Feeling a little blue",
});
});
});
describe("clearUserStatus", () => {
let client: MatrixClient;
beforeEach(() => {
client = stubClient();
});
it("clears the user status", async () => {
clearUserStatus(client);
expect(client.setExtendedProfileProperty).toHaveBeenCalledWith("org.matrix.msc4426.status", null);
});
});
});
@@ -27,6 +27,7 @@ describe("UserMenuViewModel", () => {
...mockClientMethodsUser(),
...mockClientMethodsServer(),
getAuthMetadata: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_UNRECOGNIZED" }, 404)),
setExtendedProfileProperty: jest.fn().mockResolvedValue(undefined),
});
SdkContextClass.instance.client = client;
});
@@ -153,6 +154,15 @@ describe("UserMenuViewModel", () => {
);
});
it("can clear a user status", async () => {
const vm = new UserMenuViewModel(dispatcher, client, true);
vm.setOpen(true);
vm.clearStatus();
await waitFor(() =>
expect(client.setExtendedProfileProperty).toHaveBeenCalledWith("org.matrix.msc4426.status", null),
);
});
it("should be able to open the createAccount screen as a guest", async () => {
client.isGuest.mockReturnValue(true);
const dispatcherSpy = jest.fn();
@@ -17,8 +17,8 @@ exports[`UserMenuViewModel should generate a menu options for a guest 1`] = `
"manageAccountHref": undefined,
"open": true,
"showAvatar": false,
"statusEmoji": undefined,
"userId": "@alice:domain",
"userStatus": undefined,
}
`;
@@ -39,7 +39,7 @@ exports[`UserMenuViewModel should generate a menu options for a logged in client
"manageAccountHref": undefined,
"open": true,
"showAvatar": true,
"statusEmoji": undefined,
"userId": "@alice:domain",
"userStatus": undefined,
}
`;