Skip to content

Instantly share code, notes, and snippets.

@yurynix
Created April 21, 2016 16:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yurynix/7124b3f01171382b3bb3e8dc99334191 to your computer and use it in GitHub Desktop.
Save yurynix/7124b3f01171382b3bb3e8dc99334191 to your computer and use it in GitHub Desktop.
coomments polling
/**
* 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 } ) );
};
}
/**
* 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 );
} );
}
/**
* 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
} );
/**
* 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