Skip to content

Commit 84c1f94

Browse files
pawelgrimmclaude
andcommitted
refactor(deprecated-avatar): faithful rename of Avatar w/ restored tests + snapshot
- un-inline utils into utils.ts; restore utils.test.ts (9 cases) - restore full component tests + snapshot from src/avatar@65811a36 - revert churn so every file is pure rename of original - keep @deprecated JSDoc, displayName, type exports Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 29d81d5 commit 84c1f94

6 files changed

Lines changed: 151 additions & 57 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`DeprecatedAvatar renders a background image when avatarUrl is supplied 1`] = `
4+
<div
5+
class="deprecated-avatar deprecated-size-xl box"
6+
data-testid="avatar"
7+
style="background-image: url(https://foo.bar/com.png); text-indent: -9999px;"
8+
>
9+
HM
10+
</div>
11+
`;

src/deprecated-avatar/deprecated-avatar.module.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
--reactist-deprecated-avatar-size-xlarge: 48px;
88
--reactist-deprecated-avatar-size-xxlarge: 70px;
99
--reactist-deprecated-avatar-size-xxxlarge: 100px;
10-
}
1110

12-
.deprecated-avatar {
1311
--reactist-deprecated-avatar-size: var(--reactist-deprecated-avatar-size-large);
12+
}
1413

