From 3e11fad2bd336d36cc6237623cee7789f3e599f7 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 30 Aug 2022 18:55:23 +0200 Subject: [PATCH 1/6] scroll to filtered list from security recommendations --- .../settings/devices/FilteredDeviceList.tsx | 14 ++++---- .../devices/SecurityRecommendations.tsx | 15 ++++---- .../settings/tabs/user/SessionManagerTab.tsx | 26 ++++++++++++-- .../devices/FilteredDeviceList-test.tsx | 2 +- .../devices/SecurityRecommendations-test.tsx | 36 ++++++++++++++++++- .../SecurityRecommendations-test.tsx.snap | 6 ++++ 6 files changed, 80 insertions(+), 19 deletions(-) diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx index 5687cdf0af1..16001e6f6dc 100644 --- a/src/components/views/settings/devices/FilteredDeviceList.tsx +++ b/src/components/views/settings/devices/FilteredDeviceList.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, { forwardRef } from 'react'; import { _t } from '../../../../languageHandler'; import AccessibleButton from '../../elements/AccessibleButton'; @@ -150,13 +150,13 @@ const DeviceListItem: React.FC<{ * Filtered list of devices * Sorted by latest activity descending */ -const FilteredDeviceList: React.FC = ({ +export const FilteredDeviceList: React.FC = forwardRef(({ devices, filter, expandedDeviceIds, onFilterChange, onDeviceExpandToggle, -}) => { +}, ref: React.RefObject) => { const sortedDevices = getFilteredSortedDevices(devices, filter); const options: FilterDropdownOption[] = [ @@ -185,7 +185,7 @@ const FilteredDeviceList: React.FC = ({ onFilterChange(filterId === ALL_FILTER_ID ? undefined : filterId as DeviceSecurityVariation); }; - return
+ return
{ _t('Sessions') } @@ -212,8 +212,6 @@ const FilteredDeviceList: React.FC = ({ />, ) } -
- ; -}; +
; +}); -export default FilteredDeviceList; diff --git a/src/components/views/settings/devices/SecurityRecommendations.tsx b/src/components/views/settings/devices/SecurityRecommendations.tsx index 00181f5674a..82eb49fd474 100644 --- a/src/components/views/settings/devices/SecurityRecommendations.tsx +++ b/src/components/views/settings/devices/SecurityRecommendations.tsx @@ -29,9 +29,13 @@ import { interface Props { devices: DevicesDictionary; + goToFilteredList: (filter: DeviceSecurityVariation) => void; } -const SecurityRecommendations: React.FC = ({ devices }) => { +const SecurityRecommendations: React.FC = ({ + devices, + goToFilteredList, +}) => { const devicesArray = Object.values(devices); const unverifiedDevicesCount = filterDevicesBySecurityRecommendation( @@ -49,9 +53,6 @@ const SecurityRecommendations: React.FC = ({ devices }) => { const inactiveAgeDays = INACTIVE_DEVICE_AGE_DAYS; - // TODO(kerrya) stubbed until PSG-640/652 - const noop = () => {}; - return = ({ devices }) => { > goToFilteredList(DeviceSecurityVariation.Unverified)} + data-testid='unverified-devices-cta' > { _t('View all') + ` (${unverifiedDevicesCount})` } @@ -90,7 +92,8 @@ const SecurityRecommendations: React.FC = ({ devices }) => { > goToFilteredList(DeviceSecurityVariation.Inactive)} + data-testid='inactive-devices-cta' > { _t('View all') + ` (${inactiveDevicesCount})` } diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index c4878dbb372..c5445a82a63 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { _t } from "../../../../../languageHandler"; import { useOwnDevices } from '../../devices/useOwnDevices'; import SettingsSubsection from '../../shared/SettingsSubsection'; -import FilteredDeviceList from '../../devices/FilteredDeviceList'; +import { FilteredDeviceList } from '../../devices/FilteredDeviceList'; import CurrentDeviceSection from '../../devices/CurrentDeviceSection'; import SecurityRecommendations from '../../devices/SecurityRecommendations'; import { DeviceSecurityVariation, DeviceWithVerification } from '../../devices/types'; @@ -29,6 +29,8 @@ const SessionManagerTab: React.FC = () => { const { devices, currentDeviceId, isLoading } = useOwnDevices(); const [filter, setFilter] = useState(); const [expandedDeviceIds, setExpandedDeviceIds] = useState([]); + const filteredDeviceListRef = useRef(); + const scrollIntoViewTimeoutRef = useRef>(); const onDeviceExpandToggle = (deviceId: DeviceWithVerification['device_id']): void => { if (expandedDeviceIds.includes(deviceId)) { @@ -38,11 +40,28 @@ const SessionManagerTab: React.FC = () => { } }; + console.log('hhh', { filteredDeviceListRef }); + + const onGoToFilteredList = (filter: DeviceSecurityVariation) => { + setFilter(filter); + // @TODO(kerrya) clear selection when added in PSG-659 + if (filteredDeviceListRef.current) { + clearTimeout(scrollIntoViewTimeoutRef.current); + // wait a tick for the filtered section to rerender with different height + scrollIntoViewTimeoutRef.current = + window.setTimeout(() => filteredDeviceListRef.current.scrollIntoView(true)); + } + }; + const { [currentDeviceId]: currentDevice, ...otherDevices } = devices; const shouldShowOtherSessions = Object.keys(otherDevices).length > 0; + useEffect(() => () => { + clearTimeout(scrollIntoViewTimeoutRef.current); + }, [scrollIntoViewTimeoutRef]); + return - + { expandedDeviceIds={expandedDeviceIds} onFilterChange={setFilter} onDeviceExpandToggle={onDeviceExpandToggle} + ref={filteredDeviceListRef} /> } diff --git a/test/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/components/views/settings/devices/FilteredDeviceList-test.tsx index 68ef60e76b5..ef5ae5b18b4 100644 --- a/test/components/views/settings/devices/FilteredDeviceList-test.tsx +++ b/test/components/views/settings/devices/FilteredDeviceList-test.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import { act, fireEvent, render } from '@testing-library/react'; -import FilteredDeviceList from '../../../../../src/components/views/settings/devices/FilteredDeviceList'; +import { FilteredDeviceList } from '../../../../../src/components/views/settings/devices/FilteredDeviceList'; import { DeviceSecurityVariation } from '../../../../../src/components/views/settings/devices/types'; import { flushPromises, mockPlatformPeg } from '../../../../test-utils'; diff --git a/test/components/views/settings/devices/SecurityRecommendations-test.tsx b/test/components/views/settings/devices/SecurityRecommendations-test.tsx index b54745545b7..e589a995c59 100644 --- a/test/components/views/settings/devices/SecurityRecommendations-test.tsx +++ b/test/components/views/settings/devices/SecurityRecommendations-test.tsx @@ -15,9 +15,10 @@ limitations under the License. */ import React from 'react'; -import { render } from '@testing-library/react'; +import { act, fireEvent, render } from '@testing-library/react'; import SecurityRecommendations from '../../../../../src/components/views/settings/devices/SecurityRecommendations'; +import { DeviceSecurityVariation } from '../../../../../src/components/views/settings/devices/types'; const MS_DAY = 24 * 60 * 60 * 1000; describe('', () => { @@ -32,6 +33,7 @@ describe('', () => { const defaultProps = { devices: {}, + goToFilteredList: jest.fn(), }; const getComponent = (props = {}) => (); @@ -69,4 +71,36 @@ describe('', () => { const { container } = render(getComponent({ devices })); expect(container).toMatchSnapshot(); }); + + it('clicking view all unverified devices button works', () => { + const goToFilteredList = jest.fn(); + const devices = { + [verifiedNoMetadata.device_id]: verifiedNoMetadata, + [hundredDaysOld.device_id]: hundredDaysOld, + [unverifiedNoMetadata.device_id]: unverifiedNoMetadata, + }; + const { getByTestId } = render(getComponent({ devices, goToFilteredList })); + + act(() => { + fireEvent.click(getByTestId('unverified-devices-cta')); + }); + + expect(goToFilteredList).toHaveBeenCalledWith(DeviceSecurityVariation.Unverified); + }); + + it('clicking view all inactive devices button works', () => { + const goToFilteredList = jest.fn(); + const devices = { + [verifiedNoMetadata.device_id]: verifiedNoMetadata, + [hundredDaysOld.device_id]: hundredDaysOld, + [unverifiedNoMetadata.device_id]: unverifiedNoMetadata, + }; + const { getByTestId } = render(getComponent({ devices, goToFilteredList })); + + act(() => { + fireEvent.click(getByTestId('inactive-devices-cta')); + }); + + expect(goToFilteredList).toHaveBeenCalledWith(DeviceSecurityVariation.Inactive); + }); }); diff --git a/test/components/views/settings/devices/__snapshots__/SecurityRecommendations-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/SecurityRecommendations-test.tsx.snap index 0ee9870b780..b122d918262 100644 --- a/test/components/views/settings/devices/__snapshots__/SecurityRecommendations-test.tsx.snap +++ b/test/components/views/settings/devices/__snapshots__/SecurityRecommendations-test.tsx.snap @@ -48,6 +48,7 @@ exports[` renders both cards when user has both unver >