Autofocus security key field (#10048)

This commit is contained in:
Michael Weimann 2023-02-03 09:39:25 +01:00 committed by GitHub
parent ebb8408f28
commit 089557005a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 99 deletions

View file

@ -404,6 +404,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
label={_t("Security Key")} label={_t("Security Key")}
value={this.state.recoveryKey} value={this.state.recoveryKey}
onChange={this.onRecoveryKeyChange} onChange={this.onRecoveryKeyChange}
autoFocus={true}
forceValidity={this.state.recoveryKeyCorrect} forceValidity={this.state.recoveryKeyCorrect}
autoComplete="off" autoComplete="off"
/> />

View file

@ -15,113 +15,97 @@ limitations under the License.
*/ */
import React from "react"; 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 { 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 { getMockClientWithEventEmitter, mockPlatformPeg } from "../../../test-utils";
import { findById, flushPromises } from "../../../test-utils";
import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/security/AccessSecretStorageDialog"; 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", () => { describe("AccessSecretStorageDialog", () => {
const mockClient = getMockClientWithEventEmitter({ let mockClient: Mocked<MatrixClient>;
keyBackupKeyFromRecoveryKey: jest.fn(),
checkSecretStorageKey: jest.fn(),
isValidRecoveryKey: jest.fn(),
});
const defaultProps = { const defaultProps = {
onFinished: jest.fn(), onFinished: jest.fn(),
checkPrivateKey: jest.fn(), checkPrivateKey: jest.fn(),
keyInfo: undefined, keyInfo: undefined,
}; };
const getComponent = (props = {}): ReactWrapper =>
mount(<AccessSecretStorageDialog {...defaultProps} {...props} />);
beforeEach(() => { const renderComponent = (props = {}): void => {
jest.clearAllMocks(); render(<AccessSecretStorageDialog {...defaultProps} {...props} />);
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue("a raw key" as unknown as Uint8Array); };
mockClient.isValidRecoveryKey.mockReturnValue(false);
const enterSecurityKey = (placeholder = "Security Key"): void => {
act(() => {
fireEvent.change(screen.getByPlaceholderText(placeholder), {
target: {
value: securityKey,
},
});
// wait for debounce
jest.advanceTimersByTime(250);
});
};
const submitDialog = async (): Promise<void> => {
await userEvent.click(screen.getByText("Continue"), { delay: null });
};
beforeAll(() => {
jest.useFakeTimers();
mockPlatformPeg();
}); });
afterAll(() => { 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 () => { it("Closes the dialog when the form is submitted with a valid key", async () => {
mockClient.checkSecretStorageKey.mockResolvedValue(true);
mockClient.isValidRecoveryKey.mockReturnValue(true);
const onFinished = jest.fn(); const onFinished = jest.fn();
const checkPrivateKey = jest.fn().mockResolvedValue(true); const checkPrivateKey = jest.fn().mockResolvedValue(true);
const wrapper = getComponent({ onFinished, checkPrivateKey }); renderComponent({ onFinished, checkPrivateKey });
// force into valid state // check that the input field is focused
act(() => { expect(screen.getByPlaceholderText("Security Key")).toHaveFocus();
wrapper.setState({
recoveryKeyValid: true,
recoveryKey: "a",
});
});
const e = { preventDefault: () => {} };
act(() => { await enterSecurityKey();
wrapper.find("form").simulate("submit", e); await submitDialog();
});
await flushPromises(); expect(screen.getByText("Looks good!")).toBeInTheDocument();
expect(checkPrivateKey).toHaveBeenCalledWith({ recoveryKey: securityKey });
expect(checkPrivateKey).toHaveBeenCalledWith({ recoveryKey: "a" }); expect(onFinished).toHaveBeenCalledWith({ recoveryKey: securityKey });
expect(onFinished).toHaveBeenCalledWith({ recoveryKey: "a" });
});
it("Considers a valid key to be valid", async () => {
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!");
}); });
it("Notifies the user if they input an invalid Security Key", async () => { it("Notifies the user if they input an invalid Security Key", async () => {
const checkPrivateKey = jest.fn().mockResolvedValue(false); const onFinished = jest.fn();
const wrapper = getComponent({ checkPrivateKey }); const checkPrivateKey = jest.fn().mockResolvedValue(true);
const e = { target: { value: "a" } }; renderComponent({ onFinished, checkPrivateKey });
mockClient.keyBackupKeyFromRecoveryKey.mockImplementation(() => { mockClient.keyBackupKeyFromRecoveryKey.mockImplementation(() => {
throw new Error("that's no key"); throw new Error("that's no key");
}); });
act(() => { await enterSecurityKey();
findById(wrapper, "mx_securityKey").find("input").simulate("change", e); await submitDialog();
});
// 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); expect(screen.getByText("Continue")).toBeDisabled();
// submit button is disabled when recovery key is invalid expect(screen.getByText("Invalid Security Key")).toBeInTheDocument();
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");
}); });
it("Notifies the user if they input an invalid passphrase", async function () { 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 checkPrivateKey = jest.fn().mockResolvedValue(false);
const wrapper = getComponent({ checkPrivateKey, keyInfo }); renderComponent({ checkPrivateKey, keyInfo });
mockClient.isValidRecoveryKey.mockReturnValue(false); mockClient.isValidRecoveryKey.mockReturnValue(false);
// update passphrase await enterSecurityKey("Security Phrase");
act(() => { expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);
const e = { target: { value: "a" } }; await submitDialog();
findById(wrapper, "mx_passPhraseInput").at(1).simulate("change", e);
});
wrapper.setProps({});
// input updated expect(
expect(findById(wrapper, "mx_passPhraseInput").at(0).props().value).toEqual("a"); screen.getByText(
"👎 Unable to access secret storage. Please verify that you entered the correct Security Phrase.",
// submit the form ),
act(() => { ).toBeInTheDocument();
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.",
]);
}); });
}); });