From 1705588675302ecfa8fa16f2c103d664e4906034 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Wed, 1 Feb 2023 15:23:05 +0100 Subject: [PATCH 1/2] Autofocus security key field --- .../security/AccessSecretStorageDialog.tsx | 1 + .../AccessSecretStorageDialog-test.tsx | 177 ++++++++---------- 2 files changed, 75 insertions(+), 103 deletions(-) diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index 9bad916ff5d..d7154b3aa2f 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -404,6 +404,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx index 8e5d4043229..5dbba412d60 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx @@ -15,113 +15,97 @@ limitations under the License. */ import React from "react"; -// eslint-disable-next-line deprecate/import -import { mount, ReactWrapper } from "enzyme"; -import { act } from "react-dom/test-utils"; import { IPassphraseInfo } from "matrix-js-sdk/src/crypto/api"; +import { act, fireEvent, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { mocked } from "jest-mock"; -import { findByAttr, getMockClientWithEventEmitter, unmockClientPeg } from "../../../test-utils"; -import { findById, flushPromises } from "../../../test-utils"; +import { getMockClientWithEventEmitter, mockPlatformPeg } from "../../../test-utils"; import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/security/AccessSecretStorageDialog"; +const securityKey = "EsTc WKmb ivvk jLS7 Y1NH 5CcQ mP1E JJwj B3Fd pFWm t4Dp dbyu"; + describe("AccessSecretStorageDialog", () => { - const mockClient = getMockClientWithEventEmitter({ - keyBackupKeyFromRecoveryKey: jest.fn(), - checkSecretStorageKey: jest.fn(), - isValidRecoveryKey: jest.fn(), - }); + let mockClient: MatrixClient; + const defaultProps = { onFinished: jest.fn(), checkPrivateKey: jest.fn(), keyInfo: undefined, }; - const getComponent = (props = {}): ReactWrapper => - mount(); - beforeEach(() => { - jest.clearAllMocks(); - mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue("a raw key" as unknown as Uint8Array); - mockClient.isValidRecoveryKey.mockReturnValue(false); + const renderComponent = (props = {}): void => { + render(); + }; + + const enterSecurityKey = async (placeholder = "Security Key"): Promise => { + await act(async () => { + fireEvent.change(screen.getByPlaceholderText(placeholder), { + target: { + value: securityKey, + }, + }); + // wait for debounce + jest.advanceTimersByTime(250); + }); + }; + + const submitDialog = async (): Promise => { + await userEvent.click(screen.getByText("Continue"), { delay: null }); + }; + + beforeAll(() => { + jest.useFakeTimers(); + mockPlatformPeg(); }); afterAll(() => { - unmockClientPeg(); + jest.useRealTimers(); + jest.restoreAllMocks(); + }); + + beforeEach(() => { + mockClient = getMockClientWithEventEmitter({ + keyBackupKeyFromRecoveryKey: jest.fn(), + checkSecretStorageKey: jest.fn(), + isValidRecoveryKey: jest.fn(), + }); }); it("Closes the dialog when the form is submitted with a valid key", async () => { + mocked(mockClient.checkSecretStorageKey).mockResolvedValue(true); + mocked(mockClient.isValidRecoveryKey).mockReturnValue(true); + const onFinished = jest.fn(); const checkPrivateKey = jest.fn().mockResolvedValue(true); - const wrapper = getComponent({ onFinished, checkPrivateKey }); - - // force into valid state - act(() => { - wrapper.setState({ - recoveryKeyValid: true, - recoveryKey: "a", - }); - }); - const e = { preventDefault: () => {} }; + renderComponent({ onFinished, checkPrivateKey }); - act(() => { - wrapper.find("form").simulate("submit", e); - }); + // check that the input field is focused + expect(screen.getByPlaceholderText("Security Key")).toHaveFocus(); - await flushPromises(); + await enterSecurityKey(); + await submitDialog(); - expect(checkPrivateKey).toHaveBeenCalledWith({ recoveryKey: "a" }); - expect(onFinished).toHaveBeenCalledWith({ recoveryKey: "a" }); + expect(screen.getByText("Looks good!")).toBeInTheDocument(); + expect(checkPrivateKey).toHaveBeenCalledWith({ recoveryKey: securityKey }); + expect(onFinished).toHaveBeenCalledWith({ recoveryKey: securityKey }); }); - it("Considers a valid key to be valid", async () => { + it("Notifies the user if they input an invalid Security Key", async () => { + const onFinished = jest.fn(); const checkPrivateKey = jest.fn().mockResolvedValue(true); - const wrapper = getComponent({ checkPrivateKey }); - mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue("a raw key" as unknown as Uint8Array); - mockClient.checkSecretStorageKey.mockResolvedValue(true); - - const v = "asdf"; - const e = { target: { value: v } }; - act(() => { - findById(wrapper, "mx_securityKey").find("input").simulate("change", e); - wrapper.setProps({}); - }); - await act(async () => { - // force a validation now because it debounces - // @ts-ignore - await wrapper.instance().validateRecoveryKey(); - wrapper.setProps({}); - }); - - const submitButton = findByAttr("data-testid")(wrapper, "dialog-primary-button").at(0); - // submit button is enabled when key is valid - expect(submitButton.props().disabled).toBeFalsy(); - expect(wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback").text()).toEqual("Looks good!"); - }); + renderComponent({ onFinished, checkPrivateKey }); - it("Notifies the user if they input an invalid Security Key", async () => { - const checkPrivateKey = jest.fn().mockResolvedValue(false); - const wrapper = getComponent({ checkPrivateKey }); - const e = { target: { value: "a" } }; - mockClient.keyBackupKeyFromRecoveryKey.mockImplementation(() => { + mocked(mockClient.keyBackupKeyFromRecoveryKey).mockImplementation(() => { throw new Error("that's no key"); }); - act(() => { - findById(wrapper, "mx_securityKey").find("input").simulate("change", e); - }); - // force a validation now because it debounces - // @ts-ignore private - await wrapper.instance().validateRecoveryKey(); - - const submitButton = findByAttr("data-testid")(wrapper, "dialog-primary-button").at(0); - // submit button is disabled when recovery key is invalid - expect(submitButton.props().disabled).toBeTruthy(); - expect(wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback").text()).toEqual( - "Invalid Security Key", - ); - - wrapper.setProps({}); - const notification = wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback"); - expect(notification.props().children).toEqual("Invalid Security Key"); + await enterSecurityKey(); + await submitDialog(); + + expect(screen.getByText("Continue")).toBeDisabled(); + expect(screen.getByText("Invalid Security Key")).toBeInTheDocument(); }); it("Notifies the user if they input an invalid passphrase", async function () { @@ -139,30 +123,17 @@ describe("AccessSecretStorageDialog", () => { }, }; const checkPrivateKey = jest.fn().mockResolvedValue(false); - const wrapper = getComponent({ checkPrivateKey, keyInfo }); - mockClient.isValidRecoveryKey.mockReturnValue(false); - - // update passphrase - act(() => { - const e = { target: { value: "a" } }; - findById(wrapper, "mx_passPhraseInput").at(1).simulate("change", e); - }); - wrapper.setProps({}); - - // input updated - expect(findById(wrapper, "mx_passPhraseInput").at(0).props().value).toEqual("a"); - - // submit the form - act(() => { - wrapper.find("form").at(0).simulate("submit"); - }); - await flushPromises(); - - wrapper.setProps({}); - const notification = wrapper.find(".mx_AccessSecretStorageDialog_keyStatus"); - expect(notification.props().children).toEqual([ - "\uD83D\uDC4E ", - "Unable to access secret storage. Please verify that you " + "entered the correct Security Phrase.", - ]); + renderComponent({ checkPrivateKey, keyInfo }); + mocked(mockClient.isValidRecoveryKey).mockReturnValue(false); + + await enterSecurityKey("Security Phrase"); + expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey); + await submitDialog(); + + expect( + screen.getByText( + "👎 Unable to access secret storage. Please verify that you entered the correct Security Phrase.", + ), + ).toBeInTheDocument(); }); }); From 87b9e1e066817927a32515f88c77dd28ea10fa2d Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Thu, 2 Feb 2023 09:01:48 +0100 Subject: [PATCH 2/2] Remove unnecessary async + use Mocked --- .../dialogs/AccessSecretStorageDialog-test.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx index 5dbba412d60..9a9b103fac2 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx @@ -19,7 +19,7 @@ import { IPassphraseInfo } from "matrix-js-sdk/src/crypto/api"; import { act, fireEvent, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { mocked } from "jest-mock"; +import { Mocked } from "jest-mock"; import { getMockClientWithEventEmitter, mockPlatformPeg } from "../../../test-utils"; import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/security/AccessSecretStorageDialog"; @@ -27,7 +27,7 @@ import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/ const securityKey = "EsTc WKmb ivvk jLS7 Y1NH 5CcQ mP1E JJwj B3Fd pFWm t4Dp dbyu"; describe("AccessSecretStorageDialog", () => { - let mockClient: MatrixClient; + let mockClient: Mocked; const defaultProps = { onFinished: jest.fn(), @@ -39,8 +39,8 @@ describe("AccessSecretStorageDialog", () => { render(); }; - const enterSecurityKey = async (placeholder = "Security Key"): Promise => { - await act(async () => { + const enterSecurityKey = (placeholder = "Security Key"): void => { + act(() => { fireEvent.change(screen.getByPlaceholderText(placeholder), { target: { value: securityKey, @@ -74,8 +74,8 @@ describe("AccessSecretStorageDialog", () => { }); it("Closes the dialog when the form is submitted with a valid key", async () => { - mocked(mockClient.checkSecretStorageKey).mockResolvedValue(true); - mocked(mockClient.isValidRecoveryKey).mockReturnValue(true); + mockClient.checkSecretStorageKey.mockResolvedValue(true); + mockClient.isValidRecoveryKey.mockReturnValue(true); const onFinished = jest.fn(); const checkPrivateKey = jest.fn().mockResolvedValue(true); @@ -97,7 +97,7 @@ describe("AccessSecretStorageDialog", () => { const checkPrivateKey = jest.fn().mockResolvedValue(true); renderComponent({ onFinished, checkPrivateKey }); - mocked(mockClient.keyBackupKeyFromRecoveryKey).mockImplementation(() => { + mockClient.keyBackupKeyFromRecoveryKey.mockImplementation(() => { throw new Error("that's no key"); }); @@ -124,7 +124,7 @@ describe("AccessSecretStorageDialog", () => { }; const checkPrivateKey = jest.fn().mockResolvedValue(false); renderComponent({ checkPrivateKey, keyInfo }); - mocked(mockClient.isValidRecoveryKey).mockReturnValue(false); + mockClient.isValidRecoveryKey.mockReturnValue(false); await enterSecurityKey("Security Phrase"); expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);