Created
April 21, 2016 16:55
-
-
Save yurynix/7124b3f01171382b3bb3e8dc99334191 to your computer and use it in GitHub Desktop.
coomments polling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Internal dependencies | |
*/ | |
import wpcom from 'lib/wp'; | |
import { | |
COMMENTS_RECEIVE, | |
COMMENTS_COUNT_RECEIVE, | |
COMMENTS_REQUEST, | |
COMMENTS_REQUEST_SUCCESS, | |
COMMENTS_REQUEST_FAILURE, | |
COMMENTS_REMOVE, | |
COMMENTS_ERROR | |
} from '../action-types'; | |
import { | |
createRequestId | |
} from './utils'; | |
import { | |
fetchAllCommentFields, | |
fetchCommentsByIds | |
} from './fetchers'; | |
import { | |
getPostOldestCommentDate, | |
getPostCommentRequests, | |
getPostCommentItems | |
} from './selectors'; | |
import { | |
FETCH_MODE, | |
NUMBER_OF_COMMENTS_PER_FETCH, | |
PLACEHOLDER_STATE | |
} from './constants'; | |
/*** | |
* Internal handler for comments request success | |
* @param {Object} options handler options | |
* @param {Function} options.dispatch redux dispatch function | |
* @param {String} options.requestId request identifier | |
* @param {Number} options.siteId site identifier | |
* @param {Number} options.postId post identifier | |
* @param {Object[]} options.comments comments array | |
* @param {Number} options.totalCommentsCount comments count that the server have on that post | |
*/ | |
function commentsRequestSuccess( options ) { | |
const { | |
dispatch, | |
requestId, | |
siteId, | |
postId, | |
comments, | |
totalCommentsCount | |
} = options; | |
dispatch( { | |
type: COMMENTS_REQUEST_SUCCESS, | |
requestId: requestId | |
} ); | |
dispatch( { | |
type: COMMENTS_RECEIVE, | |
siteId: siteId, | |
postId: postId, | |
comments: comments | |
} ); | |
// if the api have returned comments count, dispatch it | |
// the api will produce a count only when the request has no | |
// query modifiers such as 'before', 'after', 'type' and more. | |
// in our case it'll be only on the first request | |
if ( totalCommentsCount > -1 ) { | |
dispatch( { | |
type: COMMENTS_COUNT_RECEIVE, | |
siteId, | |
postId, | |
totalCommentsCount | |
} ); | |
} else { | |
requestPostCommentsCount( siteId, postId )( dispatch ); | |
} | |
} | |
/*** | |
* Internal handler for comments request failure | |
* @param {Function} dispatch redux dispatch function | |
* @param {String} requestId request identifier | |
* @param {Object} err error object | |
*/ | |
function commentsRequestFailure( dispatch, requestId, err ) { | |
dispatch( { | |
type: COMMENTS_REQUEST_FAILURE, | |
requestId: requestId, | |
error: err | |
} ); | |
} | |
/*** | |
* Creates a thunk that requests comments for a given post | |
* @param {Number} siteId site identifier | |
* @param {Number} postId post identifier | |
* @returns {Function} thunk that requests comments for a given post | |
*/ | |
export function requestPostComments( siteId, postId ) { | |
return ( dispatch, getState ) => { | |
const postCommentRequests = getPostCommentRequests( getState(), { siteId, postId } ); | |
const oldestCommentDateForPost = getPostOldestCommentDate( getState(), { siteId, postId } ); | |
const query = { | |
order: 'DESC', | |
number: NUMBER_OF_COMMENTS_PER_FETCH | |
}; | |
if ( oldestCommentDateForPost && oldestCommentDateForPost.toISOString ) { | |
query.before = oldestCommentDateForPost.toISOString(); | |
} | |
const requestId = createRequestId( siteId, postId, query ); | |
// if the request status is in-flight or completed successfully, no need to re-fetch it | |
if ( postCommentRequests && [ COMMENTS_REQUEST, COMMENTS_REQUEST_SUCCESS ].indexOf( postCommentRequests.get( requestId ) ) !== -1 ) { | |
return; | |
} | |
dispatch( { | |
type: COMMENTS_REQUEST, | |
requestId: requestId | |
} ); | |
// promise returned here is mainly for testing purposes | |
return wpcom.site( siteId ) | |
.post( postId ) | |
.comment() | |
.replies( query ) | |
.then( ( { comments, found } ) => commentsRequestSuccess( { dispatch, requestId, siteId, postId, comments, totalCommentsCount: found } ) ) | |
.catch( ( err ) => commentsRequestFailure( dispatch, requestId, err ) ); | |
}; | |
} | |
/*** | |
* Creates a placeholder comment for a given text and postId | |
* @param {String} commentText text of the comment | |
* @param {Number} postId post identifier | |
* @param {Number|undefined} parentCommentId parent comment identifier | |
* @returns {Object} comment placeholder | |
*/ | |
function createPlaceholderComment( commentText, postId, parentCommentId ) { | |
// We need placehodler id to be unique in the context of siteId, postId for that specific user, | |
// date milliseconds will do for that purpose. | |
return { | |
ID: 'placeholder-' + ( new Date().getTime() ), | |
parent: parentCommentId ? { ID: parentCommentId } : false, | |
date: ( new Date() ).toISOString(), | |
content: commentText, | |
status: 'pending', | |
type: 'comment', | |
post: { | |
ID: postId | |
}, | |
isPlaceholder: true, | |
placeholderState: PLACEHOLDER_STATE.PENDING | |
}; | |
} | |
/*** | |
* Creates a thunk that creates a comment for a given post | |
* @param {String} commentText text of the comment | |
* @param {Number} siteId site identifier | |
* @param {Number} postId post identifier | |
* @param {Number|undefined} parentCommentId parent comment identifier | |
* @returns {Function} a thunk that creates a comment for a given post | |
*/ | |
export function writeComment( commentText, siteId, postId, parentCommentId ) { | |
if ( ! commentText || ! siteId || ! postId ) { | |
return; | |
} | |
return ( dispatch ) => { | |
const placeholderComment = createPlaceholderComment( commentText, postId, parentCommentId ); | |
// Insert a placeholder | |
dispatch( { | |
type: COMMENTS_RECEIVE, | |
siteId, | |
postId, | |
comments: [ placeholderComment ] | |
} ); | |
let apiPromise; | |
if ( parentCommentId ) { | |
apiPromise = wpcom.site( siteId ).post( postId ).comment( parentCommentId ).reply( commentText ); | |
} else { | |
apiPromise = wpcom.site( siteId ).post( postId ).comment().add( commentText ); | |
} | |
return apiPromise.then( ( comment ) => { | |
// remove the placeholder | |
dispatch( { | |
type: COMMENTS_REMOVE, | |
siteId, | |
postId, | |
commentId: placeholderComment.ID | |
} ); | |
// insert the real comment | |
dispatch( { | |
type: COMMENTS_RECEIVE, | |
siteId, | |
postId, | |
comments: [ comment ] | |
} ); | |
return comment; | |
} ) | |
.catch( ( error ) => { | |
dispatch( { | |
type: COMMENTS_ERROR, | |
siteId, | |
postId, | |
commentId: placeholderComment.ID, | |
error | |
} ); | |
throw error; | |
} ); | |
}; | |
} | |
//TODO: WIP | |
/*** | |
* Creates a thunk that will poll for comments change on the server for the given post | |
* then remove unapproved comments and add new (totally new or just approved) comments. | |
* | |
* That function takes into account earliest (oldest) comment date we saw for that post | |
* and won't fetch any further comments. | |
* @param {Number} siteId site identifier | |
* @param {Number} postId post identifier | |
* @returns {Function} thunk that will poll for comments change on the server for the given post | |
*/ | |
export function pollComments( siteId, postId ) { | |
return ( dispatch, getState ) => { | |
const commentStateItems = getPostCommentItems( getState(), { siteId, postId } ); | |
const oldestCommentDateForPost = getPostOldestCommentDate( getState(), { siteId, postId } ); | |
// nothing to do if no comment items | |
if ( ! commentStateItems || commentStateItems.size === 0 ) { | |
return; | |
} | |
// If we have no earliest comment for post, how do we know where to stop? - We'll fetch all the comments ever | |
if ( ! oldestCommentDateForPost ) { | |
return; | |
} | |
// Since we're doing a diff, we must have all the comment ids, otherwise - the diff would be wrong | |
// and we'll remove/add many comments for nothing, it's better do nothing if we can't fetch | |
// all comment ids. | |
const currentServerCommentIdsPromise = fetchAllCommentFields( { siteId, postId, fields: [ 'ID', 'date' ], fetchMode: FETCH_MODE.ALL_OR_THROW } ) | |
.then( ( serverComments ) => | |
serverComments.map( ( comment ) => ( { ID: comment.ID, date: new Date( comment.date ) } ) ) | |
.filter( ( comment ) => comment.date >= oldestCommentDateForPost ) | |
.map( ( comment ) => comment.ID ) | |
); | |
return currentServerCommentIdsPromise.then( ( serverCommentIds ) => { | |
const serverIds = new Set( serverCommentIds ); | |
const myIds = commentStateItems.map( ( c ) => c.get( 'ID' ) ).toSet(); | |
const addIds = serverCommentIds.filter( ( commentId ) => ! myIds.has( commentId ) ); | |
const removeIds = commentStateItems.filter( ( comment ) => ! serverIds.has( comment.get( 'ID' ) ) ).map( ( c ) => c.get( 'ID' ) ); | |
removeIds.forEach( ( removeId ) => dispatch( { | |
type: COMMENTS_REMOVE, | |
siteId, | |
postId, | |
commentId: removeId | |
} ) ); | |
if ( addIds.length > 0 ) { | |
// just in case something goes wrong, don't fetch that way more than 10 comments | |
// also no harm done if we will be unable to fetch some of the comments, | |
// we'll just get them next time. | |
return fetchCommentsByIds( { siteId, commentIds: addIds.slice( 0, 10 ), fetchMode: FETCH_MODE.PARTIAL_OK } ).then( ( comments ) => { | |
dispatch( { | |
type: COMMENTS_RECEIVE, | |
siteId, | |
postId, | |
comments | |
} ); | |
return comments; | |
} ); | |
} | |
} ); | |
}; | |
} | |
//TODO: WIP, maybe will be removed | |
/*** | |
* Creates a thunk that will fetch comments count for a given post from the server | |
* @param {Number} siteId site identifier | |
* @param {Number} postId post identifier | |
* @returns {Function} a thunk that will fetch comments count for a given post from the server | |
*/ | |
export function requestPostCommentsCount( siteId, postId ) { | |
return ( dispatch ) => { | |
const query = { | |
// these are to reduce returned data, since we care only about the found count | |
fields: 'ID', | |
number: 1 | |
}; | |
// promise returned here is mainly for testing purposes | |
return wpcom.site( siteId ) | |
.post( postId ) | |
.comment() | |
.replies( query ) | |
.then( ( { found } ) => dispatch( { type: COMMENTS_COUNT_RECEIVE, siteId, postId, totalCommentsCount: found } ) ); | |
}; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* External dependencies | |
*/ | |
import uniq from 'lodash/array/uniq'; | |
import pick from 'lodash/object/pick'; | |
/** | |
* Internal dependencies | |
*/ | |
import wpcom from 'lib/wp'; | |
import { | |
createUrlFromTemplate | |
} from './utils'; | |
import { | |
FETCH_MODE, | |
MAX_NUMBER_OF_COMMENTS_IN_FETCH_RESULT | |
} from './constants'; | |
/*** | |
* Fetches all comments with given fields, default: fetch ID field | |
* @param {Object} fetchOptions arguments for the fetch | |
* @param {Number} fetchOptions.siteId site identifier | |
* @param {Number} fetchOptions.postId post identifier | |
* @param {String[]} fetchOptions.fields field names array (if not passed, default [ 'ID' ] | |
* @param {Number} fetchOptions.numberOfItemsPerBatch number of comments per batch to fetch, default MAX_NUMBER_OF_COMMENTS_IN_FETCH_RESULT | |
* @param {String} fetchOptions.fetchMode one of FETCH_MODE strings, it tells whether we should return partial result if some of the requests failed, default FETCH_MODE.ALL_OR_THROW | |
* @returns {Promise<Object[]>} promise of array of comments with specified fields | |
*/ | |
export function fetchAllCommentFields( fetchOptions ) { | |
if ( ! fetchOptions.siteId ) { | |
throw new Error( 'No siteId supplied' ); | |
} | |
if ( ! fetchOptions.postId ) { | |
throw new Error( 'No postId supplied' ); | |
} | |
const options = Object.assign( {}, { | |
fields: [ 'ID' ], | |
numberOfItemsPerBatch: MAX_NUMBER_OF_COMMENTS_IN_FETCH_RESULT, | |
fetchMode: FETCH_MODE.ALL_OR_THROW | |
}, fetchOptions ); | |
options.fields = uniq( options.fields ); | |
// Default order is DESC, so we're omitting it | |
// https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/%24post_ID/replies/ | |
const query = { | |
fields: options.fields.join( ',' ), | |
number: options.numberOfItemsPerBatch | |
}; | |
// we're doing one non-batched request to get the comments count, | |
// so we'll know how many batched requests to perform | |
return wpcom.site( options.siteId ) | |
.post( options.postId ) | |
.comment() | |
.replies( query ) | |
.then( ( { found, comments } ) => { | |
const firstComments = comments.map( ( comment ) => pick( comment, options.fields ) ); | |
if ( found > options.numberOfItemsPerBatch ) { | |
const batch = wpcom.batch(); | |
const batchUrls = []; | |
for ( let i = options.numberOfItemsPerBatch; i < found; i += options.numberOfItemsPerBatch ) { | |
const url = createUrlFromTemplate( '/sites/$site/posts/$post_ID/replies/', | |
{ site: options.siteId, post_ID: options.postId }, | |
Object.assign( {}, query, { offset: i } ) | |
); | |
batchUrls.push( url ); | |
} | |
batchUrls.forEach( batch.add.bind( batch ) ); | |
return batch.run( ).then( ( batchResultForUrls ) => { | |
let arrayOfCommentArrays = batchUrls.map( ( batchUrl ) => { | |
// we consider request a failure if comments array is not present | |
if ( ! batchResultForUrls[ batchUrl ].comments ) { | |
if ( options.fetchMode === FETCH_MODE.ALL_OR_THROW ) { | |
throw new Error( `Fetch of ${ batchUrl } failed while getting all comment ids for siteId: ${ options.siteId } postId: ${ options.postId }` ); | |
} | |
return []; | |
} | |
return batchResultForUrls[ batchUrl ].comments; | |
} ) | |
.map( ( arrayOfComments ) => arrayOfComments.map( ( comment ) => pick( comment, options.fields ) ) ); | |
return Array.prototype.concat.apply( firstComments, arrayOfCommentArrays ); | |
} ); | |
} | |
return firstComments; | |
} ); | |
} | |
/*** | |
* Fetches comments by ids | |
* @param {Object} fetchOptions arguments for the fetch | |
* @param {Number} fetchOptions.siteId site identifier | |
* @param {Number[]} fetchOptions.commentIds array of comment identifiers | |
* @param {String} fetchOptions.fetchMode one of FETCH_MODE strings, it tells whether we should return partial result if some of the requests failed | |
* @returns {Promise<Object[]>} promise of array of comments | |
*/ | |
export function fetchCommentsByIds( fetchOptions ) { | |
if ( ! fetchOptions.siteId ) { | |
throw new Error( 'No siteId supplied' ); | |
} | |
if ( ! Array.isArray( fetchOptions.commentIds ) || fetchOptions.commentIds.length < 1 ) { | |
throw new Error( 'At least a single commentId should be supplied' ); | |
} | |
const options = Object.assign( {}, { fetchMode: FETCH_MODE.ALL_OR_THROW }, fetchOptions ); | |
const batch = wpcom.batch(); | |
const batchUrls = options.commentIds.map( ( commentId ) => createUrlFromTemplate( '/sites/$site/comments/$comment_ID', { site: options.siteId, comment_ID: commentId } ) ); | |
batchUrls.forEach( batch.add.bind( batch ) ); | |
return batch.run().then( ( batchResultForUrls ) => { | |
let arrayOfComments = batchUrls.map( ( batchUrl ) => { | |
// we consider request a failure if comment ID is not present | |
if ( ! batchResultForUrls[ batchUrl ].ID ) { | |
if ( options.fetchMode === FETCH_MODE.ALL_OR_THROW ) { | |
throw new Error( `Fetch of ${ batchUrl } failed while getting comments by ids for siteId: ${ options.siteId }` ); | |
} | |
// mark it so we will filter it | |
return null; | |
} | |
return batchResultForUrls[ batchUrl ]; | |
} ); | |
return arrayOfComments.filter( ( comment ) => comment !== null ); | |
} ); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* External dependencies | |
*/ | |
import nock from 'nock'; | |
import sinon from 'sinon'; | |
import sinonChai from 'sinon-chai'; | |
import Chai, { expect } from 'chai'; | |
import Immutable from 'immutable'; | |
/** | |
* Internal dependencies | |
*/ | |
import { | |
COMMENTS_RECEIVE, | |
COMMENTS_REMOVE, | |
COMMENTS_REQUEST, | |
COMMENTS_REQUEST_SUCCESS | |
} from '../../action-types'; | |
import { | |
requestPostComments, | |
writeComment, | |
pollComments | |
} from '../actions'; | |
import { | |
createCommentTargetId, | |
createRequestId | |
} from '../utils'; | |
import { | |
NUMBER_OF_COMMENTS_PER_FETCH, | |
MAX_NUMBER_OF_COMMENTS_IN_FETCH_RESULT | |
} from '../constants' | |
Chai.use( sinonChai ); | |
const MANY_COMMENTS_POST = { | |
siteId: 91750058, | |
postId: 287 | |
}; | |
const FEW_COMMENTS_POST = { | |
siteId: 106350587, | |
postId: 2 | |
}; | |
const API_DOMAIN = 'https://public-api.wordpress.com:443'; | |
describe( 'actions', () => { | |
before( () => { | |
// make sure all requests are mocked | |
nock.disableNetConnect(); | |
} ); | |
after( () => { | |
nock.cleanAll(); | |
nock.enableNetConnect(); | |
} ); | |
describe( '#receivePost()', () => { | |
it( 'should return a thunk', () => { | |
const res = requestPostComments( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId ); | |
expect( res ).to.be.a.function; | |
} ); | |
it( 'should not dispatch a thing if the request is already in flight', () => { | |
const requestId = createRequestId( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId, { order: 'DESC', number: NUMBER_OF_COMMENTS_PER_FETCH } ); | |
const dispatchSpy = sinon.spy(); | |
const getStateStub = sinon.stub().returns( { | |
comments: Immutable.fromJS( { | |
[ createCommentTargetId( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId ) ]: { | |
requests: { | |
[ requestId ]: COMMENTS_REQUEST | |
} | |
} | |
} ) | |
} ); | |
requestPostComments( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId )( dispatchSpy, getStateStub ); | |
expect( dispatchSpy ).to.not.have.been.called; | |
} ); | |
it( 'should dispatch correct first request actions', function() { | |
const dispatchSpy = sinon.spy(); | |
const getStateStub = sinon.stub().returns( { | |
comments: Immutable.Map() | |
} ); | |
nock( API_DOMAIN ) | |
.get( `/rest/v1.1/sites/${ MANY_COMMENTS_POST.siteId }/posts/${ MANY_COMMENTS_POST.postId }/replies/` ) | |
.query( { order: 'DESC', number: NUMBER_OF_COMMENTS_PER_FETCH } ) | |
.reply( 200, { found: 123, comments: [] } ); | |
const reqPromise = requestPostComments( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId )( dispatchSpy, getStateStub ); | |
expect( dispatchSpy ).to.have.been.calledWith( { | |
type: COMMENTS_REQUEST, | |
requestId: createRequestId( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId, { order: 'DESC', number: NUMBER_OF_COMMENTS_PER_FETCH } ) | |
} ); | |
return reqPromise.then( () => { | |
expect( dispatchSpy ).to.have.been.calledWith( { | |
type: COMMENTS_REQUEST_SUCCESS, | |
requestId: createRequestId( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId, { order: 'DESC', number: NUMBER_OF_COMMENTS_PER_FETCH } ) | |
} ); | |
} ); | |
} ); | |
it( 'should dispatch correct consecutive request actions', function() { | |
const beforeDateString = '2016-02-03T04:19:26.352Z'; | |
const dispatchSpy = sinon.spy(); | |
const getStateSpy = sinon.stub().returns( { | |
comments: Immutable.fromJS( { | |
[ createCommentTargetId( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId ) ]: { | |
items: [ | |
{ ID: 123, date: beforeDateString } | |
], | |
requests: Immutable.Map() | |
} | |
} ) | |
} ); | |
nock( API_DOMAIN ) | |
.get( `/rest/v1.1/sites/${ MANY_COMMENTS_POST.siteId }/posts/${ MANY_COMMENTS_POST.postId }/replies/` ) | |
.query( { order: 'DESC', number: NUMBER_OF_COMMENTS_PER_FETCH } ) | |
.reply( 200, { found: 123, comments: [] } ) | |
.get( `/rest/v1.1/sites/${ MANY_COMMENTS_POST.siteId }/posts/${ MANY_COMMENTS_POST.postId }/replies/` ) | |
.query( { order: 'DESC', number: NUMBER_OF_COMMENTS_PER_FETCH, before: beforeDateString } ) | |
.reply( 200, { found: 123, comments: [] } ); | |
const reqPromise = requestPostComments( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId )( dispatchSpy, getStateSpy ); | |
expect( dispatchSpy ).to.have.been.calledWith( { | |
type: COMMENTS_REQUEST, | |
requestId: createRequestId( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId, { order: 'DESC', number: NUMBER_OF_COMMENTS_PER_FETCH, before: new Date( beforeDateString ).toISOString() } ) | |
} ); | |
return reqPromise.then( () => { | |
expect( dispatchSpy ).to.have.been.calledWith( { | |
type: COMMENTS_REQUEST_SUCCESS, | |
requestId: createRequestId( MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId, { order: 'DESC', number: NUMBER_OF_COMMENTS_PER_FETCH, before: new Date( beforeDateString ).toISOString() } ) | |
} ); | |
} ); | |
} ); | |
} ); // requestPostComments | |
describe( '#writeComment()', () => { | |
before( () => { | |
nock( API_DOMAIN ) | |
.post( '/rest/v1.1/sites/' + MANY_COMMENTS_POST.siteId + '/posts/' + MANY_COMMENTS_POST.postId + '/replies/new', { content: 'Hello, yes, this is dog' } ) | |
.reply( 200, | |
{ | |
ID: 13, | |
post: { | |
ID: MANY_COMMENTS_POST.postId, | |
title: 'My awesome post!', | |
type: 'post', | |
link: 'https:\/\/public-api.wordpress.com\/rest\/v1.1\/sites\/' + MANY_COMMENTS_POST.siteId + '\/posts\/' + MANY_COMMENTS_POST.postId | |
}, | |
author: { ID: 1234, login: 'tester', email: false, name: 'Testie Test', first_name: 'Testie', last_name: 'Test', nice_name: 'test', site_ID: 1234 }, | |
date: '2016-02-05T11:17:03+00:00', | |
content: '<p>Hello, yes, this is dog<\/p>\n', | |
status: 'approved', | |
parent: false, | |
type: 'comment' | |
} ); | |
} ); | |
it( 'should dispatch correct actions', function() { | |
const dispatchSpy = sinon.spy(); | |
const writeCommentThunk = writeComment( 'Hello, yes, this is dog', MANY_COMMENTS_POST.siteId, MANY_COMMENTS_POST.postId ); | |
const reqPromise = writeCommentThunk( dispatchSpy ); | |
const firstSpyCallArg = dispatchSpy.args[0][0]; | |
expect( firstSpyCallArg.type ).to.eql( COMMENTS_RECEIVE ); | |
expect( firstSpyCallArg.comments[0].ID.indexOf( 'placeholder-' ) ).to.equal( 0 ); | |
return reqPromise.then( ( comment ) => { | |
expect( comment ).to.be.object; | |
expect( comment ).to.not.equal( undefined ); | |
expect( comment ).to.not.equal( null ); | |
const secondSpyCallArg = dispatchSpy.args[1][0]; | |
const thirdSpyCallArg = dispatchSpy.args[2][0]; | |
expect( secondSpyCallArg.type ).to.eql( COMMENTS_REMOVE ); | |
expect( secondSpyCallArg.commentId.indexOf( 'placeholder-' ) ).to.equal( 0 ); | |
expect( thirdSpyCallArg.type ).to.eql( COMMENTS_RECEIVE ); | |
expect( thirdSpyCallArg.comments.length ).to.eql( 1 ); | |
expect( thirdSpyCallArg.comments[0].ID ).to.be.a.number; | |
} ); | |
} ); | |
} ); // writeComment | |
describe( '#pollComments()', () => { | |
const commentItems = [ | |
{ ID: 11, parent: { ID: 9 }, text: 'eleven', date: '2016-01-31T10:07:18-08:00' }, | |
{ ID: 10, parent: { ID: 9 }, text: 'ten', date: '2016-01-29T10:07:18-08:00' }, | |
{ ID: 9, parent: { ID: 6 }, text: 'nine', date: '2016-01-28T11:07:18-08:00' }, | |
{ ID: 8, parent: false, text: 'eight', date: '2016-01-28T10:17:18-08:00' }, | |
{ ID: 7, parent: false, text: 'seven', date: '2016-01-28T10:08:18-08:00' }, | |
{ ID: 6, parent: false, text: 'six', date: '2016-01-28T10:07:18-08:00' } | |
]; | |
const latestDate = new Date( commentItems[ 0 ].date ); | |
const latestDateCommentID = commentItems[ 0 ].ID; | |
it( 'should not change comments tree when there are no changes available', function() { | |
const dispatchSpy = sinon.spy(); | |
const getStateStub = sinon.stub().returns( { | |
comments: Immutable.fromJS( { | |
[ createCommentTargetId( FEW_COMMENTS_POST.siteId, FEW_COMMENTS_POST.postId ) ]: { | |
items: commentItems | |
} | |
} ) | |
} ); | |
nock( API_DOMAIN ) | |
.get( `/rest/v1.1/sites/${ FEW_COMMENTS_POST.siteId }/posts/${ FEW_COMMENTS_POST.postId }/replies/` ) | |
.query( { fields: 'ID,date', number: MAX_NUMBER_OF_COMMENTS_IN_FETCH_RESULT } ) | |
.reply( 200, { | |
found: commentItems.length, | |
comments: commentItems | |
} ); | |
return pollComments( FEW_COMMENTS_POST.siteId, FEW_COMMENTS_POST.postId )( dispatchSpy, getStateStub ).then( () => { | |
expect( dispatchSpy ).to.not.have.been.called; | |
} ); | |
} ); | |
it( 'should fetch new comment if it available', function() { | |
const dispatchSpy = sinon.spy(); | |
const commentItemsWithDeletedComment = commentItems.filter( c => c.ID !== latestDateCommentID ); | |
const getStateStub = sinon.stub().returns( { | |
comments: Immutable.fromJS( { | |
[ createCommentTargetId( FEW_COMMENTS_POST.siteId, FEW_COMMENTS_POST.postId ) ]: { | |
items: commentItemsWithDeletedComment | |
} | |
} ) | |
} ); | |
nock( API_DOMAIN ) | |
.get( `/rest/v1.1/sites/${ FEW_COMMENTS_POST.siteId }/posts/${ FEW_COMMENTS_POST.postId }/replies/` ) | |
.query( { fields: 'ID,date', number: MAX_NUMBER_OF_COMMENTS_IN_FETCH_RESULT } ) | |
.reply( 200, { | |
found: commentItems.length, | |
comments: commentItems | |
} ) | |
.get( `/rest/v1.1/batch?urls%5B%5D=%2Fsites%2F${ FEW_COMMENTS_POST.siteId }%2Fcomments%2F${latestDateCommentID}` ) | |
.reply( 200, { | |
[ `/sites/${ FEW_COMMENTS_POST.siteId }/comments/${latestDateCommentID}` ]: { | |
ID: latestDateCommentID, | |
date: latestDate | |
} | |
} ); | |
return pollComments( FEW_COMMENTS_POST.siteId, FEW_COMMENTS_POST.postId )( dispatchSpy, getStateStub ).then( () => { | |
expect( dispatchSpy ).to.have.been.called; | |
const action = dispatchSpy.args[0][0]; | |
expect( action.type ).to.equal( COMMENTS_RECEIVE ); | |
expect( action.comments.length ).to.equal( 1 ); | |
expect( action.comments[0].ID ).to.equal( latestDateCommentID ); | |
expect( action.siteId ).to.equal( FEW_COMMENTS_POST.siteId ); | |
expect( action.postId ).to.equal( FEW_COMMENTS_POST.postId ); | |
} ); | |
} ); | |
it( 'should remove comment if it no longer on the server', function() { | |
const dispatchSpy = sinon.spy(); | |
const removeCommentId = 123; | |
const commentItemsWithExtraComment = [ ...commentItems ].concat( [ { ID: removeCommentId, date: '2016-01-30T10:07:18-08:00' } ] ); | |
commentItemsWithExtraComment.sort( ( a, b ) => new Date( b.date ) - new Date( a.date ) ); | |
const getStateStub = sinon.stub().returns( { | |
comments: Immutable.fromJS( { | |
[ createCommentTargetId( FEW_COMMENTS_POST.siteId, FEW_COMMENTS_POST.postId ) ]: { | |
items: commentItemsWithExtraComment | |
} | |
} ) | |
} ); | |
nock( API_DOMAIN ) | |
.get( `/rest/v1.1/sites/${ FEW_COMMENTS_POST.siteId }/posts/${ FEW_COMMENTS_POST.postId }/replies/` ) | |
.query( { fields: 'ID,date', number: MAX_NUMBER_OF_COMMENTS_IN_FETCH_RESULT } ) | |
.reply( 200, { | |
found: commentItems.length, | |
comments: commentItems | |
} ); | |
return pollComments( FEW_COMMENTS_POST.siteId, FEW_COMMENTS_POST.postId )( dispatchSpy, getStateStub ).then( () => { | |
expect( dispatchSpy ).to.have.been.called; | |
const action = dispatchSpy.args[0][0]; | |
expect( action.type ).to.equal( COMMENTS_REMOVE ); | |
expect( action.commentId ).to.equal( removeCommentId ); | |
expect( action.siteId ).to.equal( FEW_COMMENTS_POST.siteId ); | |
expect( action.postId ).to.equal( FEW_COMMENTS_POST.postId ); | |
} ); | |
} ); | |
} ); // pollComments | |
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* External dependencies | |
*/ | |
import nock from 'nock'; | |
import Chai, { expect } from 'chai'; | |
/** | |
* Internal dependencies | |
*/ | |
import { | |
fetchAllCommentFields, | |
fetchCommentsByIds | |
} from '../fetchers'; | |
import { | |
FETCH_MODE | |
} from '../constants'; | |
const MANY_COMMENTS_POST = { | |
siteId: 91750058, | |
postId: 287 | |
}; | |
describe( 'fetchers', () => { | |
before( () => { | |
nock.disableNetConnect(); | |
} ); | |
after( () => { | |
nock.enableNetConnect(); | |
} ); | |
describe( '#fetchAllCommentFields()', () => { | |
before( () => { | |
const headers = { | |
'content-type': 'application/json' | |
}; | |
nock( 'https://public-api.wordpress.com:443' ) | |
.get( '/rest/v1.1/sites/91750058/posts/287/replies/' ) | |
.twice() // number of tests | |
.query( { | |
fields: 'ID', number: '30' | |
} ) | |
.reply( 200, | |
{ | |
site_ID: 91750058, | |
found: 126, | |
comments: [ { ID: 1 } ] | |
}, | |
headers ) | |
.get( '/rest/v1.1/batch' ) | |
.twice() // number of tests | |
.query( true ) // nock seems to have a bug, so we opt for the query to match anything | |
.reply( 200, { | |
'/sites/91750058/posts/287/replies/?fields=ID&number=30&offset=30': { | |
comments: [ { ID: 123 } ] | |
}, | |
'/sites/91750058/posts/287/replies/?fields=ID&number=30&offset=60': { | |
status_code: 403, | |
errors: { | |
error: 'unauthorized', | |
message: 'API calls to this endpoint have been disabled.' | |
} | |
}, | |
'/sites/91750058/posts/287/replies/?fields=ID&number=30&offset=90': { | |
comments: [ { ID: 1234 } ] | |
}, | |
'/sites/91750058/posts/287/replies/?fields=ID&number=30&offset=120': { | |
comments: [ { ID: 12345 } ] | |
} | |
}, headers ); | |
} ); | |
it( 'should return rejected promise if unable to fetch all comments and fetch mode was to throw ', () => { | |
return fetchAllCommentFields( { siteId: MANY_COMMENTS_POST.siteId, postId: MANY_COMMENTS_POST.postId, numberOfItemsPerBatch: 30, fetchMode: FETCH_MODE.ALL_OR_THROW } ).catch( ( err ) => { | |
expect( err.message ).to.equal( `Fetch of /sites/${ MANY_COMMENTS_POST.siteId }/posts/${ MANY_COMMENTS_POST.postId }/replies/?fields=ID&number=30&offset=60 failed while getting all comment ids for siteId: ${ MANY_COMMENTS_POST.siteId } postId: ${ MANY_COMMENTS_POST.postId }` ); | |
} ); | |
} ); | |
it( 'should return as much comments as possible when fetch mode set to partial ok', () => { | |
return fetchAllCommentFields( { siteId: MANY_COMMENTS_POST.siteId, postId: MANY_COMMENTS_POST.postId, numberOfItemsPerBatch: 30, fetchMode: FETCH_MODE.PARTIAL_OK } ).then( ( res ) => { | |
expect( res.length ).to.equal( 4 ); | |
} ); | |
} ); | |
} ); // fetchAllCommentIds | |
describe( '#fetchCommentsByIds()', () => { | |
before( () => { | |
const headers = { | |
'content-type': 'application/json' | |
}; | |
nock( 'https://public-api.wordpress.com:443' ) | |
.get( '/rest/v1.1/batch' ) | |
.twice() // number of tests | |
.query( true ) // nock seems to have a bug, so we opt for the query to match anything | |
.reply( 200, { | |
'/sites/91750058/comments/123': { | |
ID: 123 | |
}, | |
'/sites/91750058/comments/1234': { | |
status_code: 403, | |
errors: { | |
error: 'unauthorized', | |
message: 'API calls to this endpoint have been disabled.' | |
} | |
}, | |
'/sites/91750058/comments/12345': { | |
ID: 12345 | |
} | |
}, headers ); | |
} ); | |
it( 'should return rejected promise if unable to fetch all comments and fetch mode was to throw', () => { | |
return fetchCommentsByIds( { siteId: MANY_COMMENTS_POST.siteId, commentIds: [ 123, 1234, 12345 ], fetchMode: FETCH_MODE.ALL_OR_THROW } ).catch( ( err ) => { | |
expect( err.message ).to.equal( `Fetch of /sites/${ MANY_COMMENTS_POST.siteId }/comments/1234 failed while getting comments by ids for siteId: ${ MANY_COMMENTS_POST.siteId }` ); | |
} ); | |
} ); | |
it( 'should return as much comments as possible when fetch mode set to partial ok', () => { | |
return fetchCommentsByIds( { siteId: MANY_COMMENTS_POST.siteId, commentIds: [ 123, 1234, 12345 ], fetchMode: FETCH_MODE.PARTIAL_OK } ).then( ( res ) => { | |
expect( res.length ).to.equal( 2 ); | |
} ); | |
} ); | |
} ); // fetchCommentsByIds | |
} ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment