From 9ae0c8e3e3524beb06b760b82b91aedff03c992d Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 19 Sep 2024 15:20:19 -0700 Subject: [PATCH 1/8] Refactoring image upload button onto avatar --- src/components/Profile/ProfileImageField.jsx | 88 +++++++++++++------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/src/components/Profile/ProfileImageField.jsx b/src/components/Profile/ProfileImageField.jsx index 7f4e062e4..47afc6880 100644 --- a/src/components/Profile/ProfileImageField.jsx +++ b/src/components/Profile/ProfileImageField.jsx @@ -5,8 +5,8 @@ import { useNotification, useSession } from '@hooks'; // Material UI Imports import Avatar from '@mui/material/Avatar'; import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; import HideImageIcon from '@mui/icons-material/HideImage'; +import IconButton from '@mui/material/IconButton'; import ImageIcon from '@mui/icons-material/Image'; // Contexts Imports import { SignedInUserContext } from '@contexts'; @@ -34,6 +34,7 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => { const [profileImg, setProfileImg] = useState(localStorage.getItem('profileImage')); const [processing, setProcessing] = useState(false); const [showConfirmationModal, setShowConfirmationModal] = useState(false); + const [hover, setHover] = useState(false); const handleProfileImage = async (event) => { if (event.target.files[0].size > 1 * 1000 * 1024) { @@ -69,6 +70,15 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => { setShowConfirmationModal(true); }; + const iconButtonStyling = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + color: '#fff' + }; + return ( { gap: '10px' }} > - - {!contactProfile && - (profileImg ? ( - - ) : ( - - ))} + setHover(true)} + onMouseLeave={() => setHover(false)} + > + + {hover && ( + <> + + {contactProfile || profileImg ? ( + + + + ) : ( + + + + + )} + + )} + + Date: Mon, 23 Sep 2024 14:48:40 -0700 Subject: [PATCH 2/8] Refactoring from icon button to regular button --- src/components/Profile/ProfileImageField.jsx | 50 ++++++--- .../Profile/ProfileImageField.test.jsx | 100 +++++++++++++----- 2 files changed, 110 insertions(+), 40 deletions(-) diff --git a/src/components/Profile/ProfileImageField.jsx b/src/components/Profile/ProfileImageField.jsx index 47afc6880..734957c6a 100644 --- a/src/components/Profile/ProfileImageField.jsx +++ b/src/components/Profile/ProfileImageField.jsx @@ -5,8 +5,8 @@ import { useNotification, useSession } from '@hooks'; // Material UI Imports import Avatar from '@mui/material/Avatar'; import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; import HideImageIcon from '@mui/icons-material/HideImage'; -import IconButton from '@mui/material/IconButton'; import ImageIcon from '@mui/icons-material/Image'; // Contexts Imports import { SignedInUserContext } from '@contexts'; @@ -19,12 +19,12 @@ import ConfirmationModal from '../Modals/ConfirmationModal'; * * @memberof Profile * @name ProfileImageField - * @param {object} Props - Props used for NewMessage + * @param {object} Props - Props used for ProfileImageField * @param {() => void} Props.loadProfileData - Handler function for setting local * state for profile card in PASS * @param {object} [Props.contactProfile] - Contact object with data from profile * or null if user profile is selected - * @returns {React.JSX.Element} React component for NewMessage + * @returns {React.JSX.Element} React component for ProfileImageField */ const ProfileImageField = ({ loadProfileData, contactProfile }) => { const { addNotification } = useNotification(); @@ -72,9 +72,16 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => { const iconButtonStyling = { position: 'absolute', + width: '100%', + height: '100%', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', + borderRadius: '50%', + opacity: 0.8, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', backgroundColor: 'rgba(0, 0, 0, 0.5)', color: '#fff' }; @@ -86,8 +93,7 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => { flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - padding: '20px', - gap: '10px' + padding: '20px' }} > { {hover && ( <> @@ -115,23 +124,36 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => { }} /> {contactProfile || profileImg ? ( - } onClick={handleSelectRemoveProfileImg} + sx={iconButtonStyling} > - - + Delete + ) : ( - - + )} )} diff --git a/test/components/Profile/ProfileImageField.test.jsx b/test/components/Profile/ProfileImageField.test.jsx index 8a7d9e64e..ae3c3fdfe 100644 --- a/test/components/Profile/ProfileImageField.test.jsx +++ b/test/components/Profile/ProfileImageField.test.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import { ProfileImageField } from '../../../src/components/Profile'; import '@testing-library/jest-dom/extend-expect'; @@ -12,23 +12,19 @@ const MockProfileComponent = ({ noUserImage, mockContactProfile }) => { }; describe('ProfileImageField', () => { - it('renders Choose Img button and PersonIcon if contactProfile is null and user has no profile img', () => { - const { queryByRole, queryByTestId } = render( + it('renders PersonIcon if contactProfile is null and user has no profile image', () => { + const { queryByTestId } = render( ); - const buttonElement = queryByRole('button'); - expect(buttonElement.textContent).toBe('Choose Img'); const svgElement = queryByTestId('PersonIcon'); expect(svgElement).not.toBeNull(); }); - it('renders Remove Img button and image if contactProfile is null, but user has profile image', () => { + it('renders image if contactProfile is null but user has profile image', () => { const { queryByRole, queryByTestId } = render( ); - const buttonElement = queryByRole('button'); - expect(buttonElement.textContent).toBe('Remove Img'); const muiAvatar = queryByRole('img'); expect(muiAvatar).toHaveAttribute('src', 'https://example.com/user.png'); @@ -37,27 +33,79 @@ describe('ProfileImageField', () => { expect(svgElement).toBeNull(); }); - it('renders no button with PersonIcon if contactProfile is not null and has no profile image', () => { - const { queryByRole, queryByTestId } = render(); - const buttonElement = queryByRole('button'); - expect(buttonElement).toBeNull(); + it('renders uploadProfilePictureIcon when user hovers over avatar with no profile image uploaded', () => { + const mockContactProfile = { profileImage: 'https://example.com/client.png' }; + render(); - const svgElement = queryByTestId('PersonIcon'); - expect(svgElement).not.toBeNull(); - }); + const avatar = screen.getByAltText('PASS profile'); - it('renders no button with image if contactProfile is not null and has profile image', () => { - const mockContactProfile = { profileImage: 'https://example.com/client.png' }; - const { queryByRole, queryByTestId } = render( - - ); - const buttonElement = queryByRole('button'); - expect(buttonElement).toBeNull(); + expect(screen.queryByTestId('uploadProfilePictureIcon')).not.toBeInTheDocument(); - const muiAvatar = queryByRole('img'); - expect(muiAvatar).toHaveAttribute('src', 'https://example.com/client.png'); + fireEvent.mouseEnter(avatar); - const svgElement = queryByTestId('PersonIcon'); - expect(svgElement).toBeNull(); + expect(screen.getByTestId('uploadProfilePictureIcon')).toBeInTheDocument(); }); + + // it('renders deleteProfilePictureIcon when user hovers over avatar with profile image uploaded', () => { + // render(); + + // const avatar = screen.getByAltText('PASS profile'); + + // expect(screen.queryByTestId('deleteProfilePictureIcon')).not.toBeInTheDocument(); + + // fireEvent.mouseEnter(avatar); + + // expect(screen.getByTestId('deleteProfilePictureIcon')).toBeInTheDocument(); + // }); + + // + + // it('renders Choose Img button and PersonIcon if contactProfile is null and user has no profile img', () => { + // const { queryByRole, queryByTestId } = render( + // + // ); + // const buttonElement = queryByRole('button'); + // expect(buttonElement.textContent).toBe('Choose Img'); + + // const svgElement = queryByTestId('PersonIcon'); + // expect(svgElement).not.toBeNull(); + // }); + + // it('renders Remove Img button and image if contactProfile is null, but user has profile image', () => { + // const { queryByRole, queryByTestId } = render( + // + // ); + // const buttonElement = queryByRole('button'); + // expect(buttonElement.textContent).toBe('Remove Img'); + + // const muiAvatar = queryByRole('img'); + // expect(muiAvatar).toHaveAttribute('src', 'https://example.com/user.png'); + + // const svgElement = queryByTestId('PersonIcon'); + // expect(svgElement).toBeNull(); + // }); + + // it('renders no button with PersonIcon if contactProfile is not null and has no profile image', () => { + // const { queryByRole, queryByTestId } = render(); + // const buttonElement = queryByRole('button'); + // expect(buttonElement).toBeNull(); + + // const svgElement = queryByTestId('PersonIcon'); + // expect(svgElement).not.toBeNull(); + // }); + + // it('renders no button with image if contactProfile is not null and has profile image', () => { + // const mockContactProfile = { profileImage: 'https://example.com/client.png' }; + // const { queryByRole, queryByTestId } = render( + // + // ); + // const buttonElement = queryByRole('button'); + // expect(buttonElement).toBeNull(); + + // const muiAvatar = queryByRole('img'); + // expect(muiAvatar).toHaveAttribute('src', 'https://example.com/client.png'); + + // const svgElement = queryByTestId('PersonIcon'); + // expect(svgElement).toBeNull(); + // }); }); From c72a56cb23e876a35e2aeede04c60a3cd1593631 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 24 Sep 2024 14:05:07 -0700 Subject: [PATCH 3/8] Updating hover styling and tests --- src/components/Profile/ProfileImageField.jsx | 2 +- .../Profile/ProfileImageField.test.jsx | 85 ++++--------------- 2 files changed, 19 insertions(+), 68 deletions(-) diff --git a/src/components/Profile/ProfileImageField.jsx b/src/components/Profile/ProfileImageField.jsx index 734957c6a..2c455c3a5 100644 --- a/src/components/Profile/ProfileImageField.jsx +++ b/src/components/Profile/ProfileImageField.jsx @@ -103,7 +103,7 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => { onMouseLeave={() => setHover(false)} > { expect(svgElement).toBeNull(); }); - it('renders uploadProfilePictureIcon when user hovers over avatar with no profile image uploaded', () => { - const mockContactProfile = { profileImage: 'https://example.com/client.png' }; + it('renders upload profile picture icon when user hovers over avatar with no profile image', async () => { + const mockContactProfile = { profileImage: '' }; render(); const avatar = screen.getByAltText('PASS profile'); + expect(screen.queryByTestId('uploadProfilePictureIcon')).toBeNull(); - expect(screen.queryByTestId('uploadProfilePictureIcon')).not.toBeInTheDocument(); - - fireEvent.mouseEnter(avatar); + userEvent.hover(avatar); - expect(screen.getByTestId('uploadProfilePictureIcon')).toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByTestId('uploadProfilePictureIcon')).not.toBeNull(); + }); }); - // it('renders deleteProfilePictureIcon when user hovers over avatar with profile image uploaded', () => { - // render(); - - // const avatar = screen.getByAltText('PASS profile'); - - // expect(screen.queryByTestId('deleteProfilePictureIcon')).not.toBeInTheDocument(); - - // fireEvent.mouseEnter(avatar); - - // expect(screen.getByTestId('deleteProfilePictureIcon')).toBeInTheDocument(); - // }); - - // - - // it('renders Choose Img button and PersonIcon if contactProfile is null and user has no profile img', () => { - // const { queryByRole, queryByTestId } = render( - // - // ); - // const buttonElement = queryByRole('button'); - // expect(buttonElement.textContent).toBe('Choose Img'); - - // const svgElement = queryByTestId('PersonIcon'); - // expect(svgElement).not.toBeNull(); - // }); - - // it('renders Remove Img button and image if contactProfile is null, but user has profile image', () => { - // const { queryByRole, queryByTestId } = render( - // - // ); - // const buttonElement = queryByRole('button'); - // expect(buttonElement.textContent).toBe('Remove Img'); - - // const muiAvatar = queryByRole('img'); - // expect(muiAvatar).toHaveAttribute('src', 'https://example.com/user.png'); - - // const svgElement = queryByTestId('PersonIcon'); - // expect(svgElement).toBeNull(); - // }); - - // it('renders no button with PersonIcon if contactProfile is not null and has no profile image', () => { - // const { queryByRole, queryByTestId } = render(); - // const buttonElement = queryByRole('button'); - // expect(buttonElement).toBeNull(); - - // const svgElement = queryByTestId('PersonIcon'); - // expect(svgElement).not.toBeNull(); - // }); - - // it('renders no button with image if contactProfile is not null and has profile image', () => { - // const mockContactProfile = { profileImage: 'https://example.com/client.png' }; - // const { queryByRole, queryByTestId } = render( - // - // ); - // const buttonElement = queryByRole('button'); - // expect(buttonElement).toBeNull(); + it('renders deleteProfilePictureIcon when user hovers over avatar with profile image uploaded', () => { + const mockContactProfile = { profileImage: 'https://example.com/client.png' }; + render(); - // const muiAvatar = queryByRole('img'); - // expect(muiAvatar).toHaveAttribute('src', 'https://example.com/client.png'); + const avatar = screen.getByAltText('PASS profile'); + expect(screen.queryByTestId('deleteProfilePictureIcon')).toBeNull(); - // const svgElement = queryByTestId('PersonIcon'); - // expect(svgElement).toBeNull(); - // }); + fireEvent.mouseEnter(avatar); + expect(screen.getByTestId('deleteProfilePictureIcon')).not.toBeNull(); + }); }); + From 2288444a05a9f5f4af3fb4aef81a43d4eb5124f2 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Oct 2024 14:11:17 -0700 Subject: [PATCH 4/8] One test remaining --- src/components/Profile/ProfileImageField.jsx | 20 +++++++++++-------- .../Profile/ProfileImageField.test.jsx | 15 +++++--------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/components/Profile/ProfileImageField.jsx b/src/components/Profile/ProfileImageField.jsx index 2c455c3a5..f463ebcf2 100644 --- a/src/components/Profile/ProfileImageField.jsx +++ b/src/components/Profile/ProfileImageField.jsx @@ -8,6 +8,7 @@ import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import HideImageIcon from '@mui/icons-material/HideImage'; import ImageIcon from '@mui/icons-material/Image'; +import { useTheme } from '@mui/material/styles'; // Contexts Imports import { SignedInUserContext } from '@contexts'; // Component Imports @@ -35,6 +36,7 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => { const [processing, setProcessing] = useState(false); const [showConfirmationModal, setShowConfirmationModal] = useState(false); const [hover, setHover] = useState(false); + const theme = useTheme(); const handleProfileImage = async (event) => { if (event.target.files[0].size > 1 * 1000 * 1024) { @@ -76,14 +78,16 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => { height: '100%', top: '50%', left: '50%', + opacity: 0.8, transform: 'translate(-50%, -50%)', borderRadius: '50%', - opacity: 0.8, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: 'rgba(0, 0, 0, 0.5)', - color: '#fff' + cursor: 'pointer', + border: 'none', + color: '#FFFFFF', + backgroundColor: theme.palette.content, + '&:hover': { + backgroundColor: theme.palette.content + } }; return ( @@ -127,7 +131,7 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => {