Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 0895570

Browse files
authored
Autofocus security key field (#10048)
1 parent ebb8408 commit 0895570

File tree

2 files changed

+71
-99
lines changed

2 files changed

+71
-99
lines changed

src/components/views/dialogs/security/AccessSecretStorageDialog.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
404404
label={_t("Security Key")}
405405
value={this.state.recoveryKey}
406406
onChange={this.onRecoveryKeyChange}
407+
autoFocus={true}
407408
forceValidity={this.state.recoveryKeyCorrect}
408409
autoComplete="off"
409410
/>

test/components/views/dialogs/AccessSecretStorageDialog-test.tsx

Lines changed: 70 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -15,113 +15,97 @@ limitations under the License.
1515
*/
1616

1717
import React from "react";
18-
// eslint-disable-next-line deprecate/import
19-
import { mount, ReactWrapper } from "enzyme";
20-
import { act } from "react-dom/test-utils";
2118
import { IPassphraseInfo } from "matrix-js-sdk/src/crypto/api";
19+
import { act, fireEvent, render, screen } from "@testing-library/react";
20+
import userEvent from "@testing-library/user-event";
21+
import { MatrixClient } from "matrix-js-sdk/src/matrix";
22+
import { Mocked } from "jest-mock";
2223

23-
import { findByAttr, getMockClientWithEventEmitter, unmockClientPeg } from "../../../test-utils";
24-
import { findById, flushPromises } from "../../../test-utils";
24+
import { getMockClientWithEventEmitter, mockPlatformPeg } from "../../../test-utils";
2525
import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/security/AccessSecretStorageDialog";
2626

27+
const securityKey = "EsTc WKmb ivvk jLS7 Y1NH 5CcQ mP1E JJwj B3Fd pFWm t4Dp dbyu";
28+
2729
describe("AccessSecretStorageDialog", () => {
28-
const mockClient = getMockClientWithEventEmitter({
29-
keyBackupKeyFromRecoveryKey: jest.fn(),
30-
checkSecretStorageKey: jest.fn(),
31-
isValidRecoveryKey: jest.fn(),
32-
});
30+
let mockClient: Mocked<MatrixClient>;
31+
3332
const defaultProps = {
3433
onFinished: jest.fn(),
3534
checkPrivateKey: jest.fn(),
3635
keyInfo: undefined,
3736
};
38-
const getComponent = (props = {}): ReactWrapper =>
39-
mount(<AccessSecretStorageDialog {...defaultProps} {...props} />);
40-
41-
beforeEach(() => {
42-
jest.clearAllMocks();
43-
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue("a raw key" as unknown as Uint8Array);
44-
mockClient.isValidRecoveryKey.mockReturnValue(false);
45-
});
46-
47-
afterAll(() => {
48-
unmockClientPeg();
49-
});
5037

51-
it("Closes the dialog when the form is submitted with a valid key", async () => {
52-
const onFinished = jest.fn();
53-
const checkPrivateKey = jest.fn().mockResolvedValue(true);
54-
const wrapper = getComponent({ onFinished, checkPrivateKey });
38+
const renderComponent = (props = {}): void => {
39+
render(<AccessSecretStorageDialog {...defaultProps} {...props} />);
40+
};
5541

56-
// force into valid state
42+
const enterSecurityKey = (placeholder = "Security Key"): void => {
5743
act(() => {
58-
wrapper.setState({
59-
recoveryKeyValid: true,
60-
recoveryKey: "a",
44+
fireEvent.change(screen.getByPlaceholderText(placeholder), {
45+
target: {
46+
value: securityKey,
47+
},
6148
});
49+
// wait for debounce
50+
jest.advanceTimersByTime(250);
6251
});
63-
const e = { preventDefault: () => {} };
52+
};
6453

65-
act(() => {
66-
wrapper.find("form").simulate("submit", e);
67-
});
54+
const submitDialog = async (): Promise<void> => {
55+
await userEvent.click(screen.getByText("Continue"), { delay: null });
56+
};
6857

69-
await flushPromises();
58+
beforeAll(() => {
59+
jest.useFakeTimers();
60+
mockPlatformPeg();
61+
});
7062

71-
expect(checkPrivateKey).toHaveBeenCalledWith({ recoveryKey: "a" });
72-
expect(onFinished).toHaveBeenCalledWith({ recoveryKey: "a" });
63+
afterAll(() => {
64+
jest.useRealTimers();
65+
jest.restoreAllMocks();
7366
});
7467

75-
it("Considers a valid key to be valid", async () => {
76-
const checkPrivateKey = jest.fn().mockResolvedValue(true);
77-
const wrapper = getComponent({ checkPrivateKey });
78-
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue("a raw key" as unknown as Uint8Array);
68+
beforeEach(() => {
69+
mockClient = getMockClientWithEventEmitter({
70+
keyBackupKeyFromRecoveryKey: jest.fn(),
71+
checkSecretStorageKey: jest.fn(),
72+
isValidRecoveryKey: jest.fn(),
73+
});
74+
});
75+
76+
it("Closes the dialog when the form is submitted with a valid key", async () => {
7977
mockClient.checkSecretStorageKey.mockResolvedValue(true);
78+
mockClient.isValidRecoveryKey.mockReturnValue(true);
8079

81-
const v = "asdf";
82-
const e = { target: { value: v } };
83-
act(() => {
84-
findById(wrapper, "mx_securityKey").find("input").simulate("change", e);
85-
wrapper.setProps({});
86-
});
87-
await act(async () => {
88-
// force a validation now because it debounces
89-
// @ts-ignore
90-
await wrapper.instance().validateRecoveryKey();
91-
wrapper.setProps({});
92-
});
80+
const onFinished = jest.fn();
81+
const checkPrivateKey = jest.fn().mockResolvedValue(true);
82+
renderComponent({ onFinished, checkPrivateKey });
83+
84+
// check that the input field is focused
85+
expect(screen.getByPlaceholderText("Security Key")).toHaveFocus();
9386

94-
const submitButton = findByAttr("data-testid")(wrapper, "dialog-primary-button").at(0);
95-
// submit button is enabled when key is valid
96-
expect(submitButton.props().disabled).toBeFalsy();
97-
expect(wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback").text()).toEqual("Looks good!");
87+
await enterSecurityKey();
88+
await submitDialog();
89+
90+
expect(screen.getByText("Looks good!")).toBeInTheDocument();
91+
expect(checkPrivateKey).toHaveBeenCalledWith({ recoveryKey: securityKey });
92+
expect(onFinished).toHaveBeenCalledWith({ recoveryKey: securityKey });
9893
});
9994

10095
it("Notifies the user if they input an invalid Security Key", async () => {
101-
const checkPrivateKey = jest.fn().mockResolvedValue(false);
102-
const wrapper = getComponent({ checkPrivateKey });
103-
const e = { target: { value: "a" } };
96+
const onFinished = jest.fn();
97+
const checkPrivateKey = jest.fn().mockResolvedValue(true);
98+
renderComponent({ onFinished, checkPrivateKey });
99+
104100
mockClient.keyBackupKeyFromRecoveryKey.mockImplementation(() => {
105101
throw new Error("that's no key");
106102
});
107103

108-
act(() => {
109-
findById(wrapper, "mx_securityKey").find("input").simulate("change", e);
110-
});
111-
// force a validation now because it debounces
112-
// @ts-ignore private
113-
await wrapper.instance().validateRecoveryKey();
114-
115-
const submitButton = findByAttr("data-testid")(wrapper, "dialog-primary-button").at(0);
116-
// submit button is disabled when recovery key is invalid
117-
expect(submitButton.props().disabled).toBeTruthy();
118-
expect(wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback").text()).toEqual(
119-
"Invalid Security Key",
120-
);
121-
122-
wrapper.setProps({});
123-
const notification = wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback");
124-
expect(notification.props().children).toEqual("Invalid Security Key");
104+
await enterSecurityKey();
105+
await submitDialog();
106+
107+
expect(screen.getByText("Continue")).toBeDisabled();
108+
expect(screen.getByText("Invalid Security Key")).toBeInTheDocument();
125109
});
126110

127111
it("Notifies the user if they input an invalid passphrase", async function () {
@@ -139,30 +123,17 @@ describe("AccessSecretStorageDialog", () => {
139123
},
140124
};
141125
const checkPrivateKey = jest.fn().mockResolvedValue(false);
142-
const wrapper = getComponent({ checkPrivateKey, keyInfo });
126+
renderComponent({ checkPrivateKey, keyInfo });
143127
mockClient.isValidRecoveryKey.mockReturnValue(false);
144128

145-
// update passphrase
146-
act(() => {
147-
const e = { target: { value: "a" } };
148-
findById(wrapper, "mx_passPhraseInput").at(1).simulate("change", e);
149-
});
150-
wrapper.setProps({});
151-
152-
// input updated
153-
expect(findById(wrapper, "mx_passPhraseInput").at(0).props().value).toEqual("a");
129+
await enterSecurityKey("Security Phrase");
130+
expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);
131+
await submitDialog();
154132

155-
// submit the form
156-
act(() => {
157-
wrapper.find("form").at(0).simulate("submit");
158-
});
159-
await flushPromises();
160-
161-
wrapper.setProps({});
162-
const notification = wrapper.find(".mx_AccessSecretStorageDialog_keyStatus");
163-
expect(notification.props().children).toEqual([
164-
"\uD83D\uDC4E ",
165-
"Unable to access secret storage. Please verify that you " + "entered the correct Security Phrase.",
166-
]);
133+
expect(
134+
screen.getByText(
135+
"👎 Unable to access secret storage. Please verify that you entered the correct Security Phrase.",
136+
),
137+
).toBeInTheDocument();
167138
});
168139
});

0 commit comments

Comments
 (0)