mirror of
https://github.com/element-hq/element-web
synced 2024-11-23 17:56:01 +03:00
Autofocus security key field (#10048)
This commit is contained in:
parent
ebb8408f28
commit
089557005a
2 changed files with 71 additions and 99 deletions
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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.",
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue