Skip to content

Commit 7dd1bdf

Browse files
authored
refactor: Upgrade react and react-dom to v16.8 and refactor Avatar (#1171)
* refactor: upgrade react and react-dom to v16.8 (#761) BREAKING CHANGE: peerDep React requirement have been bumped higher * refactor(avatar): Refactor Avatar to use hooks * fix(test): Fixed various test issues, downgraded enzyme back to 3.8.0 * fix(avatar): remove redundant check
1 parent 2ba8a1d commit 7dd1bdf

File tree

7 files changed

+192
-162
lines changed

7 files changed

+192
-162
lines changed

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@
175175
"deepmerge": "^2.1.1",
176176
"draft-js": "^0.10.1",
177177
"enzyme": "3.8.0",
178-
"enzyme-adapter-react-16": "^1.10.0",
178+
"enzyme-adapter-react-16": "^1.12.1",
179179
"enzyme-to-json": "^3.3.4",
180180
"eslint": "^4.19.1",
181181
"eslint-config-airbnb": "^17.1.0",
@@ -221,10 +221,10 @@
221221
"properties-parser": "^0.3.1",
222222
"query-string": "5.1.1",
223223
"raf": "^3.4.0",
224-
"react": "^16.7.0",
224+
"react": "^16.8.0",
225225
"react-animate-height": "^2.0.4",
226226
"react-beautiful-dnd": "^9.0.2",
227-
"react-dom": "^16.7.0",
227+
"react-dom": "^16.8.0",
228228
"react-draggable": "^3.0.4",
229229
"react-immutable-proptypes": "^2.1.0",
230230
"react-intl": "^2.3.0",
@@ -270,10 +270,10 @@
270270
"mousetrap": "^1.6.1",
271271
"pikaday": "^1.6.1",
272272
"query-string": "5.1.1",
273-
"react": "^16.7.0",
273+
"react": "^16.8.0",
274274
"react-animate-height": "^2.0.4",
275275
"react-beautiful-dnd": "^9.0.2",
276-
"react-dom": "^16.7.0",
276+
"react-dom": "^16.8.0",
277277
"react-draggable": "^3.0.4",
278278
"react-immutable-proptypes": "^2.1.0",
279279
"react-intl": "^2.3.0",

src/components/avatar/Avatar.js

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @flow
2-
import * as React from 'react';
2+
import React, { useState, useEffect } from 'react';
33
import classNames from 'classnames';
44
import AvatarImage from './AvatarImage';
55
import AvatarInitials from './AvatarInitials';
@@ -30,50 +30,36 @@ type Props = {
3030
size?: $Keys<typeof SIZES>,
3131
};
3232

33-
type State = {
34-
/** boolean to determine if image did not load correctly */
35-
hasImageErrored: boolean,
36-
};
37-
38-
class Avatar extends React.PureComponent<Props, State> {
39-
state = {
40-
hasImageErrored: false,
41-
};
42-
43-
componentWillReceiveProps(nextProps: Props) {
44-
if (this.state.hasImageErrored && this.props.avatarUrl !== nextProps.avatarUrl) {
45-
this.setState({
46-
hasImageErrored: false,
47-
});
48-
}
49-
}
33+
function Avatar({ avatarUrl, className, name, id, size = '' }: Props) {
34+
const [hasImageErrored, setHasImageErrored] = useState(false);
35+
const classes = classNames(['avatar', className, { [`avatar--${size}`]: SIZES[size] }]);
5036

51-
onImageError = () => {
52-
this.setState({
53-
hasImageErrored: true,
54-
});
55-
};
37+
// Reset hasImageErrored state when avatarUrl changes
38+
useEffect(() => {
39+
setHasImageErrored(false);
40+
}, [avatarUrl]);
5641

57-
render() {
58-
const { avatarUrl, className, name, id, size = '' }: Props = this.props;
59-
const { hasImageErrored }: State = this.state;
60-
const classes = classNames(['avatar', className, { [`avatar--${size}`]: SIZES[size] }]);
61-
62-
let avatar;
63-
if (avatarUrl && !hasImageErrored) {
64-
avatar = <AvatarImage onError={this.onImageError} url={avatarUrl} />;
65-
} else if (name) {
66-
avatar = <AvatarInitials id={id} name={name} />;
67-
} else {
68-
avatar = <UnknownUserAvatar className="avatar-icon" />;
69-
}
70-
71-
return (
72-
<span className={classes} role="presentation">
73-
{avatar}
74-
</span>
42+
let avatar;
43+
if (avatarUrl && !hasImageErrored) {
44+
avatar = (
45+
<AvatarImage
46+
onError={() => {
47+
setHasImageErrored(true);
48+
}}
49+
url={avatarUrl}
50+
/>
7551
);
52+
} else if (name) {
53+
avatar = <AvatarInitials id={id} name={name} />;
54+
} else {
55+
avatar = <UnknownUserAvatar className="avatar-icon" />;
7656
}
57+
58+
return (
59+
<span className={classes} role="presentation">
60+
{avatar}
61+
</span>
62+
);
7763
}
7864

7965
export default Avatar;

src/components/avatar/__tests__/Avatar-test.js

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import React from 'react';
2+
import { act } from 'react-dom/test-utils';
23

34
import Avatar from '../Avatar';
45

6+
function MockAvatarImage() {
7+
return <div className="avatar-image" />;
8+
}
9+
10+
jest.mock('../AvatarImage', () => MockAvatarImage);
11+
512
const testDataURI = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
613

714
describe('components/avatar/Avatar', () => {
@@ -22,7 +29,7 @@ describe('components/avatar/Avatar', () => {
2229

2330
test('should render an AvatarImage when avatarUrl is passed in', () => {
2431
const wrapper = shallow(<Avatar avatarUrl={testDataURI} />);
25-
const avatarImage = wrapper.find('AvatarImage');
32+
const avatarImage = wrapper.find(MockAvatarImage);
2633
expect(avatarImage.length).toEqual(1);
2734
expect(avatarImage.prop('url')).toEqual(testDataURI);
2835
});
@@ -51,10 +58,9 @@ describe('components/avatar/Avatar', () => {
5158

5259
test('should fall back to AvatarInitials when there is an error in AvatarImage', () => {
5360
const wrapper = shallow(<Avatar avatarUrl="http://foo.bar/baz123_invalid" id="1" name="hello world" />);
61+
const avatarImage = wrapper.find(MockAvatarImage);
62+
avatarImage.prop('onError')();
5463

55-
wrapper.instance().onImageError();
56-
expect(wrapper.state('hasImageErrored')).toEqual(true);
57-
wrapper.update();
5864
const avatarInitials = wrapper.find('AvatarInitials');
5965
expect(avatarInitials.length).toEqual(1);
6066
});
@@ -66,18 +72,27 @@ describe('components/avatar/Avatar', () => {
6672
avatarUrl: 'http://foo.bar/baz123_invalid',
6773
};
6874

69-
const wrapper = shallow(<Avatar {...props} />);
70-
71-
wrapper.instance().onImageError();
72-
expect(wrapper.state('hasImageErrored')).toEqual(true);
75+
let wrapper;
76+
act(() => {
77+
wrapper = mount(<Avatar {...props} />);
78+
});
79+
expect(wrapper.find(MockAvatarImage).length).toEqual(1);
7380

74-
wrapper.setProps({
75-
...props,
76-
avatarUrl: 'http://foo.bar/baz123_invalid_new',
81+
act(() => {
82+
const avatarImage = wrapper.find(MockAvatarImage);
83+
avatarImage.prop('onError')();
7784
});
7885
wrapper.update();
79-
80-
expect(wrapper.state('hasImageErrored')).toEqual(false);
81-
expect(wrapper.find('AvatarImage').length).toEqual(1);
86+
expect(wrapper.find(MockAvatarImage).length).toEqual(0);
87+
expect(wrapper.find('AvatarInitials').length).toEqual(1);
88+
89+
act(() => {
90+
wrapper.setProps({
91+
...props,
92+
avatarUrl: 'http://foo.bar/baz123_invalid_new',
93+
});
94+
});
95+
wrapper.update();
96+
expect(wrapper.find(MockAvatarImage).length).toEqual(1);
8297
});
8398
});

src/elements/content-sidebar/__tests__/ContentSidebar-test.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,6 @@ describe('elements/content-sidebar/ContentSidebar', () => {
164164
});
165165

166166
test('should set the state with the file and view and then call fetchMetadata', () => {
167-
wrapper = getWrapper();
168-
instance = wrapper.instance();
169-
instance.setState = setState;
170-
171167
instance.fetchMetadata = jest.fn();
172168
instance.fetchFileSuccessCallback(file);
173169

src/elements/content-sidebar/activity-feed/task-new/AssigneeStatus.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const StatusIcon = ({ status, ...rest }: { status: TaskCollabStatus }) => {
3030
};
3131

3232
const AssignmentStatus = React.memo<Props>(({ user, status, getAvatarUrl, className, ...rest }: Props) => (
33-
<div className={classNames('bcs-task-assignment-status', className)} {...rest}>
33+
<div className={classNames('bcs-task-assignment-status', className)} data-testid="task-assignment-status" {...rest}>
3434
<Avatar className="bcs-task-assignment-avatar" user={user} getAvatarUrl={getAvatarUrl} />
3535
<StatusIcon
3636
status={status}

src/elements/content-sidebar/activity-feed/task-new/Assignees.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,7 @@ class Assignees extends React.Component<Props> {
7373
/>
7474
}
7575
>
76-
<AssigneeStatus
77-
status={status}
78-
user={target}
79-
getAvatarUrl={getAvatarUrl}
80-
data-testid="task-assignment-status"
81-
/>
76+
<AssigneeStatus status={status} user={target} getAvatarUrl={getAvatarUrl} />
8277
</Tooltip>
8378
);
8479
});
@@ -90,7 +85,6 @@ class Assignees extends React.Component<Props> {
9085
className="bcs-task-assignment-list-item-avatar"
9186
user={target}
9287
getAvatarUrl={getAvatarUrl}
93-
data-testid="task-assignment-status"
9488
/>
9589
<AssignmentDetails
9690
className="bcs-task-assignment-list-item-details"

0 commit comments

Comments
 (0)