twreporter-redux
Version:
redux actions and reducers for twreporter website
305 lines (288 loc) • 9.72 kB
JavaScript
/* global describe, context, it, afterEach */
/*
Testing functions:
fetchAFullTopic
fetchTopics
fetchTopicsOnIndexPage
*/
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import configureMockStore from 'redux-mock-store'
import fieldNames from '../../constants/redux-state-field-names'
import nock from 'nock'
import thunk from 'redux-thunk'
import types from '../../constants/action-types'
import * as actions from '../topics'
import pagination from '../../utils/pagination'
const { pageToOffset } = pagination
chai.use(chaiAsPromised)
const expect = chai.expect
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const topic1 = {
id: 'topic-id-1',
slug: 'topic-slug-1',
full: true,
}
const topic2 = {
id: 'topic-id-2',
slug: 'topic-slug-2',
full: true,
}
/* Fetch a full topic, whose assets like relateds, leading_video ...etc are all complete,
* @param {string} slug - slug of topic
*/
/*
========= Testing fetchAFullTopic ==========
*/
describe('Testing fetchAFullTopic:', () => {
afterEach(() => {
nock.cleanAll()
})
context('Topic is already existed in entities', () => {
it('Should dispatch types.CHANGE_SELECTED_TOPIC with topic', () => {
const mockSlug = 'mock-slug'
const mockTopic = {
id: 'mock-id',
slug: mockSlug,
full: true,
}
const store = mockStore({
[fieldNames.entities]: {
[fieldNames.topicsInEntities]: {
[mockSlug]: mockTopic,
},
},
})
store.dispatch(actions.fetchAFullTopic(mockSlug))
expect(store.getActions().length).to.equal(1) // dispatch types.CHANGE_SELECTED_TOPIC
expect(store.getActions()[0].type).to.equal(types.CHANGE_SELECTED_TOPIC)
expect(store.getActions()[0].payload).to.deep.equal(mockTopic)
})
})
context('It loads a full topic successfully', () => {
it('Should dispatch types.START_TO_GET_A_FULL_TOPIC and types.GET_A_FULL_TOPIC', () => {
const mockSlug = 'mock-slug'
const mockTopic = {
id: 'mock-id',
slug: mockSlug,
full: false,
}
const store = mockStore({
entities: {
topics: {
[mockSlug]: mockTopic,
},
},
})
const mockApiResponse = {
record: mockTopic,
}
nock('http://localhost:8080')
.get(encodeURI(`/v1/topics/${mockSlug}?full=true`))
.reply(200, mockApiResponse)
return store.dispatch(actions.fetchAFullTopic(mockSlug))
.then(() => {
expect(store.getActions().length).to.equal(2) // 2 actions: REQUEST && SUCCESS
expect(store.getActions()[0].type).to.deep.equal(types.START_TO_GET_A_FULL_TOPIC)
expect(store.getActions()[0].payload).to.deep.equal({
slug: mockSlug,
})
expect(store.getActions()[1].type).to.equal(types.GET_A_FULL_TOPIC)
expect(store.getActions()[1].payload).to.deep.equal(mockTopic)
})
})
})
context('If the api returns a failure', () => {
it('Should dispatch types.START_TO_GET_A_FULL_TOPIC and types.ERROR_TO_GET_A_FULL_TOPIC', () => {
const store = mockStore()
const mockSlug = 'mock-slug'
nock('http://localhost:8080')
.get(encodeURI(`/v1/topics/${mockSlug}?full=true`))
.reply(404)
return store.dispatch(actions.fetchAFullTopic(mockSlug))
.then(() => {
expect(store.getActions().length).to.equal(2) // 2 actions: REQUEST && FAILURE
expect(store.getActions()[0].type).to.deep.equal(types.START_TO_GET_A_FULL_TOPIC)
expect(store.getActions()[1].type).to.equal(types.ERROR_TO_GET_A_FULL_TOPIC)
expect(store.getActions()[1].payload.error).to.be.an.instanceof(Error)
})
})
})
})
/* Fetch topics(only containing meta properties),
* and it will load more if (total > items you have currently).
* @param {number} limit - the number of posts you want to get in one request
*/
/*
========= Testing fetchTopics ==========
*/
describe('Testing fetchTopics:', () => {
afterEach(() => {
nock.cleanAll()
})
context('There is no such page of topics to load', () => {
it('Should dispatch types.GET_TOPICS with empty items array', () => {
const store = mockStore({
[fieldNames.topicList]: {
page: 2,
totalPages: 2,
items: {
1: [topic1.slug],
2: [topic2.slug],
},
},
})
const page = 3
const nPerPage = 1
const { limit, offset } = pageToOffset({ page, nPerPage })
const total = 5
const mockApiResponse = {
records: [],
meta: {
limit,
total,
offset,
},
}
nock('http://localhost:8080')
.get(encodeURI(`/v1/topics?limit=${limit}&offset=${offset}`))
.reply(200, mockApiResponse)
return store.dispatch(actions.fetchTopics(page, nPerPage))
.then(() => {
expect(store.getActions().length).to.equal(2) // 2 actions: REQUEST && SUCCESS
expect(store.getActions()[1].type).to.equal(types.GET_TOPICS)
expect(store.getActions()[1].payload).to.deep.equal({
items: [],
total,
limit,
offset,
})
})
})
})
context('It loads topics successfully', () => {
it('Should dispatch types.GET_TOPICS', () => {
const store = mockStore({
[fieldNames.topicList]: {
items: {
1: [topic1.slug],
},
totalPages: 5,
},
})
const page = 2
const nPerPage = 1
const { limit, offset } = pageToOffset({ page, nPerPage })
const total = 5
const mockApiResponse = {
records: [
topic2,
],
meta: {
limit,
total,
offset,
},
}
nock('http://localhost:8080')
.get(encodeURI(`/v1/topics?limit=${limit}&offset=${offset}`))
.reply(200, mockApiResponse)
return store.dispatch(actions.fetchTopics(page, nPerPage))
.then(() => {
expect(store.getActions().length).to.equal(2) // 2 actions: REQUEST && SUCCESS
expect(store.getActions()[0].type).to.deep.equal(types.START_TO_GET_TOPICS)
expect(store.getActions()[1].type).to.equal(types.GET_TOPICS)
expect(store.getActions()[1].payload).to.deep.equal({
items: [topic2],
total,
limit,
offset,
})
})
})
})
context('If the api returns a failure', () => {
it('Should dispatch types.ERROR_TO_GET_TOPICS', () => {
const limit = 1
const offset = 1
const store = mockStore()
nock('http://localhost:8080')
.get(encodeURI(`/v1/topics?limit=${limit}&offset=${offset}`))
.reply(404)
return store.dispatch(actions.fetchTopics(limit))
.then(() => {
expect(store.getActions().length).to.equal(2) // 2 actions: REQUEST && FAILURE
expect(store.getActions()[0].type).to.deep.equal(types.START_TO_GET_TOPICS)
expect(store.getActions()[1].type).to.equal(types.ERROR_TO_GET_TOPICS)
expect(store.getActions()[1].payload.error).to.be.an.instanceof(Error)
})
})
})
context('If the parameter nPerPage is invalid', () => {
it('Should dispatch no action and return a Promise.reject(err)', () => {
const store = mockStore({})
const page = 1
const nPerPage = -1
expect(store.dispatch(actions.fetchTopics(page, nPerPage))).eventually.to.be.an.instanceof(Error)
return expect(store.getActions().length).to.equal(0) // no action
})
})
context('If the parameter page is invalid', () => {
it('Should dispatch no action and return a Promise.reject(err)', () => {
const store = mockStore({})
const page = -1
const nPerPage = 10
expect(store.dispatch(actions.fetchTopics(page, nPerPage))).eventually.to.be.an.instanceof(Error)
return expect(store.getActions().length).to.equal(0) // no action
})
})
})
/**
* fetchTopicsOnIndexPage
* This function will fetch the 2 to 5 latest topics.
* It's specifically made for index page
*/
/*
========= Testing fetchTopicsOnIndexPage ==========
*/
describe('Testing fetchTopicsOnIndexPage:', () => {
after(() => {
nock.cleanAll()
})
context('index_page.topics are already existed', () => {
it('Should do nothing', () => {
const store = mockStore({
[fieldNames.indexPage]: {
[fieldNames.sections.topicsSection]: [
topic1, topic2,
],
},
})
store.dispatch(actions.fetchTopicsOnIndexPage())
expect(store.getActions().length).to.equal(0) // no action is dispatched
return expect(store.dispatch(actions.fetchTopicsOnIndexPage())).eventually.equal(undefined)
})
})
context('Load topics if needed', () => {
it('Should dispatch types.GET_TOPICS_FOR_INDEX_PAGE)', () => {
const store = mockStore({})
nock('http://localhost:8080')
.get(encodeURI('/v1/topics?offset=1&limit=4'))
.reply(200, {
records: [topic1, topic2],
meta: {
limit: 10,
total: 2,
offset: 0,
},
})
return store.dispatch(actions.fetchTopicsOnIndexPage())
.then(() => {
expect(store.getActions().length).to.equal(2) // START and GET
expect(store.getActions()[1].type).to.equal(types.GET_TOPICS_FOR_INDEX_PAGE)
expect(store.getActions()[1].payload.items.length).to.equal(2)
})
})
})
})