Add tests for various user profile setups to sidebar (#212)
* Add tests for various user profile setups to sidebar * Don't show empty user image if it isn't there * @adamchalmer review
This commit is contained in:
@ -20,6 +20,7 @@ export const ActionButton = ({
|
|||||||
to = '/',
|
to = '/',
|
||||||
Element = 'button',
|
Element = 'button',
|
||||||
children,
|
children,
|
||||||
|
...props
|
||||||
}: ActionButtonProps) => {
|
}: ActionButtonProps) => {
|
||||||
const classNames = `group mono text-base flex items-center gap-2 rounded-sm border border-chalkboard-40 dark:border-chalkboard-60 hover:border-liquid-40 dark:hover:bg-chalkboard-90 p-[3px] ${
|
const classNames = `group mono text-base flex items-center gap-2 rounded-sm border border-chalkboard-40 dark:border-chalkboard-60 hover:border-liquid-40 dark:hover:bg-chalkboard-90 p-[3px] ${
|
||||||
icon ? 'pr-2' : 'px-2'
|
icon ? 'pr-2' : 'px-2'
|
||||||
@ -27,21 +28,21 @@ export const ActionButton = ({
|
|||||||
|
|
||||||
if (Element === 'button') {
|
if (Element === 'button') {
|
||||||
return (
|
return (
|
||||||
<button onClick={onClick} className={classNames}>
|
<button onClick={onClick} className={classNames} {...props}>
|
||||||
{icon && <ActionIcon {...icon} />}
|
{icon && <ActionIcon {...icon} />}
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
} else if (Element === 'link') {
|
} else if (Element === 'link') {
|
||||||
return (
|
return (
|
||||||
<Link to={to} className={classNames}>
|
<Link to={to} className={classNames} {...props}>
|
||||||
{icon && <ActionIcon {...icon} />}
|
{icon && <ActionIcon {...icon} />}
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Element onClick={onClick} className={classNames}>
|
<Element onClick={onClick} className={classNames} {...props}>
|
||||||
{icon && <ActionIcon {...icon} />}
|
{icon && <ActionIcon {...icon} />}
|
||||||
{children}
|
{children}
|
||||||
</Element>
|
</Element>
|
||||||
|
66
src/components/UserSidebarMenu.test.tsx
Normal file
66
src/components/UserSidebarMenu.test.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import { User } from '../useStore'
|
||||||
|
import UserSidebarMenu from './UserSidebarMenu'
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
|
|
||||||
|
it("Renders user's name and email if available", () => {
|
||||||
|
const userWellFormed: User = {
|
||||||
|
id: '8675309',
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'kittycad.sidebar.test@example.com',
|
||||||
|
image: 'https://placekitten.com/200/200',
|
||||||
|
created_at: 'yesteryear',
|
||||||
|
updated_at: 'today',
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<UserSidebarMenu user={userWellFormed} />
|
||||||
|
</BrowserRouter>
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
|
||||||
|
|
||||||
|
expect(screen.getByTestId('username')).toHaveTextContent(
|
||||||
|
userWellFormed.name || ''
|
||||||
|
)
|
||||||
|
expect(screen.getByTestId('email')).toHaveTextContent(userWellFormed.email)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Renders just the user's email if no name is available", () => {
|
||||||
|
const userNoName: User = {
|
||||||
|
id: '8675309',
|
||||||
|
email: 'kittycad.sidebar.test@example.com',
|
||||||
|
image: 'https://placekitten.com/200/200',
|
||||||
|
created_at: 'yesteryear',
|
||||||
|
updated_at: 'today',
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<UserSidebarMenu user={userNoName} />
|
||||||
|
</BrowserRouter>
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
|
||||||
|
|
||||||
|
expect(screen.getByTestId('username')).toHaveTextContent(userNoName.email)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders a menu button if no user avatar is available', () => {
|
||||||
|
const userNoAvatar: User = {
|
||||||
|
id: '8675309',
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'kittycad.sidebar.test@example.com',
|
||||||
|
created_at: 'yesteryear',
|
||||||
|
updated_at: 'today',
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<UserSidebarMenu user={userNoAvatar} />
|
||||||
|
</BrowserRouter>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByTestId('user-sidebar-toggle')).toHaveTextContent('Menu')
|
||||||
|
})
|
@ -3,24 +3,45 @@ import { User, useStore } from '../useStore'
|
|||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { A } from '@tauri-apps/api/path-c062430b'
|
import { useState } from 'react'
|
||||||
|
|
||||||
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||||
|
const displayedName = getDisplayName(user)
|
||||||
|
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { setToken } = useStore((s) => ({
|
const { setToken } = useStore((s) => ({
|
||||||
setToken: s.setToken,
|
setToken: s.setToken,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Fallback logic for displaying user's "name":
|
||||||
|
// 1. user.name
|
||||||
|
// 2. user.first_name + ' ' + user.last_name
|
||||||
|
// 3. user.first_name
|
||||||
|
// 4. user.email
|
||||||
|
function getDisplayName(user?: User) {
|
||||||
|
if (!user) return null
|
||||||
|
if (user.name) return user.name
|
||||||
|
if (user.first_name) {
|
||||||
|
if (user.last_name) return user.first_name + ' ' + user.last_name
|
||||||
|
return user.first_name
|
||||||
|
}
|
||||||
|
return user.email
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
{user?.image ? (
|
{user?.image && !imageLoadFailed ? (
|
||||||
<Popover.Button className="border-0 rounded-full w-fit p-0">
|
<Popover.Button
|
||||||
|
className="border-0 rounded-full w-fit p-0"
|
||||||
|
data-testid="user-sidebar-toggle"
|
||||||
|
>
|
||||||
<div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 overflow-hidden">
|
<div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={user?.image || ''}
|
src={user?.image || ''}
|
||||||
alt={user?.name || ''}
|
alt={user?.name || ''}
|
||||||
className="h-8 w-8"
|
className="h-8 w-8"
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
|
onError={() => setImageLoadFailed(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
@ -29,6 +50,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
Element={Popover.Button}
|
Element={Popover.Button}
|
||||||
icon={{ icon: faBars }}
|
icon={{ icon: faBars }}
|
||||||
className="border-transparent"
|
className="border-transparent"
|
||||||
|
data-testid="user-sidebar-toggle"
|
||||||
>
|
>
|
||||||
Menu
|
Menu
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
@ -38,23 +60,29 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<Popover.Panel className="fixed inset-0 left-auto z-30 w-64 bg-chalkboard-10 dark:bg-chalkboard-100 border border-liquid-100 shadow-md rounded-l-lg">
|
<Popover.Panel className="fixed inset-0 left-auto z-30 w-64 bg-chalkboard-10 dark:bg-chalkboard-100 border border-liquid-100 shadow-md rounded-l-lg">
|
||||||
{user && (
|
{user && (
|
||||||
<div className="flex items-center gap-4 px-4 py-3 bg-liquid-100">
|
<div className="flex items-center gap-4 px-4 py-3 bg-liquid-100">
|
||||||
|
{user.image && !imageLoadFailed && (
|
||||||
<div className="rounded-full shadow-inner overflow-hidden">
|
<div className="rounded-full shadow-inner overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={user?.image || ''}
|
src={user.image}
|
||||||
alt={user?.name || ''}
|
alt={user.name || ''}
|
||||||
className="h-8 w-8"
|
className="h-8 w-8"
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
|
onError={() => setImageLoadFailed(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="m-0 text-liquid-10 text-mono">
|
<p
|
||||||
{user.name ||
|
className="m-0 text-liquid-10 text-mono"
|
||||||
user.first_name + ' ' + user.last_name ||
|
data-testid="username"
|
||||||
user.email}
|
>
|
||||||
|
{displayedName || ''}
|
||||||
|
</p>
|
||||||
|
{displayedName !== user.email && (
|
||||||
|
<p className="m-0 text-liquid-40 text-xs" data-testid="email">
|
||||||
|
{user.email}
|
||||||
</p>
|
</p>
|
||||||
{(user.name || user.first_name) && (
|
|
||||||
<p className="m-0 text-liquid-40 text-xs">{user.email}</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import create from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
import { addLineHighlight, EditorView } from './editor/highlightextension'
|
import { addLineHighlight, EditorView } from './editor/highlightextension'
|
||||||
import { abstractSyntaxTree } from './lang/abstractSyntaxTree'
|
import { abstractSyntaxTree } from './lang/abstractSyntaxTree'
|
||||||
|
Reference in New Issue
Block a user