diff --git a/.eslintignore b/.eslintignore index a4865e1f6..83564d3ff 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ dist client/lib +**/*.html diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 3a6a796e4..5819076c6 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -1,4 +1,5 @@ import React from 'react'; +import {SelectField, Option} from 'react-mdl-selectfield'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../../translations.json'; import styles from './Configure.css'; @@ -12,6 +13,12 @@ import { Icon } from 'react-mdl'; +const TIMESTAMPS = { + weeks: 60 * 60 * 24 * 7, + days: 60 * 60 * 24, + hours: 60 * 60 +}; + const updateCharCountEnable = (updateSettings, charCountChecked) => () => { const charCountEnable = !charCountChecked; updateSettings({charCountEnable}); @@ -47,6 +54,21 @@ const updateClosedMessage = (updateSettings) => (event) => { updateSettings({closedMessage}); }; +// If we are changing the measure we need to recalculate using the old amount +// Same thing if we are just changing the amount +const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => { + if (isMeasure) { + const amount = getTimeoutAmount(ts); + const closedTimeout = amount * TIMESTAMPS[event]; + updateSettings({closedTimeout}); + } else { + const val = event.target.value; + const measure = getTimeoutMeasure(ts); + const closedTimeout = val * TIMESTAMPS[measure]; + updateSettings({closedTimeout}); + } +}; + const CommentSettings = ({updateSettings, settingsError, settings, errors}) => @@ -110,6 +132,27 @@ const CommentSettings = ({updateSettings, settingsError, settings, errors}) => < rows={3}/> + + + {lang.t('configure.close-after')} +
+ +
+ + + + + +
+
+
{lang.t('configure.closed-comments-desc')} @@ -124,4 +167,20 @@ const CommentSettings = ({updateSettings, settingsError, settings, errors}) => < export default CommentSettings; +// To see if we are talking about weeks, days or hours +// We talk the remainder of the division and see if it's 0 +const getTimeoutMeasure = ts => { + if (ts % TIMESTAMPS['weeks'] === 0) { + return 'weeks'; + } else if (ts % TIMESTAMPS['days'] === 0) { + return 'days'; + } else if (ts % TIMESTAMPS['hours'] === 0) { + return 'hours'; + } +}; + +// Dividing the amount by it's measure (hours, days, weeks) we +// obtain the amount of time +const getTimeoutAmount = ts => ts / TIMESTAMPS[getTimeoutMeasure(ts)]; + const lang = new I18n(translations); diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/containers/Configure/Configure.css index 667bb4744..c0646c9e2 100644 --- a/client/coral-admin/src/containers/Configure/Configure.css +++ b/client/coral-admin/src/containers/Configure/Configure.css @@ -63,6 +63,11 @@ display: block; } +.configTimeoutSelect { + display: inline-block; + margin-left: 20px; +} + .charCountTexfield { width: 4em; padding: 0px; diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index e462f7567..ef00f1cf8 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -57,6 +57,10 @@ "community": "Community", "closed-comments-desc": "Write a message for closed threads", "closed-comments-label": "Write a message...", + "hours": "Hours", + "days": "Days", + "weeks": "Weeks", + "close-after": "Close comments after", "comment-count-header": "Limit Comment Length", "comment-count-text-pre": "Comments will be limited to ", "comment-count-text-post": " characters.", @@ -117,6 +121,11 @@ "community": "Comunidad", "closed-comments-desc": "Escribe un mensaje para cuando los comentarios se encuentran cerrados", "closed-comments-label": "Escribe un mensaje...", + "never": "Nunca", + "hours": "Horas", + "days": "Días", + "weeks": "Semanas", + "close-after": "Cerrar comentarios luego de", "comment-count-header": "Limitar el largo del comentario", "comment-count-text-pre": "El largo de comentarios será ", "comment-count-text-post": " caracteres", diff --git a/client/coral-configure/containers/ConfigureStreamContainer.js b/client/coral-configure/containers/ConfigureStreamContainer.js index d4b71f4e5..cb8bf0012 100644 --- a/client/coral-configure/containers/ConfigureStreamContainer.js +++ b/client/coral-configure/containers/ConfigureStreamContainer.js @@ -1,11 +1,14 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; +import {I18n} from '../../coral-framework'; import {updateOpenStatus, updateConfiguration} from '../../coral-framework/actions/config'; import CloseCommentsInfo from '../components/CloseCommentsInfo'; import ConfigureCommentStream from '../components/ConfigureCommentStream'; +const lang = new I18n(); + class ConfigureStreamContainer extends Component { constructor (props) { super(props); @@ -47,8 +50,15 @@ class ConfigureStreamContainer extends Component { this.props.updateStatus(this.props.config.status === 'open' ? 'closed' : 'open'); } + getClosedIn () { + const {config} = this.props; + const {created_at, closedTimeout} = config; + return lang.timeago(new Date(created_at).getTime() + (1000 * closedTimeout)); + } + render () { const {status} = this.props; + return (

{status === 'open' ? 'Close' : 'Open'} Comment Stream

+ {status === 'open' ?

The comment stream will close in {this.getClosedIn()}.

: ''} 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) => { + dispatch({type: actions.COMMENTS_BY_USER_REQUEST}); + return coralApi(`/comments?user_id${userId}`) + .then(({comments, assets}) => { + comments.forEach(comment => dispatch(addItem(comment, 'comments'))); + + assets.forEach(asset => 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 => { + console.error(error.stack); + console.error('FAILURE_COMMENTS_BY_USER', error); + dispatch({type: actions.COMMENTS_BY_USER_FAILURE, error}); + }); + }; +}; diff --git a/client/coral-framework/constants/assets.js b/client/coral-framework/constants/assets.js new file mode 100644 index 000000000..0224e0945 --- /dev/null +++ b/client/coral-framework/constants/assets.js @@ -0,0 +1,3 @@ +export const MULTIPLE_ASSETS_REQUEST = 'MULTIPLE_ASSETS_REQUEST'; +export const MULTIPLE_ASSETS_SUCCESS = 'MULTIPLE_ASSETS_SUCCESS'; +export const MULTIPLE_ASSSETS_FAILURE = 'MULTIPLE_ASSSETS_FAILURE'; \ No newline at end of file diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js index 0c316d48a..6e09726d3 100644 --- a/client/coral-framework/constants/user.js +++ b/client/coral-framework/constants/user.js @@ -1,3 +1,6 @@ export const SAVE_BIO_REQUEST = 'SAVE_BIO_REQUEST'; export const SAVE_BIO_SUCCESS = 'SAVE_BIO_SUCCESS'; export const SAVE_BIO_FAILURE = 'SAVE_BIO_FAILURE'; +export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST'; +export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS'; +export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE'; diff --git a/client/coral-framework/reducers/items.js b/client/coral-framework/reducers/items.js index 93388c1ab..268557809 100644 --- a/client/coral-framework/reducers/items.js +++ b/client/coral-framework/reducers/items.js @@ -6,6 +6,7 @@ import * as actions from '../actions/items'; const initialState = fromJS({ comments: {}, users: {}, + assets: {}, actions: {} }); diff --git a/client/coral-framework/reducers/user.js b/client/coral-framework/reducers/user.js index 11b57fc15..6e9f3529c 100644 --- a/client/coral-framework/reducers/user.js +++ b/client/coral-framework/reducers/user.js @@ -1,11 +1,14 @@ -import {Map} from 'immutable'; +import {Map, fromJS} from 'immutable'; import * as authActions from '../constants/auth'; import * as actions from '../constants/user'; +import * as assetActions from '../constants/assets'; const initialState = Map({ displayName: '', profiles: [], - settings: {} + settings: {}, + myComments: [], + myAssets: [] // the assets from which myComments (above) originated }); const purge = user => { @@ -30,6 +33,10 @@ export default function user (state = initialState, action) { case actions.SAVE_BIO_SUCCESS: return state .set('settings', action.settings); + case actions.COMMENTS_BY_USER_SUCCESS: + return state.set('myComments', fromJS(action.comments)); + case assetActions.MULTIPLE_ASSETS_SUCCESS: + return state.set('myAssets', fromJS(action.assets)); default : return state; } diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js index 7a27c1982..87f656c22 100644 --- a/client/coral-plugin-comment-count/CommentCount.js +++ b/client/coral-plugin-comment-count/CommentCount.js @@ -1,20 +1,23 @@ import React from 'react'; import {I18n} from '../coral-framework'; import translations from './translations.json'; +import has from 'lodash/has'; +import reduce from 'lodash/reduce'; const name = 'coral-plugin-comment-count'; const CommentCount = ({items, id}) => { let count = 0; - if (items.assets[id] && items.assets[id].comments) { + if (has(items, `assets.${id}.comments`)) { count += items.assets[id].comments.length; } - const itemKeys = Object.keys(items.comments); - for (let i = 0; i < itemKeys.length; i++) { - const item = items.comments[itemKeys[i]]; - if (item.children) { - count += item.children.length; + + // lodash reduce works on {} + count += reduce(items.comments, (accum, comment) => { + if (comment.children) { + accum += comment.children.length; } - } + return accum; + }, 0); return
{`${count} ${count === 1 ? lang.t('comment') : lang.t('comment-plural')}`} diff --git a/client/coral-plugin-history/Comment.css b/client/coral-plugin-history/Comment.css new file mode 100644 index 000000000..ba2a59f4b --- /dev/null +++ b/client/coral-plugin-history/Comment.css @@ -0,0 +1,8 @@ +.assetURL { + font-size: 16px; + color: black; +} + +.commentBody { + +} diff --git a/client/coral-plugin-history/Comment.js b/client/coral-plugin-history/Comment.js new file mode 100644 index 000000000..c54a0feef --- /dev/null +++ b/client/coral-plugin-history/Comment.js @@ -0,0 +1,25 @@ +import React, {PropTypes} from 'react'; + +import styles from './Comment.css'; + +const Comment = props => { + return ( +
+

+ {props.asset.url} +

+

{props.comment.body}

+
+ ); +}; + +Comment.propTypes = { + comment: PropTypes.shape({ + body: PropTypes.string + }).isRequired, + asset: PropTypes.shape({ + url: PropTypes.string + }).isRequired +}; + +export default Comment; diff --git a/client/coral-settings/components/CommentHistory.css b/client/coral-plugin-history/CommentHistory.css similarity index 100% rename from client/coral-settings/components/CommentHistory.css rename to client/coral-plugin-history/CommentHistory.css diff --git a/client/coral-plugin-history/CommentHistory.js b/client/coral-plugin-history/CommentHistory.js new file mode 100644 index 000000000..44d06bc91 --- /dev/null +++ b/client/coral-plugin-history/CommentHistory.js @@ -0,0 +1,27 @@ +import React, {PropTypes} from 'react'; +import Comment from './Comment'; +import styles from './CommentHistory.css'; + +const CommentHistory = props => { + return ( +
+

All Comments

+
+ {props.comments.map((comment, i) => { + const asset = props.assets.find(asset => asset.id === comment.asset_id); + return ; + })} +
+
+ ); +}; + +CommentHistory.propTypes = { + comments: PropTypes.arrayOf(PropTypes.object).isRequired, + assets: PropTypes.arrayOf(PropTypes.object).isRequired +}; + +export default CommentHistory; diff --git a/client/coral-settings/components/CommentHistory.js b/client/coral-settings/components/CommentHistory.js deleted file mode 100644 index 3160c8a88..000000000 --- a/client/coral-settings/components/CommentHistory.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import styles from './CommentHistory.css'; - -export default ({comments = []}) => ( -
-

Comments

-
    - {comments.map(() => ( -
  • - {/* Comment Data*/} -
  • - ))} -
