mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 15:47:53 -05:00
Merge pull request #5100 from paulkaplan/comments-reducer
Extract comments reducer from preview reducer
This commit is contained in:
commit
40122f809a
8 changed files with 537 additions and 430 deletions
|
@ -19,9 +19,9 @@
|
|||
"test:unit:jest": "npm run test:unit:jest:unit && npm run test:unit:jest:localization",
|
||||
"test:unit:jest:unit": "jest ./test/unit/ --reporters=default",
|
||||
"test:unit:jest:localization": "jest ./test/localization/*.test.js --reporters=default",
|
||||
"test:unit:tap": "tap ./test/{unit-legacy,localization-legacy}/*.js --no-coverage -R classic",
|
||||
"test:unit:tap": "tap ./test/{unit-legacy,localization-legacy}/ --no-coverage -R classic",
|
||||
"test:unit:convertReportToXunit": "tap ./test/results/unit-raw.tap --no-coverage -R xunit > ./test/results/unit-tap-results.xml",
|
||||
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/*.js --coverage --coverage-report=lcov",
|
||||
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/ --coverage --coverage-report=lcov",
|
||||
"build": "npm run clean && npm run translate && NODE_OPTIONS=--max_old_space_size=8000 webpack --bail",
|
||||
"clean": "rm -rf ./build && rm -rf ./intl && mkdir -p build && mkdir -p intl",
|
||||
"deploy": "npm run deploy:s3 && npm run deploy:fastly",
|
||||
|
|
188
src/redux/comments.js
Normal file
188
src/redux/comments.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
const keyMirror = require('keymirror');
|
||||
const mergeWith = require('lodash.mergewith');
|
||||
const uniqBy = require('lodash.uniqby');
|
||||
|
||||
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'
|
||||
});
|
|
@ -1,14 +1,9 @@
|
|||
const defaults = require('lodash.defaults');
|
||||
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,
|
||||
|
@ -19,7 +14,6 @@ module.exports.Status = keyMirror({
|
|||
module.exports.getInitialState = () => ({
|
||||
status: {
|
||||
project: module.exports.Status.NOT_FETCHED,
|
||||
comments: module.exports.Status.NOT_FETCHED,
|
||||
faved: module.exports.Status.NOT_FETCHED,
|
||||
loved: module.exports.Status.NOT_FETCHED,
|
||||
original: module.exports.Status.NOT_FETCHED,
|
||||
|
@ -33,8 +27,6 @@ module.exports.getInitialState = () => ({
|
|||
},
|
||||
projectInfo: {},
|
||||
remixes: [],
|
||||
comments: [],
|
||||
replies: {},
|
||||
faved: false,
|
||||
loved: false,
|
||||
original: {},
|
||||
|
@ -42,7 +34,6 @@ module.exports.getInitialState = () => ({
|
|||
projectStudios: [],
|
||||
curatedStudios: [],
|
||||
currentStudioIds: [],
|
||||
moreCommentsToLoad: false,
|
||||
projectNotAvailable: false,
|
||||
visibilityInfo: {}
|
||||
});
|
||||
|
@ -96,76 +87,6 @@ module.exports.previewReducer = (state, action) => {
|
|||
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':
|
||||
return Object.assign({}, state, {
|
||||
loved: action.info
|
||||
|
@ -182,10 +103,6 @@ module.exports.previewReducer = (state, action) => {
|
|||
state = JSON.parse(JSON.stringify(state));
|
||||
state.status.studioRequests[action.studioId] = action.status;
|
||||
return state;
|
||||
case 'SET_MORE_COMMENTS_TO_LOAD':
|
||||
return Object.assign({}, state, {
|
||||
moreCommentsToLoad: action.moreCommentsToLoad
|
||||
});
|
||||
case 'SET_VISIBILITY_INFO':
|
||||
return Object.assign({}, state, {
|
||||
visibilityInfo: action.visibilityInfo
|
||||
|
@ -247,16 +164,6 @@ module.exports.setProjectStudios = 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 => ({
|
||||
type: 'SET_CURATED_STUDIOS',
|
||||
items: items
|
||||
|
@ -284,64 +191,6 @@ module.exports.setStudioFetchStatus = (studioId, 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 => ({
|
||||
type: 'SET_VISIBILITY_INFO',
|
||||
visibilityInfo: visibilityInfo
|
||||
|
@ -462,94 +311,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 => {
|
||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHING));
|
||||
if (faved) {
|
||||
|
@ -882,62 +643,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 => {
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
|
||||
api({
|
||||
|
|
180
src/redux/project-comment-actions.js
Normal file
180
src/redux/project-comment-actions.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
const eachLimit = require('async/eachLimit');
|
||||
|
||||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
||||
const COMMENT_LIMIT = 20;
|
||||
|
||||
const {
|
||||
addNewComment,
|
||||
resetComments,
|
||||
Status,
|
||||
setFetchStatus,
|
||||
setCommentDeleted,
|
||||
setCommentReported,
|
||||
setCommentRestored,
|
||||
setMoreCommentsToLoad,
|
||||
setComments,
|
||||
setError,
|
||||
setReplies,
|
||||
setRepliesDeleted,
|
||||
setRepliesRestored
|
||||
} = require('../redux/comments.js');
|
||||
|
||||
const getReplies = (projectId, commentIds, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('replies', 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(setFetchStatus('replies', Status.ERROR));
|
||||
dispatch(setError(err));
|
||||
return;
|
||||
}
|
||||
dispatch(setFetchStatus('replies', Status.FETCHED));
|
||||
dispatch(setReplies(fetchedReplies));
|
||||
});
|
||||
});
|
||||
|
||||
const getTopLevelComments = (id, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('comments', 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(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError('No comment info'));
|
||||
return;
|
||||
}
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments(body));
|
||||
dispatch(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(setMoreCommentsToLoad(body.length === COMMENT_LIMIT));
|
||||
});
|
||||
});
|
||||
|
||||
const getCommentById = (projectId, commentId, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('comments', Status.FETCHING));
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${commentId}`,
|
||||
authentication: token ? token : null
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError(err));
|
||||
return;
|
||||
}
|
||||
if (!body || res.statusCode >= 400) { // NotFound
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError('No comment info'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.parent_id) {
|
||||
// If the comment is a reply, load the parent
|
||||
return dispatch(getCommentById(projectId, body.parent_id, ownerUsername, isAdmin, token));
|
||||
}
|
||||
|
||||
// If the comment is not a reply, show it as top level and load replies
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments([body]));
|
||||
dispatch(getReplies(projectId, [body.id], 0, ownerUsername, isAdmin, token));
|
||||
});
|
||||
});
|
||||
|
||||
const 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(setCommentDeleted(commentId, topLevelCommentId));
|
||||
if (!topLevelCommentId) {
|
||||
dispatch(setRepliesDeleted(commentId));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const 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(setCommentReported(commentId, topLevelCommentId));
|
||||
});
|
||||
});
|
||||
|
||||
const 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(setCommentRestored(commentId, topLevelCommentId));
|
||||
if (!topLevelCommentId) {
|
||||
dispatch(setRepliesRestored(commentId));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
getTopLevelComments,
|
||||
getCommentById,
|
||||
getReplies,
|
||||
deleteComment,
|
||||
reportComment,
|
||||
restoreComment,
|
||||
|
||||
// Re-export these specific action creators directly so the implementer
|
||||
// does not need to go to two places for comment actions
|
||||
addNewComment,
|
||||
resetComments
|
||||
};
|
|
@ -5,6 +5,7 @@ const Page = require('../../components/page/www/page.jsx');
|
|||
const render = require('../../lib/render.jsx');
|
||||
|
||||
const previewActions = require('../../redux/preview.js');
|
||||
const commentActions = require('../../redux/comments.js');
|
||||
|
||||
const isSupportedBrowser = require('../../lib/supported-browser').default;
|
||||
const UnsupportedBrowser = require('./unsupported-browser.jsx');
|
||||
|
@ -16,6 +17,7 @@ if (isSupportedBrowser()) {
|
|||
document.getElementById('app'),
|
||||
{
|
||||
preview: previewActions.previewReducer,
|
||||
comments: commentActions.commentsReducer,
|
||||
...ProjectView.guiReducers
|
||||
},
|
||||
{
|
||||
|
|
|
@ -29,6 +29,7 @@ const Meta = require('./meta.jsx');
|
|||
const sessionActions = require('../../redux/session.js');
|
||||
const navigationActions = require('../../redux/navigation.js');
|
||||
const previewActions = require('../../redux/preview.js');
|
||||
const projectCommentActions = require('../../redux/project-comment-actions.js');
|
||||
|
||||
const frameless = require('../../lib/frameless');
|
||||
|
||||
|
@ -998,7 +999,7 @@ const mapStateToProps = state => {
|
|||
canShare: userOwnsProject && state.permissions.social,
|
||||
canToggleComments: userOwnsProject || isAdmin,
|
||||
canUseBackpack: isLoggedIn,
|
||||
comments: state.preview.comments,
|
||||
comments: state.comments.comments,
|
||||
enableCommunity: projectInfoPresent,
|
||||
faved: state.preview.faved,
|
||||
favedLoaded: state.preview.status.faved === previewActions.Status.FETCHED,
|
||||
|
@ -1013,7 +1014,7 @@ const mapStateToProps = state => {
|
|||
isShared: isShared,
|
||||
loved: state.preview.loved,
|
||||
lovedLoaded: state.preview.status.loved === previewActions.Status.FETCHED,
|
||||
moreCommentsToLoad: state.preview.moreCommentsToLoad,
|
||||
moreCommentsToLoad: state.comments.moreCommentsToLoad,
|
||||
original: state.preview.original,
|
||||
parent: state.preview.parent,
|
||||
playerMode: state.scratchGui.mode.isPlayerOnly,
|
||||
|
@ -1022,7 +1023,7 @@ const mapStateToProps = state => {
|
|||
projectStudios: state.preview.projectStudios,
|
||||
registrationOpen: state.navigation.registrationOpen,
|
||||
remixes: state.preview.remixes,
|
||||
replies: state.preview.replies,
|
||||
replies: state.comments.replies,
|
||||
sessionStatus: state.session.status, // check if used
|
||||
useScratch3Registration: state.navigation.useScratch3Registration,
|
||||
user: state.session.session.user,
|
||||
|
@ -1034,16 +1035,16 @@ const mapStateToProps = state => {
|
|||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleAddComment: (comment, topLevelCommentId) => {
|
||||
dispatch(previewActions.addNewComment(comment, topLevelCommentId));
|
||||
dispatch(projectCommentActions.addNewComment(comment, topLevelCommentId));
|
||||
},
|
||||
handleDeleteComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||
dispatch(previewActions.deleteComment(projectId, commentId, topLevelCommentId, token));
|
||||
dispatch(projectCommentActions.deleteComment(projectId, commentId, topLevelCommentId, token));
|
||||
},
|
||||
handleReportComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||
dispatch(previewActions.reportComment(projectId, commentId, topLevelCommentId, token));
|
||||
dispatch(projectCommentActions.reportComment(projectId, commentId, topLevelCommentId, token));
|
||||
},
|
||||
handleRestoreComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||
dispatch(previewActions.restoreComment(projectId, commentId, topLevelCommentId, token));
|
||||
dispatch(projectCommentActions.restoreComment(projectId, commentId, topLevelCommentId, token));
|
||||
},
|
||||
handleOpenRegistration: event => {
|
||||
event.preventDefault();
|
||||
|
@ -1061,8 +1062,8 @@ const mapDispatchToProps = dispatch => ({
|
|||
dispatch(navigationActions.toggleLoginOpen());
|
||||
},
|
||||
handleSeeAllComments: (id, ownerUsername, isAdmin, token) => {
|
||||
dispatch(previewActions.resetComments());
|
||||
dispatch(previewActions.getTopLevelComments(id, 0, ownerUsername, isAdmin, token));
|
||||
dispatch(projectCommentActions.resetComments());
|
||||
dispatch(projectCommentActions.getTopLevelComments(id, 0, ownerUsername, isAdmin, token));
|
||||
},
|
||||
handleUpdateProjectThumbnail: (id, blob) => {
|
||||
dispatch(previewActions.updateProjectThumbnail(id, blob));
|
||||
|
@ -1093,13 +1094,13 @@ const mapDispatchToProps = dispatch => ({
|
|||
}
|
||||
},
|
||||
getTopLevelComments: (id, offset, ownerUsername, isAdmin, token) => {
|
||||
dispatch(previewActions.getTopLevelComments(id, offset, ownerUsername, isAdmin, token));
|
||||
dispatch(projectCommentActions.getTopLevelComments(id, offset, ownerUsername, isAdmin, token));
|
||||
},
|
||||
getCommentById: (projectId, commentId, ownerUsername, isAdmin, token) => {
|
||||
dispatch(previewActions.getCommentById(projectId, commentId, ownerUsername, isAdmin, token));
|
||||
dispatch(projectCommentActions.getCommentById(projectId, commentId, ownerUsername, isAdmin, token));
|
||||
},
|
||||
getMoreReplies: (projectId, commentId, offset, ownerUsername, isAdmin, token) => {
|
||||
dispatch(previewActions.getReplies(projectId, [commentId], offset, ownerUsername, isAdmin, token));
|
||||
dispatch(projectCommentActions.getReplies(projectId, [commentId], offset, ownerUsername, isAdmin, token));
|
||||
},
|
||||
getFavedStatus: (id, username, token) => {
|
||||
dispatch(previewActions.getFavedStatus(id, username, token));
|
||||
|
@ -1136,7 +1137,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
},
|
||||
remixProject: () => {
|
||||
dispatch(GUI.remixProject());
|
||||
dispatch(previewActions.resetComments());
|
||||
dispatch(projectCommentActions.resetComments());
|
||||
},
|
||||
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();
|
||||
});
|
||||
|
||||
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