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-framework/actions/items.js b/client/coral-framework/actions/items.js
index 8f0e789af..cd798c6b9 100644
--- a/client/coral-framework/actions/items.js
+++ b/client/coral-framework/actions/items.js
@@ -2,6 +2,10 @@ 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';
diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js
index cda2d765d..69f4882bb 100644
--- a/client/coral-framework/actions/user.js
+++ b/client/coral-framework/actions/user.js
@@ -1,5 +1,7 @@
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';
@@ -19,3 +21,30 @@ 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) => {
+ 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 (
+
+ );
+};
+
+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 33b21f0da..4ec24a27a 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",
@@ -92,6 +92,7 @@
"copy-webpack-plugin": "^4.0.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",
@@ -105,8 +106,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",
@@ -121,6 +124,7 @@
"precss": "^1.4.0",
"pym.js": "^1.1.1",
"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",
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)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+