Merge branch 'e2e-env-fix' of github.com:coralproject/talk into e2e-env-fix

This commit is contained in:
David Jay
2017-01-26 16:52:12 -05:00
36 changed files with 326 additions and 890 deletions
+2
View File
@@ -21,6 +21,8 @@ test:
override:
# Run the tests using the junit reporter.
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml NPM_PACKAGE_CONFIG_MOCHA_REPORTER=mocha-junit-reporter npm run test
# Run the e2e test suite
- npm run e2e
deployment:
release:
@@ -7,47 +7,50 @@ import translations from '../translations.json';
const lang = new I18n(translations);
export default ({handleChange, handleApply, changed, ...props}) => (
<div className={styles.wrapper}>
<div className={styles.container}>
<h3>{lang.t('configureCommentStream.title')}</h3>
<p>{lang.t('configureCommentStream.description')}</p>
<Button
className={styles.apply}
cStyle={changed ? 'green' : 'darkGrey'}
onClick={handleApply}
>
{lang.t('configureCommentStream.apply')}
</Button>
</div>
<ul>
<li>
<Checkbox
className={styles.checkbox}
cStyle={changed ? 'green' : 'darkGrey'}
name="premod"
<form onSubmit={handleApply}>
<div className={styles.wrapper}>
<div className={styles.container}>
<h3>{lang.t('configureCommentStream.title')}</h3>
<p>{lang.t('configureCommentStream.description')}</p>
<Button
type="submit"
className={styles.apply}
onChange={handleChange}
checked={props.premod}
info={{
title: lang.t('configureCommentStream.enablePremod'),
description: lang.t('configureCommentStream.enablePremodDescription')
}}
/>
<ul>
<li>
<Checkbox
className={styles.checkbox}
cStyle={changed ? 'green' : 'darkGrey'}
name="premodLinks"
onChange={handleChange}
checked={props.premodLinks}
info={{
title: lang.t('configureCommentStream.enablePremodLinks'),
description: lang.t('configureCommentStream.enablePremodDescription')
}}
/>
</li>
</ul>
</li>
</ul>
</div>
cStyle={changed ? 'green' : 'darkGrey'} >
{lang.t('configureCommentStream.apply')}
</Button>
</div>
<ul>
<li>
<Checkbox
className={styles.checkbox}
cStyle={changed ? 'green' : 'darkGrey'}
name="premod"
onChange={handleChange}
defaultChecked={props.premod}
info={{
title: lang.t('configureCommentStream.enablePremod'),
description: lang.t('configureCommentStream.enablePremodDescription')
}} />
{/* To be implimented
<ul>
<li>
<Checkbox
className={styles.checkbox}
cStyle={changed ? 'green' : 'darkGrey'}
name="premodLinks"
onChange={handleChange}
defaultChecked={props.premodLinks}
info={{
title: lang.t('configureCommentStream.enablePremodLinks'),
description: lang.t('configureCommentStream.enablePremodDescription')
}} />
</li>
</ul>
*/}
</li>
</ul>
</div>
</form>
);
@@ -1,8 +1,9 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import {I18n} from '../../coral-framework';
import {updateOpenStatus, updateConfiguration} from '../../coral-framework/actions/asset';
import {I18n} from 'coral-framework';
import {updateOpenStatus, updateConfiguration} from 'coral-framework/actions/asset';
import CloseCommentsInfo from '../components/CloseCommentsInfo';
import ConfigureCommentStream from '../components/ConfigureCommentStream';
@@ -13,11 +14,8 @@ class ConfigureStreamContainer extends Component {
constructor (props) {
super(props);
console.log('moderation', props.asset.settings.moderation);
this.state = {
premod: props.asset.settings.moderation === 'PRE',
premodLinks: false
changed: false
};
this.toggleStatus = this.toggleStatus.bind(this);
@@ -25,11 +23,18 @@ class ConfigureStreamContainer extends Component {
this.handleApply = this.handleApply.bind(this);
}
handleApply () {
const {premod, changed} = this.state;
handleApply (e) {
e.preventDefault();
const {elements} = e.target;
const premod = elements.premod.checked;
// const premodLinks = elements.premodLinks.checked;
const {changed} = this.state;
const newConfig = {
moderation: premod ? 'PRE' : 'POST'
};
if (changed) {
this.props.updateConfiguration(newConfig);
setTimeout(() => {
@@ -40,16 +45,16 @@ class ConfigureStreamContainer extends Component {
}
}
handleChange (e) {
const {name, checked} = e.target;
handleChange () {
this.setState({
[name]: checked,
changed: true
});
}
toggleStatus () {
this.props.updateStatus(this.props.asset.closedAt === null ? 'closed' : 'open');
this.props.updateStatus(
this.props.asset.closedAt === null ? 'closed' : 'open'
);
}
getClosedIn () {
@@ -60,13 +65,16 @@ class ConfigureStreamContainer extends Component {
render () {
const status = this.props.asset.closedAt === null ? 'open' : 'closed';
const premod = this.props.asset.settings.moderation === 'PRE';
return (
<div>
<ConfigureCommentStream
handleChange={this.handleChange}
handleApply={this.handleApply}
changed={this.state.changed}
{...this.state}
premodLinks={false}
premod={premod}
/>
<hr />
<h3>{status === 'open' ? 'Close' : 'Open'} Comment Stream</h3>
@@ -89,7 +97,6 @@ const mapDispatchToProps = dispatch => ({
updateConfiguration: newConfig => dispatch(updateConfiguration(newConfig))
});
export default connect(
mapStateToProps,
mapDispatchToProps
export default compose(
connect(mapStateToProps, mapDispatchToProps)
)(ConfigureStreamContainer);
+14 -13
View File
@@ -26,6 +26,11 @@ class Comment extends React.Component {
}
static propTypes = {
reactKey: PropTypes.string.isRequired,
// id of currently opened ReplyBox. tracked in Stream.js
activeReplyBox: PropTypes.string.isRequired,
setActiveReplyBox: PropTypes.func.isRequired,
refetch: PropTypes.func.isRequired,
showSignInDialog: PropTypes.func.isRequired,
postAction: PropTypes.func.isRequired,
@@ -60,15 +65,6 @@ class Comment extends React.Component {
}).isRequired
}
onReplyBoxClick = () => {
if (this.props.currentUser) {
this.setState({replyBoxVisible: !this.state.replyBoxVisible});
} else {
const offset = document.getElementById(`c_${this.props.comment.id}`).getBoundingClientRect().top - 75;
this.props.showSignInDialog(offset);
}
}
render () {
const {
comment,
@@ -81,6 +77,8 @@ class Comment extends React.Component {
addNotification,
showSignInDialog,
postAction,
setActiveReplyBox,
activeReplyBox,
deleteAction
} = this.props;
@@ -106,7 +104,7 @@ class Comment extends React.Component {
<Content body={comment.body} />
<div className="commentActionsLeft">
<ReplyButton
onClick={this.onReplyBoxClick}
onClick={() => setActiveReplyBox(comment.id)}
parentCommentId={parentId || comment.id}
currentUserId={currentUser && currentUser.id}
banned={false} />
@@ -130,13 +128,13 @@ class Comment extends React.Component {
<PermalinkButton articleURL={asset.url} commentId={comment.id} />
</div>
{
this.state.replyBoxVisible
activeReplyBox === comment.id
? <ReplyBox
commentPostedHandler={() => {
console.log('replyPostedHandler');
this.setState({replyBoxVisible: false});
setActiveReplyBox('');
refetch();
}}
setActiveReplyBox={setActiveReplyBox}
parentId={parentId || comment.id}
addNotification={addNotification}
authorId={currentUser.id}
@@ -149,6 +147,8 @@ class Comment extends React.Component {
comment.replies.map(reply => {
return <Comment
refetch={refetch}
setActiveReplyBox={setActiveReplyBox}
activeReplyBox={activeReplyBox}
addNotification={addNotification}
parentId={comment.id}
postItem={postItem}
@@ -158,6 +158,7 @@ class Comment extends React.Component {
postAction={postAction}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
reactKey={reply.id}
key={reply.id}
comment={reply} />;
})
+8 -5
View File
@@ -9,8 +9,8 @@ const {logout, showSignInDialog} = authActions;
const {addNotification, clearNotification} = notificationActions;
const {fetchAssetSuccess} = assetActions;
import {queryStream} from './graphql/queries';
import {postComment, postAction, deleteAction} from './graphql/mutations';
import {queryStream} from 'coral-framework/graphql/queries';
import {postComment, postAction, deleteAction} from 'coral-framework/graphql/mutations';
import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework';
import Stream from './Stream';
@@ -90,9 +90,12 @@ class Embed extends Component {
render () {
const {activeTab} = this.state;
const {closedAt} = this.props.asset;
const {loading, asset, refetch} = this.props.data;
const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth;
const openStream = closedAt === null;
const expandForLogin = showSignInDialog ? {
minHeight: document.body.scrollHeight + 200
} : {};
@@ -109,7 +112,7 @@ class Embed extends Component {
{loggedIn && <UserBox user={user} logout={this.props.logout} />}
<TabContent show={activeTab === 0}>
{
asset.closedAt === null
openStream
? <div id="commentBox">
<InfoBox
content={asset.settings.infoBoxContent}
@@ -181,10 +184,10 @@ class Embed extends Component {
}
const mapStateToProps = state => ({
items: state.items.toJS(),
notification: state.notification.toJS(),
auth: state.auth.toJS(),
userData: state.user.toJS()
userData: state.user.toJS(),
asset: state.asset.toJS()
});
const mapDispatchToProps = dispatch => ({
+66 -43
View File
@@ -1,49 +1,72 @@
import React, {PropTypes} from 'react';
import Comment from './Comment';
const Stream = ({
comments,
currentUser,
asset,
postItem,
addNotification,
postAction,
deleteAction,
showSignInDialog,
refetch
}) => {
return (
<div>
{
comments.map(comment => {
return <Comment
refetch={refetch}
addNotification={addNotification}
depth={0}
postItem={postItem}
asset={asset}
currentUser={currentUser}
postAction={postAction}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
key={comment.id}
comment={comment} />;
})
}
</div>
);
};
class Stream extends React.Component {
Stream.propTypes = {
refetch: PropTypes.func.isRequired,
addNotification: PropTypes.func.isRequired,
postItem: PropTypes.func.isRequired,
asset: PropTypes.object.isRequired,
comments: PropTypes.array.isRequired,
currentUser: PropTypes.shape({
displayName: PropTypes.string,
id: PropTypes.string
})
};
static propTypes = {
refetch: PropTypes.func.isRequired,
addNotification: PropTypes.func.isRequired,
postItem: PropTypes.func.isRequired,
asset: PropTypes.object.isRequired,
comments: PropTypes.array.isRequired,
currentUser: PropTypes.shape({
displayName: PropTypes.string,
id: PropTypes.string
})
}
constructor(props) {
super(props);
this.state = {activeReplyBox: ''};
this.setActiveReplyBox = this.setActiveReplyBox.bind(this);
}
setActiveReplyBox (reactKey) {
if (!this.props.currentUser) {
const offset = document.getElementById(`c_${reactKey}`).getBoundingClientRect().top - 75;
this.props.showSignInDialog(offset);
} else {
this.setState({activeReplyBox: reactKey});
}
}
render () {
const {
comments,
currentUser,
asset,
postItem,
addNotification,
postAction,
deleteAction,
showSignInDialog,
refetch
} = this.props;
return (
<div>
{
comments.map(comment => {
return <Comment
refetch={refetch}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.state.activeReplyBox}
addNotification={addNotification}
depth={0}
postItem={postItem}
asset={asset}
currentUser={currentUser}
postAction={postAction}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
key={comment.id}
reactKey={comment.id}
comment={comment} />;
})
}
</div>
);
}
}
export default Stream;
@@ -1,23 +0,0 @@
fragment commentView on Comment {
id
body
status
user {
name: displayName
}
actions {
type: action_type
count
current: current_user {
id
created_at
}
}
}
mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) {
createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) {
...commentView
}
}
+9 -9
View File
@@ -10,30 +10,30 @@ export const fetchAssetRequest = () => ({type: actions.FETCH_ASSET_REQUEST});
export const fetchAssetSuccess = asset => ({type: actions.FETCH_ASSET_SUCCESS, asset});
export const fetchAssetFailure = error => ({type: actions.FETCH_ASSET_FAILURE, error});
const updateConfigRequest = () => ({type: actions.UPDATE_CONFIG_REQUEST});
const updateConfigSuccess = config => ({type: actions.UPDATE_CONFIG_SUCCESS, config});
const updateConfigFailure = () => ({type: actions.UPDATE_CONFIG_FAILURE});
const updateAssetSettingsRequest = () => ({type: actions.UPDATE_ASSET_SETTINGS_REQUEST});
const updateAssetSettingsSuccess = settings => ({type: actions.UPDATE_ASSET_SETTINGS_SUCCESS, settings});
const updateAssetSettingsFailure = () => ({type: actions.UPDATE_ASSET_SETTINGS_FAILURE});
export const updateConfiguration = newConfig => (dispatch, getState) => {
const assetId = getState().asset.toJS().id;
dispatch(updateConfigRequest());
dispatch(updateAssetSettingsRequest());
coralApi(`/assets/${assetId}/settings`, {method: 'PUT', body: newConfig})
.then(() => {
dispatch(addNotification('success', lang.t('successUpdateSettings')));
dispatch(updateConfigSuccess(newConfig));
dispatch(updateAssetSettingsSuccess(newConfig));
})
.catch(error => dispatch(updateConfigFailure(error)));
.catch(error => dispatch(updateAssetSettingsFailure(error)));
};
export const updateOpenStream = closedBody => (dispatch, getState) => {
const assetId = getState().asset.toJS().id;
dispatch(updateConfigRequest());
dispatch(fetchAssetRequest());
coralApi(`/assets/${assetId}/status`, {method: 'PUT', body: closedBody})
.then(() => {
dispatch(addNotification('success', lang.t('successUpdateSettings')));
dispatch(updateConfigSuccess(closedBody));
dispatch(fetchAssetSuccess(closedBody));
})
.catch(error => dispatch(updateConfigFailure(error)));
.catch(error => dispatch(fetchAssetFailure(error)));
};
const openStream = () => ({type: actions.OPEN_COMMENTS});
-3
View File
@@ -3,7 +3,6 @@ import translations from './../translations';
const lang = new I18n(translations);
import * as actions from '../constants/auth';
import coralApi, {base} from '../helpers/response';
import {addItem} from './items';
// Dialog Actions
export const showSignInDialog = (offset = 0) => ({type: actions.SHOW_SIGNIN_DIALOG, offset});
@@ -30,7 +29,6 @@ export const fetchSignIn = (formData) => (dispatch) => {
const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length;
dispatch(signInSuccess(user, isAdmin));
dispatch(hideSignInDialog());
dispatch(addItem(user, 'users'));
})
.catch(() => dispatch(signInFailure(lang.t('error.emailPasswordError'))));
};
@@ -59,7 +57,6 @@ export const facebookCallback = (err, data) => dispatch => {
const user = JSON.parse(data);
dispatch(signInFacebookSuccess(user));
dispatch(hideSignInDialog());
dispatch(addItem(user, 'users'));
} catch (err) {
dispatch(signInFacebookFailure(err));
return;
-268
View File
@@ -1,268 +0,0 @@
import coralApi from '../helpers/response';
import {fromJS} from 'immutable';
import {UPDATE_CONFIG} from '../constants/config';
/**
* Action name constants
*/
export const ADD_ITEM = 'ADD_ITEM';
export const UPDATE_ITEM = 'UPDATE_ITEM';
export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY';
/**
* Action creators
*/
/*
* Adds an item to the local store without posting it to the server
* Useful for optimistic posting, etc.
*
* @params
* item - the item to be posted
*
*/
export const addItem = (item, item_type) => {
if (!item.id) {
console.warn('addItem called without an item id.');
}
return {
type: ADD_ITEM,
item,
item_type,
id: item.id
};
};
/*
* Updates an item in the local store without posting it to the server
* Useful for item-level toggles, etc.
*
* @params
* id - the id of the item to be posted
* property - the property to be updated
* value - the value that the property should be set to
* item_type - the type of the item being updated (users, comments, etc)
*
*/
export const updateItem = (id, property, value, item_type) => {
return {
type: UPDATE_ITEM,
id,
property,
value,
item_type
};
};
/*
* Appends data to an array in an item in the local store without posting it to the server
* Useful for adding a recently posted reply to a comment, etc.
*
* @params
* id - the id of the item to be posted
* property - the property to be updated (should be an array)
* value - the value that should be added to the array
* add_to_front - boolean that defines whether value is added at the beginning (unshift) or end (push)
* item_type - the type of the item being updated (users, comments, etc)
*
*/
export const appendItemArray = (id, property, value, add_to_front, item_type) => {
return {
type: APPEND_ITEM_ARRAY,
id,
property,
value,
add_to_front,
item_type
};
};
/*
* Get Items from Query
* Gets a set of items from a predefined query
*
* @params
* Query - a predefiend query for retreiving items
*
* @returns
* A promise resolving to a set of items
*
* @dispatches
* A set of items to the item store
*/
export function getStream (assetUrl) {
return (dispatch) => {
return coralApi(`/stream?asset_url=${encodeURIComponent(assetUrl)}`)
.then((json) => {
/* Add items to the store */
Object.keys(json).forEach(type => {
if (type === 'actions') {
json[type].forEach(action => {
action.id = `${action.action_type}_${action.item_id}`;
dispatch(addItem(action, 'actions'));
});
} else if (type === 'settings') {
dispatch({type: UPDATE_CONFIG, config: fromJS(json[type])});
} else {
json[type].forEach(item => {
dispatch(addItem(item, type));
});
}
});
const assetId = json.assets[0].id;
/* Sort comments by date*/
json.comments.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
const rels = json.comments.reduce((h, item) => {
/* Check for root and child comments. */
if (
item.asset_id === assetId &&
!item.parent_id) {
h.rootComments.push(item.id);
} else if (
item.asset_id === assetId
) {
let children = h.childComments[item.parent_id] || [];
h.childComments[item.parent_id] = children.concat(item.id);
}
return h;
}, {rootComments: [], childComments: {}});
dispatch(updateItem(assetId, 'comments', rels.rootComments, 'assets'));
Object.keys(rels.childComments).forEach(key => {
dispatch(updateItem(key, 'children', rels.childComments[key].reverse(), 'comments'));
});
/* Hydrate actions on comments */
json.actions.forEach(action => {
dispatch(updateItem(action.item_id, action.action_type, action.id, 'comments'));
});
return (json);
});
};
}
/*
* Get Items Array
* Gets a set of items from an array of item ids
*
* @params
* Query - a predefiend query for retreiving items
*
* @returns
* A promise resolving to a set of items
*
* @dispatches
* A set of items to the item store
*/
export function getItemsArray (ids) {
return (dispatch) => {
return coralApi(`/item/${ids}`)
.then((json) => {
for (let i = 0; i < json.items.length; i++) {
dispatch(addItem(json.items[i]));
}
return json.items;
});
};
}
/*
* PutItem
* Puts an item
*
* @params
* Item - the item to be put
*
* @returns
* A promise resolving to an item is
*
* @dispatches
* The newly put item to the item store
*/
export function postItem (item, type, id, mutate) {
console.log(
item,
type,
id,
mutate
);
mutate({
variables: {
asset_id: id,
body: item,
parent_id: null
}
}).then(({data}) => {
console.log('it workt');
console.log(data);
});
// return (dispatch) => {
// if (id) {
// item.id = id;
// }
// return coralApi(`/${type}`, {method: 'POST', body: item})
// .then((json) => {
// dispatch(addItem({...item, id:json.id}, type));
// return json;
// });
// };
}
/*
* PostAction
* Posts an action to an item
*
* @params
* id - the id of the item on which the action is taking place
* action - the action object.
* Must include an 'action_type' string.
* May optionally include a `metadata` object with arbitrary action data.
* user - the user performing the action
* host - the coral host
*
* @returns
* A promise resolving to null or an error
*
*/
export function postAction (item_id, item_type, action) {
return (dispatch) => {
return coralApi(`/${item_type}/${item_id}/actions`, {method: 'POST', body: action})
.then((json) => {
dispatch(updateItem(action.item_id, action.action_type, action.id, item_type));
return json;
});
};
}
/*
* DeleteAction
* Deletes an action to an item
*
* @params
* id - the id of the item on which the action is taking place
* action - the name of the action
* user - the user performing the action
* host - the coral host
*
* @returns
* A promise resolving to null or an error
*
*/
export function deleteAction (action_id) {
return () => {
return coralApi(`/actions/${action_id}`, {method: 'DELETE'});
};
}
-35
View File
@@ -1,7 +1,5 @@
import * as actions from '../constants/user';
import * as assetActions from '../constants/assets';
import {addNotification} from '../actions/notification';
import {addItem} from '../actions/items';
import coralApi from '../helpers/response';
import I18n from 'coral-framework/modules/i18n/i18n';
@@ -21,36 +19,3 @@ export const saveBio = (user_id, formData) => dispatch => {
})
.catch(error => dispatch(saveBioFailure(error)));
};
/**
*
* Get a list of comments by a single user
*
* @param {string} user_id
* @returns Promise
*/
export const fetchCommentsByUserId = userId => {
return (dispatch, getState) => {
dispatch({type: actions.COMMENTS_BY_USER_REQUEST});
return coralApi(`/comments?user_id=${userId}`)
.then(({comments, assets}) => {
const state = getState();
comments.forEach(comment => dispatch(addItem(comment, 'comments')));
assets.forEach(asset => {
const prevAsset = state.items.getIn(['assets', asset.id]);
if (prevAsset) {
// Include data such as hydrated comments from assets already in the system.
dispatch(addItem({...prevAsset.toJS(), ...asset}, 'assets'));
} else {
dispatch(addItem(asset, 'assets'));
}
});
dispatch({type: actions.COMMENTS_BY_USER_SUCCESS, comments: comments.map(comment => comment.id)});
dispatch({type: assetActions.MULTIPLE_ASSETS_SUCCESS, assets: assets.map(asset => asset.id)});
})
.catch(error => dispatch({type: actions.COMMENTS_BY_USER_FAILURE, error}));
};
};
+1
View File
@@ -2,6 +2,7 @@ import ApolloClient, {addTypename} from 'apollo-client';
import getNetworkInterface from './transport';
export const client = new ApolloClient({
connectToDevTools: true,
queryTransformer: addTypename,
dataIdFromObject: (result) => {
if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle
+3 -6
View File
@@ -2,12 +2,9 @@ export const FETCH_ASSET_REQUEST = 'FETCH_ASSET_REQUEST';
export const FETCH_ASSET_FAILURE = 'FETCH_ASSET_FAILURE';
export const FETCH_ASSET_SUCCESS = 'FETCH_ASSET_SUCCESS';
export const UPDATE_CONFIG_REQUEST = 'UPDATE_CONFIG_REQUEST';
export const UPDATE_CONFIG_SUCCESS = 'UPDATE_CONFIG_SUCCESS';
export const UPDATE_CONFIG_FAILURE = 'UPDATE_CONFIG_FAILURE';
export const UPDATE_CONFIG = 'UPDATE_CONFIG';
export const UPDATE_ASSET_SETTINGS_REQUEST = 'UPDATE_ASSET_SETTINGS_REQUEST';
export const UPDATE_ASSET_SETTINGS_SUCCESS = 'UPDATE_ASSET_SETTINGS_SUCCESS';
export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE';
export const OPEN_COMMENTS = 'OPEN_COMMENTS';
export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
export const ADD_ITEM = 'ADD_ITEM';
@@ -0,0 +1,20 @@
fragment commentView on Comment {
id
body
created_at
user {
id
name: displayName
settings {
bio
}
}
actions {
type: action_type
count
current: current_user {
id
created_at
}
}
}
@@ -3,7 +3,12 @@ import POST_COMMENT from './postComment.graphql';
import POST_ACTION from './postAction.graphql';
import DELETE_ACTION from './deleteAction.graphql';
import commentView from '../fragments/commentView.graphql';
export const postComment = graphql(POST_COMMENT, {
options: () => ({
fragments: commentView
}),
props: ({mutate}) => ({
postItem: ({asset_id, body, parent_id} /* , type */ ) => {
return mutate({
@@ -0,0 +1,7 @@
#import "../fragments/commentView.graphql"
mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) {
createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) {
...commentView
}
}
@@ -1,5 +1,6 @@
import {graphql} from 'react-apollo';
import STREAM_QUERY from './streamQuery.graphql';
import MY_COMMENT_HISTORY from './myCommentHistory.graphql';
function getQueryVariable(variable) {
let query = window.location.search.substring(1);
@@ -13,5 +14,11 @@ function getQueryVariable(variable) {
}
export const queryStream = graphql(STREAM_QUERY, {
options: {variables: {asset_url: getQueryVariable('asset_url')}}
options: () => ({
variables: {
asset_url: getQueryVariable('asset_url')
}
})
});
export const myCommentHistory = graphql(MY_COMMENT_HISTORY, {});
@@ -0,0 +1,9 @@
query myCommentHistory {
me {
comments {
id
body
created_at
}
}
}
@@ -1,24 +1,4 @@
fragment commentView on Comment {
id
body
created_at
user {
id
name: displayName
settings {
bio
}
}
actions {
type: action_type
count
current: current_user {
id
created_at
}
}
}
#import "../fragments/commentView.graphql"
query AssetQuery($asset_url: String!) {
asset(url: $asset_url) {
+7 -9
View File
@@ -1,19 +1,17 @@
import Notification from './modules/notification/Notification';
import store from './store';
import * as itemActions from './actions/items';
import pym from './PymConnection';
import I18n from './modules/i18n/i18n';
import * as notificationActions from './actions/notification';
import * as authActions from './actions/auth';
import * as assetActions from './actions/asset';
import pym from './PymConnection';
import * as notificationActions from './actions/notification';
import Notification from './modules/notification/Notification';
export {
Notification,
store,
itemActions,
pym,
I18n,
notificationActions,
store,
authActions,
assetActions,
pym
Notification,
notificationActions
};
+3 -14
View File
@@ -13,23 +13,12 @@ const initialState = Map({
export default function asset (state = initialState, action) {
switch (action.type) {
case actions.FETCH_ASSET_SUCCESS :
case actions.FETCH_ASSET_SUCCESS:
return state
.merge(action.asset);
case actions.UPDATE_CONFIG:
case actions.UPDATE_ASSET_SETTINGS_SUCCESS:
return state
.merge(action.config);
case actions.UPDATE_CONFIG_SUCCESS:
return state
.merge(action.config);
case actions.OPEN_COMMENTS:
return state
.set('status', 'open')
.set('closedAt', null);
case actions.CLOSE_COMMENTS:
return state
.set('status', 'closed')
.set('closedAt', Date.now());
.setIn(['settings'], action.settings);
default:
return state;
}
-2
View File
@@ -1,13 +1,11 @@
import auth from './auth';
import user from './user';
import asset from './asset';
import items from './items';
import notification from './notification';
export default {
auth,
user,
asset,
items,
notification
};
-30
View File
@@ -1,30 +0,0 @@
/* Items Reducer */
import {fromJS} from 'immutable';
import * as actions from '../actions/items';
const initialState = fromJS({
comments: {},
users: {},
assets: {},
actions: {}
});
export default (state = initialState, action) => {
switch (action.type) {
case actions.ADD_ITEM:
return state.setIn([action.item_type, action.id], fromJS(action.item));
case actions.UPDATE_ITEM:
return state.setIn([action.item_type, action.id, action.property], fromJS(action.value));
case actions.APPEND_ITEM_ARRAY:
return state.updateIn([action.item_type, action.id, action.property], (prop) => {
if (action.add_to_front) {
return prop ? prop.unshift(fromJS(action.value)) : fromJS([action.value]);
} else {
return prop ? prop.push(fromJS(action.value)) : fromJS([action.value]);
}
});
default:
return state;
}
};
@@ -13,6 +13,7 @@ class CommentBox extends Component {
// comments: PropTypes.array,
commentPostedHandler: PropTypes.func,
postItem: PropTypes.func.isRequired,
cancelButtonClicked: PropTypes.func,
assetId: PropTypes.string.isRequired,
parentId: PropTypes.string,
authorId: PropTypes.string.isRequired,
@@ -89,7 +90,14 @@ class CommentBox extends Component {
render () {
const {styles, isReply, authorId, charCount} = this.props;
let {cancelButtonClicked} = this.props;
const length = this.state.body.length;
if (isReply && typeof cancelButtonClicked !== 'function') {
console.warn('the CommentBox component should have a cancelButtonClicked callback defined if it lives in a Reply');
cancelButtonClicked = () => {};
}
return <div>
<div
className={`${name}-container`}>
@@ -115,6 +123,19 @@ class CommentBox extends Component {
}
</div>
<div className={`${name}-button-container`}>
{
isReply && (
<Button
cStyle='darkGrey'
className={`${name}-cancel-button`}
onClick={() => {
console.log('cancel button in comment box');
cancelButtonClicked('');
}}>
{lang.t('cancel')}
</Button>
)
}
{ authorId && (
<Button
cStyle={!length || (charCount && length > charCount) ? 'lightGrey' : 'darkGrey'}
@@ -1,6 +1,7 @@
{
"en": {
"post": "Post",
"cancel": "Cancel",
"reply": "Reply",
"comment": "Comment",
"name": "Name",
@@ -11,6 +12,7 @@
},
"es": {
"post": "Publicar",
"cancel": "Cancelar",
"reply": "Respuesta",
"comment": "Comentario",
"name": "Nombre",
@@ -7,12 +7,11 @@ const CommentHistory = props => {
<div className={`${styles.header} commentHistory`}>
<div className="commentHistory__list">
{props.comments.map((comment, i) => {
const asset = props.assets.find(asset => asset.id === comment.asset_id);
return <Comment
key={i}
comment={comment}
link={props.link}
asset={asset} />;
asset={props.asset} />;
})}
</div>
</div>
@@ -20,8 +19,8 @@ const CommentHistory = props => {
};
CommentHistory.propTypes = {
comments: PropTypes.arrayOf(PropTypes.object).isRequired,
assets: PropTypes.arrayOf(PropTypes.object).isRequired
comments: PropTypes.array.isRequired,
asset: PropTypes.object.isRequired
};
export default CommentHistory;
+3 -1
View File
@@ -3,11 +3,12 @@ import CommentBox from '../coral-plugin-commentbox/CommentBox';
const name = 'coral-plugin-replies';
const ReplyBox = ({styles, postItem, assetId, authorId, addNotification, parentId, commentPostedHandler}) => (
const ReplyBox = ({styles, postItem, assetId, authorId, addNotification, parentId, commentPostedHandler, setActiveReplyBox}) => (
<div className={`${name}-textarea`} style={styles && styles.container}>
<CommentBox
commentPostedHandler={commentPostedHandler}
parentId={parentId}
cancelButtonClicked={setActiveReplyBox}
addNotification={addNotification}
authorId={authorId}
assetId={assetId}
@@ -17,6 +18,7 @@ const ReplyBox = ({styles, postItem, assetId, authorId, addNotification, parentI
);
ReplyBox.propTypes = {
setActiveReplyBox: PropTypes.func.isRequired,
commentPostedHandler: PropTypes.func,
parentId: PropTypes.string,
addNotification: PropTypes.func.isRequired,
@@ -1,17 +1,18 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {saveBio, fetchCommentsByUserId} from 'coral-framework/actions/user';
import {link} from 'coral-framework/PymConnection';
import BioContainer from './BioContainer';
import NotLoggedIn from '../components/NotLoggedIn';
import {TabBar, Tab, TabContent} from '../../coral-ui';
import CommentHistory from 'coral-plugin-history/CommentHistory';
import SettingsHeader from '../components/SettingsHeader';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
import {compose} from 'react-apollo';
import React, {Component} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import {myCommentHistory} from 'coral-framework/graphql/queries';
import {saveBio} from 'coral-framework/actions/user';
import BioContainer from './BioContainer';
import {link} from 'coral-framework/PymConnection';
import NotLoggedIn from '../components/NotLoggedIn';
import {TabBar, Tab, TabContent, Spinner} from 'coral-ui';
import SettingsHeader from '../components/SettingsHeader';
import CommentHistory from 'coral-plugin-history/CommentHistory';
import translations from '../translations';
const lang = new I18n(translations);
@@ -25,12 +26,6 @@ class SettingsContainer extends Component {
this.handleTabChange = this.handleTabChange.bind(this);
}
componentWillMount () {
// Fetch commentHistory
this.props.fetchCommentsByUserId(this.props.userData.id);
}
handleTabChange(tab) {
this.setState({
activeTab: tab
@@ -38,55 +33,56 @@ class SettingsContainer extends Component {
}
render() {
const {loggedIn, userData, showSignInDialog, items, user} = this.props;
const {loggedIn, userData, asset, showSignInDialog, data, user} = this.props;
const {activeTab} = this.state;
const {me} = this.props.data;
const commentsMostRecentFirst = user
.myComments.map(id => items.comments[id])
.sort(({created_at:a}, {created_at:b}) => {
if (!loggedIn || !me) {
return <NotLoggedIn showSignInDialog={showSignInDialog}/>;
}
// descending order, created_at
// js date strings can be sorted lexigraphically.
const aLessThanB = a < b ? 1 : 0;
return a > b ? -1 : aLessThanB;
});
if (data.loading) {
return <Spinner/>;
}
return (
<RestrictedContent restricted={!loggedIn} restrictedComp={<NotLoggedIn showSignInDialog={showSignInDialog} />}>
<div>
<SettingsHeader {...this.props} />
<TabBar onChange={this.handleTabChange} activeTab={activeTab} cStyle='material'>
<Tab>All Comments ({user.myComments.length})</Tab>
<Tab>Profile Settings</Tab>
<Tab>{lang.t('allComments')} ({user.myComments.length})</Tab>
<Tab>{lang.t('profileSettings')}</Tab>
</TabBar>
<TabContent show={activeTab === 0}>
{
user.myComments.length && user.myAssets.length
? <CommentHistory
comments={commentsMostRecentFirst}
link={link}
assets={user.myAssets.map(id => items.assets[id])} />
: <p>{lang.t('user-no-comment')}</p>
me.comments.length ?
<CommentHistory
comments={me.comments}
asset={asset}
link={link}
/>
:
<p>{lang.t('userNoComment')}</p>
}
</TabContent>
<TabContent show={activeTab === 1}>
<BioContainer bio={userData.settings.bio} handleSave={this.handleSave} {...this.props} />
</TabContent>
</RestrictedContent>
</div>
);
}
}
const mapStateToProps = state => ({
items: state.items.toJS(),
user: state.user.toJS()
user: state.user.toJS(),
asset: state.asset.toJS(),
auth: state.auth.toJS()
});
const mapDispatchToProps = dispatch => ({
saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData)),
fetchCommentsByUserId: userId => dispatch(fetchCommentsByUserId(userId))
saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData))
});
export default connect(
mapStateToProps,
mapDispatchToProps
export default compose(
connect(mapStateToProps, mapDispatchToProps),
myCommentHistory
)(SettingsContainer);
+8 -2
View File
@@ -1,8 +1,14 @@
{
"en":{
"user-no-comment": "This user has not yet left a comment."
"userNoComment": "This user has not yet left a comment.",
"allComments": "All Comments",
"profileSettings": "Profile Settings",
"myCommentHistory": "My comment History"
},
"es":{
"user-no-comment": "Aún no ha escrito ningún comentario."
"userNoComment": "Aún no ha escrito ningún comentario.",
"allComments": "Todos los comentarios",
"profileSettings": "Configuración del perfil",
"myCommentHistory": "Mi historial de comentarios"
}
}
@@ -20,7 +20,7 @@ import {
invalidForm,
validForm,
checkLogin
} from '../../coral-framework/actions/auth';
} from 'coral-framework/actions/auth';
class SignInContainer extends Component {
initialState = {
+2 -2
View File
@@ -1,9 +1,9 @@
import React from 'react';
import styles from './Checkbox.css';
export default ({name, cStyle = 'base', onChange, label, className, info, checked = 'false'}) => (
export default ({name, cStyle = 'base', onChange, label, className, info, ...props}) => (
<label className={`${styles.label} ${styles[`type--${cStyle}`]} ${className}`} htmlFor={name}>
<input type="checkbox" id={name} name={name} onChange={onChange} checked={checked} />
<input type="checkbox" id={name} name={name} onChange={onChange} {...props} />
<span className={styles.checkbox}></span>
{label && <span>{label}</span>}
{info && (
@@ -1,198 +0,0 @@
import 'react';
import 'redux';
import {expect} from 'chai';
import fetchMock from 'fetch-mock';
import * as actions from '../../../../client/coral-framework/actions/items';
import {Map} from 'immutable';
import configureStore from 'redux-mock-store';
const mockStore = configureStore();
describe('itemActions', () => {
let store;
beforeEach(() => {
store = mockStore(new Map({}));
fetchMock.restore();
});
describe('getStream', () => {
const assetUrl = 'http://www.test.com';
const response = {
assets: [{
id: '1234', url: assetUrl
}],
comments: [
{body: 'stuff', id: '123'},
{body: 'morestuff', id: '456'}
],
actions: [
{
action_type: 'like',
item_id: '123',
count: 1,
id: 'like_123',
current_user: false
},
{
action_type: 'flag',
item_id: '456',
count: 5,
id: 'flag_456',
current_user: true
}
]
};
it('should get an stream from an asset_url and send the appropriate dispatches', () => {
fetchMock.get('*', JSON.stringify(response));
return actions.getStream(assetUrl)(store.dispatch)
.then((res) => {
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/stream?asset_url=http%3A%2F%2Fwww.test.com');
expect(res).to.deep.equal(response);
expect(store.getActions()[1]).to.deep.equal({
type: actions.ADD_ITEM,
item: response.comments[0],
item_type: 'comments',
id: '123'
});
expect(store.getActions()[2]).to.deep.equal({
type: actions.ADD_ITEM,
item: response.comments[1],
item_type: 'comments',
id: '456'
});
});
});
it('should handle an error', () => {
fetchMock.get('*', 404);
return actions.getStream(assetUrl)(store.dispatch)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
// Disabling tests for this function until is is used again.
xdescribe('getItemsArray', () => {
const response = {items: [{type: 'comment', id: '123'}, {type: 'comment', id: '456'}]};
const ids = [1, 2];
it('should get an item from an array of ids and send the appropriate dispatches', () => {
fetchMock.get('*', JSON.stringify(response));
return actions.getItemsArray(ids)(store.dispatch)
.then((res) => {
expect(res).to.deep.equal(response.items);
expect(store.getActions()[0]).to.deep.equal({
type: actions.ADD_ITEM,
item: {
type: 'comment',
id: '123'
},
id: '123'
});
expect(store.getActions()[1]).to.deep.equal({
type: actions.ADD_ITEM,
item: {
type: 'comment', id: '456'
},
id: '456'
});
});
});
it('should handle an error', () => {
fetchMock.get('*', 404);
return actions.getItemsArray(ids)(store.dispatch)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
// NEED TO FIGURE OUT HOW TO TEST WITH CSRF TOKEN IN.
xdescribe('postItem', () => {
const item = {
type: 'comments',
data: {body: 'stuff'}
};
it ('should post an item, return an id, then dispatch that item to the store', () => {
fetchMock.post('*', {id: '123'});
return actions.postItem(item.data, item.type, undefined)(store.dispatch, store.getState)
.then((id) => {
expect(fetchMock.calls().matched[0][1]).to.deep.equal(
{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type':'application/json'
},
credentials: 'same-origin',
body: JSON.stringify(item.data)
}
);
expect(id).to.deep.equal({id: '123'});
expect(store.getActions()[0]).to.deep.equal({
type: actions.ADD_ITEM,
item: {
body: 'stuff',
id: '123'
},
item_type: 'comments',
id: '123'
});
});
});
it('should handle an error', () => {
fetchMock.post('*', 404);
return actions.postItem(item)(store.dispatch, store.getState)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
xdescribe('postAction', () => {
it ('should post an action', () => {
fetchMock.post('*', {id: '456'});
const action = {
action_type: 'flag',
detail: 'Comment smells funny'
};
return actions.postAction('abc', 'comments', action)(store.dispatch, store.getState)
.then(response => {
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions');
expect(response).to.deep.equal({id:'456'});
});
});
it('should handle an error', () => {
fetchMock.post('*', 404);
return actions.postAction('abc', 'flag', '123')(store.dispatch, store.getState)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
describe('deleteAction', () => {
it ('should remove an action', () => {
fetchMock.delete('*', {});
return actions.deleteAction('abc', 'flag', '123', 'comments')(store.dispatch)
.then(response => {
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/actions/abc');
expect(response).to.deep.equal({});
});
});
xit('should handle an error', () => {
fetchMock.post('*', 404);
return actions.postAction('abc', 'flag', '123')(store.dispatch, store.getState)
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
});
@@ -1,95 +0,0 @@
import {Map, fromJS} from 'immutable';
import {expect} from 'chai';
import itemsReducer from '../../../../client/coral-framework/reducers/items';
describe ('itemsReducer', () => {
describe('ADD_ITEM', () => {
it('should add an item', () => {
const action = {
type: 'ADD_ITEM',
item: {
body: 'stuff',
id: '123'
},
item_type: 'comments',
id: '123'
};
const store = new Map({});
const result = itemsReducer(store, action);
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
body: 'stuff',
id: '123'
});
});
});
describe ('UPDATE_ITEM', () => {
it ('should update an item', () => {
const action = {
type: 'UPDATE_ITEM',
property: 'stuff',
value: 'things',
item_type: 'comments',
id: '123'
};
const store = fromJS({
'comments': {
'123': {
id: '123',
stuff: 'morestuff'
}
}
});
const result = itemsReducer(store, action);
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
id: '123',
stuff: 'things'
});
});
});
describe('APPEND_ITEM_ARRAY', () => {
let action;
let store;
beforeEach (() => {
action = {
type: 'APPEND_ITEM_ARRAY',
property: 'stuff',
value: 'things',
id: '123',
item_type: 'comments'
};
store = fromJS({
'comments': {
'123': {
id: '123',
stuff: ['morestuff']
}
}
});
});
it ('should append to an existing array', () => {
const result = itemsReducer(store, action);
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
id: '123',
stuff: ['morestuff', 'things']
});
});
it ('should create a new array', () => {
store = fromJS({
'comments': {
'123': {
id: '123'
}
}
});
const result = itemsReducer(store, action);
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
id: '123',
stuff: ['things']
});
});
});
});
@@ -6,14 +6,26 @@ import CommentHistory from '../../../client/coral-plugin-history/CommentHistory'
describe('coral-plugin-history/CommentHistory', () => {
let render;
const comments = [{body: 'a comment or something', 'status_history':[{'type':'premod', 'created_at':'2016-12-09T01:40:53.327Z', 'assigned_by':null}, {'created_at':'2016-12-09T22:52:44.888Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-09T01:40:53.360Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-09T22:52:44.893Z', 'id':'3962c2ea-4ec4-42e4-b9bd-c571ff30f56b'}, {'body':'another comment', 'status_history':[{'type':'premod', 'created_at':'2016-12-09T22:53:43.148Z', 'assigned_by':null}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-09T22:53:43.158Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'premod', '__v':0, 'updated_at':'2016-12-09T22:53:43.158Z', 'id':'b51e27af-bcfd-4932-91be-e3f01a4802e6'}, {'body':'can I comment?', 'status_history':[{'type':'premod', 'created_at':'2016-12-13T23:23:47.123Z', 'assigned_by':null}, {'created_at':'2016-12-13T23:23:58.487Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'cef81015-1b53-4d70-b9af-6eca680f22fc', 'created_at':'2016-12-13T23:23:47.131Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-13T23:23:58.493Z', 'id':'dc9d7be1-b911-4dc3-8e1e-400e8b8d110e'}, {'body':'pre-mod comment', 'status_history':[{'type':'premod', 'created_at':'2016-12-08T21:34:56.994Z', 'assigned_by':null}, {'created_at':'2016-12-08T21:38:04.961Z', 'type':'rejected', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T21:34:56.997Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'rejected', '__v':0, 'updated_at':'2016-12-08T21:38:04.965Z', 'id':'6f02af16-a8f8-4ead-80ea-0d48824eb74d'}, {'body':'a flagged commetn', 'status_history':[{'type':'premod', 'created_at':'2016-12-08T21:38:26.342Z', 'assigned_by':null}, {'created_at':'2016-12-09T23:47:27.009Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T21:38:26.344Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-09T23:47:27.018Z', 'id':'784c5f91-36b9-4bda-b4ca-a114cef2c9f0'}, {'body':'a post mod comment', 'status_history':[{'type':'premod', 'created_at':'2016-12-08T22:19:05.870Z', 'assigned_by':null}, {'created_at':'2016-12-09T23:26:41.427Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T22:19:05.874Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-09T23:26:41.450Z', 'id':'e8b86039-f850-4e53-bd9d-f8c9186a9637'}, {'body':'an actual post-mod comment here', 'status_history':[], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T22:20:11.147Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':null, '__v':0, 'updated_at':'2016-12-08T22:20:11.147Z', 'id':'cff1a318-50c6-431e-9a63-de7a7b7136bf'}];
const assets = [{'settings': null, 'created_at':'2016-12-06T21:36:09.302Z', 'url':'localhost:3000/', 'scraped':null, 'status':'open', 'updated_at':'2016-12-08T02:11:15.943Z', '_id':'58472f499e775a38f23d5da0', 'type':'article', 'closedMessage':null, 'id':'7302e637-f884-47c0-9723-02cc10a18617', 'closedAt':null}, {'settings':null, 'created_at':'2016-12-07T02:25:31.983Z', 'url':'http://localhost:3000/', 'scraped':null, 'status':'open', 'updated_at':'2016-12-13T22:58:36.061Z', '_id':'5847731b9e775a38f23d5da1', 'type':'article', 'closedMessage':null, 'id':'96fddf96-7c83-4008-80ad-50091997d006', 'closedAt':null}, {'settings':null, 'created_at':'2016-12-12T19:04:05.770Z', 'url':'http://localhost:3000/embed/stream', 'scraped':null, 'updated_at':'2016-12-14T20:13:21.934Z', '_id':'584ef4a59e775a38f23d5e86', 'type':'article', 'closedMessage':null, 'id':'cef81015-1b53-4d70-b9af-6eca680f22fc', 'closedAt':null}];
const asset = {
'settings': null,
'created_at':'2016-12-06T21:36:09.302Z',
'url':'localhost:3000/',
'scraped':null,
'status':'open',
'updated_at':'2016-12-08T02:11:15.943Z',
'_id':'58472f499e775a38f23d5da0',
'type':'article',
'closedMessage':null,
'id':'7302e637-f884-47c0-9723-02cc10a18617',
'closedAt':null
};
beforeEach(() => {
render = shallow(<CommentHistory comments={comments} assets={assets} link={()=>{}}/>);
render = shallow(<CommentHistory comments={comments} asset={asset} link={()=>{}}/>);
});
it('should render Comments as children when given comments and assets', () => {
const wrapper = mount(<CommentHistory comments={comments} assets={assets} link={()=>{}}/>);
const wrapper = mount(<CommentHistory comments={comments} asset={asset} link={()=>{}}/>);
expect(wrapper.find('.commentHistory__list').children()).to.have.length(7);
});