Support for login + E2EE set up with QR (#9403)
* Support for login + E2EE set up with QR * Whitespace * Padding * Refactor of fetch * Whitespace * CSS whitespace * Add link to MSC3906 * Handle incorrect typing in MatrixClientPeg.get() * Use unstable class name * fix: use unstable class name * Use default fetch client instead * Update to revised function name * Refactor device manager panel and make it work with new sessions manager * Lint fix * Add missing interstitials and update wording * Linting * i18n * Lint * Use sensible sdk config name for fallback server * Improve error handling for QR code generation * Refactor feature availability logic * Hide device manager panel if no options available * Put sign in with QR behind lab setting * Reduce scope of PR to just showing code on existing device * i18n updates * Handle null features * Testing for LoginWithQRSection * Refactor to handle UIA * Imports * Reduce diff complexity * Remove unnecessary change * Remove unused styles * Support UIA * Tidy up * i18n * Remove additional unused parts of flow * Add extra instruction when showing QR code * Add getVersions to server mocks * Use proper colours for theme support * Test cases * Lint * Remove obsolete snapshot * Don't override error if already set * Remove unused var * Update src/components/views/settings/devices/LoginWithQRSection.tsx Co-authored-by: Travis Ralston <travisr@matrix.org> * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston <travisr@matrix.org> * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston <travisr@matrix.org> * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston <travisr@matrix.org> * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston <travisr@matrix.org> * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston <travisr@matrix.org> * Update res/css/views/auth/_LoginWithQR.pcss Co-authored-by: Kerry <kerrya@element.io> * Use spacing variables * Remove debug * Style + docs * preventDefault * Names of tests * Fixes for js-sdk refactor * Update snapshots to match test names * Refactor labs config to make deployment simpler * i18n * Unused imports * Typo * Stateless component * Whitespace * Use context not MatrixClientPeg * Add missing context * Type updates to match js-sdk * Wrap click handlers in useCallback * Update src/components/views/settings/DevicesPanel.tsx Co-authored-by: Travis Ralston <travisr@matrix.org> * Wait for DOM update instead of timeout * Add missing snapshot update from last commit * Remove void keyword in favour of then() clauses * test main paths in LoginWithQR Co-authored-by: Travis Ralston <travisr@matrix.org> Co-authored-by: Kerry <kerrya@element.io>
This commit is contained in:
parent
e946674df3
commit
3c3df11d32
23 changed files with 1638 additions and 12 deletions
|
@ -28,6 +28,7 @@ import {
|
|||
mkPusher,
|
||||
mockClientMethodsUser,
|
||||
} from "../../../test-utils";
|
||||
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
|
||||
|
||||
describe('<DevicesPanel />', () => {
|
||||
const userId = '@alice:server.org';
|
||||
|
@ -46,7 +47,10 @@ describe('<DevicesPanel />', () => {
|
|||
setPusher: jest.fn(),
|
||||
});
|
||||
|
||||
const getComponent = () => <DevicesPanel />;
|
||||
const getComponent = () =>
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<DevicesPanel />
|
||||
</MatrixClientContext.Provider>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
|
297
test/components/views/settings/devices/LoginWithQR-test.tsx
Normal file
297
test/components/views/settings/devices/LoginWithQR-test.tsx
Normal file
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { mocked } from 'jest-mock';
|
||||
import React from 'react';
|
||||
import { MSC3886SimpleHttpRendezvousTransport } from 'matrix-js-sdk/src/rendezvous/transports';
|
||||
import { MSC3906Rendezvous, RendezvousFailureReason } from 'matrix-js-sdk/src/rendezvous';
|
||||
|
||||
import LoginWithQR, { Mode } from '../../../../../src/components/views/auth/LoginWithQR';
|
||||
import type { MatrixClient } from 'matrix-js-sdk/src/matrix';
|
||||
import { flushPromisesWithFakeTimers } from '../../../../test-utils';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('matrix-js-sdk/src/rendezvous');
|
||||
jest.mock('matrix-js-sdk/src/rendezvous/transports');
|
||||
jest.mock('matrix-js-sdk/src/rendezvous/channels');
|
||||
|
||||
function makeClient() {
|
||||
return mocked({
|
||||
getUser: jest.fn(),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
isUserIgnored: jest.fn(),
|
||||
isCryptoEnabled: jest.fn(),
|
||||
getUserId: jest.fn(),
|
||||
on: jest.fn(),
|
||||
isSynapseAdministrator: jest.fn().mockResolvedValue(false),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
mxcUrlToHttp: jest.fn().mockReturnValue('mock-mxcUrlToHttp'),
|
||||
doesServerSupportUnstableFeature: jest.fn().mockReturnValue(true),
|
||||
removeListener: jest.fn(),
|
||||
requestLoginToken: jest.fn(),
|
||||
currentState: {
|
||||
on: jest.fn(),
|
||||
},
|
||||
} as unknown as MatrixClient);
|
||||
}
|
||||
|
||||
describe('<LoginWithQR />', () => {
|
||||
const client = makeClient();
|
||||
const defaultProps = {
|
||||
mode: Mode.Show,
|
||||
onFinished: jest.fn(),
|
||||
};
|
||||
const mockConfirmationDigits = 'mock-confirmation-digits';
|
||||
const newDeviceId = 'new-device-id';
|
||||
|
||||
const getComponent = (props: { client: MatrixClient, onFinished?: () => void }) =>
|
||||
(<LoginWithQR {...defaultProps} {...props} />);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, 'generateCode').mockRestore();
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, 'cancel').mockResolvedValue();
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, 'declineLoginOnExistingDevice').mockResolvedValue();
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, 'startAfterShowingCode').mockResolvedValue(mockConfirmationDigits);
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, 'approveLoginOnExistingDevice').mockResolvedValue(newDeviceId);
|
||||
client.requestLoginToken.mockResolvedValue({
|
||||
login_token: 'token',
|
||||
expires_in: 1000,
|
||||
});
|
||||
// @ts-ignore
|
||||
client.crypto = undefined;
|
||||
});
|
||||
|
||||
it('no content in case of no support', async () => {
|
||||
// simulate no support
|
||||
jest.spyOn(MSC3906Rendezvous.prototype, 'generateCode').mockRejectedValue('');
|
||||
const { container } = render(getComponent({ client }));
|
||||
await waitFor(() => screen.getAllByTestId('cancellation-message').length === 1);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders spinner while generating code', async () => {
|
||||
const { container } = render(getComponent({ client }));
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('cancels rendezvous after user goes back', async () => {
|
||||
const { getByTestId } = render(getComponent({ client }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
|
||||
// flush generate code promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(getByTestId('back-button'));
|
||||
|
||||
// wait for cancel
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(rendezvous.cancel).toHaveBeenCalledWith(RendezvousFailureReason.UserCancelled);
|
||||
});
|
||||
|
||||
it('displays qr code after it is created', async () => {
|
||||
const { container, getByText } = render(getComponent({ client }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(rendezvous.generateCode).toHaveBeenCalled();
|
||||
expect(getByText('Sign in with QR code')).toBeTruthy();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('displays confirmation digits after connected to rendezvous', async () => {
|
||||
const { container, getByText } = render(getComponent({ client }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
|
||||
// flush generate code promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
// flush waiting for connection promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(getByText(mockConfirmationDigits)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('displays unknown error if connection to rendezvous fails', async () => {
|
||||
const { container } = render(getComponent({ client }));
|
||||
expect(MSC3886SimpleHttpRendezvousTransport).toHaveBeenCalledWith({
|
||||
onFailure: expect.any(Function),
|
||||
client,
|
||||
});
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
mocked(rendezvous).startAfterShowingCode.mockRejectedValue('oups');
|
||||
|
||||
// flush generate code promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
// flush waiting for connection promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('declines login', async () => {
|
||||
const { getByTestId } = render(getComponent({ client }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
|
||||
// flush generate code promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
// flush waiting for connection promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(getByTestId('decline-login-button'));
|
||||
|
||||
expect(rendezvous.declineLoginOnExistingDevice).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('displays error when approving login fails', async () => {
|
||||
const { container, getByTestId } = render(getComponent({ client }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
client.requestLoginToken.mockRejectedValue('oups');
|
||||
|
||||
// flush generate code promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
// flush waiting for connection promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(getByTestId('approve-login-button'));
|
||||
|
||||
expect(client.requestLoginToken).toHaveBeenCalled();
|
||||
// flush token request promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('approves login and waits for new device', async () => {
|
||||
const { container, getByTestId, getByText } = render(getComponent({ client }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
|
||||
// flush generate code promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
// flush waiting for connection promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(getByTestId('approve-login-button'));
|
||||
|
||||
expect(client.requestLoginToken).toHaveBeenCalled();
|
||||
// flush token request promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(getByText('Waiting for device to sign in')).toBeTruthy();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('does not continue with verification when user denies login', async () => {
|
||||
const onFinished = jest.fn();
|
||||
const { getByTestId } = render(getComponent({ client, onFinished }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
// no device id returned => user denied
|
||||
mocked(rendezvous).approveLoginOnExistingDevice.mockReturnValue(undefined);
|
||||
|
||||
// flush generate code promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
// flush waiting for connection promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(getByTestId('approve-login-button'));
|
||||
|
||||
// flush token request promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalled();
|
||||
|
||||
await flushPromisesWithFakeTimers();
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
expect(rendezvous.verifyNewDeviceOnExistingDevice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('waits for device approval on existing device and finishes when crypto is not setup', async () => {
|
||||
const { getByTestId } = render(getComponent({ client }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
|
||||
// flush generate code promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
// flush waiting for connection promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(getByTestId('approve-login-button'));
|
||||
|
||||
// flush token request promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalled();
|
||||
await flushPromisesWithFakeTimers();
|
||||
expect(defaultProps.onFinished).toHaveBeenCalledWith(true);
|
||||
// didnt attempt verification
|
||||
expect(rendezvous.verifyNewDeviceOnExistingDevice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('waits for device approval on existing device and verifies device', async () => {
|
||||
const { getByTestId } = render(getComponent({ client }));
|
||||
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
|
||||
// @ts-ignore assign to private prop
|
||||
rendezvous.code = 'rendezvous-code';
|
||||
// we just check for presence of crypto
|
||||
// pretend it is set up
|
||||
// @ts-ignore
|
||||
client.crypto = {};
|
||||
|
||||
// flush generate code promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
// flush waiting for connection promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(getByTestId('approve-login-button'));
|
||||
|
||||
// flush token request promise
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalled();
|
||||
// flush login approval
|
||||
await flushPromisesWithFakeTimers();
|
||||
expect(rendezvous.verifyNewDeviceOnExistingDevice).toHaveBeenCalled();
|
||||
// flush verification
|
||||
await flushPromisesWithFakeTimers();
|
||||
expect(defaultProps.onFinished).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { mocked } from 'jest-mock';
|
||||
import { IServerVersions, MatrixClient } from 'matrix-js-sdk/src/matrix';
|
||||
import React from 'react';
|
||||
|
||||
import LoginWithQRSection from '../../../../../src/components/views/settings/devices/LoginWithQRSection';
|
||||
import { MatrixClientPeg } from '../../../../../src/MatrixClientPeg';
|
||||
import { SettingLevel } from '../../../../../src/settings/SettingLevel';
|
||||
import SettingsStore from '../../../../../src/settings/SettingsStore';
|
||||
|
||||
function makeClient() {
|
||||
return mocked({
|
||||
getUser: jest.fn(),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
isUserIgnored: jest.fn(),
|
||||
isCryptoEnabled: jest.fn(),
|
||||
getUserId: jest.fn(),
|
||||
on: jest.fn(),
|
||||
isSynapseAdministrator: jest.fn().mockResolvedValue(false),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
mxcUrlToHttp: jest.fn().mockReturnValue('mock-mxcUrlToHttp'),
|
||||
removeListener: jest.fn(),
|
||||
currentState: {
|
||||
on: jest.fn(),
|
||||
},
|
||||
} as unknown as MatrixClient);
|
||||
}
|
||||
|
||||
function makeVersions(unstableFeatures: Record<string, boolean>): IServerVersions {
|
||||
return {
|
||||
versions: [],
|
||||
unstable_features: unstableFeatures,
|
||||
};
|
||||
}
|
||||
|
||||
describe('<LoginWithQRSection />', () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(makeClient());
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
onShowQr: () => {},
|
||||
versions: undefined,
|
||||
};
|
||||
|
||||
const getComponent = (props = {}) =>
|
||||
(<LoginWithQRSection {...defaultProps} {...props} />);
|
||||
|
||||
describe('should not render', () => {
|
||||
it('no support at all', () => {
|
||||
const { container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('feature enabled', async () => {
|
||||
await SettingsStore.setValue('feature_qr_signin_reciprocate_show', null, SettingLevel.DEVICE, true);
|
||||
const { container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('only feature + MSC3882 enabled', async () => {
|
||||
await SettingsStore.setValue('feature_qr_signin_reciprocate_show', null, SettingLevel.DEVICE, true);
|
||||
const { container } = render(getComponent({ versions: makeVersions({ 'org.matrix.msc3882': true }) }));
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should render panel', () => {
|
||||
it('enabled by feature + MSC3882 + MSC3886', async () => {
|
||||
await SettingsStore.setValue('feature_qr_signin_reciprocate_show', null, SettingLevel.DEVICE, true);
|
||||
const { container } = render(getComponent({ versions: makeVersions({
|
||||
'org.matrix.msc3882': true,
|
||||
'org.matrix.msc3886': true,
|
||||
}) }));
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,367 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LoginWithQR /> approves login and waits for new device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Back"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<h1 />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_spinner"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading..."
|
||||
class="mx_Spinner_icon"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
Waiting for device to sign in
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQR /> displays confirmation digits after connected to rendezvous 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
>
|
||||
<h1>
|
||||
<div
|
||||
class="normal"
|
||||
/>
|
||||
Devices connected
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<p>
|
||||
Check that the code below matches with your other device:
|
||||
</p>
|
||||
<div
|
||||
class="mx_LoginWithQR_confirmationDigits"
|
||||
>
|
||||
mock-confirmation-digits
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_confirmationAlert"
|
||||
>
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
<div>
|
||||
By approving access for this device, it will have full access to your account.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
data-testid="decline-login-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
data-testid="approve-login-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Approve
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQR /> displays error when approving login fails 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
>
|
||||
<h1>
|
||||
<div
|
||||
class="error"
|
||||
/>
|
||||
Connection failed
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
An unexpected error occurred.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Try again
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQR /> displays qr code after it is created 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Back"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<h1>
|
||||
Sign in with QR code
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<p>
|
||||
Scan the QR code below with your device that's signed out.
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
Start at the sign in screen
|
||||
</li>
|
||||
<li>
|
||||
Select 'Scan QR code'
|
||||
</li>
|
||||
<li>
|
||||
Review and approve the sign in
|
||||
</li>
|
||||
</ol>
|
||||
<div
|
||||
class="mx_LoginWithQR_qrWrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading..."
|
||||
class="mx_Spinner_icon"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQR /> displays unknown error if connection to rendezvous fails 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
>
|
||||
<h1>
|
||||
<div
|
||||
class="error"
|
||||
/>
|
||||
Connection failed
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
An unexpected error occurred.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Try again
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQR /> no content in case of no support 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
>
|
||||
<h1>
|
||||
<div
|
||||
class="error"
|
||||
/>
|
||||
Connection failed
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<p
|
||||
data-testid="cancellation-message"
|
||||
>
|
||||
The homeserver doesn't support signing in another device.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Try again
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<LoginWithQR /> renders spinner while generating code 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
|
||||
data-testid="back-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Back"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<h1 />
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_main"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_spinner"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading..."
|
||||
class="mx_Spinner_icon"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LoginWithQR_buttons"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,45 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LoginWithQRSection /> should not render feature enabled 1`] = `<div />`;
|
||||
|
||||
exports[`<LoginWithQRSection /> should not render no support at all 1`] = `<div />`;
|
||||
|
||||
exports[`<LoginWithQRSection /> should not render only feature + MSC3882 enabled 1`] = `<div />`;
|
||||
|
||||
exports[`<LoginWithQRSection /> should render panel enabled by feature + MSC3882 + MSC3886 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_SettingsSubsection"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Sign in with QR code
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsSubsection_content"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQRSection"
|
||||
>
|
||||
<p
|
||||
class="mx_SettingsTab_subsectionText"
|
||||
>
|
||||
You can use this device to sign in a new device with a QR code. You will need to scan the QR code shown on this device with your device that's signed out.
|
||||
</p>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Show QR code
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -17,6 +17,7 @@ import { render } from '@testing-library/react';
|
|||
import React from 'react';
|
||||
|
||||
import SecurityUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/SecurityUserSettingsTab";
|
||||
import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
|
||||
import SettingsStore from '../../../../../../src/settings/SettingsStore';
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
|
@ -31,11 +32,10 @@ describe('<SecurityUserSettingsTab />', () => {
|
|||
const defaultProps = {
|
||||
closeSettingsFn: jest.fn(),
|
||||
};
|
||||
const getComponent = () => <SecurityUserSettingsTab {...defaultProps} />;
|
||||
|
||||
const userId = '@alice:server.org';
|
||||
const deviceId = 'alices-device';
|
||||
getMockClientWithEventEmitter({
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsServer(),
|
||||
...mockClientMethodsDevice(deviceId),
|
||||
|
@ -44,6 +44,11 @@ describe('<SecurityUserSettingsTab />', () => {
|
|||
getIgnoredUsers: jest.fn(),
|
||||
});
|
||||
|
||||
const getComponent = () =>
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<SecurityUserSettingsTab {...defaultProps} />
|
||||
</MatrixClientContext.Provider>;
|
||||
|
||||
const settingsValueSpy = jest.spyOn(SettingsStore, 'getValue');
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -92,6 +92,7 @@ describe('<SessionManagerTab />', () => {
|
|||
getPushers: jest.fn(),
|
||||
setPusher: jest.fn(),
|
||||
setLocalNotificationSettings: jest.fn(),
|
||||
getVersions: jest.fn().mockResolvedValue({}),
|
||||
});
|
||||
|
||||
const defaultProps = {};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue