Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Upgrade react and react-dom to v16.8 and refactor Avatar #1171

Merged
merged 6 commits into from
May 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
"deepmerge": "^2.1.1",
"draft-js": "^0.10.1",
"enzyme": "3.8.0",
"enzyme-adapter-react-16": "^1.10.0",
"enzyme-adapter-react-16": "^1.12.1",
"enzyme-to-json": "^3.3.4",
"eslint": "^4.19.1",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@j3tan j3tan May 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we will be adding this plugin. I'll discuss it at the next FTL meeting but it'll be an update to @box/frontend

"eslint-config-airbnb": "^17.1.0",
Expand Down Expand Up @@ -221,10 +221,10 @@
"properties-parser": "^0.3.1",
"query-string": "5.1.1",
"raf": "^3.4.0",
"react": "^16.7.0",
"react": "^16.8.0",
"react-animate-height": "^2.0.4",
"react-beautiful-dnd": "^9.0.2",
"react-dom": "^16.7.0",
"react-dom": "^16.8.0",
"react-draggable": "^3.0.4",
"react-immutable-proptypes": "^2.1.0",
"react-intl": "^2.3.0",
Expand Down Expand Up @@ -270,10 +270,10 @@
"mousetrap": "^1.6.1",
"pikaday": "^1.6.1",
"query-string": "5.1.1",
"react": "^16.7.0",
"react": "^16.8.0",
"react-animate-height": "^2.0.4",
"react-beautiful-dnd": "^9.0.2",
"react-dom": "^16.7.0",
"react-dom": "^16.8.0",
"react-draggable": "^3.0.4",
"react-immutable-proptypes": "^2.1.0",
"react-intl": "^2.3.0",
Expand Down
68 changes: 27 additions & 41 deletions src/components/avatar/Avatar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
import * as React from 'react';
import React, { useState, useEffect } from 'react';
import classNames from 'classnames';
import AvatarImage from './AvatarImage';
import AvatarInitials from './AvatarInitials';
Expand Down Expand Up @@ -30,50 +30,36 @@ type Props = {
size?: $Keys<typeof SIZES>,
};

type State = {
/** boolean to determine if image did not load correctly */
hasImageErrored: boolean,
};