-
-); diff --git a/client/coral-settings/containers/SettingsContainer.js b/client/coral-settings/containers/SettingsContainer.js index 96020a10c..8f1b32687 100644 --- a/client/coral-settings/containers/SettingsContainer.js +++ b/client/coral-settings/containers/SettingsContainer.js @@ -1,12 +1,12 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; -import {saveBio} from 'coral-framework/actions/user'; +import {saveBio, fetchCommentsByUserId} from 'coral-framework/actions/user'; import BioContainer from './BioContainer'; import NotLoggedIn from '../components/NotLoggedIn'; import {TabBar, Tab, TabContent} from '../../coral-ui'; -import CommentHistory from '../components/CommentHistory'; +import CommentHistory from 'coral-plugin-history/CommentHistory'; import SettingsHeader from '../components/SettingsHeader'; import RestrictedContent from 'coral-framework/components/RestrictedContent'; @@ -22,6 +22,7 @@ class SignInContainer extends Component { componentWillMount () { // Fetch commentHistory + this.props.fetchCommentsByUserId(this.props.userData.id); } handleTabChange(tab) { @@ -31,7 +32,7 @@ class SignInContainer extends Component { } render() { - const {loggedIn, userData, showSignInDialog} = this.props; + const {loggedIn, userData, showSignInDialog, items, user} = this.props; const {activeTab} = this.state; return ( }> @@ -41,7 +42,13 @@ class SignInContainer extends Component { Profile Settings - + { + user.myComments.length && user.myAssets.length + ? items.comments[id])} + assets={user.myAssets.map(id => items.assets[id])} /> + :

Loading comment history...

+ }
@@ -51,12 +58,14 @@ class SignInContainer extends Component { } } -const mapStateToProps = () => ({ +const mapStateToProps = state => ({ + items: state.items.toJS(), + user: state.user.toJS() }); const mapDispatchToProps = dispatch => ({ saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData)), - getHistory: () => dispatch(), + fetchCommentsByUserId: userId => dispatch(fetchCommentsByUserId(userId)) }); export default connect( diff --git a/models/asset.js b/models/asset.js index 09c06a09d..3ca2b95cf 100644 --- a/models/asset.js +++ b/models/asset.js @@ -152,6 +152,16 @@ AssetSchema.statics.search = (value) => value.length === 0 ? Asset.find({}) : As } }); +/** + * Finds multiple assets with matching ids + * @param {Array} ids an array of Strings of asset_id + * @return {Promise} resolves to list of Assets + */ +AssetSchema.statics.findMultipleById = function (ids) { + const query = ids.map(id => ({id})); + return Asset.find(query); +}; + const Asset = mongoose.model('Asset', AssetSchema); module.exports = Asset; diff --git a/models/comment.js b/models/comment.js index b3b3bc52f..a855862d2 100644 --- a/models/comment.js +++ b/models/comment.js @@ -324,6 +324,15 @@ CommentSchema.statics.removeAction = (item_id, user_id, action_type) => Action.r */ CommentSchema.statics.all = () => Comment.find(); +/** + * Returns all the comments by user + * probably to be paginated at some point in the future + * @return {Promise} array resolves to an array of comments by that user + */ +CommentSchema.statics.findByUserId = function (author_id) { + return Comment.find({author_id}); +}; + // Comment model. const Comment = mongoose.model('Comment', CommentSchema); diff --git a/package.json b/package.json index 0bbcb18de..e38809065 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build-watch": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.config.dev.js --watch", "lint": "./node_modules/.bin/eslint bin/* .", "lint-fix": "./node_modules/.bin/eslint bin/* . --fix", - "test": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register --recursive tests", + "test": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register tests/helpers/*.js --require ignore-styles --recursive tests", "test-watch": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register --recursive -w tests", "pree2e": "NODE_ENV=test ./scripts/pree2e.sh", "e2e": "NODE_ENV=test ./node_modules/.bin/nightwatch", @@ -94,6 +94,7 @@ "csurf": "^1.9.0", "css-loader": "^0.25.0", "dialog-polyfill": "^0.4.4", + "enzyme": "^2.6.0", "eslint": "^3.12.1", "eslint-config-postcss": "^2.0.2", "eslint-config-standard": "^6.2.1", @@ -107,8 +108,10 @@ "exports-loader": "^0.6.3", "fetch-mock": "^5.5.0", "hammerjs": "^2.0.8", + "ignore-styles": "^5.0.1", "immutable": "^3.8.1", "imports-loader": "^0.6.5", + "jsdom": "^9.8.3", "json-loader": "^0.5.4", "keymaster": "^1.6.2", "material-design-lite": "^1.2.1", @@ -122,8 +125,9 @@ "pre-git": "^3.10.0", "precss": "^1.4.0", "pym.js": "^1.1.1", - "react": "^15.3.2", - "react-dom": "^15.3.2", + "react": "15.3.2", + "react-addons-test-utils": "15.3.2", + "react-dom": "15.3.2", "react-linkify": "^0.1.3", "react-mdl": "^1.7.2", "react-mdl-selectfield": "^0.2.0", diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 4141c64df..8f21a9488 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -14,7 +14,8 @@ router.get('/', authorization.needed('admin'), (req, res, next) => { const { status = null, action_type = null, - asset_id = null + asset_id = null, + user_id = null } = req.query; /** @@ -32,6 +33,8 @@ router.get('/', authorization.needed('admin'), (req, res, next) => { if (status) { query = assetIDWrap(Comment.findByStatus(status === 'new' ? null : status)); + } else if (user_id) { + query = Comment.findByUserId(user_id); } else if (action_type) { query = Comment .findIdsByActionType(action_type) @@ -47,13 +50,15 @@ router.get('/', authorization.needed('admin'), (req, res, next) => { query.then((comments) => { return Promise.all([ comments, + Asset.findMultipleById(comments.map(comment => comment.asset_id)), User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))), Action.getActionSummariesFromComments(asset_id, comments, req.user ? req.user.id : false) ]); }) - .then(([comments, users, actions])=> + .then(([comments, assets, users, actions]) => res.status(200).json({ comments, + assets, users, actions })) diff --git a/tests/client/coral-plugin-history/Comment.spec.js b/tests/client/coral-plugin-history/Comment.spec.js new file mode 100644 index 000000000..7d49630d6 --- /dev/null +++ b/tests/client/coral-plugin-history/Comment.spec.js @@ -0,0 +1,30 @@ +import React from 'react'; +import {shallow, mount} from 'enzyme'; +import {expect} from 'chai'; +import Comment from '../../../client/coral-plugin-history/Comment'; + +describe('coral-plugin-history/Comment', () => { + let render; + const comment = {body: 'this is a comment'}; + const asset = {url: 'https://google.com'}; + + beforeEach(() => { + render = shallow(); + }); + + it('should render the provided comment body', () => { + const wrapper = mount(); + expect(wrapper.find('.myCommentBody')).to.have.length(1); + expect(wrapper.find('.myCommentBody').text()).to.equal('this is a comment'); + }); + + it('should render the asset url as a link', () => { + const wrapper = mount(); + expect(wrapper.find('.myCommentAnchor')).to.have.length(1); + expect(wrapper.find('.myCommentAnchor').text()).to.equal('https://google.com'); + }); + + it('should render the comment with styles', () => { + expect(render.props().style).to.be.defined; + }); +}); diff --git a/tests/client/coral-plugin-history/CommentHistory.spec.js b/tests/client/coral-plugin-history/CommentHistory.spec.js new file mode 100644 index 000000000..3b6f5ee47 --- /dev/null +++ b/tests/client/coral-plugin-history/CommentHistory.spec.js @@ -0,0 +1,24 @@ +import React from 'react'; +import {shallow, mount} from 'enzyme'; +import {expect} from 'chai'; +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}]; + + beforeEach(() => { + render = shallow(); + }); + + it('should render Comments as children when given comments and assets', () => { + const wrapper = mount(); + expect(wrapper.find('.commentHistory__list').children()).to.have.length(7); + }); + + it('should render with styles', () => { + expect(render.props().style).to.be.defined; + }); +}); + diff --git a/tests/helpers/browser.js b/tests/helpers/browser.js new file mode 100644 index 000000000..3fd54e0fe --- /dev/null +++ b/tests/helpers/browser.js @@ -0,0 +1,49 @@ +const jsdom = require('jsdom').jsdom; +const fs = require('fs'); +const path = require('path'); + +// Storage Mock +function storageMock() { + const storage = {}; + + return { + setItem: function(key, value) { + storage[key] = value || ''; + }, + getItem: function(key) { + return storage[key] || null; + }, + removeItem: function(key) { + delete storage[key]; + }, + get length() { + return Object.keys(storage).length; + }, + key: function(i) { + const keys = Object.keys(storage); + return keys[i] || null; + } + }; +} + +global.document = jsdom(fs.readFileSync(path.resolve(__dirname, 'index.test.html'))); +global.window = document.defaultView; + +// these lines are required for react-mdl +global.window.CustomEvent = undefined; +require('react-mdl/extra/material'); + +global.Element = global.window.Element; + +global.navigator = { + userAgent: 'node.js' +}; + +global.documentRef = document; +global.localStorage = {}; +global.sessionStorage = storageMock(); +global.XMLHttpRequest = storageMock(); + +global.Headers = function(headers) { + return headers; +}; diff --git a/tests/helpers/index.test.html b/tests/helpers/index.test.html new file mode 100644 index 000000000..4619df971 --- /dev/null +++ b/tests/helpers/index.test.html @@ -0,0 +1,84 @@ + + + + + + + Coral - (Beta) + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + +