react-use-api
Version:
<div align="center"> <h1> <img width="120" src="./icon.svg"> <br/> React useApi() <br /> <br /> </h1> </div>
362 lines (340 loc) • 9.93 kB
text/typescript
/// <reference path="../typings.d.ts" />
import LRU from 'lru-cache'
import { configure, defaultSettings } from '../common'
import * as ssrModule from '../ssr'
jest.mock('../common', () => {
const common = jest.requireActual('../common')
return {
...common,
axiosAll: jest.fn(),
}
})
const { feedRequests, injectSSRHtml, loadApiCache } = ssrModule
const originalLog = console.log
const originalErrorLog = console.error
beforeEach(() => {
jest.resetModules()
})
afterEach(() => {
console.log = originalLog
console.error = originalErrorLog
})
const copySettings = () => ({
...defaultSettings,
cache: new LRU<string, ReactUseApi.CacheData | any>(),
isSSR: () => true,
})
const testCache = new LRU<string, ReactUseApi.CacheData | any>()
testCache.set('foo', 'bar')
testCache.set('abc', 123)
const cacheData = testCache.dump()
describe('feedRequests tests', () => {
const { axiosAll } = require('../common')
it('should work as expected', async () => {
let count = 1
const ssrConfigs = [
{
config: {
url: '/api/v1/api1',
},
cacheKey: 'foo',
},
]
const renderSSR = jest.fn(() => {
if (count-- > 0) {
// for the second time
ssrConfigs.push({
config: {
url: '/api/v1/api2',
},
cacheKey: 'abc',
})
}
return '<div>Hello World!</div>'
})
const context = configure({
settings: {
...copySettings(),
renderSSR,
debug: false,
},
})
const {
settings: { cache },
collection,
} = context
collection.ssrConfigs = ssrConfigs
const response = {
data: {
message: 'ok',
},
}
axiosAll.mockReset().mockResolvedValue(response)
const ssrHtml = await feedRequests(context, '')
const { cacheKeys } = collection
expect(ssrConfigs.length).toBe(0)
expect(cacheKeys.size).toBe(0)
expect(ssrHtml).toBe('<div>Hello World!</div>')
expect(cache.dump()).toEqual([
{ k: 'abc', v: { response }, e: 0 },
{ k: 'foo', v: { response }, e: 0 },
])
expect(renderSSR).toHaveBeenCalledTimes(2)
})
it('should work as expected if error', async () => {
const ssrConfigs = [
{
config: {
url: '/api/v1/api1',
},
cacheKey: 'foo',
},
]
const renderSSR = jest.fn().mockReturnValue('<div>Hello World!</div>')
const context = configure({
settings: {
...copySettings(),
renderSSR,
debug: true,
},
})
const {
settings: { cache },
collection,
} = context
collection.ssrConfigs = ssrConfigs
const error = {
response: {
data: {
message: 'fail',
},
},
}
axiosAll.mockReset().mockRejectedValue(error)
console.log = jest.fn()
const ssrHtml = await feedRequests(context, '')
expect(ssrConfigs.length).toBe(0)
expect(console.log).toHaveBeenCalledWith(
'[ReactUseApi][Collecting Requests]'
)
expect(console.log).toHaveBeenCalledWith('[ReactUseApi][Fetch]', 'foo')
expect(console.log).toHaveBeenCalledWith(
'[ReactUseApi][Requests Count] =',
0
)
expect(console.log).toHaveBeenCalledWith(
'[ReactUseApi][Executed Times] =',
1
)
expect(ssrHtml).toBe('<div>Hello World!</div>')
expect(cache.dump()).toEqual([
{ k: 'foo', v: { error: error.response }, e: 0 },
])
expect(renderSSR).toHaveBeenCalledTimes(1)
})
it('should work as expected if throw an error', async () => {
const ssrConfigs = [
{
config: {
url: '/api/v1/api1',
},
cacheKey: 'foo',
},
]
const renderSSR = jest.fn().mockReturnValue('<div>Hello World!</div>')
const context = configure({
settings: {
...copySettings(),
renderSSR,
},
})
const {
settings: { cache },
collection,
} = context
collection.ssrConfigs = ssrConfigs
const error = {
message: 'fail',
}
axiosAll.mockReset().mockRejectedValue(error)
await expect(feedRequests(context, '')).rejects.toEqual(error)
expect(ssrConfigs.length).toBe(1)
expect(cache.length).toBe(0)
expect(renderSSR).not.toHaveBeenCalled()
})
it('should work well with no ssr configs', async () => {
const context = configure({
settings: {
...copySettings(),
debug: true,
},
})
console.log = jest.fn()
const {
collection: { ssrConfigs },
} = context
const ssrHtml = await feedRequests(context, '')
expect(console.log).toHaveBeenCalledWith(
'[ReactUseApi][Executed Times] =',
0
)
expect(ssrConfigs.length).toBe(0)
expect(ssrHtml).toBe('')
})
it('should work as expected if ssr rendering reaches max requests number', async () => {
const context = configure({
settings: {
...copySettings(),
},
})
const { collection } = context
collection.ssrConfigs = [
{
config: {
url: '/api/v1/foo/bar',
},
cacheKey: '',
},
]
console.error = jest.fn()
await feedRequests(context, '', 0)
expect(console.error.mock.calls[0][0]).toEqual(
'[ReactUseApi][ERROR] - Maximum executing times while fetching axios requests'
)
})
})
describe('injectSSRHtml tests', () => {
const html = '<div>Hello World</>'
const feedRequests = jest
.spyOn(ssrModule, 'feedRequests')
.mockResolvedValue(html)
const apiTestCache = new LRU<string, ReactUseApi.CacheData | any>()
apiTestCache.set(JSON.stringify({ url: 'foo' }), 'bar')
const apiTestCacheJson = JSON.stringify(apiTestCache.dump()).replace(
/</g,
'\\u003c'
)
it('should injectSSRHtml work well with settings.renderSSR', async () => {
const renderSSR = jest.fn().mockReturnValue(html)
const context = configure({
settings: {
...copySettings(),
renderSSR,
},
})
const { settings, isSSR } = context
const { cache } = settings
cache.reset = jest.fn()
cache.set(JSON.stringify({ url: 'foo' }), 'bar')
expect.hasAssertions()
const ssrHtml = await injectSSRHtml(context)
expect(isSSR).toBe(true)
expect(renderSSR).toHaveBeenCalled()
expect(cache.reset).toHaveBeenCalled()
expect(feedRequests).toHaveBeenLastCalledWith(context, html)
expect(ssrHtml).toEqual(
`${html}<script>window.__USE_API_CACHE__ = ${apiTestCacheJson}</script>`
)
})
it('should injectSSRHtml work well without the html of the api cache script', async () => {
const renderSSR = jest.fn().mockReturnValue(html)
const context = configure({
settings: {
...copySettings(),
renderSSR,
useCacheData: false,
},
})
const { settings } = context
const { cache } = settings
cache.reset = jest.fn()
cache.dump = jest.fn()
expect.hasAssertions()
const ssrHtml = await injectSSRHtml(context)
expect(renderSSR).toHaveBeenCalled()
expect(cache.reset).toHaveBeenCalled()
expect(feedRequests).toHaveBeenLastCalledWith(context, html)
expect(ssrHtml).toEqual(html)
})
it('should injectSSRHtml work well with postProcess', async () => {
const renderSSR = jest.fn().mockReturnValue(html)
const context = configure({
settings: {
...copySettings(),
},
})
const { settings } = context
const { cache } = settings
cache.reset = jest.fn()
cache.set(JSON.stringify({ url: 'foo' }), 'bar')
expect.hasAssertions()
let originSSRHtml = ''
const postProcess = jest.fn((ssrHtml: string, apiCacheScript: string) => {
originSSRHtml = ssrHtml + apiCacheScript
return '404'
})
const ssrHtml = await injectSSRHtml(context, renderSSR, postProcess)
expect(renderSSR).toHaveBeenCalled()
expect(cache.reset).toHaveBeenCalled()
expect(postProcess).toHaveBeenCalled()
expect(feedRequests).toHaveBeenLastCalledWith(context, html)
expect(originSSRHtml).toEqual(
`${html}<script>window.__USE_API_CACHE__ = ${apiTestCacheJson}</script>`
)
expect(ssrHtml).toEqual('404')
})
it('should rule the uncached data out by shouldUseApiCache()', async () => {
const renderSSR = jest.fn().mockReturnValue(html)
const context = configure({
settings: {
...copySettings(),
renderSSR,
shouldUseApiCache(config: ReactUseApi.SingleConfig, cacheKey) {
if (
cacheKey.includes('/no/cache') ||
config.url.includes('/nodata')
) {
return false
}
},
},
})
const { settings } = context
const { cache } = settings
cache.reset = jest.fn()
cache.set(JSON.stringify({ url: '/no/cache' }), 'no cache')
cache.set(JSON.stringify({ url: '/nodata' }), 'no data')
cache.set(JSON.stringify({ url: 'foo' }), 'bar')
expect.hasAssertions()
const ssrHtml = await injectSSRHtml(context)
expect(renderSSR).toHaveBeenCalled()
expect(ssrHtml).toEqual(
`${html}<script>window.__USE_API_CACHE__ = ${apiTestCacheJson}</script>`
)
})
})
describe('loadApiCache tests', () => {
it('should work well with cache data', () => {
const { clientCacheVar, cache } = defaultSettings
Object.assign(window, {
[clientCacheVar]: cacheData,
})
loadApiCache()
expect(cache.dump()).toEqual(cacheData)
expect(window.hasOwnProperty(clientCacheVar)).toBe(false)
})
it('should nothing happen if there is no cache data', () => {
const context = configure({
settings: {
...copySettings(),
},
})
const {
settings: { clientCacheVar, cache },
} = context
delete window[clientCacheVar]
loadApiCache(context)
expect(cache.length).toBe(0)
})
})
// TODO: an integration test