Skip to content

Commit

Permalink
Fav button into ES6 (#3717)
Browse files Browse the repository at this point in the history
* Convert favorites button to ES6. Add unit tests

* Fix TypeError when favorites collection doesnt have subjects
  • Loading branch information
srallen authored and eatyourgreens committed Apr 12, 2017
1 parent de1e380 commit d3673a3
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 132 deletions.
123 changes: 0 additions & 123 deletions app/collections/favorites-button.cjsx

This file was deleted.

184 changes: 184 additions & 0 deletions app/collections/favorites-button.jsx
@@ -0,0 +1,184 @@
import React from 'react';
import apiClient from 'panoptes-client/lib/api-client';
import classnames from 'classnames';
import alert from '../lib/alert';
import SignInPrompt from '../partials/sign-in-prompt';

export default class FavoritesButton extends React.Component {
constructor(props) {
super(props);

this.state = {
favorites: null,
favorited: false
};

this.findFavoriteCollection = this.findFavoriteCollection.bind(this);
this.favoriteSubject = this.favoriteSubject.bind(this);
this.addSubjectTo = this.addSubjectTo.bind(this);
this.removeSubjectFrom = this.removeSubjectFrom.bind(this);
this.logSubjLike = this.logSubjLike.bind(this);
this.createFavorites = this.createFavorites.bind(this);
this.toggleFavorite = this.toggleFavorite.bind(this);
}

componentWillMount() {
this.favoriteSubject(this.props.isFavorite);
}

componentWillReceiveProps(nextProps) {
if (this.props.isFavorite !== nextProps.isFavorite) {
this.favoriteSubject(nextProps.isFavorite);
}
}

componentDidUpdate(prevProps) {
if (prevProps.subject !== this.props.subject) {
this.favoriteSubject(this.props.isFavorite);
}
}

promptToSignIn() {
alert((resolve) => {
return (
<SignInPrompt onChoose={resolve()}>
<p>You must be signed in to save your favorites.</p>
</SignInPrompt>
);
});
}

findFavoriteCollection() {
return apiClient.type('collections')
.get({ project_ids: this.props.project.id, favorite: true, owner: this.props.user.login })
.then(([favorites]) => { return (favorites || null); });
}

favoriteSubject(isFavorite) {
const favorited = isFavorite;
this.setState({ favorited });
}

addSubjectTo(collection) {
this.setState({ favorited: true });
collection.addLink('subjects', [this.props.subject.id.toString()]);
}

removeSubjectFrom(collection) {
this.setState({ favorited: false });
collection.removeLink('subjects', [this.props.subject.id.toString()]);
}

getFavoritesName(project) {
if (project) {
return `Favorites ${project.slug}`;
}

return 'Favorites';
}

logSubjLike(liked) {
if (this.context.geordi) {
this.context.geordi.logEvent({ type: liked });
}
}

createFavorites() {
return (
new Promise((resolve, reject) => {
this.findFavoriteCollection()
.catch((err) => { reject(err); })
.then((favorites) => {
if (favorites) {
this.setState({ favorites });
resolve(favorites);
} else {
const display_name = this.getFavoritesName(this.props.project);
const project = this.props.project.id;
const subjects = [];
const favorite = true;

const links = { subjects };
if (project) {
links.projects = [project];
}
const collection = { favorite, display_name, links };
apiClient.type('collections')
.create(collection)
.save()
.catch((err) => { reject(err); })
.then((newFavorites) => {
this.setState({ favorites: newFavorites });
resolve(newFavorites);
});
}
});
})
);
}

toggleFavorite() {
if (this.props.user) {
if (!this.state.favorites) {
this.setState({ favorited: true });
this.createFavorites()
.then((favorites) => { this.addSubjectTo(favorites); });
this.logSubjLike('favorite');
} else if (this.state.favorited) {
this.removeSubjectFrom(this.state.favorites);
this.logSubjLike('unfavorite');
} else {
this.addSubjectTo(this.state.favorites);
this.logSubjLike('favorite');
}
} else {
this.promptToSignIn();
this.logSubjLike('favorite');
}
}

render() {
const iconClasses = classnames({
'favorited': this.state.favorited,
'fa fa-heart': this.state.favorited,
'fa fa-heart-o': !this.state.favorited,
'fa-fw': true
});

return (
<button
aria-label={(this.state.favorited) ? 'Unfavorite' : 'Favorite'}
className={`favorites-button ${this.props.className || ''}`}
type="button"
title={(this.state.favorited) ? 'Unfavorite' : 'Favorite'}
onClick={this.toggleFavorite}
>
<i className={iconClasses} />
</button>
);
}
}

FavoritesButton.defaultProps = {
isFavorite: false,
subject: { id: '' },
project: null,
user: null
};

FavoritesButton.propTypes = {
className: React.PropTypes.string,
isFavorite: React.PropTypes.bool,
subject: React.PropTypes.shape({ id: React.PropTypes.string }).isRequired,
project: React.PropTypes.shape({
id: React.PropTypes.string,
slug: React.PropTypes.string
}),
user: React.PropTypes.shape({
login: React.PropTypes.string
})
};

FavoritesButton.contextTypes = {
geordi: React.PropTypes.object
};
41 changes: 41 additions & 0 deletions app/collections/favorites-button.spec.js
@@ -0,0 +1,41 @@
import React from 'react';
import assert from 'assert';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import FavoritesButton from './favorites-button';

const subject = { id: '4' };

describe('<Favorites Button />', function() {
let wrapper;
let toggleFavoriteSpy;
let button;
let icon;
before(function() {
toggleFavoriteSpy = sinon.spy(FavoritesButton.prototype, 'toggleFavorite');
wrapper = shallow(<FavoritesButton subject={subject} />);
button = wrapper.find('button');
icon = wrapper.find('i');
});

it('should render a button and an icon', function() {
assert.equal(button.length, 1);
assert.equal(icon.length, 1);
});

it('should render an empty heart icon if subject is not a favorite', function() {
assert.equal(icon.props().className.includes('fa-heart-o'), true);
assert.equal(wrapper.state('favorited'), false);
});

it('should render a filled heart icon if subject is a favorite', function() {
wrapper.setProps({ isFavorite: true });
assert.equal(icon.props().className.includes('fa-heart'), true);
assert.equal(wrapper.state('favorited'), true);
});

it('should call toggleFavorite on click', function() {
button.simulate('click');
sinon.assert.calledOnce(toggleFavoriteSpy);
});
});
7 changes: 0 additions & 7 deletions app/collections/get-favorites-name.cjsx

This file was deleted.

2 changes: 1 addition & 1 deletion app/collections/show-list.cjsx
Expand Up @@ -61,7 +61,7 @@ SubjectNode = React.createClass

apiClient.type('collections').get(query)
.then ([favoritesCollection]) =>
if favoritesCollection?
if favoritesCollection? and favoritesCollection.links.subjects?
isFavorite = @props.subject.id in favoritesCollection.links.subjects
@setState({ isFavorite })

Expand Down
2 changes: 1 addition & 1 deletion app/components/subject-viewer.cjsx
@@ -1,5 +1,5 @@
React = require 'react'
FavoritesButton = require '../collections/favorites-button'
`import FavoritesButton from '../collections/favorites-button';`
Dialog = require 'modal-form/dialog'
{Markdown} = require 'markdownz'
classnames = require 'classnames'
Expand Down

0 comments on commit d3673a3

Please sign in to comment.