@twreporter/redux
Advanced tools
Comparing version 4.0.0 to 4.0.1
### Unrelease | ||
### 4.0.1 | ||
- Enable pagination on posts action and reducer | ||
### 4.0.0 | ||
@@ -3,0 +6,0 @@ - Move bookmark feature to @twreporter/registration |
@@ -192,6 +192,23 @@ 'use strict'; | ||
}); | ||
it('Items of page are already fetched', function () { | ||
var mockArgs = ['mock_target_uuid', 'categories', 10, 1]; | ||
var store = mockStore({ | ||
lists: { | ||
mock_target_uuid: { | ||
total: 10, | ||
items: [post1, post2, post3, post4], | ||
pages: { | ||
1: [0, 3] | ||
} | ||
} | ||
} | ||
}); | ||
store.dispatch(actions.fetchListedPosts.apply(actions, mockArgs)); | ||
expect(store.getActions().length).to.equal(0); | ||
return expect(store.dispatch(actions.fetchListedPosts.apply(actions, mockArgs))).eventually.equal(undefined); | ||
}); | ||
}); | ||
context('It loads posts successfully', function () { | ||
it('Should dispatch types.START_TO_GET_POSTS and types.GET_LISTED_POSTS', function () { | ||
var mockArgs = ['mock_target_uuid', 'categories', 1]; | ||
it('Should load items when there is no items in the store', function () { | ||
var mockArgs = ['mock_target_uuid', 'categories', 1, 0]; | ||
var store = mockStore({ | ||
@@ -233,3 +250,4 @@ lists: {} | ||
total: 2, | ||
listID: 'mock_target_uuid' | ||
listID: 'mock_target_uuid', | ||
page: 0 | ||
} | ||
@@ -245,2 +263,52 @@ }; | ||
}); | ||
it('Should load items when page is provided', function () { | ||
var mockArgs = ['mock_target_uuid', 'categories', 1, 2]; | ||
var store = mockStore({ | ||
lists: {} | ||
}); | ||
var mockUrl = (0, _formApiUrl2.default)('posts?where={"categories":{"in":["mock_target_uuid"]}}&limit=1&offset=1'); | ||
var indexOfSlash = mockUrl.indexOf('/', 7); | ||
var mockHost = mockUrl.slice(0, indexOfSlash); | ||
var mockPath = mockUrl.slice(indexOfSlash); | ||
var mockApiResponse = { | ||
records: [{ | ||
_id: 'mock_category_article_id_01', | ||
title: 'mock_category_article_title', | ||
slug: 'mock-category-article-slug', | ||
style: 'article', | ||
og_description: 'mock_category_article_title_og_description' | ||
}], | ||
meta: { | ||
limit: 1, | ||
total: 2, | ||
offset: 0 | ||
} | ||
}; | ||
var expectedRequestAction = { | ||
type: _actionTypes2.default.START_TO_GET_POSTS, | ||
url: mockUrl | ||
}; | ||
var expectedSuccessAction = { | ||
type: _actionTypes2.default.GET_LISTED_POSTS, | ||
payload: { | ||
items: [{ | ||
_id: 'mock_category_article_id_01', | ||
title: 'mock_category_article_title', | ||
slug: 'mock-category-article-slug', | ||
style: 'article', | ||
og_description: 'mock_category_article_title_og_description' | ||
}], | ||
total: 2, | ||
listID: 'mock_target_uuid', | ||
page: 2 | ||
} | ||
}; | ||
(0, _nock2.default)(mockHost).get(mockPath).reply(200, mockApiResponse); | ||
return store.dispatch(actions.fetchListedPosts.apply(actions, mockArgs)).then(function () { | ||
expect(store.getActions().length).to.equal(2); | ||
expect(store.getActions()[0]).to.deep.equal(expectedRequestAction); | ||
expect(store.getActions()[1].type).to.equal(expectedSuccessAction.type); | ||
expect(store.getActions()[1].payload).to.deep.equal(expectedSuccessAction.payload); | ||
}); | ||
}); | ||
}); | ||
@@ -247,0 +315,0 @@ context('If the api returns a failure', function () { |
@@ -128,2 +128,3 @@ 'use strict'; | ||
var limit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 10; | ||
var page = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; | ||
@@ -138,2 +139,6 @@ return function (dispatch, getState) { | ||
if (page > 0 && Array.isArray(_.get(list, ['pages', page]))) { | ||
return Promise.resolve(); | ||
} | ||
var where = _defineProperty({}, listType, { | ||
@@ -143,6 +148,7 @@ in: [listID] | ||
var offset = _.get(list, 'items.length', 0); | ||
var offset = page > 0 ? (page - 1) * limit : _.get(list, 'items.length', 0); | ||
var path = _apiEndpoints2.default.posts + '?where=' + JSON.stringify(where) + '&limit=' + limit + '&offset=' + offset; | ||
return _fetchPosts(dispatch, path, _actionTypes2.default.GET_LISTED_POSTS, _actionTypes2.default.ERROR_TO_GET_LISTED_POSTS, { listID: listID }); | ||
return _fetchPosts(dispatch, path, _actionTypes2.default.GET_LISTED_POSTS, _actionTypes2.default.ERROR_TO_GET_LISTED_POSTS, { listID: listID, page: page }); | ||
}; | ||
@@ -149,0 +155,0 @@ } |
@@ -88,3 +88,4 @@ 'use strict'; | ||
total: 10, | ||
listID: 'mock-list-id' | ||
listID: 'mock-list-id', | ||
page: 0 | ||
} | ||
@@ -95,5 +96,25 @@ })).to.deep.equal({ | ||
total: 10, | ||
error: null | ||
error: null, | ||
pages: {} | ||
} | ||
}); | ||
(0, _chai.expect)((0, _posts.posts)({}, { | ||
type: _actionTypes2.default.GET_LISTED_POSTS, | ||
payload: { | ||
items: [post1, post2, post3, post4], | ||
total: 10, | ||
listID: 'mock-list-id', | ||
page: 2 | ||
} | ||
})).to.deep.equal({ | ||
'mock-list-id': { | ||
items: [post1.slug, post2.slug, post3.slug, post4.slug], | ||
total: 10, | ||
error: null, | ||
pages: { | ||
2: [0, 3] | ||
} | ||
} | ||
}); | ||
}); | ||
@@ -100,0 +121,0 @@ |
@@ -83,3 +83,5 @@ 'use strict'; | ||
var listID = _.get(action, 'payload.listID', ''); | ||
var page = _.get(action, 'payload.page', 0); | ||
var list = _.get(state, listID, { | ||
pages: {}, | ||
items: [], | ||
@@ -90,2 +92,4 @@ total: 0, | ||
var itemsNum = list.items.length || 0; | ||
list.items = _.concat(list.items, _.map(items, function (item) { | ||
@@ -97,2 +101,6 @@ return item.slug; | ||
if (page > 0) { | ||
_.set(list.pages, page, [itemsNum, itemsNum + (items.length - 1)]); | ||
} | ||
return _.merge({}, state, _defineProperty({}, listID, list)); | ||
@@ -99,0 +107,0 @@ } |
{ | ||
"name": "@twreporter/redux", | ||
"version": "4.0.0", | ||
"version": "4.0.1", | ||
"description": "redux actions and reducers for twreporter website", | ||
@@ -13,2 +13,3 @@ "main": "lib/index.js", | ||
"lint": "eslint -c .eslintrc src ", | ||
"lint-fix": "eslint -c .eslintrc src --fix", | ||
"validate": "npm ls", | ||
@@ -15,0 +16,0 @@ "dev": "npm run build:cus && gulp watch" |
@@ -7,2 +7,49 @@ [![Tag](https://img.shields.io/github/tag/twreporter/twreporter-redux.svg)](https://github.com/twreporter/twreporter-redux/tags) | ||
## Development | ||
``` | ||
CUSTOMER_FOLDER=../entry_project/ npm run dev | ||
// Assume your entry project is located at /home/nickli/entry_project. | ||
// In the entry_project, you install @twreporter/redux, | ||
// all the @twreporter/redux codes will be at /home/nickli/entry_project/@twreporter/redux. | ||
// npm run dev will copy @twreporter/redux transpiled es5 javascript codes to your custom folder, that is entry_project. | ||
``` | ||
## Build | ||
`npm run build` | ||
## Publish | ||
`npm publish` | ||
## actions | ||
### index-page | ||
Fetch all posts and topics [index page](https://www.twreporter.org) needed. | ||
### posts | ||
* Fetch a full post, which will include details, other than metadata, like what topic it belongs to, | ||
or what other posts it is releated to. | ||
* Fetch a list of posts, which will only include the metadata(like slug, title, description, published data ...etc) of posts. | ||
### topics | ||
* Fetch a full topic, which will include all the posts belonging to it. | ||
* Fetch a list of topics, which will only include the metadata(like slug, title, description, published data ...etc) of topics. | ||
## reducers | ||
### index-page | ||
`reduxState.indexPage` will contain each sections(like editor_picks, review, latest ...etc) in the [homepage of twreporter](https://www.twreporter.org) | ||
### posts | ||
`reduxState.post` will store `slug`, `error` and `isFetching` | ||
`reduxState.posts` will store `items`, `error`and `total` | ||
### topics | ||
`reduxState.topic` will store `slug`, `error` and `isFetching` | ||
`reduxState.topics` will store `items`, `totalPages`, `page`, `nPerPage`, `error` and `isFetching` | ||
### entities | ||
`reduxState.entities.posts` will store ${POST_SLUG}: ${POST_DATA} (string: Object) pair in a map | ||
`reduxState.entities.topics` will store ${TOPIC_SLUG}: ${TOPIC_DATA} {string: Object} pair in a map | ||
@@ -192,5 +192,28 @@ /* global describe, context, it, afterEach */ | ||
}) | ||
it('Items of page are already fetched', () => { | ||
const mockArgs = [ | ||
'mock_target_uuid', // listID | ||
'categories', // type | ||
10, // limit | ||
1, // page | ||
] | ||
const store = mockStore({ | ||
lists: { | ||
mock_target_uuid: { | ||
total: 10, | ||
items: [post1, post2, post3, post4], | ||
pages: { | ||
// page 1 already fetched, and items post1, post2, post3 and post4 | ||
1: [0, 3], | ||
}, | ||
}, | ||
}, | ||
}) | ||
store.dispatch(actions.fetchListedPosts(...mockArgs)) | ||
expect(store.getActions().length).to.equal(0) // no action is dispatched | ||
return expect(store.dispatch(actions.fetchListedPosts(...mockArgs))).eventually.equal(undefined) | ||
}) | ||
}) | ||
context('It loads posts successfully', () => { | ||
it('Should dispatch types.START_TO_GET_POSTS and types.GET_LISTED_POSTS', () => { | ||
it('Should load items when there is no items in the store', () => { | ||
const mockArgs = [ | ||
@@ -200,2 +223,3 @@ 'mock_target_uuid', // listID | ||
1, // limit | ||
0, // page | ||
] | ||
@@ -243,2 +267,3 @@ const store = mockStore({ | ||
listID: 'mock_target_uuid', | ||
page: 0, | ||
}, | ||
@@ -257,2 +282,64 @@ } | ||
}) | ||
it('Should load items when page is provided', () => { | ||
const mockArgs = [ | ||
'mock_target_uuid', // listID | ||
'categories', // type | ||
1, // limit | ||
2, // page | ||
] | ||
const store = mockStore({ | ||
// empty lists | ||
lists: { | ||
}, | ||
}) | ||
const mockUrl = formatUrl('posts?where={"categories":{"in":["mock_target_uuid"]}}&limit=1&offset=1') | ||
const indexOfSlash = mockUrl.indexOf('/', 7) | ||
const mockHost = mockUrl.slice(0, indexOfSlash) // example: 'http://localhost:8080' | ||
const mockPath = mockUrl.slice(indexOfSlash) // example: '/posts?where=.....' | ||
const mockApiResponse = { | ||
records: [ | ||
{ | ||
_id: 'mock_category_article_id_01', | ||
title: 'mock_category_article_title', | ||
slug: 'mock-category-article-slug', | ||
style: 'article', | ||
og_description: 'mock_category_article_title_og_description', | ||
}, | ||
], | ||
meta: { | ||
limit: 1, | ||
total: 2, | ||
offset: 0, | ||
}, | ||
} | ||
const expectedRequestAction = { | ||
type: types.START_TO_GET_POSTS, | ||
url: mockUrl, | ||
} | ||
const expectedSuccessAction = { | ||
type: types.GET_LISTED_POSTS, | ||
payload: { | ||
items: [{ | ||
_id: 'mock_category_article_id_01', | ||
title: 'mock_category_article_title', | ||
slug: 'mock-category-article-slug', | ||
style: 'article', | ||
og_description: 'mock_category_article_title_og_description', | ||
}], | ||
total: 2, | ||
listID: 'mock_target_uuid', | ||
page: 2, | ||
}, | ||
} | ||
nock(mockHost) | ||
.get(mockPath) | ||
.reply(200, mockApiResponse) | ||
return store.dispatch(actions.fetchListedPosts(...mockArgs)) | ||
.then(() => { | ||
expect(store.getActions().length).to.equal(2) // 2 actions: REQUEST && SUCCESS | ||
expect(store.getActions()[0]).to.deep.equal(expectedRequestAction) | ||
expect(store.getActions()[1].type).to.equal(expectedSuccessAction.type) | ||
expect(store.getActions()[1].payload).to.deep.equal(expectedSuccessAction.payload) | ||
}) | ||
}) | ||
}) | ||
@@ -259,0 +346,0 @@ context('If the api returns a failure', () => { |
@@ -106,3 +106,3 @@ import apiConfig from '../conf/api-config.json' | ||
*/ | ||
export function fetchListedPosts(listID, listType, limit = 10) { | ||
export function fetchListedPosts(listID, listType, limit = 10, page = 0) { | ||
return (dispatch, getState) => { | ||
@@ -117,2 +117,7 @@ const state = getState() | ||
// items of page are already fetched | ||
if (page > 0 && Array.isArray(_.get(list, ['pages', page]))) { | ||
return Promise.resolve() | ||
} | ||
const where = { | ||
@@ -124,6 +129,10 @@ [listType]: { | ||
const offset = _.get(list, 'items.length', 0) | ||
// if page provided(should bigger than 0), | ||
// use page to count offset, | ||
// otherwise, use current length of items | ||
const offset = page > 0 ? (page - 1) * limit : _.get(list, 'items.length', 0) | ||
const path = `${apiEndpoints.posts}?where=${JSON.stringify(where)}&limit=${limit}&offset=${offset}` | ||
return _fetchPosts(dispatch, path, types.GET_LISTED_POSTS, types.ERROR_TO_GET_LISTED_POSTS, { listID }) | ||
return _fetchPosts(dispatch, path, types.GET_LISTED_POSTS, types.ERROR_TO_GET_LISTED_POSTS, { listID, page }) | ||
} | ||
@@ -130,0 +139,0 @@ } |
@@ -96,2 +96,3 @@ /* global describe, it */ | ||
listID: 'mock-list-id', | ||
page: 0, | ||
}, | ||
@@ -104,4 +105,28 @@ }), | ||
error: null, | ||
pages: {}, | ||
}, | ||
}) | ||
// page is provided | ||
expect( | ||
posts({ | ||
}, { | ||
type: types.GET_LISTED_POSTS, | ||
payload: { | ||
items: [post1, post2, post3, post4], | ||
total: 10, | ||
listID: 'mock-list-id', | ||
page: 2, | ||
}, | ||
}), | ||
).to.deep.equal({ | ||
'mock-list-id': { | ||
items: [post1.slug, post2.slug, post3.slug, post4.slug], | ||
total: 10, | ||
error: null, | ||
pages: { | ||
2: [0, 3], | ||
}, | ||
}, | ||
}) | ||
}) | ||
@@ -108,0 +133,0 @@ |
@@ -49,3 +49,12 @@ import types from '../constants/action-types' | ||
const listID = _.get(action, 'payload.listID', '') | ||
const page = _.get(action, 'payload.page', 0) | ||
const list = _.get(state, listID, { | ||
// pages is used to store items position, | ||
// say, if | ||
// pages = { | ||
// 1: [0, 9] | ||
// } | ||
// which means, items of page 1 are stored | ||
// from items[0] to items[9] | ||
pages: {}, | ||
items: [], | ||
@@ -56,2 +65,4 @@ total: 0, | ||
const itemsNum = list.items.length || 0 | ||
list.items = _.concat(list.items, _.map(items, item => item.slug)) | ||
@@ -61,2 +72,8 @@ list.total = total | ||
// not support page = 0 | ||
// page starts from 1, not 0 | ||
if (page > 0) { | ||
_.set(list.pages, page, [itemsNum, (itemsNum + (items.length - 1))]) | ||
} | ||
return _.merge({}, state, { | ||
@@ -63,0 +80,0 @@ [listID]: list, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
341906
5453
55
0