diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 6515bd90d30..3f23e6de572 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -28,6 +28,7 @@ @import "./components/views/messages/_MBeaconBody.pcss"; @import "./components/views/messages/shared/_MediaProcessingError.pcss"; @import "./components/views/settings/devices/_DeviceDetails.pcss"; +@import "./components/views/settings/devices/_DeviceExpandDetailsButton.pcss"; @import "./components/views/settings/devices/_DeviceSecurityCard.pcss"; @import "./components/views/settings/devices/_DeviceTile.pcss"; @import "./components/views/settings/devices/_FilteredDeviceList.pcss"; @@ -333,12 +334,12 @@ @import "./views/toasts/_IncomingCallToast.pcss"; @import "./views/toasts/_NonUrgentEchoFailureToast.pcss"; @import "./views/typography/_Heading.pcss"; +@import "./views/user-onboarding/_UserOnboardingButton.pcss"; @import "./views/user-onboarding/_UserOnboardingFeedback.pcss"; @import "./views/user-onboarding/_UserOnboardingHeader.pcss"; @import "./views/user-onboarding/_UserOnboardingList.pcss"; @import "./views/user-onboarding/_UserOnboardingPage.pcss"; @import "./views/user-onboarding/_UserOnboardingTask.pcss"; -@import "./views/user-onboarding/_UserOnboardingButton.pcss"; @import "./views/verification/_VerificationShowSas.pcss"; @import "./views/voip/CallView/_CallViewButtons.pcss"; @import "./views/voip/_CallPreview.pcss"; diff --git a/res/css/components/views/settings/devices/_DeviceDetails.pcss b/res/css/components/views/settings/devices/_DeviceDetails.pcss index df1341dbb17..76cacfa1c95 100644 --- a/res/css/components/views/settings/devices/_DeviceDetails.pcss +++ b/res/css/components/views/settings/devices/_DeviceDetails.pcss @@ -17,18 +17,20 @@ limitations under the License. .mx_DeviceDetails { display: flex; flex-direction: column; + box-sizing: border-box; width: 100%; + margin-top: $spacing-16; padding: $spacing-16; border-radius: 8px; - border: 1px solid $system; + border: 1px solid $quinary-content; } .mx_DeviceDetails_section { padding-bottom: $spacing-16; margin-bottom: $spacing-16; - border-bottom: 1px solid $system; + border-bottom: 1px solid $quinary-content; &:last-child { padding-bottom: 0; diff --git a/res/css/components/views/settings/devices/_DeviceExpandDetailsButton.pcss b/res/css/components/views/settings/devices/_DeviceExpandDetailsButton.pcss new file mode 100644 index 00000000000..4c9d787fdbe --- /dev/null +++ b/res/css/components/views/settings/devices/_DeviceExpandDetailsButton.pcss @@ -0,0 +1,41 @@ +/* +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. +*/ + +.mx_DeviceExpandDetailsButton { + height: 32px; + width: 32px; + background: transparent; + + border-radius: 4px; + color: $secondary-content; + + --icon-transform: rotate(-90deg); +} + +.mx_DeviceExpandDetailsButton.mx_DeviceExpandDetailsButton_expanded { + --icon-transform: rotate(0deg); + + background: $system; +} + +.mx_DeviceExpandDetailsButton_icon { + height: 12px; + width: 12px; + + transition: all 0.3s; + transform: var(--icon-transform); + transform-origin: center; +} diff --git a/res/css/views/elements/_AccessibleButton.pcss b/res/css/views/elements/_AccessibleButton.pcss index 7d01c17e125..8718d862337 100644 --- a/res/css/views/elements/_AccessibleButton.pcss +++ b/res/css/views/elements/_AccessibleButton.pcss @@ -76,6 +76,12 @@ limitations under the License. mask-image: url('$(res)/img/feather-customised/x.svg'); } } + + &.mx_AccessibleButton_kind_icon { + padding: 0; + height: 32px; + width: 32px; + } } &.mx_AccessibleButton_kind_primary, diff --git a/res/img/feather-customised/dropdown-arrow.svg b/res/img/feather-customised/dropdown-arrow.svg index a1d46fa61a6..24645d2bbaa 100644 --- a/res/img/feather-customised/dropdown-arrow.svg +++ b/res/img/feather-customised/dropdown-arrow.svg @@ -1,5 +1,5 @@ - + diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index f54a8d4bff5..a2337444cfa 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -33,7 +33,8 @@ type AccessibleButtonKind = | 'primary' | 'link_inline' | 'link_sm' | 'confirm_sm' - | 'cancel_sm'; + | 'cancel_sm' + | 'icon'; /** * This type construct allows us to specifically pass those props down to the element we’re creating that the element diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx new file mode 100644 index 00000000000..c0826d54122 --- /dev/null +++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx @@ -0,0 +1,74 @@ +/* +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 React, { useState } from 'react'; + +import { _t } from '../../../../languageHandler'; +import Spinner from '../../elements/Spinner'; +import SettingsSubsection from '../shared/SettingsSubsection'; +import DeviceDetails from './DeviceDetails'; +import DeviceExpandDetailsButton from './DeviceExpandDetailsButton'; +import DeviceSecurityCard from './DeviceSecurityCard'; +import DeviceTile from './DeviceTile'; +import { + DeviceSecurityVariation, + DeviceWithVerification, +} from './types'; + +interface Props { + device?: DeviceWithVerification; + isLoading: boolean; +} + +const CurrentDeviceSection: React.FC = ({ + device, isLoading, +}) => { + const [isExpanded, setIsExpanded] = useState(false); + const securityCardProps = device?.isVerified ? { + variation: DeviceSecurityVariation.Verified, + heading: _t('Verified session'), + description: _t('This session is ready for secure messaging.'), + } : { + variation: DeviceSecurityVariation.Unverified, + heading: _t('Unverified session'), + description: _t('Verify or sign out from this session for best security and reliability.'), + }; + return + { isLoading && } + { !!device && <> + + setIsExpanded(!isExpanded)} + /> + + { isExpanded && } +
+ + + } + ; +}; + +export default CurrentDeviceSection; diff --git a/src/components/views/settings/devices/DeviceExpandDetailsButton.tsx b/src/components/views/settings/devices/DeviceExpandDetailsButton.tsx new file mode 100644 index 00000000000..57a8c35e940 --- /dev/null +++ b/src/components/views/settings/devices/DeviceExpandDetailsButton.tsx @@ -0,0 +1,41 @@ +/* +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 classNames from 'classnames'; +import React from 'react'; + +import { Icon as CaretIcon } from '../../../../../res/img/feather-customised/dropdown-arrow.svg'; +import AccessibleButton from '../../elements/AccessibleButton'; + +interface Props { + isExpanded: boolean; + onClick: () => void; +} + +const DeviceExpandDetailsButton: React.FC = ({ isExpanded, onClick, ...rest }) => { + return + + ; +}; + +export default DeviceExpandDetailsButton; diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index a7201e360aa..3a0a9b976be 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -17,13 +17,10 @@ limitations under the License. import React from 'react'; import { _t } from "../../../../../languageHandler"; -import Spinner from '../../../elements/Spinner'; import { useOwnDevices } from '../../devices/useOwnDevices'; -import DeviceTile from '../../devices/DeviceTile'; -import DeviceSecurityCard from '../../devices/DeviceSecurityCard'; import SettingsSubsection from '../../shared/SettingsSubsection'; import FilteredDeviceList from '../../devices/FilteredDeviceList'; -import { DeviceSecurityVariation } from '../../devices/types'; +import CurrentDeviceSection from '../../devices/CurrentDeviceSection'; import SecurityRecommendations from '../../devices/SecurityRecommendations'; import SettingsTab from '../SettingsTab'; @@ -33,34 +30,12 @@ const SessionManagerTab: React.FC = () => { const { [currentDeviceId]: currentDevice, ...otherDevices } = devices; const shouldShowOtherSessions = Object.keys(otherDevices).length > 0; - const securityCardProps = currentDevice?.isVerified ? { - variation: DeviceSecurityVariation.Verified, - heading: _t('Verified session'), - description: _t('This session is ready for secure messaging.'), - } : { - variation: DeviceSecurityVariation.Unverified, - heading: _t('Unverified session'), - description: _t('Verify or sign out from this session for best security and reliability.'), - }; - return - - { isLoading && } - { !!currentDevice && <> - -
- - - } -
+ { shouldShowOtherSessions && ', () => { + const deviceId = 'alices_device'; + + const alicesVerifiedDevice = { + device_id: deviceId, + isVerified: false, + }; + const alicesUnverifiedDevice = { + device_id: deviceId, + isVerified: false, + }; + + const defaultProps = { + device: alicesVerifiedDevice, + isLoading: false, + }; + const getComponent = (props = {}): React.ReactElement => + (); + + it('renders spinner while device is loading', () => { + const { container } = render(getComponent({ device: undefined, isLoading: true })); + expect(container.getElementsByClassName('mx_Spinner').length).toBeTruthy(); + }); + + it('handles when device is falsy', async () => { + const { container } = render(getComponent({ device: undefined })); + expect(container).toMatchSnapshot(); + }); + + it('renders device and correct security card when device is verified', () => { + const { container } = render(getComponent()); + expect(container).toMatchSnapshot(); + }); + + it('renders device and correct security card when device is unverified', () => { + const { container } = render(getComponent({ device: alicesUnverifiedDevice })); + expect(container).toMatchSnapshot(); + }); + + it('displays device details on toggle click', () => { + const { container, getByTestId } = render(getComponent({ device: alicesUnverifiedDevice })); + + act(() => { + fireEvent.click(getByTestId('current-session-toggle-details')); + }); + + expect(container.getElementsByClassName('mx_DeviceDetails')).toMatchSnapshot(); + + act(() => { + fireEvent.click(getByTestId('current-session-toggle-details')); + }); + + // device details are hidden + expect(container.getElementsByClassName('mx_DeviceDetails').length).toBeFalsy(); + }); +}); diff --git a/test/components/views/settings/devices/DeviceExpandDetailsButton-test.tsx b/test/components/views/settings/devices/DeviceExpandDetailsButton-test.tsx new file mode 100644 index 00000000000..3d790e43c2d --- /dev/null +++ b/test/components/views/settings/devices/DeviceExpandDetailsButton-test.tsx @@ -0,0 +1,47 @@ +/* +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 React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import DeviceExpandDetailsButton from '../../../../../src/components/views/settings/devices/DeviceExpandDetailsButton'; + +describe('', () => { + const defaultProps = { + isExpanded: false, + onClick: jest.fn(), + }; + const getComponent = (props = {}) => + ; + + it('renders when not expanded', () => { + const { container } = render(getComponent()); + expect({ container }).toMatchSnapshot(); + }); + + it('renders when expanded', () => { + const { container } = render(getComponent({ isExpanded: true })); + expect({ container }).toMatchSnapshot(); + }); + + it('calls onClick', () => { + const onClick = jest.fn(); + const { getByTestId } = render(getComponent({ 'data-testid': 'test', onClick })); + fireEvent.click(getByTestId('test')); + + expect(onClick).toHaveBeenCalled(); + }); +}); diff --git a/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap new file mode 100644 index 00000000000..d7ea1baa7d8 --- /dev/null +++ b/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap @@ -0,0 +1,267 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` displays device details on toggle click 1`] = ` +HTMLCollection [ +
+
+

+ alices_device +

+
+
+

+ Session details +

+ + + + + + + + + + +
+ Session ID + + alices_device +
+ Last activity + +
+ + + + + + + + + + + +
+ Device +
+ IP address + +
+
+
, +] +`; + +exports[` handles when device is falsy 1`] = ` +
+
+

+ Current session +

+
+
+
+`; + +exports[` renders device and correct security card when device is unverified 1`] = ` +
+
+

+ Current session +

+
+
+
+

+ alices_device +

+ +
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Unverified session +

+

+ Verify or sign out from this session for best security and reliability. +

+
+
+
+
+
+`; + +exports[` renders device and correct security card when device is verified 1`] = ` +
+
+

+ Current session +

+
+
+
+

+ alices_device +

+ +
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Unverified session +

+

+ Verify or sign out from this session for best security and reliability. +

+
+
+
+
+
+`; diff --git a/test/components/views/settings/devices/__snapshots__/DeviceExpandDetailsButton-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/DeviceExpandDetailsButton-test.tsx.snap new file mode 100644 index 00000000000..bcbc2c8592d --- /dev/null +++ b/test/components/views/settings/devices/__snapshots__/DeviceExpandDetailsButton-test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders when expanded 1`] = ` +Object { + "container":
+
+
+
+
, +} +`; + +exports[` renders when not expanded 1`] = ` +Object { + "container":
+
+
+
+
, +} +`; diff --git a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap index 2e56729eadc..bb2094aa53d 100644 --- a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap +++ b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap @@ -39,7 +39,18 @@ exports[` renders current session section with a verified s
+ > +
+
+
+

renders current session section with an unverifie
+ > +
+
+
+

sets device verification status correctly 1`] = `
+ > +
+
+
+
`;