* Refactoring of DisambiguatedProfile into shared components * correct values and refactoring * Add username color classes to Storybook and clean up DisambiguatedProfile stories * Refactor DisambiguatedProfileView to use class component and enhance props structure * Refactor DisambiguatedProfile components to use member object and enhance props structure * Update copyright year to 2026 and adjust the tests to fit the correct memberinfro interface * Add DisambiguatedProfileViewModel class * Refactor DisambiguatedProfileViewModel to use member object and the rest of the props * Refactor SenderProfile to use DisambiguatedProfileViewModel and update DisambiguatedProfile styles * Refactor DisambiguatedProfileView to enhance interface documentation * Refactor DisambiguatedProfileView to use CSS modules for styling * Updated css + tests to fit the new changes * Update of the test snap to fit the current tests * Adjusted RoomMemberTitleView and SenderProfile to use the new viewmodel, removed the old component. * Implemented new viewmodel test for DisambiguatedProfileViewModel * Update copyright text * update css class names * update to correct snapshot after css name changes. * Apply suggestion from @florianduros Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * Moved logic to viewmodel instead of having it in the view. Removed unessecery functions and css. * removed unessecery file that I copied from root folder, this is no longed needed as I use the root file instead in the viewmodel * Better Formatting * Fix issues after merging develop * FIxed issues with eslint * Added Visible, non-interactive elements with click handlers must have at least one keyboard listener from eslint docs * Updated snapshot the fit the latest update with eslint button requirment * Update snapshot screens for new tests. * Update tests to reflect snapshots * Update snapshot due of outdated CSS module classes * Add useEffect to call setProps on the DisambiguatedProfileViewModel when props change, ensuring the view updates with the correct display name. Update LayoutSwitcher snapshot for new CSS classes. * Fix Playwright editing tests by adding exact match for Edit button selector The DisambiguatedProfile refactoring added role="button" to the component, causing the selector { name: "Edit" } to match both the user "Edith" and the actual Edit button. * Fix ForwardDialog location tests for async hook rendering The SenderProfile component now uses hooks that trigger async state updates. * Fix SenderProfile useEffect to only update changeable props * Added letter spacing * Added ClassName prop * Update snapshot * Update letter-spacing * Update snapshot screenshots * Update Snapshots * Update snapshot * Removal of letter spacing to test CI * Apply suggestion from @florianduros Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * Added closing brackets + added back letter-spacing * Update snapshots * Update snapshot * Update span to correctly apply to the CI tests, it wasn't possible to use classname as a prop * Update snapshot * Added comment to explain the span classNames * DisambiguatedProfileViewModel.setProps to runtime-changing props * replace DisambiguatedProfileViewModel setProps with explicit setters and update call sites * Update Setters * Prettier FIx * Update Setters * update DisambiguatedProfileViewModel setters and tests * Update SenderProfile to show connect display name * clone snapshot in setters to trigger reactive updates * use snapshot.merge in DisambiguatedProfileViewModel setters * emove duplicated logic in DisambiguatedProfileViewModel * Change snapshot name * Update viewmodel * Updated Tests * typo * Update src/viewmodels/profile/DisambiguatedProfileViewModel.ts Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * Removal of unused function * Update snapshots * Update tests to pass coverage * Update Eslint --------- Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
213 lines
7.3 KiB
TypeScript
213 lines
7.3 KiB
TypeScript
/*
|
|
* 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 { composeStories } from "@storybook/react-vite";
|
|
import React from "react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { createEvent, fireEvent } from "@testing-library/dom";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { render, screen } from "@test-utils";
|
|
|
|
import * as stories from "./DisambiguatedProfile.stories";
|
|
import {
|
|
DisambiguatedProfileView,
|
|
type DisambiguatedProfileViewActions,
|
|
type DisambiguatedProfileViewSnapshot,
|
|
} from "./DisambiguatedProfileView";
|
|
import { MockViewModel } from "../../viewmodel/MockViewModel";
|
|
|
|
const { Default, WithMxid, WithColorClass, Emphasized, WithTooltip, FullExample } = composeStories(stories);
|
|
|
|
describe("DisambiguatedProfileView", () => {
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("renders the default state", () => {
|
|
const { container } = render(<Default />);
|
|
expect(container).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders with MXID for disambiguation", () => {
|
|
const { container } = render(<WithMxid />);
|
|
expect(container).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders with color class", () => {
|
|
const { container } = render(<WithColorClass />);
|
|
expect(container).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders with emphasized display name", () => {
|
|
const { container } = render(<Emphasized />);
|
|
expect(container).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders with tooltip", () => {
|
|
const { container } = render(<WithTooltip />);
|
|
expect(container).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders the full example", () => {
|
|
const { container } = render(<FullExample />);
|
|
expect(container).toMatchSnapshot();
|
|
});
|
|
|
|
class DisambiguatedProfileViewModel
|
|
extends MockViewModel<DisambiguatedProfileViewSnapshot>
|
|
implements DisambiguatedProfileViewActions
|
|
{
|
|
public onClick?: DisambiguatedProfileViewActions["onClick"];
|
|
|
|
public constructor(snapshot: DisambiguatedProfileViewSnapshot, actions: DisambiguatedProfileViewActions = {}) {
|
|
super(snapshot);
|
|
this.onClick = actions.onClick;
|
|
}
|
|
}
|
|
|
|
const getProfileContainer = (displayName: string): HTMLDivElement => {
|
|
const profileContainer = screen.getByText(displayName).closest("div");
|
|
if (!profileContainer) {
|
|
throw new Error("Expected profile container to exist");
|
|
}
|
|
return profileContainer;
|
|
};
|
|
|
|
it("should display the display name", () => {
|
|
const vm = new DisambiguatedProfileViewModel({
|
|
displayName: "Eve",
|
|
});
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
expect(screen.getByText("Eve")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should display the MXID when provided", () => {
|
|
const vm = new DisambiguatedProfileViewModel({
|
|
displayName: "Test User",
|
|
displayIdentifier: "@test:example.org",
|
|
});
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
expect(screen.getByText("@test:example.org")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should call onClick when clicked", async () => {
|
|
const onClick = vi.fn();
|
|
const user = userEvent.setup();
|
|
const vm = new DisambiguatedProfileViewModel(
|
|
{
|
|
displayName: "Clickable User",
|
|
},
|
|
{ onClick },
|
|
);
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
await user.click(screen.getByText("Clickable User"));
|
|
expect(onClick).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should set button semantics when onClick is provided", () => {
|
|
const vm = new DisambiguatedProfileViewModel(
|
|
{
|
|
displayName: "Keyboard User",
|
|
},
|
|
{ onClick: vi.fn() },
|
|
);
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
const profileContainer = getProfileContainer("Keyboard User");
|
|
expect(profileContainer).toHaveAttribute("role", "button");
|
|
expect(profileContainer).toHaveAttribute("tabIndex", "0");
|
|
});
|
|
|
|
it("should call onClick on keyboard activation keys", () => {
|
|
const onClick = vi.fn();
|
|
const vm = new DisambiguatedProfileViewModel(
|
|
{
|
|
displayName: "Keyboard User",
|
|
},
|
|
{ onClick },
|
|
);
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
const profileContainer = getProfileContainer("Keyboard User");
|
|
|
|
const enterEvent = createEvent.keyDown(profileContainer, { key: "Enter" });
|
|
fireEvent(profileContainer, enterEvent);
|
|
|
|
const spaceEvent = createEvent.keyDown(profileContainer, { key: " " });
|
|
fireEvent(profileContainer, spaceEvent);
|
|
|
|
expect(enterEvent.defaultPrevented).toBe(true);
|
|
expect(spaceEvent.defaultPrevented).toBe(true);
|
|
expect(onClick).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("should not call onClick for non-activation keys", () => {
|
|
const onClick = vi.fn();
|
|
const vm = new DisambiguatedProfileViewModel(
|
|
{
|
|
displayName: "Keyboard User",
|
|
},
|
|
{ onClick },
|
|
);
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
const profileContainer = getProfileContainer("Keyboard User");
|
|
|
|
fireEvent.keyDown(profileContainer, { key: "Escape" });
|
|
expect(onClick).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should not set button semantics when onClick is not provided", () => {
|
|
const vm = new DisambiguatedProfileViewModel({
|
|
displayName: "Static User",
|
|
});
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
const profileContainer = getProfileContainer("Static User");
|
|
expect(profileContainer).not.toHaveAttribute("role");
|
|
expect(profileContainer).not.toHaveAttribute("tabIndex");
|
|
});
|
|
|
|
it("should display tooltip title when provided", () => {
|
|
const vm = new DisambiguatedProfileViewModel({
|
|
displayName: "User With Tooltip",
|
|
title: "User With Tooltip (@user:example.org)",
|
|
});
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
expect(screen.getByText("User With Tooltip").closest("div")).toHaveAttribute(
|
|
"title",
|
|
"User With Tooltip (@user:example.org)",
|
|
);
|
|
});
|
|
|
|
it("should apply color class when provided", () => {
|
|
const vm = new DisambiguatedProfileViewModel({
|
|
displayName: "Colored User",
|
|
colorClass: "mx_Username_color3",
|
|
});
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
const displayNameElement = screen.getByText("Colored User");
|
|
expect(displayNameElement).toHaveClass("mx_Username_color3");
|
|
});
|
|
|
|
it("should apply emphasis styling when emphasizeDisplayName is true", () => {
|
|
const vm = new DisambiguatedProfileViewModel({
|
|
displayName: "Emphasized User",
|
|
emphasizeDisplayName: true,
|
|
});
|
|
|
|
render(<DisambiguatedProfileView vm={vm} />);
|
|
const displayNameElement = screen.getByText("Emphasized User");
|
|
expect(displayNameElement).toHaveClass("mx_DisambiguatedProfile_displayName");
|
|
});
|
|
});
|