Add new verification section to user profile (#29200)
* Create new verification section * Remove old code and use new VerificationSection * Add styling and translation * Fix tests * Remove dead code * Fix broken test * Remove imports * Remove console.log * Update snapshots * Fix broken tests * Fix lint * Make badge expand with content * Remove unused code
This commit is contained in:
@@ -16,15 +16,15 @@ import { _t } from "../../../../src/languageHandler";
|
||||
describe("<TabbedView />", () => {
|
||||
const generalTab = new Tab("GENERAL", "common|general", "general", <div>general</div>);
|
||||
const labsTab = new Tab("LABS", "common|labs", "labs", <div>labs</div>);
|
||||
const securityTab = new Tab("SECURITY", "common|security", "security", <div>security</div>);
|
||||
const appearanceTab = new Tab("APPEARANCE", "common|appearance", "appearance", <div>appearance</div>);
|
||||
const defaultProps = {
|
||||
tabLocation: TabLocation.LEFT,
|
||||
tabs: [generalTab, labsTab, securityTab] as NonEmptyArray<Tab<any>>,
|
||||
tabs: [generalTab, labsTab, appearanceTab] as NonEmptyArray<Tab<any>>,
|
||||
onChange: () => {},
|
||||
};
|
||||
const getComponent = (
|
||||
props: {
|
||||
activeTabId: "GENERAL" | "LABS" | "SECURITY";
|
||||
activeTabId: "GENERAL" | "LABS" | "APPEARANCE";
|
||||
onChange?: () => any;
|
||||
tabs?: NonEmptyArray<Tab<any>>;
|
||||
} = {
|
||||
@@ -44,9 +44,9 @@ describe("<TabbedView />", () => {
|
||||
});
|
||||
|
||||
it("renders activeTabId tab as active when valid", () => {
|
||||
const { container } = render(getComponent({ activeTabId: securityTab.id }));
|
||||
expect(getActiveTab(container)?.textContent).toEqual(_t(securityTab.label));
|
||||
expect(getActiveTabBody(container)?.textContent).toEqual("security");
|
||||
const { container } = render(getComponent({ activeTabId: appearanceTab.id }));
|
||||
expect(getActiveTab(container)?.textContent).toEqual(_t(appearanceTab.label));
|
||||
expect(getActiveTabBody(container)?.textContent).toEqual("appearance");
|
||||
});
|
||||
|
||||
it("calls onchange on on tab click", () => {
|
||||
@@ -54,10 +54,10 @@ describe("<TabbedView />", () => {
|
||||
const { getByTestId } = render(getComponent({ activeTabId: "GENERAL", onChange }));
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(getByTestId(getTabTestId(securityTab)));
|
||||
fireEvent.click(getByTestId(getTabTestId(appearanceTab)));
|
||||
});
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(securityTab.id);
|
||||
expect(onChange).toHaveBeenCalledWith(appearanceTab.id);
|
||||
});
|
||||
|
||||
it("keeps same tab active when order of tabs changes", () => {
|
||||
@@ -66,7 +66,7 @@ describe("<TabbedView />", () => {
|
||||
|
||||
expect(getActiveTab(container)?.textContent).toEqual(_t(labsTab.label));
|
||||
|
||||
rerender(getComponent({ tabs: [labsTab, generalTab, securityTab], activeTabId: labsTab.id }));
|
||||
rerender(getComponent({ tabs: [labsTab, generalTab, appearanceTab], activeTabId: labsTab.id }));
|
||||
|
||||
// labs tab still active
|
||||
expect(getActiveTab(container)?.textContent).toEqual(_t(labsTab.label));
|
||||
|
||||
@@ -47,21 +47,21 @@ exports[`<TabbedView /> renders tabs 1`] = `
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
aria-controls="mx_tabpanel_SECURITY"
|
||||
aria-controls="mx_tabpanel_APPEARANCE"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-SECURITY"
|
||||
data-testid="settings-tab-APPEARANCE"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="mx_TabbedView_maskedIcon security"
|
||||
class="mx_TabbedView_maskedIcon appearance"
|
||||
/>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_SECURITY_label"
|
||||
id="mx_tabpanel_APPEARANCE_label"
|
||||
>
|
||||
Security
|
||||
Appearance
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen, cleanup, act, within, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import { fireEvent, render, screen, cleanup, act, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { type Mocked, mocked } from "jest-mock";
|
||||
import {
|
||||
@@ -28,12 +28,10 @@ import {
|
||||
VerificationPhase as Phase,
|
||||
VerificationRequestEvent,
|
||||
type CryptoApi,
|
||||
type DeviceVerificationStatus,
|
||||
} from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import UserInfo, {
|
||||
BanToggleButton,
|
||||
DeviceItem,
|
||||
disambiguateDevices,
|
||||
getPowerLevels,
|
||||
isMuted,
|
||||
@@ -48,9 +46,7 @@ import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPan
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import MultiInviter from "../../../../../src/utils/MultiInviter";
|
||||
import * as mockVerification from "../../../../../src/verification";
|
||||
import Modal from "../../../../../src/Modal";
|
||||
import { E2EStatus } from "../../../../../src/utils/ShieldUtils";
|
||||
import { DirectoryMember, startDmOnFirstMessage } from "../../../../../src/utils/direct-messages";
|
||||
import { clearAllModals, flushPromises } from "../../../../test-utils";
|
||||
import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog";
|
||||
@@ -445,20 +441,6 @@ describe("<UserInfo />", () => {
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
});
|
||||
|
||||
it("renders a device list which can be expanded", async () => {
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text
|
||||
const devicesButton = screen.getByRole("button", { name: "1 session" });
|
||||
|
||||
// click it
|
||||
await userEvent.click(devicesButton);
|
||||
|
||||
// there should now be a button with the device id which should contain the device name
|
||||
expect(screen.getByRole("button", { name: "my device" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders <BasicUserInfo />", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
|
||||
@@ -468,190 +450,9 @@ describe("<UserInfo />", () => {
|
||||
room: mockRoom,
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
await expect(screen.findByRole("button", { name: "Verify" })).resolves.toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("device dehydration", () => {
|
||||
it("hides a verified dehydrated device (unverified user)", async () => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
const device2 = new Device({
|
||||
deviceId: "d2",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[device1.deviceId, device1],
|
||||
[device2.deviceId, device2],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "1 session" });
|
||||
|
||||
// click it
|
||||
await act(() => {
|
||||
return userEvent.click(devicesButton);
|
||||
});
|
||||
|
||||
// there should now be a button with the non-dehydrated device ID
|
||||
expect(screen.getByRole("button", { name: "my device" })).toBeInTheDocument();
|
||||
|
||||
// but not for the dehydrated device ID
|
||||
expect(screen.queryByRole("button", { name: "dehydrated device" })).not.toBeInTheDocument();
|
||||
|
||||
// there should be a line saying that the user has "Offline device" enabled
|
||||
expect(screen.getByText("Offline device enabled")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides a verified dehydrated device (verified user)", async () => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
const device2 = new Device({
|
||||
deviceId: "d2",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[device1.deviceId, device1],
|
||||
[device2.deviceId, device2],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, true));
|
||||
mockCrypto.getDeviceVerificationStatus.mockResolvedValue({
|
||||
isVerified: () => true,
|
||||
} as DeviceVerificationStatus);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "1 verified session" });
|
||||
|
||||
// click it
|
||||
await act(() => {
|
||||
return userEvent.click(devicesButton);
|
||||
});
|
||||
|
||||
// there should now be a button with the non-dehydrated device ID
|
||||
expect(screen.getByTitle("d1")).toBeInTheDocument();
|
||||
|
||||
// but not for the dehydrated device ID
|
||||
expect(screen.queryByTitle("d2")).not.toBeInTheDocument();
|
||||
|
||||
// there should be a line saying that the user has "Offline device" enabled
|
||||
expect(screen.getByText("Offline device enabled")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows an unverified dehydrated device", async () => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
const device2 = new Device({
|
||||
deviceId: "d2",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[device1.deviceId, device1],
|
||||
[device2.deviceId, device2],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, true));
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await flushPromises();
|
||||
|
||||
// the dehydrated device should be shown as an unverified device, which means
|
||||
// there should now be a button with the device id ...
|
||||
const deviceButton = screen.getByRole("button", { name: "dehydrated device" });
|
||||
|
||||
// ... which should contain the device name
|
||||
expect(within(deviceButton).getByText("dehydrated device")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows dehydrated devices if there is more than one", async () => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device 1",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const device2 = new Device({
|
||||
deviceId: "d2",
|
||||
userId: defaultUserId,
|
||||
displayName: "dehydrated device 2",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
dehydrated: true,
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([
|
||||
[device1.deviceId, device1],
|
||||
[device2.deviceId, device2],
|
||||
]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "2 sessions" });
|
||||
|
||||
// click it
|
||||
await act(() => {
|
||||
return userEvent.click(devicesButton);
|
||||
});
|
||||
|
||||
// the dehydrated devices should be shown as an unverified device, which means
|
||||
// there should now be a button with the first dehydrated device...
|
||||
const device1Button = screen.getByRole("button", { name: "dehydrated device 1" });
|
||||
expect(device1Button).toBeVisible();
|
||||
|
||||
// ... which should contain the device name
|
||||
expect(within(device1Button).getByText("dehydrated device 1")).toBeInTheDocument();
|
||||
// and a button with the second dehydrated device...
|
||||
const device2Button = screen.getByRole("button", { name: "dehydrated device 2" });
|
||||
expect(device2Button).toBeVisible();
|
||||
|
||||
// ... which should contain the device name
|
||||
expect(within(device2Button).getByText("dehydrated device 2")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render a deactivate button for users of the same server if we are a server admin", async () => {
|
||||
mockClient.isSynapseAdministrator.mockResolvedValue(true);
|
||||
mockClient.getDomain.mockReturnValue("example.com");
|
||||
@@ -668,34 +469,6 @@ describe("<UserInfo />", () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with an encrypted room", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it("renders unverified user info", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
renderComponent({ room: mockRoom });
|
||||
await flushPromises();
|
||||
|
||||
const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
|
||||
|
||||
// there should be a "normal" E2E padlock
|
||||
expect(userHeading.getElementsByClassName("mx_E2EIcon_normal")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renders verified user info", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, false, false));
|
||||
renderComponent({ room: mockRoom });
|
||||
await flushPromises();
|
||||
|
||||
const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
|
||||
|
||||
// there should be a "verified" E2E padlock
|
||||
expect(userHeading.getElementsByClassName("mx_E2EIcon_verified")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("<UserInfoHeader />", () => {
|
||||
@@ -707,179 +480,51 @@ describe("<UserInfoHeader />", () => {
|
||||
};
|
||||
|
||||
const renderComponent = (props = {}) => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([[device1.deviceId, device1]]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
mockClient.doesServerSupportUnstableFeature.mockResolvedValue(true);
|
||||
const Wrapper = (wrapperProps = {}) => {
|
||||
return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
|
||||
};
|
||||
|
||||
return render(<UserInfoHeader {...defaultProps} {...props} />, {
|
||||
return render(<UserInfoHeader {...defaultProps} {...props} devices={[device1]} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
};
|
||||
|
||||
it("does not render an e2e icon in the header if e2eStatus prop is undefined", () => {
|
||||
renderComponent();
|
||||
const header = screen.getByRole("heading", { name: defaultUserId });
|
||||
|
||||
expect(header.getElementsByClassName("mx_E2EIcon")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("renders an e2e icon in the header if e2eStatus prop is defined", () => {
|
||||
renderComponent({ e2eStatus: E2EStatus.Normal });
|
||||
const header = screen.getByRole("heading");
|
||||
|
||||
expect(header.getElementsByClassName("mx_E2EIcon")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renders custom user identifiers in the header", () => {
|
||||
renderComponent();
|
||||
|
||||
expect(screen.getByText("customUserIdentifier")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("<DeviceItem />", () => {
|
||||
const device = { deviceId: "deviceId", displayName: "deviceName" } as Device;
|
||||
const defaultProps = {
|
||||
userId: defaultUserId,
|
||||
device,
|
||||
isUserVerified: false,
|
||||
};
|
||||
|
||||
const renderComponent = (props = {}) => {
|
||||
const Wrapper = (wrapperProps = {}) => {
|
||||
return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
|
||||
};
|
||||
|
||||
return render(<DeviceItem {...defaultProps} {...props} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
};
|
||||
|
||||
const setMockDeviceTrust = (isVerified = false, isCrossSigningVerified = false) => {
|
||||
mockCrypto.getDeviceVerificationStatus.mockResolvedValue({
|
||||
isVerified: () => isVerified,
|
||||
crossSigningVerified: isCrossSigningVerified,
|
||||
} as DeviceVerificationStatus);
|
||||
};
|
||||
|
||||
const mockVerifyDevice = jest.spyOn(mockVerification, "verifyDevice");
|
||||
|
||||
beforeEach(() => {
|
||||
setMockDeviceTrust();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockCrypto.getDeviceVerificationStatus.mockReset();
|
||||
mockVerifyDevice.mockClear();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockVerifyDevice.mockRestore();
|
||||
});
|
||||
|
||||
it("with unverified user and device, displays button without a label", async () => {
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByRole("button", { name: device.displayName! })).toBeInTheDocument();
|
||||
expect(screen.queryByText(/trusted/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("with verified user only, displays button with a 'Not trusted' label", async () => {
|
||||
renderComponent({ isUserVerified: true });
|
||||
await flushPromises();
|
||||
|
||||
const button = screen.getByRole("button", { name: device.displayName });
|
||||
expect(button).toHaveTextContent(`${device.displayName}Not trusted`);
|
||||
});
|
||||
|
||||
it("with verified device only, displays no button without a label", async () => {
|
||||
setMockDeviceTrust(true);
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByText(device.displayName!)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/trusted/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", async () => {
|
||||
const deferred = defer<DeviceVerificationStatus>();
|
||||
mockCrypto.getDeviceVerificationStatus.mockReturnValue(deferred.promise);
|
||||
|
||||
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
|
||||
mockClient.getUserId.mockReturnValueOnce(defaultUserId);
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
|
||||
// set trust to be false for isVerified, true for isCrossSigningVerified
|
||||
deferred.resolve({
|
||||
isVerified: () => false,
|
||||
crossSigningVerified: true,
|
||||
} as DeviceVerificationStatus);
|
||||
|
||||
await expect(screen.findByText(device.displayName!)).resolves.toBeInTheDocument();
|
||||
// expect to see no button in this case
|
||||
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("with verified user and device, displays no button and a 'Trusted' label", async () => {
|
||||
setMockDeviceTrust(true);
|
||||
renderComponent({ isUserVerified: true });
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
||||
expect(screen.getByText(device.displayName!)).toBeInTheDocument();
|
||||
expect(screen.getByText("Trusted")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not call verifyDevice if client.getUser returns null", async () => {
|
||||
mockClient.getUser.mockReturnValueOnce(null);
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
|
||||
const button = screen.getByRole("button", { name: device.displayName! });
|
||||
expect(button).toBeInTheDocument();
|
||||
await userEvent.click(button);
|
||||
|
||||
expect(mockVerifyDevice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls verifyDevice if client.getUser returns an object", async () => {
|
||||
mockClient.getUser.mockReturnValueOnce(defaultUser);
|
||||
// set mock return of isGuest to short circuit verifyDevice call to avoid
|
||||
// even more mocking
|
||||
mockClient.isGuest.mockReturnValueOnce(true);
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
|
||||
const button = screen.getByRole("button", { name: device.displayName! });
|
||||
expect(button).toBeInTheDocument();
|
||||
await userEvent.click(button);
|
||||
|
||||
expect(mockVerifyDevice).toHaveBeenCalledTimes(1);
|
||||
expect(mockVerifyDevice).toHaveBeenCalledWith(mockClient, defaultUser, device);
|
||||
});
|
||||
|
||||
it("with display name", async () => {
|
||||
it("renders verified badge when user is verified", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, false));
|
||||
const { container } = renderComponent();
|
||||
await flushPromises();
|
||||
|
||||
await waitFor(() => expect(screen.getByText("Verified")).toBeInTheDocument());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("without display name", async () => {
|
||||
const device = { deviceId: "deviceId" } as Device;
|
||||
const { container } = renderComponent({ device, userId: defaultUserId });
|
||||
await flushPromises();
|
||||
|
||||
it("renders verify button", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
mockCrypto.userHasCrossSigningKeys.mockResolvedValue(true);
|
||||
const { container } = renderComponent();
|
||||
await waitFor(() => expect(screen.getByText("Verify User")).toBeInTheDocument());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("ambiguous display name", async () => {
|
||||
const device = { deviceId: "deviceId", ambiguous: true, displayName: "my display name" };
|
||||
const { container } = renderComponent({ device, userId: defaultUserId });
|
||||
await flushPromises();
|
||||
|
||||
it("renders verification unavailable message", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
mockCrypto.userHasCrossSigningKeys.mockResolvedValue(false);
|
||||
const { container } = renderComponent();
|
||||
await waitFor(() => expect(screen.getByText("(User verification unavailable)")).toBeInTheDocument());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,74 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<DeviceItem /> ambiguous display name 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="my display name (deviceId)"
|
||||
class="mx_AccessibleButton mx_UserInfo_device mx_UserInfo_device_unverified"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_normal"
|
||||
/>
|
||||
<div
|
||||
class="mx_UserInfo_device_name"
|
||||
>
|
||||
my display name (deviceId)
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_device_trusted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceItem /> with display name 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="deviceName"
|
||||
class="mx_AccessibleButton mx_UserInfo_device mx_UserInfo_device_unverified"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_normal"
|
||||
/>
|
||||
<div
|
||||
class="mx_UserInfo_device_name"
|
||||
>
|
||||
deviceName
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_device_trusted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DeviceItem /> without display name 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="deviceId"
|
||||
class="mx_AccessibleButton mx_UserInfo_device mx_UserInfo_device_unverified"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_normal"
|
||||
/>
|
||||
<div
|
||||
class="mx_UserInfo_device_name"
|
||||
>
|
||||
deviceId
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_device_trusted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
|
||||
<div>
|
||||
<div
|
||||
@@ -88,7 +19,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby=":r74:"
|
||||
aria-labelledby=":r6m:"
|
||||
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
@@ -180,45 +111,17 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<h2>
|
||||
Security
|
||||
</h2>
|
||||
<p>
|
||||
Messages in this room are not end-to-end encrypted.
|
||||
</p>
|
||||
<div
|
||||
class="mx_UserInfo_container_verifyButton"
|
||||
class="mx_Flex mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0;"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_UserInfo_field mx_UserInfo_verifyButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 mx_UserInfo_verification_unavailable"
|
||||
>
|
||||
Verify
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_devices"
|
||||
>
|
||||
<div />
|
||||
<div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_UserInfo_expand mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_normal"
|
||||
/>
|
||||
<div>
|
||||
1 session
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
(
|
||||
User verification unavailable
|
||||
)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -402,7 +305,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby=":r9a:"
|
||||
aria-labelledby=":r70:"
|
||||
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
@@ -494,45 +397,25 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<h2>
|
||||
Security
|
||||
</h2>
|
||||
<p>
|
||||
Messages in this room are not end-to-end encrypted.
|
||||
</p>
|
||||
<div
|
||||
class="mx_UserInfo_container_verifyButton"
|
||||
class="mx_Flex mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0;"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_UserInfo_field mx_UserInfo_verifyButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
<svg
|
||||
class="_icon_1ye7b_27"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
style="width: 24px; height: 24px;"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
Verify
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_devices"
|
||||
>
|
||||
<div />
|
||||
<div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_UserInfo_expand mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_normal"
|
||||
/>
|
||||
<div>
|
||||
1 session
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -737,3 +620,270 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfoHeader /> renders verification unavailable message 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_UserInfo_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition_child"
|
||||
>
|
||||
<button
|
||||
aria-label="Profile picture"
|
||||
aria-live="off"
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
style="--cpd-avatar-size: 120px;"
|
||||
title="customUserIdentifier"
|
||||
>
|
||||
u
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container mx_UserInfo_header"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_UserInfo_profile"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
|
||||
>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
dir="auto"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_UserInfo_profile_name"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
|
||||
>
|
||||
@user:example.com
|
||||
</div>
|
||||
</h1>
|
||||
<div
|
||||
class="mx_PresenceLabel mx_UserInfo_profileStatus"
|
||||
>
|
||||
Unknown
|
||||
</div>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 mx_UserInfo_profile_mxid"
|
||||
>
|
||||
<div
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
customUserIdentifier
|
||||
<div
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Flex mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0;"
|
||||
>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 mx_UserInfo_verification_unavailable"
|
||||
>
|
||||
(
|
||||
User verification unavailable
|
||||
)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfoHeader /> renders verified badge when user is verified 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_UserInfo_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition_child"
|
||||
>
|
||||
<button
|
||||
aria-label="Profile picture"
|
||||
aria-live="off"
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
style="--cpd-avatar-size: 120px;"
|
||||
title="customUserIdentifier"
|
||||
>
|
||||
u
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container mx_UserInfo_header"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_UserInfo_profile"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
|
||||
>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
dir="auto"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_UserInfo_profile_name"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
|
||||
>
|
||||
@user:example.com
|
||||
</div>
|
||||
</h1>
|
||||
<div
|
||||
class="mx_PresenceLabel mx_UserInfo_profileStatus"
|
||||
>
|
||||
Unknown
|
||||
</div>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 mx_UserInfo_profile_mxid"
|
||||
>
|
||||
<div
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
customUserIdentifier
|
||||
<div
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Flex mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0;"
|
||||
>
|
||||
<span
|
||||
class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50 _badge_1171v_17 mx_UserInfo_verified_badge"
|
||||
data-kind="green"
|
||||
>
|
||||
<svg
|
||||
class="mx_UserInfo_verified_icon"
|
||||
fill="currentColor"
|
||||
height="16px"
|
||||
viewBox="0 0 24 24"
|
||||
width="16px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.15 21.75 6.7 19.3l-2.75-.6a.943.943 0 0 1-.6-.387.928.928 0 0 1-.175-.688L3.45 14.8l-1.875-2.15a.934.934 0 0 1-.25-.65c0-.25.083-.467.25-.65L3.45 9.2l-.275-2.825a.928.928 0 0 1 .175-.688.943.943 0 0 1 .6-.387l2.75-.6 1.45-2.45a.983.983 0 0 1 .55-.438.97.97 0 0 1 .7.038l2.6 1.1 2.6-1.1a.97.97 0 0 1 .7-.038.983.983 0 0 1 .55.438L17.3 4.7l2.75.6c.25.05.45.18.6.388.15.208.208.437.175.687L20.55 9.2l1.875 2.15c.167.183.25.4.25.65s-.083.467-.25.65L20.55 14.8l.275 2.825a.928.928 0 0 1-.175.688.943.943 0 0 1-.6.387l-2.75.6-1.45 2.45a.983.983 0 0 1-.55.438.97.97 0 0 1-.7-.038l-2.6-1.1-2.6 1.1a.97.97 0 0 1-.7.038.983.983 0 0 1-.55-.438Zm2.8-9.05L9.5 11.275A.933.933 0 0 0 8.813 11c-.275 0-.513.1-.713.3a.948.948 0 0 0-.275.7.95.95 0 0 0 .275.7l2.15 2.15c.2.2.433.3.7.3.267 0 .5-.1.7-.3l4.25-4.25c.2-.2.296-.433.287-.7a1.055 1.055 0 0 0-.287-.7 1.02 1.02 0 0 0-.713-.313.93.93 0 0 0-.712.288L10.95 12.7Z"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50 mx_UserInfo_verified_label"
|
||||
>
|
||||
Verified
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfoHeader /> renders verify button 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_UserInfo_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition_child"
|
||||
>
|
||||
<button
|
||||
aria-label="Profile picture"
|
||||
aria-live="off"
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
style="--cpd-avatar-size: 120px;"
|
||||
title="customUserIdentifier"
|
||||
>
|
||||
u
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container mx_UserInfo_header"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_UserInfo_profile"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
|
||||
>
|
||||
<h1
|
||||
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
|
||||
dir="auto"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_UserInfo_profile_name"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
|
||||
>
|
||||
@user:example.com
|
||||
</div>
|
||||
</h1>
|
||||
<div
|
||||
class="mx_PresenceLabel mx_UserInfo_profileStatus"
|
||||
>
|
||||
Unknown
|
||||
</div>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 mx_UserInfo_profile_mxid"
|
||||
>
|
||||
<div
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
customUserIdentifier
|
||||
<div
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Flex mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0;"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_container_verifyButton"
|
||||
>
|
||||
<button
|
||||
class="_button_i91xf_17 mx_UserInfo_verify_button"
|
||||
data-kind="tertiary"
|
||||
data-size="sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Verify User
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user