14+
.deprecated-avatar {
1515
flex-shrink: 0;
1616
background-position: center;
1717
color: white;

src/deprecated-avatar/deprecated-avatar.test.tsx

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,63 @@ import * as React from 'react'
22

33
import { render, screen } from '@testing-library/react'
44

5-
import { DeprecatedAvatar } from './index'
5+
import { DeprecatedAvatar } from './deprecated-avatar'
66

77
describe('DeprecatedAvatar', () => {
8-
it('keeps the legacy avatarUrl API under the deprecated export name', () => {
9-
render(
10-
<DeprecatedAvatar
11-
data-testid="avatar"
12-
user={{ name: 'Henning Mus', email: 'henning@doist.com' }}
13-
avatarUrl="https://example.com/avatar.png"
14-
/>,
15-
)
8+
it('renders a background image when avatarUrl is supplied', () => {
9+
render(getAvatar({ avatarUrl: 'https://foo.bar/com.png' }))
10+
11+
const avatar = screen.getByTestId('avatar')
1612

17-
expect(screen.getByTestId('avatar')).toHaveStyle({
18-
backgroundImage: 'url(https://example.com/avatar.png)',
19-
textIndent: '-9999px',
20-
})
13+
expect(avatar).toMatchSnapshot()
2114
})
2215

23-
it('keeps the legacy initials and responsive size behavior', () => {
24-
render(
25-
<DeprecatedAvatar
26-
data-testid="avatar"
27-
user={{ email: 'henning@doist.com' }}
28-
size={{ mobile: 's', tablet: 'xxl', desktop: 'xl' }}
29-
/>,
30-
)
16+
it('renders initials of user name when avatarUrl is not supplied', () => {
17+
render(getAvatar())
18+
19+
const avatar = screen.getByTestId('avatar')
20+
21+
expect(avatar).toHaveTextContent('HM')
22+
})
23+
24+
it('renders initials of user email when avatarUrl is not supplied', () => {
25+
render(getAvatar({ user: { email: 'henning@doist.com' } }))
3126

3227
const avatar = screen.getByTestId('avatar')
28+
3329
expect(avatar).toHaveTextContent('H')
30+
})
31+
32+
it('supports responsive values', () => {
33+
render(
34+
getAvatar({
35+
size: {
36+
mobile: 's',
37+
desktop: 'xl',
38+
tablet: 'xxl',
39+
},
40+
}),
41+
)
42+
const avatar = screen.getByTestId('avatar')
43+
3444
expect(avatar).toHaveClass('deprecated-size-s')
35-
expect(avatar).toHaveClass('tablet-deprecated-size-xxl')
3645
expect(avatar).toHaveClass('desktop-deprecated-size-xl')
46+
expect(avatar).toHaveClass('tablet-deprecated-size-xxl')
3747
})
48+
49+
// Helpers ================================================================
50+
function getAvatar(
51+
props?: Omit<React.ComponentProps<typeof DeprecatedAvatar>, 'user'> & {
52+
user?: { name?: string; email: string }
53+
},
54+
) {
55+
return (
56+
<DeprecatedAvatar
57+
data-testid="avatar"
58+
user={{ name: 'Henning Mus', email: 'henning@doist.com' }}
59+
size="xl"
60+
{...props}
61+
/>
62+
)
63+
}
3864
})

src/deprecated-avatar/deprecated-avatar.tsx

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import * as React from 'react'
33
import { Box } from '../box'
44
import { getClassNames } from '../utils/responsive-props'
55

6+
import { emailToIndex, getInitials } from './utils'
7+
68
import styles from './deprecated-avatar.module.css'
79

810
import type { ObfuscatedClassName } from '../utils/common-types'
@@ -32,7 +34,7 @@ const AVATAR_COLORS = [
3234
type DeprecatedAvatarSize = 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl' | 'xxxl'
3335

3436
type DeprecatedAvatarProps = ObfuscatedClassName & {
35-
/** @deprecated Please use `exceptionallySetClassName`. */
37+
/** @deprecated Please use `exceptionallySetClassName` */
3638
className?: string
3739
/** @deprecated */
3840
colorList?: string[]
@@ -53,18 +55,19 @@ function DeprecatedAvatar({
5355
exceptionallySetClassName,
5456
...props
5557
}: DeprecatedAvatarProps) {
56-
const userInitials = getLegacyInitials(user.name) || getLegacyInitials(user.email)
58+
const userInitials = getInitials(user.name) || getInitials(user.email)
59+
const avatarSize = size ? size : 'l'
5760

5861
const style = avatarUrl
5962
? {
6063
backgroundImage: `url(${avatarUrl})`,
61-
textIndent: '-9999px',
64+
textIndent: '-9999px', // hide the initials
6265
}
6366
: {
6467
backgroundColor: colorList[emailToIndex(user.email, colorList.length)],
6568
}
6669

67-
const sizeClassName = getClassNames(styles, 'deprecated-size', size)
70+
const sizeClassName = getClassNames(styles, 'deprecated-size', avatarSize)
6871

6972
return (
7073
<Box
@@ -83,34 +86,5 @@ function DeprecatedAvatar({
8386
}
8487
DeprecatedAvatar.displayName = 'DeprecatedAvatar'
8588

86-
function getLegacyInitials(name?: string) {
87-
if (!name) {
88-
return ''
89-
}
90-
91-
const seed = name.trim().split(' ')
92-
const firstInitial = seed[0]
93-
const lastInitial = seed[seed.length - 1]
94-
95-
let initials = firstInitial?.[0]
96-
if (
97-
firstInitial != null &&
98-
lastInitial != null &&
99-
initials != null &&
100-
// Better readable this way.
101-
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
102-
firstInitial[0] !== lastInitial[0]
103-
) {
104-
initials += lastInitial[0]
105-
}
106-
return initials?.toUpperCase()
107-
}
108-
109-
function emailToIndex(email: string, maxIndex: number) {
110-
const seed = email.split('@')[0]
111-
const hash = seed ? seed.charCodeAt(0) + seed.charCodeAt(seed.length - 1) || 0 : 0
112-
return hash % maxIndex
113-
}
114-
11589
export { DeprecatedAvatar }
11690
export type { DeprecatedAvatarProps, DeprecatedAvatarSize }
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { emailToIndex, getInitials } from './utils'
2+
3+
describe('Utils', () => {
4+
describe('getInitials', () => {
5+
it('returns uppercased initials for two names', () => {
6+
const initials = getInitials('henning mus')
7+
expect(initials).toBe('HM')
8+
})
9+
10+
it('returns first and last name initials for more than two names', () => {
11+
const initials = getInitials('henning is awesome mus')
12+
expect(initials).toBe('HM')
13+
})
14+
15+
it('returns first initial for a single name', () => {
16+
const initials = getInitials('henningmus')
17+
expect(initials).toBe('H')
18+
})
19+
20+
it('returns only first initial if first and second initials are the same', () => {
21+
const initials = getInitials('henning hen')
22+
expect(initials).toBe('H')
23+
})
24+
25+
it('returns an empty string for an empty name', () => {
26+
const initials = getInitials('')
27+
expect(initials).toBe('')
28+
})
29+
30+
it('returns an empty string for when called without name', () => {
31+
const initials = getInitials()
32+
expect(initials).toBe('')
33+
})
34+
})
35+
36+
describe('emailToIndex', () => {
37+
it('returns an index for a given mail', () => {
38+
const index = emailToIndex('henning@doist.com', 13)
39+
expect(index).toBe(12)
40+
})
41+
42+
it('returns the index if the local part of email is the same', () => {
43+
const index1 = emailToIndex('henning@doist.com', 13)
44+
const index2 = emailToIndex('henning@foobar.com', 13)
45+
expect(index1).toBe(index2)
46+
})
47+
48+
it('returns 0 index if local part of email is empty', () => {
49+
const index1 = emailToIndex('@doist.com', 13)
50+
expect(index1).toBe(0)
51+
})
52+
})
53+
})

src/deprecated-avatar/utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
function getInitials(name?: string) {
2+
if (!name) {
3+
return ''
4+
}
5+
6+
const seed = name.trim().split(' ')
7+
const firstInitial = seed[0]
8+
const lastInitial = seed[seed.length - 1]
9+
10+
let initials = firstInitial?.[0]
11+
if (
12+
firstInitial != null &&
13+
lastInitial != null &&
14+
initials != null &&
15+
// Better readable this way.
16+
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
17+
firstInitial[0] !== lastInitial[0]
18+
) {
19+
initials += lastInitial[0]
20+
}
21+
return initials?.toUpperCase()
22+
}
23+
24+
function emailToIndex(email: string, maxIndex: number) {
25+
const seed = email.split('@')[0]
26+
const hash = seed ? seed.charCodeAt(0) + seed.charCodeAt(seed.length - 1) || 0 : 0
27+
return hash % maxIndex
28+
}
29+
30+
export { emailToIndex, getInitials }

0 commit comments

Comments
 (0)