class Avatar extends React.PureComponent<Props, State> {
state = {
hasImageErrored: false,
};

componentWillReceiveProps(nextProps: Props) {
if (this.state.hasImageErrored && this.props.avatarUrl !== nextProps.avatarUrl) {
this.setState({
hasImageErrored: false,
});
}
}
function Avatar({ avatarUrl, className, name, id, size = '' }: Props) {
const [hasImageErrored, setHasImageErrored] = useState(false);
const classes = classNames(['avatar', className, { [`avatar--${size}`]: SIZES[size] }]);

onImageError = () => {
this.setState({
hasImageErrored: true,
});
};
// Reset hasImageErrored state when avatarUrl changes
useEffect(() => {
setHasImageErrored(false);
}, [avatarUrl]);

render() {
const { avatarUrl, className, name, id, size = '' }: Props = this.props;
const { hasImageErrored }: State = this.state;
const classes = classNames(['avatar', className, { [`avatar--${size}`]: SIZES[size] }]);

let avatar;
if (avatarUrl && !hasImageErrored) {
avatar = <AvatarImage onError={this.onImageError} url={avatarUrl} />;
} else if (name) {
avatar = <AvatarInitials id={id} name={name} />;
} else {
avatar = <UnknownUserAvatar className="avatar-icon" />;
}

return (
<span className={classes} role="presentation">
{avatar}
</span>
let avatar;
if (avatarUrl && !hasImageErrored) {
avatar = (
<AvatarImage
onError={() => {
setHasImageErrored(true);
}}
url={avatarUrl}
/>
);
} else if (name) {
avatar = <AvatarInitials id={id} name={name} />;
} else {
avatar = <UnknownUserAvatar className="avatar-icon" />;
}

return (
<span className={classes} role="presentation">
{avatar}
</span>
);
}

export default Avatar;
43 changes: 29 additions & 14 deletions src/components/avatar/__tests__/Avatar-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import React from 'react';
import { act } from 'react-dom/test-utils';

import Avatar from '../Avatar';

function MockAvatarImage() {
return <div className="avatar-image" />;
}

jest.mock('../AvatarImage', () => MockAvatarImage);

const testDataURI = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';

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

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

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

wrapper.instance().onImageError();
expect(wrapper.state('hasImageErrored')).toEqual(true);
wrapper.update();
const avatarInitials = wrapper.find('AvatarInitials');
expect(avatarInitials.length).toEqual(1);
});
Expand All @@ -66,18 +72,27 @@ describe('components/avatar/Avatar', () => {
avatarUrl: 'http://foo.bar/baz123_invalid',
};

const wrapper = shallow(<Avatar {...props} />);

wrapper.instance().onImageError();
expect(wrapper.state('hasImageErrored')).toEqual(true);
let wrapper;
act(() => {
jstoffan marked this conversation as resolved.
Show resolved Hide resolved
wrapper = mount(<Avatar {...props} />);
});
expect(wrapper.find(MockAvatarImage).length).toEqual(1);

wrapper.setProps({
...props,
avatarUrl: 'http://foo.bar/baz123_invalid_new',
act(() => {
const avatarImage = wrapper.find(MockAvatarImage);
avatarImage.prop('onError')();
jstoffan marked this conversation as resolved.
Show resolved Hide resolved
});
wrapper.update();

expect(wrapper.state('hasImageErrored')).toEqual(false);
expect(wrapper.find('AvatarImage').length).toEqual(1);
expect(wrapper.find(MockAvatarImage).length).toEqual(0);
expect(wrapper.find('AvatarInitials').length).toEqual(1);

act(() => {
wrapper.setProps({
...props,
avatarUrl: 'http://foo.bar/baz123_invalid_new',
});
});
wrapper.update();
expect(wrapper.find(MockAvatarImage).length).toEqual(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,6 @@ describe('elements/content-sidebar/ContentSidebar', () => {
});

test('should set the state with the file and view and then call fetchMetadata', () => {
wrapper = getWrapper();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary due to beforeEach, also was causing a random failure

instance = wrapper.instance();
instance.setState = setState;

instance.fetchMetadata = jest.fn();
instance.fetchFileSuccessCallback(file);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const StatusIcon = ({ status, ...rest }: { status: TaskCollabStatus }) => {
};

const AssignmentStatus = React.memo<Props>(({ user, status, getAvatarUrl, className, ...rest }: Props) => (
<div className={classNames('bcs-task-assignment-status', className)} {...rest}>
<div className={classNames('bcs-task-assignment-status', className)} data-testid="task-assignment-status" {...rest}>
<Avatar className="bcs-task-assignment-avatar" user={user} getAvatarUrl={getAvatarUrl} />
<StatusIcon
status={status}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,7 @@ class Assignees extends React.Component<Props> {
/>
}
>
<AssigneeStatus
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for whatever reason, the data-testid in the wrapper.find was returning both this top level function and the nested div causing 2x the number of objects returned. moving the data-testid into the child component fixes that without impacting usage

status={status}
user={target}
getAvatarUrl={getAvatarUrl}
data-testid="task-assignment-status"
/>
<AssigneeStatus status={status} user={target} getAvatarUrl={getAvatarUrl} />
</Tooltip>
);
});
Expand All @@ -90,7 +85,6 @@ class Assignees extends React.Component<Props> {
className="bcs-task-assignment-list-item-avatar"
user={target}
getAvatarUrl={getAvatarUrl}
data-testid="task-assignment-status"
/>
<AssignmentDetails
className="bcs-task-assignment-list-item-details"
Expand Down
Loading