diff --git a/res/css/components/views/settings/devices/_DeviceSecurityCard.pcss b/res/css/components/views/settings/devices/_DeviceSecurityCard.pcss
index 52d3acc011..2c267b4314 100644
--- a/res/css/components/views/settings/devices/_DeviceSecurityCard.pcss
+++ b/res/css/components/views/settings/devices/_DeviceSecurityCard.pcss
@@ -64,7 +64,7 @@ limitations under the License.
margin: 0 0 $spacing-4 0;
}
.mx_DeviceSecurityCard_description {
- margin: 0 0 $spacing-8 0;
+ margin: 0;
font-size: $font-12px;
color: $secondary-content;
}
diff --git a/res/css/components/views/settings/devices/_FilteredDeviceList.pcss b/res/css/components/views/settings/devices/_FilteredDeviceList.pcss
index 307241c78f..e0deb5546d 100644
--- a/res/css/components/views/settings/devices/_FilteredDeviceList.pcss
+++ b/res/css/components/views/settings/devices/_FilteredDeviceList.pcss
@@ -15,9 +15,45 @@ limitations under the License.
*/
.mx_FilteredDeviceList {
+ .mx_Dropdown {
+ flex: 1 0 80px;
+ }
+}
+
+.mx_FilteredDeviceList_header {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ box-sizing: border-box;
+
+ width: 100%;
+ height: 48px;
+ padding: 0 $spacing-16;
+ margin-bottom: $spacing-32;
+
+ background-color: $system;
+ border-radius: 8px;
+ color: $secondary-content;
+}
+
+.mx_FilteredDeviceList_headerLabel {
+ flex: 1 1 100%;
+}
+
+.mx_FilteredDeviceList_list {
list-style-type: none;
display: grid;
grid-gap: $spacing-16;
margin: 0;
padding: 0 $spacing-8;
}
+
+.mx_FilteredDeviceList_securityCard {
+ margin-bottom: $spacing-32;
+}
+
+.mx_FilteredDeviceList_noResults {
+ width: 100%;
+ text-align: center;
+ margin-bottom: $spacing-32;
+}
diff --git a/src/components/views/settings/devices/DeviceTile.tsx b/src/components/views/settings/devices/DeviceTile.tsx
index 3b553ac401..c791d2cd25 100644
--- a/src/components/views/settings/devices/DeviceTile.tsx
+++ b/src/components/views/settings/devices/DeviceTile.tsx
@@ -22,7 +22,7 @@ import { formatDate, formatRelativeTime } from "../../../../DateUtils";
import TooltipTarget from "../../elements/TooltipTarget";
import { Alignment } from "../../elements/Tooltip";
import Heading from "../../typography/Heading";
-import { INACTIVE_DEVICE_AGE_MS, isDeviceInactive } from "./filter";
+import { INACTIVE_DEVICE_AGE_DAYS, isDeviceInactive } from "./filter";
import { DeviceWithVerification } from "./types";
export interface DeviceTileProps {
device: DeviceWithVerification;
@@ -64,12 +64,11 @@ const getInactiveMetadata = (device: DeviceWithVerification): { id: string, valu
if (!isInactive) {
return undefined;
}
- const inactiveAgeDays = Math.round(INACTIVE_DEVICE_AGE_MS / MS_DAY);
return { id: 'inactive', value: (
<>
{
- _t('Inactive for %(inactiveAgeDays)s+ days', { inactiveAgeDays }) +
+ _t('Inactive for %(inactiveAgeDays)s+ days', { inactiveAgeDays: INACTIVE_DEVICE_AGE_DAYS }) +
` (${formatLastActivity(device.last_seen_ts)})`
}
>),
diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx
index 116066d78f..4fc8703161 100644
--- a/src/components/views/settings/devices/FilteredDeviceList.tsx
+++ b/src/components/views/settings/devices/FilteredDeviceList.tsx
@@ -16,40 +16,178 @@ limitations under the License.
import React from 'react';
+import { _t } from '../../../../languageHandler';
+import AccessibleButton from '../../elements/AccessibleButton';
+import Dropdown from '../../elements/Dropdown';
+import DeviceSecurityCard from './DeviceSecurityCard';
import DeviceTile from './DeviceTile';
-import { filterDevicesBySecurityRecommendation } from './filter';
-import { DevicesDictionary, DeviceWithVerification } from './types';
+import {
+ filterDevicesBySecurityRecommendation,
+ INACTIVE_DEVICE_AGE_DAYS,
+} from './filter';
+import {
+ DevicesDictionary,
+ DeviceSecurityVariation,
+ DeviceWithVerification,
+} from './types';
interface Props {
devices: DevicesDictionary;
+ filter?: DeviceSecurityVariation;
+ onFilterChange: (filter: DeviceSecurityVariation | undefined) => void;
}
// devices without timestamp metadata should be sorted last
const sortDevicesByLatestActivity = (left: DeviceWithVerification, right: DeviceWithVerification) =>
(right.last_seen_ts || 0) - (left.last_seen_ts || 0);
-const getFilteredSortedDevices = (devices: DevicesDictionary) =>
- filterDevicesBySecurityRecommendation(Object.values(devices), [])
+const getFilteredSortedDevices = (devices: DevicesDictionary, filter: DeviceSecurityVariation) =>
+ filterDevicesBySecurityRecommendation(Object.values(devices), filter ? [filter] : [])
.sort(sortDevicesByLatestActivity);
+const ALL_FILTER_ID = 'ALL';
+
+const FilterSecurityCard: React.FC<{ filter?: DeviceSecurityVariation | string }> = ({ filter }) => {
+ switch (filter) {
+ case DeviceSecurityVariation.Verified:
+ return
+
+
+ ;
+ case DeviceSecurityVariation.Unverified:
+ return
+
+
+ ;
+ case DeviceSecurityVariation.Inactive:
+ return
+
+
+ ;
+ default:
+ return null;
+ }
+};
+
+const getNoResultsMessage = (filter: DeviceSecurityVariation): string => {
+ switch (filter) {
+ case DeviceSecurityVariation.Verified:
+ return _t('No verified sessions found.');
+ case DeviceSecurityVariation.Unverified:
+ return _t('No unverified sessions found.');
+ case DeviceSecurityVariation.Inactive:
+ return _t('No inactive sessions found.');
+ default:
+ return _t('No sessions found.');
+ }
+};
+interface NoResultsProps { filter: DeviceSecurityVariation, clearFilter: () => void}
+const NoResults: React.FC = ({ filter, clearFilter }) =>
+
+ { getNoResultsMessage(filter) }
+ {
+ /* No clear filter button when filter is falsy (ie 'All') */
+ !!filter &&
+ <>
+
+
+ { _t('Show all') }
+
+ >
+ }
+
;
+
/**
* Filtered list of devices
* Sorted by latest activity descending
- * TODO(kerrya) Filtering to added as part of PSG-648
*/
-const FilteredDeviceList: React.FC = ({ devices }) => {
- const sortedDevices = getFilteredSortedDevices(devices);
+const FilteredDeviceList: React.FC = ({ devices, filter, onFilterChange }) => {
+ const sortedDevices = getFilteredSortedDevices(devices, filter);
- return
- { sortedDevices.map((device) =>
- -
-
-
,
+ const options = [
+ { id: ALL_FILTER_ID, label: _t('All') },
+ {
+ id: DeviceSecurityVariation.Verified,
+ label: _t('Verified'),
+ description: _t('Ready for secure messaging'),
+ },
+ {
+ id: DeviceSecurityVariation.Unverified,
+ label: _t('Unverified'),
+ description: _t('Not ready for secure messaging'),
+ },
+ {
+ id: DeviceSecurityVariation.Inactive,
+ label: _t('Inactive'),
+ description: _t(
+ 'Inactive for %(inactiveAgeDays)s days or longer',
+ { inactiveAgeDays: INACTIVE_DEVICE_AGE_DAYS },
+ ),
+ },
+ ];
- ) }
-
;
+ const onFilterOptionChange = (filterId: DeviceSecurityVariation | typeof ALL_FILTER_ID) => {
+ onFilterChange(filterId === ALL_FILTER_ID ? undefined : filterId as DeviceSecurityVariation);
+ };
+
+ return
+
+
+ { _t('Sessions') }
+
+
+ { options.map(({ id, label }) =>
+ { label }
,
+ ) }
+
+
+ { !!sortedDevices.length
+ ?
+ :
onFilterChange(undefined)} />
+ }
+
+ { sortedDevices.map((device) =>
+ -
+
+
,
+
+ ) }
+
+
+ ;
};
export default FilteredDeviceList;
diff --git a/src/components/views/settings/devices/SecurityRecommendations.tsx b/src/components/views/settings/devices/SecurityRecommendations.tsx
index fcb8f086c9..00181f5674 100644
--- a/src/components/views/settings/devices/SecurityRecommendations.tsx
+++ b/src/components/views/settings/devices/SecurityRecommendations.tsx
@@ -20,16 +20,19 @@ import { _t } from '../../../../languageHandler';
import AccessibleButton from '../../elements/AccessibleButton';
import SettingsSubsection from '../shared/SettingsSubsection';
import DeviceSecurityCard from './DeviceSecurityCard';
-import { filterDevicesBySecurityRecommendation, INACTIVE_DEVICE_AGE_MS } from './filter';
-import { DevicesDictionary, DeviceSecurityVariation } from './types';
+import { filterDevicesBySecurityRecommendation, INACTIVE_DEVICE_AGE_DAYS } from './filter';
+import {
+ DeviceSecurityVariation,
+ DeviceWithVerification,
+ DevicesDictionary,
+} from './types';
interface Props {
devices: DevicesDictionary;
}
-const MS_DAY = 24 * 60 * 60 * 1000;
const SecurityRecommendations: React.FC = ({ devices }) => {
- const devicesArray = Object.values(devices);
+ const devicesArray = Object.values(devices);
const unverifiedDevicesCount = filterDevicesBySecurityRecommendation(
devicesArray,
@@ -44,7 +47,7 @@ const SecurityRecommendations: React.FC = ({ devices }) => {
return null;
}
- const inactiveAgeDays = INACTIVE_DEVICE_AGE_MS / MS_DAY;
+ const inactiveAgeDays = INACTIVE_DEVICE_AGE_DAYS;
// TODO(kerrya) stubbed until PSG-640/652
const noop = () => {};
diff --git a/src/components/views/settings/devices/filter.ts b/src/components/views/settings/devices/filter.ts
index 302c66969b..ad2bc92152 100644
--- a/src/components/views/settings/devices/filter.ts
+++ b/src/components/views/settings/devices/filter.ts
@@ -18,7 +18,9 @@ import { DeviceWithVerification, DeviceSecurityVariation } from "./types";
type DeviceFilterCondition = (device: DeviceWithVerification) => boolean;
+const MS_DAY = 24 * 60 * 60 * 1000;
export const INACTIVE_DEVICE_AGE_MS = 7.776e+9; // 90 days
+export const INACTIVE_DEVICE_AGE_DAYS = INACTIVE_DEVICE_AGE_MS / MS_DAY;
export const isDeviceInactive: DeviceFilterCondition = device =>
!!device.last_seen_ts && device.last_seen_ts < Date.now() - INACTIVE_DEVICE_AGE_MS;
diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx
index 3a0a9b976b..ddd2293254 100644
--- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx
+++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { _t } from "../../../../../languageHandler";
import { useOwnDevices } from '../../devices/useOwnDevices';
@@ -22,10 +22,12 @@ import SettingsSubsection from '../../shared/SettingsSubsection';
import FilteredDeviceList from '../../devices/FilteredDeviceList';
import CurrentDeviceSection from '../../devices/CurrentDeviceSection';
import SecurityRecommendations from '../../devices/SecurityRecommendations';
+import { DeviceSecurityVariation } from '../../devices/types';
import SettingsTab from '../SettingsTab';
const SessionManagerTab: React.FC = () => {
const { devices, currentDeviceId, isLoading } = useOwnDevices();
+ const [filter, setFilter] = useState();
const { [currentDeviceId]: currentDevice, ...otherDevices } = devices;
const shouldShowOtherSessions = Object.keys(otherDevices).length > 0;
@@ -46,7 +48,11 @@ const SessionManagerTab: React.FC = () => {
)}
data-testid='other-sessions-section'
>
-
+
}
;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 2928afb553..efc1036039 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1708,13 +1708,26 @@
"This session is ready for secure messaging.": "This session is ready for secure messaging.",
"Unverified session": "Unverified session",
"Verify or sign out from this session for best security and reliability.": "Verify or sign out from this session for best security and reliability.",
- "Security recommendations": "Security recommendations",
- "Improve your account security by following these recommendations": "Improve your account security by following these recommendations",
+ "Verified sessions": "Verified sessions",
+ "For best security, sign out from any session that you don't recognize or use anymore.": "For best security, sign out from any session that you don't recognize or use anymore.",
"Unverified sessions": "Unverified sessions",
"Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.": "Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.",
- "View all": "View all",
"Inactive sessions": "Inactive sessions",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore",
+ "No verified sessions found.": "No verified sessions found.",
+ "No unverified sessions found.": "No unverified sessions found.",
+ "No inactive sessions found.": "No inactive sessions found.",
+ "No sessions found.": "No sessions found.",
+ "Show all": "Show all",
+ "All": "All",
+ "Ready for secure messaging": "Ready for secure messaging",
+ "Not ready for secure messaging": "Not ready for secure messaging",
+ "Inactive": "Inactive",
+ "Inactive for %(inactiveAgeDays)s days or longer": "Inactive for %(inactiveAgeDays)s days or longer",
+ "Filter devices": "Filter devices",
+ "Security recommendations": "Security recommendations",
+ "Improve your account security by following these recommendations": "Improve your account security by following these recommendations",
+ "View all": "View all",
"Unable to remove contact information": "Unable to remove contact information",
"Remove %(email)s?": "Remove %(email)s?",
"Invalid Email Address": "Invalid Email Address",
@@ -2234,7 +2247,6 @@
"Error decrypting video": "Error decrypting video",
"Error processing voice message": "Error processing voice message",
"Add reaction": "Add reaction",
- "Show all": "Show all",
"Reactions": "Reactions",
"%(reactors)s reacted with %(content)s": "%(reactors)s reacted with %(content)s",
"reacted with %(shortName)s": "reacted with %(shortName)s",
diff --git a/test/components/views/settings/DevicesPanel-test.tsx b/test/components/views/settings/DevicesPanel-test.tsx
index e03274c0ae..d9a66ab7bd 100644
--- a/test/components/views/settings/DevicesPanel-test.tsx
+++ b/test/components/views/settings/DevicesPanel-test.tsx
@@ -128,7 +128,7 @@ describe('', () => {
await flushPromises();
// modal rendering has some weird sleeps
- await sleep(10);
+ await sleep(100);
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith([device2.device_id], undefined);
diff --git a/test/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/components/views/settings/devices/FilteredDeviceList-test.tsx
index 3545e0b261..f814a6d703 100644
--- a/test/components/views/settings/devices/FilteredDeviceList-test.tsx
+++ b/test/components/views/settings/devices/FilteredDeviceList-test.tsx
@@ -15,25 +15,39 @@ limitations under the License.
*/
import React from 'react';
-import { render } from '@testing-library/react';
+import { act, fireEvent, render } from '@testing-library/react';
import FilteredDeviceList from '../../../../../src/components/views/settings/devices/FilteredDeviceList';
+import { DeviceSecurityVariation } from '../../../../../src/components/views/settings/devices/types';
+import { flushPromises, mockPlatformPeg } from '../../../../test-utils';
+mockPlatformPeg();
+
+const MS_DAY = 86400000;
describe('', () => {
- const noMetaDevice = { device_id: 'no-meta-device', isVerified: true };
- const oldDevice = { device_id: 'old', last_seen_ts: new Date(1993, 7, 3, 4).getTime(), isVerified: true };
const newDevice = {
device_id: 'new',
- last_seen_ts: new Date().getTime() - 500,
+ last_seen_ts: Date.now() - 500,
last_seen_ip: '123.456.789',
display_name: 'My Device',
isVerified: true,
};
+ const unverifiedNoMetadata = { device_id: 'unverified-no-metadata', isVerified: false };
+ const verifiedNoMetadata = { device_id: 'verified-no-metadata', isVerified: true };
+ const hundredDaysOld = { device_id: '100-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 100) };
+ const hundredDaysOldUnverified = {
+ device_id: 'unverified-100-days-old',
+ isVerified: false,
+ last_seen_ts: Date.now() - (MS_DAY * 100),
+ };
const defaultProps = {
+ onFilterChange: jest.fn(),
devices: {
- [noMetaDevice.device_id]: noMetaDevice,
- [oldDevice.device_id]: oldDevice,
+ [unverifiedNoMetadata.device_id]: unverifiedNoMetadata,
+ [verifiedNoMetadata.device_id]: verifiedNoMetadata,
[newDevice.device_id]: newDevice,
+ [hundredDaysOld.device_id]: hundredDaysOld,
+ [hundredDaysOldUnverified.device_id]: hundredDaysOldUnverified,
},
};
const getComponent = (props = {}) =>
@@ -43,14 +57,16 @@ describe('', () => {
const { container } = render(getComponent());
const tiles = container.querySelectorAll('.mx_DeviceTile');
expect(tiles[0].getAttribute('data-testid')).toEqual(`device-tile-${newDevice.device_id}`);
- expect(tiles[1].getAttribute('data-testid')).toEqual(`device-tile-${oldDevice.device_id}`);
- expect(tiles[2].getAttribute('data-testid')).toEqual(`device-tile-${noMetaDevice.device_id}`);
+ expect(tiles[1].getAttribute('data-testid')).toEqual(`device-tile-${hundredDaysOld.device_id}`);
+ expect(tiles[2].getAttribute('data-testid')).toEqual(`device-tile-${hundredDaysOldUnverified.device_id}`);
+ expect(tiles[3].getAttribute('data-testid')).toEqual(`device-tile-${unverifiedNoMetadata.device_id}`);
+ expect(tiles[4].getAttribute('data-testid')).toEqual(`device-tile-${verifiedNoMetadata.device_id}`);
});
it('updates list order when devices change', () => {
- const updatedOldDevice = { ...oldDevice, last_seen_ts: new Date().getTime() };
+ const updatedOldDevice = { ...hundredDaysOld, last_seen_ts: new Date().getTime() };
const updatedDevices = {
- [oldDevice.device_id]: updatedOldDevice,
+ [hundredDaysOld.device_id]: updatedOldDevice,
[newDevice.device_id]: newDevice,
};
const { container, rerender } = render(getComponent());
@@ -59,7 +75,108 @@ describe('', () => {
const tiles = container.querySelectorAll('.mx_DeviceTile');
expect(tiles.length).toBe(2);
- expect(tiles[0].getAttribute('data-testid')).toEqual(`device-tile-${oldDevice.device_id}`);
+ expect(tiles[0].getAttribute('data-testid')).toEqual(`device-tile-${hundredDaysOld.device_id}`);
expect(tiles[1].getAttribute('data-testid')).toEqual(`device-tile-${newDevice.device_id}`);
});
+
+ it('displays no results message when there are no devices', () => {
+ const { container } = render(getComponent({ devices: {} }));
+
+ expect(container.getElementsByClassName('mx_FilteredDeviceList_noResults')).toMatchSnapshot();
+ });
+
+ describe('filtering', () => {
+ const setFilter = async (
+ container: HTMLElement,
+ option: DeviceSecurityVariation | string,
+ ) => await act(async () => {
+ const dropdown = container.querySelector('[aria-label="Filter devices"]');
+
+ fireEvent.click(dropdown);
+ // tick to let dropdown render
+ await flushPromises();
+
+ fireEvent.click(container.querySelector(`#device-list-filter__${option}`));
+ });
+
+ it('does not display filter description when filter is falsy', () => {
+ const { container } = render(getComponent({ filter: undefined }));
+ const tiles = container.querySelectorAll('.mx_DeviceTile');
+ expect(container.getElementsByClassName('mx_FilteredDeviceList_securityCard').length).toBeFalsy();
+ expect(tiles.length).toEqual(5);
+ });
+
+ it('updates filter when prop changes', () => {
+ const { container, rerender } = render(getComponent({ filter: DeviceSecurityVariation.Verified }));
+ const tiles = container.querySelectorAll('.mx_DeviceTile');
+ expect(tiles.length).toEqual(3);
+ expect(tiles[0].getAttribute('data-testid')).toEqual(`device-tile-${newDevice.device_id}`);
+ expect(tiles[1].getAttribute('data-testid')).toEqual(`device-tile-${hundredDaysOld.device_id}`);
+ expect(tiles[2].getAttribute('data-testid')).toEqual(`device-tile-${verifiedNoMetadata.device_id}`);
+
+ rerender(getComponent({ filter: DeviceSecurityVariation.Inactive }));
+
+ const rerenderedTiles = container.querySelectorAll('.mx_DeviceTile');
+ expect(rerenderedTiles.length).toEqual(2);
+ expect(rerenderedTiles[0].getAttribute('data-testid')).toEqual(`device-tile-${hundredDaysOld.device_id}`);
+ expect(rerenderedTiles[1].getAttribute('data-testid')).toEqual(
+ `device-tile-${hundredDaysOldUnverified.device_id}`,
+ );
+ });
+
+ it('calls onFilterChange handler', async () => {
+ const onFilterChange = jest.fn();
+ const { container } = render(getComponent({ onFilterChange }));
+ await setFilter(container, DeviceSecurityVariation.Verified);
+
+ expect(onFilterChange).toHaveBeenCalledWith(DeviceSecurityVariation.Verified);
+ });
+
+ it('calls onFilterChange handler correctly when setting filter to All', async () => {
+ const onFilterChange = jest.fn();
+ const { container } = render(getComponent({ onFilterChange, filter: DeviceSecurityVariation.Verified }));
+ await setFilter(container, 'ALL');
+
+ // filter is cleared
+ expect(onFilterChange).toHaveBeenCalledWith(undefined);
+ });
+
+ it.each([
+ [DeviceSecurityVariation.Verified, [newDevice, hundredDaysOld, verifiedNoMetadata]],
+ [DeviceSecurityVariation.Unverified, [hundredDaysOldUnverified, unverifiedNoMetadata]],
+ [DeviceSecurityVariation.Inactive, [hundredDaysOld, hundredDaysOldUnverified]],
+ ])('filters correctly for %s', (filter, expectedDevices) => {
+ const { container } = render(getComponent({ filter }));
+ expect(container.getElementsByClassName('mx_FilteredDeviceList_securityCard')).toMatchSnapshot();
+ const tileDeviceIds = [...container.querySelectorAll('.mx_DeviceTile')]
+ .map(tile => tile.getAttribute('data-testid'));
+ expect(tileDeviceIds).toEqual(expectedDevices.map(device => `device-tile-${device.device_id}`));
+ });
+
+ it.each([
+ [DeviceSecurityVariation.Verified],
+ [DeviceSecurityVariation.Unverified],
+ [DeviceSecurityVariation.Inactive],
+ ])('renders no results correctly for %s', (filter) => {
+ const { container } = render(getComponent({ filter, devices: {} }));
+ expect(container.getElementsByClassName('mx_FilteredDeviceList_securityCard').length).toBeFalsy();
+ expect(container.getElementsByClassName('mx_FilteredDeviceList_noResults')).toMatchSnapshot();
+ });
+
+ it('clears filter from no results message', () => {
+ const onFilterChange = jest.fn();
+ const { getByTestId } = render(getComponent({
+ onFilterChange,
+ filter: DeviceSecurityVariation.Verified,
+ devices: {
+ [unverifiedNoMetadata.device_id]: unverifiedNoMetadata,
+ },
+ }));
+ act(() => {
+ fireEvent.click(getByTestId('devices-clear-filter-btn'));
+ });
+
+ expect(onFilterChange).toHaveBeenCalledWith(undefined);
+ });
+ });
});
diff --git a/test/components/views/settings/devices/__snapshots__/FilteredDeviceList-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/FilteredDeviceList-test.tsx.snap
new file mode 100644
index 0000000000..c0f5b9af98
--- /dev/null
+++ b/test/components/views/settings/devices/__snapshots__/FilteredDeviceList-test.tsx.snap
@@ -0,0 +1,173 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` displays no results message when there are no devices 1`] = `
+HTMLCollection [
+
+ No sessions found.
+
,
+]
+`;
+
+exports[` filtering filters correctly for Inactive 1`] = `
+HTMLCollection [
+
+
+
+
+
+ Inactive sessions
+
+
+ Consider signing out from old sessions (90 days or older) you don't use anymore
+
+
+
+
,
+]
+`;
+
+exports[` filtering filters correctly for Unverified 1`] = `
+HTMLCollection [
+
+
+
+
+
+ Unverified sessions
+
+
+ Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.
+
+
+
+
,
+]
+`;
+
+exports[` filtering filters correctly for Verified 1`] = `
+HTMLCollection [
+
+
+
+
+
+ Verified sessions
+
+
+ For best security, sign out from any session that you don't recognize or use anymore.
+
+
+
+
,
+]
+`;
+
+exports[` filtering renders no results correctly for Inactive 1`] = `
+HTMLCollection [
+
+ No inactive sessions found.
+
+
+ Show all
+
+
,
+]
+`;
+
+exports[` filtering renders no results correctly for Unverified 1`] = `
+HTMLCollection [
+
+ No unverified sessions found.
+
+
+ Show all
+
+
,
+]
+`;
+
+exports[` filtering renders no results correctly for Verified 1`] = `
+HTMLCollection [
+
+ No verified sessions found.
+
+
+ Show all
+
+
,
+]
+`;