mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 09:35:56 -05:00
Move comments out of project reducer
This commit is contained in:
parent
f9864e8351
commit
27dafac7f0
6 changed files with 503 additions and 429 deletions
336
src/redux/comments.js
Normal file
336
src/redux/comments.js
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
const keyMirror = require('keymirror');
|
||||||
|
const eachLimit = require('async/eachLimit');
|
||||||
|
const mergeWith = require('lodash.mergewith');
|
||||||
|
const uniqBy = require('lodash.uniqby');
|
||||||
|
|
||||||
|
const api = require('../lib/api');
|
||||||
|
const log = require('../lib/log');
|
||||||
|
|
||||||
|
const COMMENT_LIMIT = 20;
|
||||||
|
|
||||||
|
module.exports.Status = keyMirror({
|
||||||
|
FETCHED: null,
|
||||||
|
NOT_FETCHED: null,
|
||||||
|
FETCHING: null,
|
||||||
|
ERROR: null
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.getInitialState = () => ({
|
||||||
|
status: {
|
||||||
|
comments: module.exports.Status.NOT_FETCHED
|
||||||
|
},
|
||||||
|
comments: [],
|
||||||
|
replies: {},
|
||||||
|
moreCommentsToLoad: false
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.commentsReducer = (state, action) => {
|
||||||
|
if (typeof state === 'undefined') {
|
||||||
|
state = module.exports.getInitialState();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case 'RESET_TO_INTIAL_STATE':
|
||||||
|
return module.exports.getInitialState();
|
||||||
|
case 'RESET_COMMENTS':
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
comments: [],
|
||||||
|
replies: {}
|
||||||
|
});
|
||||||
|
case 'SET_COMMENT_FETCH_STATUS':
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
status: Object.assign({}, state.status, {
|
||||||
|
[action.infoType]: action.status
|
||||||
|
})
|
||||||
|
});
|
||||||
|
case 'SET_COMMENTS':
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
comments: uniqBy(state.comments.concat(action.items), 'id')
|
||||||
|
});
|
||||||
|
case 'UPDATE_COMMENT':
|
||||||
|
if (action.topLevelCommentId) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
replies: Object.assign({}, state.replies, {
|
||||||
|
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].map(comment => {
|
||||||
|
if (comment.id === action.commentId) {
|
||||||
|
return Object.assign({}, comment, action.comment);
|
||||||
|
}
|
||||||
|
return comment;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
comments: state.comments.map(comment => {
|
||||||
|
if (comment.id === action.commentId) {
|
||||||
|
return Object.assign({}, comment, action.comment);
|
||||||
|
}
|
||||||
|
return comment;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
case 'ADD_NEW_COMMENT':
|
||||||
|
if (action.topLevelCommentId) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
replies: Object.assign({}, state.replies, {
|
||||||
|
// Replies to comments go at the end of the thread
|
||||||
|
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].concat(action.comment)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply to the top level project, put the reply at the beginning
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
comments: [action.comment, ...state.comments],
|
||||||
|
replies: Object.assign({}, state.replies, {[action.comment.id]: []})
|
||||||
|
});
|
||||||
|
case 'UPDATE_ALL_REPLIES':
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
replies: Object.assign({}, state.replies, {
|
||||||
|
[action.commentId]: state.replies[action.commentId].map(reply =>
|
||||||
|
Object.assign({}, reply, action.comment)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
case 'SET_REPLIES':
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
// Append new replies to the state.replies structure
|
||||||
|
replies: mergeWith({}, state.replies, action.replies, (replies, newReplies) => (
|
||||||
|
uniqBy((replies || []).concat(newReplies || []), 'id')
|
||||||
|
)),
|
||||||
|
// Also set the `moreRepliesToLoad` property on the top-level comments
|
||||||
|
comments: state.comments.map(comment => {
|
||||||
|
if (action.replies[comment.id]) {
|
||||||
|
return Object.assign({}, comment, {
|
||||||
|
moreRepliesToLoad: action.replies[comment.id].length === COMMENT_LIMIT
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return comment;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
case 'SET_MORE_COMMENTS_TO_LOAD':
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
moreCommentsToLoad: action.moreCommentsToLoad
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setFetchStatus = (type, status) => ({
|
||||||
|
type: 'SET_COMMENT_FETCH_STATUS',
|
||||||
|
infoType: type,
|
||||||
|
status: status
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.setComments = items => ({
|
||||||
|
type: 'SET_COMMENTS',
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.setReplies = replies => ({
|
||||||
|
type: 'SET_REPLIES',
|
||||||
|
replies: replies
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.setCommentDeleted = (commentId, topLevelCommentId) => ({
|
||||||
|
type: 'UPDATE_COMMENT',
|
||||||
|
commentId: commentId,
|
||||||
|
topLevelCommentId: topLevelCommentId,
|
||||||
|
comment: {
|
||||||
|
visibility: 'deleted'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.setRepliesDeleted = commentId => ({
|
||||||
|
type: 'UPDATE_ALL_REPLIES',
|
||||||
|
commentId: commentId,
|
||||||
|
comment: {
|
||||||
|
visibility: 'deleted'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.setCommentReported = (commentId, topLevelCommentId) => ({
|
||||||
|
type: 'UPDATE_COMMENT',
|
||||||
|
commentId: commentId,
|
||||||
|
topLevelCommentId: topLevelCommentId,
|
||||||
|
comment: {
|
||||||
|
visibility: 'reported'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.setCommentRestored = (commentId, topLevelCommentId) => ({
|
||||||
|
type: 'UPDATE_COMMENT',
|
||||||
|
commentId: commentId,
|
||||||
|
topLevelCommentId: topLevelCommentId,
|
||||||
|
comment: {
|
||||||
|
visibility: 'visible'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.setRepliesRestored = commentId => ({
|
||||||
|
type: 'UPDATE_ALL_REPLIES',
|
||||||
|
commentId: commentId,
|
||||||
|
comment: {
|
||||||
|
visibility: 'visible'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.addNewComment = (comment, topLevelCommentId) => ({
|
||||||
|
type: 'ADD_NEW_COMMENT',
|
||||||
|
comment: comment,
|
||||||
|
topLevelCommentId: topLevelCommentId
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.setMoreCommentsToLoad = moreCommentsToLoad => ({
|
||||||
|
type: 'SET_MORE_COMMENTS_TO_LOAD',
|
||||||
|
moreCommentsToLoad: moreCommentsToLoad
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.resetComments = () => ({
|
||||||
|
type: 'RESET_COMMENTS'
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.getTopLevelComments = (id, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
||||||
|
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING));
|
||||||
|
api({
|
||||||
|
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${id}/comments`,
|
||||||
|
authentication: token ? token : null,
|
||||||
|
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
||||||
|
}, (err, body, res) => {
|
||||||
|
if (err) {
|
||||||
|
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||||
|
dispatch(module.exports.setError(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
||||||
|
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||||
|
dispatch(module.exports.setError('No comment info'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED));
|
||||||
|
dispatch(module.exports.setComments(body));
|
||||||
|
dispatch(module.exports.getReplies(id, body.map(comment => comment.id), 0, ownerUsername, isAdmin, token));
|
||||||
|
|
||||||
|
// If we loaded a full page of comments, assume there are more to load.
|
||||||
|
// This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require
|
||||||
|
// any more server query complexity, so seems worth it. In the case of a project with
|
||||||
|
// number of comments divisible by the COMMENT_LIMIT, the load more button will be
|
||||||
|
// clickable, but upon clicking it will go away.
|
||||||
|
dispatch(module.exports.setMoreCommentsToLoad(body.length === COMMENT_LIMIT));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.getCommentById = (projectId, commentId, ownerUsername, isAdmin, token) => (dispatch => {
|
||||||
|
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING));
|
||||||
|
api({
|
||||||
|
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${commentId}`,
|
||||||
|
authentication: token ? token : null
|
||||||
|
}, (err, body, res) => {
|
||||||
|
if (err) {
|
||||||
|
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||||
|
dispatch(module.exports.setError(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!body || res.statusCode >= 400) { // NotFound
|
||||||
|
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||||
|
dispatch(module.exports.setError('No comment info'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.parent_id) {
|
||||||
|
// If the comment is a reply, load the parent
|
||||||
|
return dispatch(module.exports.getCommentById(projectId, body.parent_id, ownerUsername, isAdmin, token));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the comment is not a reply, show it as top level and load replies
|
||||||
|
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED));
|
||||||
|
dispatch(module.exports.setComments([body]));
|
||||||
|
dispatch(module.exports.getReplies(projectId, [body.id], 0, ownerUsername, isAdmin, token));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.getReplies = (projectId, commentIds, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
||||||
|
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHING));
|
||||||
|
const fetchedReplies = {};
|
||||||
|
eachLimit(commentIds, 10, (parentId, callback) => {
|
||||||
|
api({
|
||||||
|
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${parentId}/replies`,
|
||||||
|
authentication: token ? token : null,
|
||||||
|
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
||||||
|
}, (err, body, res) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(`Error fetching comment replies: ${err}`);
|
||||||
|
}
|
||||||
|
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
||||||
|
return callback('No comment reply information');
|
||||||
|
}
|
||||||
|
fetchedReplies[parentId] = body;
|
||||||
|
callback(null, body);
|
||||||
|
});
|
||||||
|
}, err => {
|
||||||
|
if (err) {
|
||||||
|
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.ERROR));
|
||||||
|
dispatch(module.exports.setError(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHED));
|
||||||
|
dispatch(module.exports.setReplies(fetchedReplies));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.deleteComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||||
|
/* TODO fetching/fetched/error states updates for comment deleting */
|
||||||
|
api({
|
||||||
|
uri: `/proxy/comments/project/${projectId}/comment/${commentId}`,
|
||||||
|
authentication: token,
|
||||||
|
withCredentials: true,
|
||||||
|
method: 'DELETE',
|
||||||
|
useCsrf: true
|
||||||
|
}, (err, body, res) => {
|
||||||
|
if (err || res.statusCode !== 200) {
|
||||||
|
log.error(err || res.body);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(module.exports.setCommentDeleted(commentId, topLevelCommentId));
|
||||||
|
if (!topLevelCommentId) {
|
||||||
|
dispatch(module.exports.setRepliesDeleted(commentId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.reportComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||||
|
api({
|
||||||
|
uri: `/proxy/project/${projectId}/comment/${commentId}/report`,
|
||||||
|
authentication: token,
|
||||||
|
withCredentials: true,
|
||||||
|
method: 'POST',
|
||||||
|
useCsrf: true
|
||||||
|
}, (err, body, res) => {
|
||||||
|
if (err || res.statusCode !== 200) {
|
||||||
|
log.error(err || res.body);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO use the reportId in the response for unreporting functionality
|
||||||
|
dispatch(module.exports.setCommentReported(commentId, topLevelCommentId));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.restoreComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||||
|
api({
|
||||||
|
uri: `/proxy/admin/project/${projectId}/comment/${commentId}/undelete`,
|
||||||
|
authentication: token,
|
||||||
|
withCredentials: true,
|
||||||
|
method: 'PUT',
|
||||||
|
useCsrf: true
|
||||||
|
}, (err, body, res) => {
|
||||||
|
if (err || res.statusCode !== 200) {
|
||||||
|
log.error(err || res.body);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(module.exports.setCommentRestored(commentId, topLevelCommentId));
|
||||||
|
if (!topLevelCommentId) {
|
||||||
|
dispatch(module.exports.setRepliesRestored(commentId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,14 +1,9 @@
|
||||||
const defaults = require('lodash.defaults');
|
const defaults = require('lodash.defaults');
|
||||||
const keyMirror = require('keymirror');
|
const keyMirror = require('keymirror');
|
||||||
const eachLimit = require('async/eachLimit');
|
|
||||||
const mergeWith = require('lodash.mergewith');
|
|
||||||
const uniqBy = require('lodash.uniqby');
|
|
||||||
|
|
||||||
const api = require('../lib/api');
|
const api = require('../lib/api');
|
||||||
const log = require('../lib/log');
|
const log = require('../lib/log');
|
||||||
|
|
||||||
const COMMENT_LIMIT = 20;
|
|
||||||
|
|
||||||
module.exports.Status = keyMirror({
|
module.exports.Status = keyMirror({
|
||||||
FETCHED: null,
|
FETCHED: null,
|
||||||
NOT_FETCHED: null,
|
NOT_FETCHED: null,
|
||||||
|
@ -19,7 +14,6 @@ module.exports.Status = keyMirror({
|
||||||
module.exports.getInitialState = () => ({
|
module.exports.getInitialState = () => ({
|
||||||
status: {
|
status: {
|
||||||
project: module.exports.Status.NOT_FETCHED,
|
project: module.exports.Status.NOT_FETCHED,
|
||||||
comments: module.exports.Status.NOT_FETCHED,
|
|
||||||
faved: module.exports.Status.NOT_FETCHED,
|
faved: module.exports.Status.NOT_FETCHED,
|
||||||
loved: module.exports.Status.NOT_FETCHED,
|
loved: module.exports.Status.NOT_FETCHED,
|
||||||
original: module.exports.Status.NOT_FETCHED,
|
original: module.exports.Status.NOT_FETCHED,
|
||||||
|
@ -32,9 +26,6 @@ module.exports.getInitialState = () => ({
|
||||||
studioRequests: {}
|
studioRequests: {}
|
||||||
},
|
},
|
||||||
projectInfo: {},
|
projectInfo: {},
|
||||||
remixes: [],
|
|
||||||
comments: [],
|
|
||||||
replies: {},
|
|
||||||
faved: false,
|
faved: false,
|
||||||
loved: false,
|
loved: false,
|
||||||
original: {},
|
original: {},
|
||||||
|
@ -42,7 +33,6 @@ module.exports.getInitialState = () => ({
|
||||||
projectStudios: [],
|
projectStudios: [],
|
||||||
curatedStudios: [],
|
curatedStudios: [],
|
||||||
currentStudioIds: [],
|
currentStudioIds: [],
|
||||||
moreCommentsToLoad: false,
|
|
||||||
projectNotAvailable: false,
|
projectNotAvailable: false,
|
||||||
visibilityInfo: {}
|
visibilityInfo: {}
|
||||||
});
|
});
|
||||||
|
@ -96,76 +86,6 @@ module.exports.previewReducer = (state, action) => {
|
||||||
item !== action.studioId
|
item !== action.studioId
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
case 'RESET_COMMENTS':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
comments: [],
|
|
||||||
replies: {}
|
|
||||||
});
|
|
||||||
case 'SET_COMMENTS':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
comments: uniqBy(state.comments.concat(action.items), 'id')
|
|
||||||
});
|
|
||||||
case 'UPDATE_COMMENT':
|
|
||||||
if (action.topLevelCommentId) {
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
replies: Object.assign({}, state.replies, {
|
|
||||||
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].map(comment => {
|
|
||||||
if (comment.id === action.commentId) {
|
|
||||||
return Object.assign({}, comment, action.comment);
|
|
||||||
}
|
|
||||||
return comment;
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
comments: state.comments.map(comment => {
|
|
||||||
if (comment.id === action.commentId) {
|
|
||||||
return Object.assign({}, comment, action.comment);
|
|
||||||
}
|
|
||||||
return comment;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
case 'ADD_NEW_COMMENT':
|
|
||||||
if (action.topLevelCommentId) {
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
replies: Object.assign({}, state.replies, {
|
|
||||||
// Replies to comments go at the end of the thread
|
|
||||||
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].concat(action.comment)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reply to the top level project, put the reply at the beginning
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
comments: [action.comment, ...state.comments],
|
|
||||||
replies: Object.assign({}, state.replies, {[action.comment.id]: []})
|
|
||||||
});
|
|
||||||
case 'UPDATE_ALL_REPLIES':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
replies: Object.assign({}, state.replies, {
|
|
||||||
[action.commentId]: state.replies[action.commentId].map(reply =>
|
|
||||||
Object.assign({}, reply, action.comment)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
case 'SET_REPLIES':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
// Append new replies to the state.replies structure
|
|
||||||
replies: mergeWith({}, state.replies, action.replies, (replies, newReplies) => (
|
|
||||||
uniqBy((replies || []).concat(newReplies || []), 'id')
|
|
||||||
)),
|
|
||||||
// Also set the `moreRepliesToLoad` property on the top-level comments
|
|
||||||
comments: state.comments.map(comment => {
|
|
||||||
if (action.replies[comment.id]) {
|
|
||||||
return Object.assign({}, comment, {
|
|
||||||
moreRepliesToLoad: action.replies[comment.id].length === COMMENT_LIMIT
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return comment;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
case 'SET_LOVED':
|
case 'SET_LOVED':
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
loved: action.info
|
loved: action.info
|
||||||
|
@ -182,10 +102,6 @@ module.exports.previewReducer = (state, action) => {
|
||||||
state = JSON.parse(JSON.stringify(state));
|
state = JSON.parse(JSON.stringify(state));
|
||||||
state.status.studioRequests[action.studioId] = action.status;
|
state.status.studioRequests[action.studioId] = action.status;
|
||||||
return state;
|
return state;
|
||||||
case 'SET_MORE_COMMENTS_TO_LOAD':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
moreCommentsToLoad: action.moreCommentsToLoad
|
|
||||||
});
|
|
||||||
case 'SET_VISIBILITY_INFO':
|
case 'SET_VISIBILITY_INFO':
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
visibilityInfo: action.visibilityInfo
|
visibilityInfo: action.visibilityInfo
|
||||||
|
@ -247,16 +163,6 @@ module.exports.setProjectStudios = items => ({
|
||||||
items: items
|
items: items
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.setComments = items => ({
|
|
||||||
type: 'SET_COMMENTS',
|
|
||||||
items: items
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.setReplies = replies => ({
|
|
||||||
type: 'SET_REPLIES',
|
|
||||||
replies: replies
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.setCuratedStudios = items => ({
|
module.exports.setCuratedStudios = items => ({
|
||||||
type: 'SET_CURATED_STUDIOS',
|
type: 'SET_CURATED_STUDIOS',
|
||||||
items: items
|
items: items
|
||||||
|
@ -284,64 +190,6 @@ module.exports.setStudioFetchStatus = (studioId, status) => ({
|
||||||
status: status
|
status: status
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.setCommentDeleted = (commentId, topLevelCommentId) => ({
|
|
||||||
type: 'UPDATE_COMMENT',
|
|
||||||
commentId: commentId,
|
|
||||||
topLevelCommentId: topLevelCommentId,
|
|
||||||
comment: {
|
|
||||||
visibility: 'deleted'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.setRepliesDeleted = commentId => ({
|
|
||||||
type: 'UPDATE_ALL_REPLIES',
|
|
||||||
commentId: commentId,
|
|
||||||
comment: {
|
|
||||||
visibility: 'deleted'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.setCommentReported = (commentId, topLevelCommentId) => ({
|
|
||||||
type: 'UPDATE_COMMENT',
|
|
||||||
commentId: commentId,
|
|
||||||
topLevelCommentId: topLevelCommentId,
|
|
||||||
comment: {
|
|
||||||
visibility: 'reported'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.setCommentRestored = (commentId, topLevelCommentId) => ({
|
|
||||||
type: 'UPDATE_COMMENT',
|
|
||||||
commentId: commentId,
|
|
||||||
topLevelCommentId: topLevelCommentId,
|
|
||||||
comment: {
|
|
||||||
visibility: 'visible'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.setRepliesRestored = commentId => ({
|
|
||||||
type: 'UPDATE_ALL_REPLIES',
|
|
||||||
commentId: commentId,
|
|
||||||
comment: {
|
|
||||||
visibility: 'visible'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.addNewComment = (comment, topLevelCommentId) => ({
|
|
||||||
type: 'ADD_NEW_COMMENT',
|
|
||||||
comment: comment,
|
|
||||||
topLevelCommentId: topLevelCommentId
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.setMoreCommentsToLoad = moreCommentsToLoad => ({
|
|
||||||
type: 'SET_MORE_COMMENTS_TO_LOAD',
|
|
||||||
moreCommentsToLoad: moreCommentsToLoad
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.resetComments = () => ({
|
|
||||||
type: 'RESET_COMMENTS'
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.setVisibilityInfo = visibilityInfo => ({
|
module.exports.setVisibilityInfo = visibilityInfo => ({
|
||||||
type: 'SET_VISIBILITY_INFO',
|
type: 'SET_VISIBILITY_INFO',
|
||||||
visibilityInfo: visibilityInfo
|
visibilityInfo: visibilityInfo
|
||||||
|
@ -462,94 +310,6 @@ module.exports.getFavedStatus = (id, username, token) => (dispatch => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.getTopLevelComments = (id, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
|
||||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING));
|
|
||||||
api({
|
|
||||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${id}/comments`,
|
|
||||||
authentication: token ? token : null,
|
|
||||||
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
|
||||||
}, (err, body, res) => {
|
|
||||||
if (err) {
|
|
||||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
|
||||||
dispatch(module.exports.setError(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
|
||||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
|
||||||
dispatch(module.exports.setError('No comment info'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED));
|
|
||||||
dispatch(module.exports.setComments(body));
|
|
||||||
dispatch(module.exports.getReplies(id, body.map(comment => comment.id), 0, ownerUsername, isAdmin, token));
|
|
||||||
|
|
||||||
// If we loaded a full page of comments, assume there are more to load.
|
|
||||||
// This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require
|
|
||||||
// any more server query complexity, so seems worth it. In the case of a project with
|
|
||||||
// number of comments divisible by the COMMENT_LIMIT, the load more button will be
|
|
||||||
// clickable, but upon clicking it will go away.
|
|
||||||
dispatch(module.exports.setMoreCommentsToLoad(body.length === COMMENT_LIMIT));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.getCommentById = (projectId, commentId, ownerUsername, isAdmin, token) => (dispatch => {
|
|
||||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING));
|
|
||||||
api({
|
|
||||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${commentId}`,
|
|
||||||
authentication: token ? token : null
|
|
||||||
}, (err, body, res) => {
|
|
||||||
if (err) {
|
|
||||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
|
||||||
dispatch(module.exports.setError(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!body || res.statusCode >= 400) { // NotFound
|
|
||||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
|
||||||
dispatch(module.exports.setError('No comment info'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.parent_id) {
|
|
||||||
// If the comment is a reply, load the parent
|
|
||||||
return dispatch(module.exports.getCommentById(projectId, body.parent_id, ownerUsername, isAdmin, token));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the comment is not a reply, show it as top level and load replies
|
|
||||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED));
|
|
||||||
dispatch(module.exports.setComments([body]));
|
|
||||||
dispatch(module.exports.getReplies(projectId, [body.id], 0, ownerUsername, isAdmin, token));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.getReplies = (projectId, commentIds, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
|
||||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHING));
|
|
||||||
const fetchedReplies = {};
|
|
||||||
eachLimit(commentIds, 10, (parentId, callback) => {
|
|
||||||
api({
|
|
||||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${parentId}/replies`,
|
|
||||||
authentication: token ? token : null,
|
|
||||||
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
|
||||||
}, (err, body, res) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(`Error fetching comment replies: ${err}`);
|
|
||||||
}
|
|
||||||
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
|
||||||
return callback('No comment reply information');
|
|
||||||
}
|
|
||||||
fetchedReplies[parentId] = body;
|
|
||||||
callback(null, body);
|
|
||||||
});
|
|
||||||
}, err => {
|
|
||||||
if (err) {
|
|
||||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.ERROR));
|
|
||||||
dispatch(module.exports.setError(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHED));
|
|
||||||
dispatch(module.exports.setReplies(fetchedReplies));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.setFavedStatus = (faved, id, username, token) => (dispatch => {
|
module.exports.setFavedStatus = (faved, id, username, token) => (dispatch => {
|
||||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHING));
|
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHING));
|
||||||
if (faved) {
|
if (faved) {
|
||||||
|
@ -882,62 +642,6 @@ module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.deleteComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
|
||||||
/* TODO fetching/fetched/error states updates for comment deleting */
|
|
||||||
api({
|
|
||||||
uri: `/proxy/comments/project/${projectId}/comment/${commentId}`,
|
|
||||||
authentication: token,
|
|
||||||
withCredentials: true,
|
|
||||||
method: 'DELETE',
|
|
||||||
useCsrf: true
|
|
||||||
}, (err, body, res) => {
|
|
||||||
if (err || res.statusCode !== 200) {
|
|
||||||
log.error(err || res.body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(module.exports.setCommentDeleted(commentId, topLevelCommentId));
|
|
||||||
if (!topLevelCommentId) {
|
|
||||||
dispatch(module.exports.setRepliesDeleted(commentId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.reportComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
|
||||||
api({
|
|
||||||
uri: `/proxy/project/${projectId}/comment/${commentId}/report`,
|
|
||||||
authentication: token,
|
|
||||||
withCredentials: true,
|
|
||||||
method: 'POST',
|
|
||||||
useCsrf: true
|
|
||||||
}, (err, body, res) => {
|
|
||||||
if (err || res.statusCode !== 200) {
|
|
||||||
log.error(err || res.body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO use the reportId in the response for unreporting functionality
|
|
||||||
dispatch(module.exports.setCommentReported(commentId, topLevelCommentId));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.restoreComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
|
||||||
api({
|
|
||||||
uri: `/proxy/admin/project/${projectId}/comment/${commentId}/undelete`,
|
|
||||||
authentication: token,
|
|
||||||
withCredentials: true,
|
|
||||||
method: 'PUT',
|
|
||||||
useCsrf: true
|
|
||||||
}, (err, body, res) => {
|
|
||||||
if (err || res.statusCode !== 200) {
|
|
||||||
log.error(err || res.body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(module.exports.setCommentRestored(commentId, topLevelCommentId));
|
|
||||||
if (!topLevelCommentId) {
|
|
||||||
dispatch(module.exports.setRepliesRestored(commentId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.shareProject = (projectId, token) => (dispatch => {
|
module.exports.shareProject = (projectId, token) => (dispatch => {
|
||||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
|
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
|
||||||
api({
|
api({
|
||||||
|
|
|
@ -5,6 +5,7 @@ const Page = require('../../components/page/www/page.jsx');
|
||||||
const render = require('../../lib/render.jsx');
|
const render = require('../../lib/render.jsx');
|
||||||
|
|
||||||
const previewActions = require('../../redux/preview.js');
|
const previewActions = require('../../redux/preview.js');
|
||||||
|
const commentsActions = require('../../redux/comments.js');
|
||||||
|
|
||||||
const isSupportedBrowser = require('../../lib/supported-browser').default;
|
const isSupportedBrowser = require('../../lib/supported-browser').default;
|
||||||
const UnsupportedBrowser = require('./unsupported-browser.jsx');
|
const UnsupportedBrowser = require('./unsupported-browser.jsx');
|
||||||
|
@ -16,6 +17,7 @@ if (isSupportedBrowser()) {
|
||||||
document.getElementById('app'),
|
document.getElementById('app'),
|
||||||
{
|
{
|
||||||
preview: previewActions.previewReducer,
|
preview: previewActions.previewReducer,
|
||||||
|
comments: commentsActions.commentsReducer,
|
||||||
...ProjectView.guiReducers
|
...ProjectView.guiReducers
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,6 +29,7 @@ const Meta = require('./meta.jsx');
|
||||||
const sessionActions = require('../../redux/session.js');
|
const sessionActions = require('../../redux/session.js');
|
||||||
const navigationActions = require('../../redux/navigation.js');
|
const navigationActions = require('../../redux/navigation.js');
|
||||||
const previewActions = require('../../redux/preview.js');
|
const previewActions = require('../../redux/preview.js');
|
||||||
|
const commentsActions = require('../../redux/comments.js');
|
||||||
|
|
||||||
const frameless = require('../../lib/frameless');
|
const frameless = require('../../lib/frameless');
|
||||||
|
|
||||||
|
@ -998,7 +999,7 @@ const mapStateToProps = state => {
|
||||||
canShare: userOwnsProject && state.permissions.social,
|
canShare: userOwnsProject && state.permissions.social,
|
||||||
canToggleComments: userOwnsProject || isAdmin,
|
canToggleComments: userOwnsProject || isAdmin,
|
||||||
canUseBackpack: isLoggedIn,
|
canUseBackpack: isLoggedIn,
|
||||||
comments: state.preview.comments,
|
comments: state.comments.comments,
|
||||||
enableCommunity: projectInfoPresent,
|
enableCommunity: projectInfoPresent,
|
||||||
faved: state.preview.faved,
|
faved: state.preview.faved,
|
||||||
favedLoaded: state.preview.status.faved === previewActions.Status.FETCHED,
|
favedLoaded: state.preview.status.faved === previewActions.Status.FETCHED,
|
||||||
|
@ -1013,7 +1014,7 @@ const mapStateToProps = state => {
|
||||||
isShared: isShared,
|
isShared: isShared,
|
||||||
loved: state.preview.loved,
|
loved: state.preview.loved,
|
||||||
lovedLoaded: state.preview.status.loved === previewActions.Status.FETCHED,
|
lovedLoaded: state.preview.status.loved === previewActions.Status.FETCHED,
|
||||||
moreCommentsToLoad: state.preview.moreCommentsToLoad,
|
moreCommentsToLoad: state.commentsmoreCommentsToLoad,
|
||||||
original: state.preview.original,
|
original: state.preview.original,
|
||||||
parent: state.preview.parent,
|
parent: state.preview.parent,
|
||||||
playerMode: state.scratchGui.mode.isPlayerOnly,
|
playerMode: state.scratchGui.mode.isPlayerOnly,
|
||||||
|
@ -1022,7 +1023,7 @@ const mapStateToProps = state => {
|
||||||
projectStudios: state.preview.projectStudios,
|
projectStudios: state.preview.projectStudios,
|
||||||
registrationOpen: state.navigation.registrationOpen,
|
registrationOpen: state.navigation.registrationOpen,
|
||||||
remixes: state.preview.remixes,
|
remixes: state.preview.remixes,
|
||||||
replies: state.preview.replies,
|
replies: state.comments.replies,
|
||||||
sessionStatus: state.session.status, // check if used
|
sessionStatus: state.session.status, // check if used
|
||||||
useScratch3Registration: state.navigation.useScratch3Registration,
|
useScratch3Registration: state.navigation.useScratch3Registration,
|
||||||
user: state.session.session.user,
|
user: state.session.session.user,
|
||||||
|
@ -1034,16 +1035,16 @@ const mapStateToProps = state => {
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
handleAddComment: (comment, topLevelCommentId) => {
|
handleAddComment: (comment, topLevelCommentId) => {
|
||||||
dispatch(previewActions.addNewComment(comment, topLevelCommentId));
|
dispatch(commentsActions.addNewComment(comment, topLevelCommentId));
|
||||||
},
|
},
|
||||||
handleDeleteComment: (projectId, commentId, topLevelCommentId, token) => {
|
handleDeleteComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||||
dispatch(previewActions.deleteComment(projectId, commentId, topLevelCommentId, token));
|
dispatch(commentsActions.deleteComment(projectId, commentId, topLevelCommentId, token));
|
||||||
},
|
},
|
||||||
handleReportComment: (projectId, commentId, topLevelCommentId, token) => {
|
handleReportComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||||
dispatch(previewActions.reportComment(projectId, commentId, topLevelCommentId, token));
|
dispatch(commentsActions.reportComment(projectId, commentId, topLevelCommentId, token));
|
||||||
},
|
},
|
||||||
handleRestoreComment: (projectId, commentId, topLevelCommentId, token) => {
|
handleRestoreComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||||
dispatch(previewActions.restoreComment(projectId, commentId, topLevelCommentId, token));
|
dispatch(commentsActions.restoreComment(projectId, commentId, topLevelCommentId, token));
|
||||||
},
|
},
|
||||||
handleOpenRegistration: event => {
|
handleOpenRegistration: event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -1061,8 +1062,8 @@ const mapDispatchToProps = dispatch => ({
|
||||||
dispatch(navigationActions.toggleLoginOpen());
|
dispatch(navigationActions.toggleLoginOpen());
|
||||||
},
|
},
|
||||||
handleSeeAllComments: (id, ownerUsername, isAdmin, token) => {
|
handleSeeAllComments: (id, ownerUsername, isAdmin, token) => {
|
||||||
dispatch(previewActions.resetComments());
|
dispatch(commentsActions.resetComments());
|
||||||
dispatch(previewActions.getTopLevelComments(id, 0, ownerUsername, isAdmin, token));
|
dispatch(commentsActions.getTopLevelComments(id, 0, ownerUsername, isAdmin, token));
|
||||||
},
|
},
|
||||||
handleUpdateProjectThumbnail: (id, blob) => {
|
handleUpdateProjectThumbnail: (id, blob) => {
|
||||||
dispatch(previewActions.updateProjectThumbnail(id, blob));
|
dispatch(previewActions.updateProjectThumbnail(id, blob));
|
||||||
|
@ -1093,13 +1094,13 @@ const mapDispatchToProps = dispatch => ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getTopLevelComments: (id, offset, ownerUsername, isAdmin, token) => {
|
getTopLevelComments: (id, offset, ownerUsername, isAdmin, token) => {
|
||||||
dispatch(previewActions.getTopLevelComments(id, offset, ownerUsername, isAdmin, token));
|
dispatch(commentsActions.getTopLevelComments(id, offset, ownerUsername, isAdmin, token));
|
||||||
},
|
},
|
||||||
getCommentById: (projectId, commentId, ownerUsername, isAdmin, token) => {
|
getCommentById: (projectId, commentId, ownerUsername, isAdmin, token) => {
|
||||||
dispatch(previewActions.getCommentById(projectId, commentId, ownerUsername, isAdmin, token));
|
dispatch(commentsActions.getCommentById(projectId, commentId, ownerUsername, isAdmin, token));
|
||||||
},
|
},
|
||||||
getMoreReplies: (projectId, commentId, offset, ownerUsername, isAdmin, token) => {
|
getMoreReplies: (projectId, commentId, offset, ownerUsername, isAdmin, token) => {
|
||||||
dispatch(previewActions.getReplies(projectId, [commentId], offset, ownerUsername, isAdmin, token));
|
dispatch(commentsActions.getReplies(projectId, [commentId], offset, ownerUsername, isAdmin, token));
|
||||||
},
|
},
|
||||||
getFavedStatus: (id, username, token) => {
|
getFavedStatus: (id, username, token) => {
|
||||||
dispatch(previewActions.getFavedStatus(id, username, token));
|
dispatch(previewActions.getFavedStatus(id, username, token));
|
||||||
|
@ -1136,7 +1137,7 @@ const mapDispatchToProps = dispatch => ({
|
||||||
},
|
},
|
||||||
remixProject: () => {
|
remixProject: () => {
|
||||||
dispatch(GUI.remixProject());
|
dispatch(GUI.remixProject());
|
||||||
dispatch(previewActions.resetComments());
|
dispatch(commentsActions.resetComments());
|
||||||
},
|
},
|
||||||
setPlayer: player => {
|
setPlayer: player => {
|
||||||
dispatch(GUI.setPlayer(player));
|
dispatch(GUI.setPlayer(player));
|
||||||
|
|
151
test/unit-legacy/redux/comments-test.js
Normal file
151
test/unit-legacy/redux/comments-test.js
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
const tap = require('tap');
|
||||||
|
const Comments = require('../../../src/redux/comments');
|
||||||
|
const initialState = Comments.getInitialState();
|
||||||
|
const reducer = Comments.commentsReducer;
|
||||||
|
|
||||||
|
let state;
|
||||||
|
|
||||||
|
tap.tearDown(() => process.nextTick(process.exit));
|
||||||
|
|
||||||
|
tap.test('Reducer', t => {
|
||||||
|
t.type(reducer, 'function');
|
||||||
|
t.type(initialState, 'object');
|
||||||
|
|
||||||
|
// Reducers should return their default state when called without state
|
||||||
|
let undefinedState;
|
||||||
|
t.deepEqual(initialState, reducer(undefinedState, {type: 'fake action'}));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('setFetchStatus', t => {
|
||||||
|
// initial value
|
||||||
|
t.equal(initialState.status.comments, Comments.Status.NOT_FETCHED);
|
||||||
|
|
||||||
|
state = reducer(initialState, Comments.setFetchStatus('comments', Comments.Status.FETCHING));
|
||||||
|
t.equal(state.status.comments, Comments.Status.FETCHING);
|
||||||
|
|
||||||
|
state = reducer(state, Comments.setFetchStatus('comments', Comments.Status.FETCHED));
|
||||||
|
t.equal(state.status.comments, Comments.Status.FETCHED);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('setComments', t => {
|
||||||
|
// Initial value
|
||||||
|
t.deepEqual(initialState.comments, []);
|
||||||
|
|
||||||
|
state = reducer(initialState, Comments.setComments([{id: 1}, {id: 2}]));
|
||||||
|
state = reducer(state, Comments.setComments([{id: 3}, {id: 4}]));
|
||||||
|
t.deepEqual(state.comments, [{id: 1}, {id: 2}, {id: 3}, {id: 4}]);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const commentState = {
|
||||||
|
comments: [
|
||||||
|
{id: 'id1', visibility: 'visible'},
|
||||||
|
{id: 'id2', visibility: 'visible'},
|
||||||
|
{id: 'id3', visibility: 'visible'}
|
||||||
|
],
|
||||||
|
replies: {
|
||||||
|
id1: [
|
||||||
|
{id: 'id4', visibility: 'visible'},
|
||||||
|
{id: 'id5', visibility: 'visible'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tap.test('setComments, discards duplicates', t => {
|
||||||
|
state = reducer(commentState, Comments.setComments([{id: 'id1'}]));
|
||||||
|
// Does not increase the number of comments, still 3
|
||||||
|
t.equal(state.comments.length, 3);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('setCommentDeleted, top level comment', t => {
|
||||||
|
state = reducer(commentState, Comments.setCommentDeleted('id2'));
|
||||||
|
t.equal(state.comments[1].visibility, 'deleted');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('setCommentDeleted, reply comment', t => {
|
||||||
|
state = reducer(commentState, Comments.setCommentDeleted('id4', 'id1'));
|
||||||
|
t.equal(state.replies.id1[0].visibility, 'deleted');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('setRepliesDeleted/Restored', t => {
|
||||||
|
state = reducer(commentState, Comments.setRepliesDeleted('id1'));
|
||||||
|
t.equal(state.replies.id1[0].visibility, 'deleted');
|
||||||
|
t.equal(state.replies.id1[1].visibility, 'deleted');
|
||||||
|
|
||||||
|
state = reducer(state, Comments.setRepliesRestored('id1'));
|
||||||
|
t.equal(state.replies.id1[0].visibility, 'visible');
|
||||||
|
t.equal(state.replies.id1[1].visibility, 'visible');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('setCommentReported, top level comment', t => {
|
||||||
|
state = reducer(commentState, Comments.setCommentReported('id2'));
|
||||||
|
t.equal(state.comments[1].visibility, 'reported');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('setCommentReported, reply comment', t => {
|
||||||
|
state = reducer(commentState, Comments.setCommentReported('id4', 'id1'));
|
||||||
|
t.equal(state.replies.id1[0].visibility, 'reported');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('addNewComment, top level comment', t => {
|
||||||
|
state = reducer(commentState, Comments.addNewComment({id: 'new comment'}));
|
||||||
|
// Adds comment to beginning of list
|
||||||
|
t.equal(state.comments[0].id, 'new comment');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('addNewComment, reply comment', t => {
|
||||||
|
state = reducer(commentState, Comments.addNewComment({id: 'new comment'}, 'id1'));
|
||||||
|
// Adds replies to the end of the replies list
|
||||||
|
t.equal(state.replies.id1[2].id, 'new comment');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('setReplies', t => {
|
||||||
|
// setReplies should append new replies
|
||||||
|
state = reducer(commentState, Comments.setReplies({
|
||||||
|
id1: {id: 'id6'}
|
||||||
|
}));
|
||||||
|
t.equal(state.replies.id1[2].id, 'id6');
|
||||||
|
t.equal(state.comments[0].moreRepliesToLoad, false);
|
||||||
|
|
||||||
|
// setReplies should ignore duplicates, do the same as above again
|
||||||
|
t.equal(state.replies.id1.length, 3);
|
||||||
|
state = reducer(state, Comments.setReplies({id1: {id: 'id6'}}));
|
||||||
|
t.equal(state.replies.id1.length, 3);
|
||||||
|
|
||||||
|
// setReplies can add replies to a comment that didn't have any
|
||||||
|
state = reducer(state, Comments.setReplies({
|
||||||
|
id2: {id: 'id7'}
|
||||||
|
}));
|
||||||
|
t.equal(state.replies.id1.length, 3);
|
||||||
|
t.equal(state.replies.id2.length, 1);
|
||||||
|
t.equal(state.replies.id2[0].id, 'id7');
|
||||||
|
t.equal(state.comments[0].moreRepliesToLoad, false);
|
||||||
|
t.equal(state.comments[1].moreRepliesToLoad, false);
|
||||||
|
|
||||||
|
// Getting 20 (COMMENT_LIMIT) replies sets moreRepliesToLoad to true
|
||||||
|
state = reducer(state, Comments.setReplies({
|
||||||
|
id3: (new Array(20)).map((_, i) => ({id: `id${i + 1}`}))
|
||||||
|
}));
|
||||||
|
t.equal(state.comments[0].moreRepliesToLoad, false);
|
||||||
|
t.equal(state.comments[1].moreRepliesToLoad, false);
|
||||||
|
t.equal(state.comments[2].moreRepliesToLoad, true);
|
||||||
|
|
||||||
|
// Getting one more reply sets moreRepliesToLoad back to false
|
||||||
|
state = reducer(state, Comments.setReplies({
|
||||||
|
id3: {id: 'id21'}
|
||||||
|
}));
|
||||||
|
t.equal(state.comments[2].moreRepliesToLoad, false);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -54,123 +54,3 @@ tap.test('updateProjectInfo', t => {
|
||||||
});
|
});
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('setComments', t => {
|
|
||||||
// Initial value
|
|
||||||
t.deepEqual(initialState.comments, []);
|
|
||||||
|
|
||||||
state = reducer(initialState, Preview.setComments([{id: 1}, {id: 2}]));
|
|
||||||
state = reducer(state, Preview.setComments([{id: 3}, {id: 4}]));
|
|
||||||
t.deepEqual(state.comments, [{id: 1}, {id: 2}, {id: 3}, {id: 4}]);
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
const commentState = {
|
|
||||||
comments: [
|
|
||||||
{id: 'id1', visibility: 'visible'},
|
|
||||||
{id: 'id2', visibility: 'visible'},
|
|
||||||
{id: 'id3', visibility: 'visible'}
|
|
||||||
],
|
|
||||||
replies: {
|
|
||||||
id1: [
|
|
||||||
{id: 'id4', visibility: 'visible'},
|
|
||||||
{id: 'id5', visibility: 'visible'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tap.test('setComments, discards duplicates', t => {
|
|
||||||
state = reducer(commentState, Preview.setComments([{id: 'id1'}]));
|
|
||||||
// Does not increase the number of comments, still 3
|
|
||||||
t.equal(state.comments.length, 3);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('setCommentDeleted, top level comment', t => {
|
|
||||||
state = reducer(commentState, Preview.setCommentDeleted('id2'));
|
|
||||||
t.equal(state.comments[1].visibility, 'deleted');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('setCommentDeleted, reply comment', t => {
|
|
||||||
state = reducer(commentState, Preview.setCommentDeleted('id4', 'id1'));
|
|
||||||
t.equal(state.replies.id1[0].visibility, 'deleted');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('setRepliesDeleted/Restored', t => {
|
|
||||||
state = reducer(commentState, Preview.setRepliesDeleted('id1'));
|
|
||||||
t.equal(state.replies.id1[0].visibility, 'deleted');
|
|
||||||
t.equal(state.replies.id1[1].visibility, 'deleted');
|
|
||||||
|
|
||||||
state = reducer(state, Preview.setRepliesRestored('id1'));
|
|
||||||
t.equal(state.replies.id1[0].visibility, 'visible');
|
|
||||||
t.equal(state.replies.id1[1].visibility, 'visible');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('setCommentReported, top level comment', t => {
|
|
||||||
state = reducer(commentState, Preview.setCommentReported('id2'));
|
|
||||||
t.equal(state.comments[1].visibility, 'reported');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('setCommentReported, reply comment', t => {
|
|
||||||
state = reducer(commentState, Preview.setCommentReported('id4', 'id1'));
|
|
||||||
t.equal(state.replies.id1[0].visibility, 'reported');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('addNewComment, top level comment', t => {
|
|
||||||
state = reducer(commentState, Preview.addNewComment({id: 'new comment'}));
|
|
||||||
// Adds comment to beginning of list
|
|
||||||
t.equal(state.comments[0].id, 'new comment');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('addNewComment, reply comment', t => {
|
|
||||||
state = reducer(commentState, Preview.addNewComment({id: 'new comment'}, 'id1'));
|
|
||||||
// Adds replies to the end of the replies list
|
|
||||||
t.equal(state.replies.id1[2].id, 'new comment');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('setReplies', t => {
|
|
||||||
// setReplies should append new replies
|
|
||||||
state = reducer(commentState, Preview.setReplies({
|
|
||||||
id1: {id: 'id6'}
|
|
||||||
}));
|
|
||||||
t.equal(state.replies.id1[2].id, 'id6');
|
|
||||||
t.equal(state.comments[0].moreRepliesToLoad, false);
|
|
||||||
|
|
||||||
// setReplies should ignore duplicates, do the same as above again
|
|
||||||
t.equal(state.replies.id1.length, 3);
|
|
||||||
state = reducer(state, Preview.setReplies({id1: {id: 'id6'}}));
|
|
||||||
t.equal(state.replies.id1.length, 3);
|
|
||||||
|
|
||||||
// setReplies can add replies to a comment that didn't have any
|
|
||||||
state = reducer(state, Preview.setReplies({
|
|
||||||
id2: {id: 'id7'}
|
|
||||||
}));
|
|
||||||
t.equal(state.replies.id1.length, 3);
|
|
||||||
t.equal(state.replies.id2.length, 1);
|
|
||||||
t.equal(state.replies.id2[0].id, 'id7');
|
|
||||||
t.equal(state.comments[0].moreRepliesToLoad, false);
|
|
||||||
t.equal(state.comments[1].moreRepliesToLoad, false);
|
|
||||||
|
|
||||||
// Getting 20 (COMMENT_LIMIT) replies sets moreRepliesToLoad to true
|
|
||||||
state = reducer(state, Preview.setReplies({
|
|
||||||
id3: (new Array(20)).map((_, i) => ({id: `id${i + 1}`}))
|
|
||||||
}));
|
|
||||||
t.equal(state.comments[0].moreRepliesToLoad, false);
|
|
||||||
t.equal(state.comments[1].moreRepliesToLoad, false);
|
|
||||||
t.equal(state.comments[2].moreRepliesToLoad, true);
|
|
||||||
|
|
||||||
// Getting one more reply sets moreRepliesToLoad back to false
|
|
||||||
state = reducer(state, Preview.setReplies({
|
|
||||||
id3: {id: 'id21'}
|
|
||||||
}));
|
|
||||||
t.equal(state.comments[2].moreRepliesToLoad, false);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
Loading…
Reference in a new issue