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

Commit 23e3a40

Browse files
author
Kerry Archibald
committed
add current session section, test
1 parent 34db0ac commit 23e3a40

File tree

5 files changed

+251
-7
lines changed

5 files changed

+251
-7
lines changed

src/components/views/settings/shared/SettingsSubsection.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React from "react";
17+
import React, { HTMLAttributes } from "react";
1818

1919
import Heading from "../../typography/Heading";
2020

21-
export interface SettingsSubsectionProps {
21+
export interface SettingsSubsectionProps extends HTMLAttributes<HTMLDivElement> {
2222
heading: string;
2323
description?: string | React.ReactNode;
2424
children?: React.ReactNode;
2525
}
2626

27-
const SettingsSubsection: React.FC<SettingsSubsectionProps> = ({ heading, description, children }) => (
28-
<div className="mx_SettingsSubsection">
27+
const SettingsSubsection: React.FC<SettingsSubsectionProps> = ({ heading, description, children, ...rest }) => (
28+
<div {...rest} className="mx_SettingsSubsection">
2929
<Heading className="mx_SettingsSubsection_heading" size='h3'>{ heading }</Heading>
3030
{ !!description && <div className="mx_SettingsSubsection_description">{ description }</div> }
3131
<div className="mx_SettingsSubsection_content">

src/components/views/settings/tabs/user/SessionManagerTab.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,26 @@ limitations under the License.
1717
import React from 'react';
1818

1919
import { _t } from "../../../../../languageHandler";
20+
import Spinner from '../../../elements/Spinner';
21+
import { useOwnDevices } from '../../devices/useOwnDevices';
22+
import DeviceTile from '../../devices/DeviceTile';
2023
import SettingsSubsection from '../../shared/SettingsSubsection';
2124
import SettingsTab from '../SettingsTab';
2225

2326
const SessionManagerTab: React.FC = () => {
27+
const { devices, currentDeviceId, isLoading } = useOwnDevices();
28+
29+
const currentDevice = devices[currentDeviceId];
2430
return <SettingsTab heading={_t('Sessions')}>
2531
<SettingsSubsection
2632
heading={_t('Current session')}
27-
// TODO session content coming here
28-
// in next PR
29-
/>
33+
data-testid='current-session-section'
34+
>
35+
{ isLoading && <Spinner /> }
36+
{ !!currentDevice && <DeviceTile
37+
device={currentDevice}
38+
/> }
39+
</SettingsSubsection>
3040
</SettingsTab>;
3141
};
3242

src/i18n/strings/en_EN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,6 +1693,8 @@
16931693
"Verification code": "Verification code",
16941694
"Discovery options will appear once you have added a phone number above.": "Discovery options will appear once you have added a phone number above.",
16951695
"Last activity": "Last activity",
1696+
"Verified": "Verified",
1697+
"Unverified": "Unverified",
16961698
"Unable to remove contact information": "Unable to remove contact information",
16971699
"Remove %(email)s?": "Remove %(email)s?",
16981700
"Invalid Email Address": "Invalid Email Address",
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from 'react';
18+
import { render } from '@testing-library/react';
19+
import { act } from 'react-dom/test-utils';
20+
import { DeviceInfo } from 'matrix-js-sdk/src/crypto/deviceinfo';
21+
import { logger } from 'matrix-js-sdk/src/logger';
22+
import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
23+
24+
import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab';
25+
import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
26+
import {
27+
flushPromisesWithFakeTimers,
28+
getMockClientWithEventEmitter,
29+
mockClientMethodsUser,
30+
} from '../../../../../test-utils';
31+
32+
jest.useFakeTimers();
33+
34+
describe('<SessionManagerTab />', () => {
35+
const aliceId = '@alice:server.org';
36+
const deviceId = 'alices_device';
37+
38+
const alicesDevice = {
39+
device_id: deviceId,
40+
};
41+
const alicesMobileDevice = {
42+
device_id: 'alices_mobile_device',
43+
};
44+
45+
const mockCrossSigningInfo = {
46+
checkDeviceTrust: jest.fn(),
47+
};
48+
const mockClient = getMockClientWithEventEmitter({
49+
...mockClientMethodsUser(aliceId),
50+
getStoredCrossSigningForUser: jest.fn().mockReturnValue(mockCrossSigningInfo),
51+
getDevices: jest.fn(),
52+
getStoredDevice: jest.fn(),
53+
getDeviceId: jest.fn().mockReturnValue(deviceId),
54+
});
55+
56+
const defaultProps = {};
57+
const getComponent = (props = {}): React.ReactElement =>
58+
(
59+
<MatrixClientContext.Provider value={mockClient}>
60+
<SessionManagerTab {...defaultProps} {...props} />
61+
</MatrixClientContext.Provider>
62+
);
63+
64+
beforeEach(() => {
65+
jest.clearAllMocks();
66+
jest.spyOn(logger, 'error').mockRestore();
67+
mockClient.getDevices.mockResolvedValue({ devices: [] });
68+
mockClient.getStoredDevice.mockImplementation((_userId, id) => {
69+
const device = [alicesDevice, alicesMobileDevice].find(device => device.device_id === id);
70+
return device ? new DeviceInfo(device.device_id) : null;
71+
});
72+
mockCrossSigningInfo.checkDeviceTrust
73+
.mockReset()
74+
.mockReturnValue(new DeviceTrustLevel(false, false, false, false));
75+
});
76+
77+
it('renders spinner while devices load', () => {
78+
const { container } = render(getComponent());
79+
expect(container.getElementsByClassName('mx_Spinner').length).toBeTruthy();
80+
});
81+
82+
it('removes spinner when device fetch fails', async () => {
83+
mockClient.getDevices.mockRejectedValue({ httpStatus: 404 });
84+
const { container } = render(getComponent());
85+
expect(mockClient.getDevices).toHaveBeenCalled();
86+
87+
await act(async () => {
88+
await flushPromisesWithFakeTimers();
89+
});
90+
expect(container.getElementsByClassName('mx_Spinner').length).toBeFalsy();
91+
});
92+
93+
it('removes spinner when device fetch fails', async () => {
94+
// eat the expected error log
95+
jest.spyOn(logger, 'error').mockImplementation(() => {});
96+
mockClient.getDevices.mockRejectedValue({ httpStatus: 404 });
97+
const { container } = render(getComponent());
98+
99+
await act(async () => {
100+
await flushPromisesWithFakeTimers();
101+
});
102+
expect(container.getElementsByClassName('mx_Spinner').length).toBeFalsy();
103+
104+
// TODO when UI is done, check for error UI
105+
});
106+
107+
it('does not fail when checking device verification fails', async () => {
108+
const logSpy = jest.spyOn(logger, 'error').mockImplementation(() => {});
109+
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
110+
const noCryptoError = new Error("End-to-end encryption disabled");
111+
mockClient.getStoredDevice.mockImplementation(() => { throw noCryptoError; });
112+
render(getComponent());
113+
114+
await act(async () => {
115+
await flushPromisesWithFakeTimers();
116+
});
117+
118+
// called for each device despite error
119+
expect(mockClient.getStoredDevice).toHaveBeenCalledWith(aliceId, alicesDevice.device_id);
120+
expect(mockClient.getStoredDevice).toHaveBeenCalledWith(aliceId, alicesMobileDevice.device_id);
121+
expect(logSpy).toHaveBeenCalledWith('Error getting device cross-signing info', noCryptoError);
122+
// TODO when UI is done, check for error UI
123+
});
124+
125+
it('sets device verification status correctly', async () => {
126+
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
127+
mockCrossSigningInfo.checkDeviceTrust
128+
// alices device is trusted
129+
.mockReturnValueOnce(new DeviceTrustLevel(true, true, false, false))
130+
// alices mobile device is not
131+
.mockReturnValueOnce(new DeviceTrustLevel(false, false, false, false));
132+
133+
const { getByTestId } = render(getComponent());
134+
135+
await act(async () => {
136+
await flushPromisesWithFakeTimers();
137+
});
138+
139+
expect(mockCrossSigningInfo.checkDeviceTrust).toHaveBeenCalledTimes(2);
140+
expect(getByTestId(`device-tile-${alicesDevice.device_id}`)).toMatchSnapshot();
141+
});
142+
143+
it('renders current session section', async () => {
144+
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
145+
const noCryptoError = new Error("End-to-end encryption disabled");
146+
mockClient.getStoredDevice.mockImplementation(() => { throw noCryptoError; });
147+
const { getByTestId } = render(getComponent());
148+
149+
await act(async () => {
150+
await flushPromisesWithFakeTimers();
151+
});
152+
153+
expect(getByTestId('current-session-section')).toMatchSnapshot();
154+
});
155+
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<SessionManagerTab /> renders current session section 1`] = `
4+
<div
5+
class="mx_SettingsSubsection"
6+
data-testid="current-session-section"
7+
>
8+
<h3
9+
class="mx_Heading_h3 mx_SettingsSubsection_heading"
10+
>
11+
Current session
12+
</h3>
13+
<div
14+
class="mx_SettingsSubsection_content"
15+
>
16+
<div
17+
class="mx_DeviceTile"
18+
data-testid="device-tile-alices_device"
19+
>
20+
<div
21+
class="mx_DeviceTile_info"
22+
>
23+
<h4
24+
class="mx_Heading_h4"
25+
>
26+
alices_device
27+
</h4>
28+
<div
29+
class="mx_DeviceTile_metadata"
30+
>
31+
<span
32+
data-testid="device-metadata-isVerified"
33+
>
34+
Unverified
35+
</span>
36+
·
37+
·
38+
</div>
39+
</div>
40+
<div
41+
class="mx_DeviceTile_actions"
42+
/>
43+
</div>
44+
</div>
45+
</div>
46+
`;
47+
48+
exports[`<SessionManagerTab /> sets device verification status correctly 1`] = `
49+
<div
50+
class="mx_DeviceTile"
51+
data-testid="device-tile-alices_device"
52+
>
53+
<div
54+
class="mx_DeviceTile_info"
55+
>
56+
<h4
57+
class="mx_Heading_h4"
58+
>
59+
alices_device
60+
</h4>
61+
<div
62+
class="mx_DeviceTile_metadata"
63+
>
64+
<span
65+
data-testid="device-metadata-isVerified"
66+
>
67+
Verified
68+
</span>
69+
·
70+
·
71+
</div>
72+
</div>
73+
<div
74+
class="mx_DeviceTile_actions"
75+
/>
76+
</div>
77+
`;

0 commit comments

Comments
 (0)