antv-l7/.cache/__tests__/loader.js

555 lines
17 KiB
JavaScript

// This is by no means a full test file for loader.js so feel free to add more tests.
import mock from "xhr-mock"
import { ProdLoader } from "../loader"
import emitter from "../emitter"
jest.mock(`../emitter`)
describe(`Production loader`, () => {
describe(`loadPageDataJson`, () => {
let originalBasePath
let originalPathPrefix
let xhrCount
/**
* @param {string} path
* @param {number} status
* @param {string|Object?} responseText
* @param {boolean?} json
*/
const mockPageData = (path, status, responseText = ``, json = false) => {
mock.get(`/page-data${path}/page-data.json`, (req, res) => {
xhrCount++
if (json) {
res.header(`content-type`, `application/json`)
}
return res
.status(status)
.body(
typeof responseText === `string`
? responseText
: JSON.stringify(responseText)
)
})
}
const defaultPayload = {
path: `/mypage/`,
}
// replace the real XHR object with the mock XHR object before each test
beforeEach(() => {
originalBasePath = global.__BASE_PATH__
originalPathPrefix = global.__PATH_PREFIX__
global.__BASE_PATH__ = ``
global.__PATH_PREFIX__ = ``
xhrCount = 0
mock.setup()
})
// put the real XHR object back and clear the mocks after each test
afterEach(() => {
global.__BASE_PATH__ = originalBasePath
global.__PATH_PREFIX__ = originalPathPrefix
mock.teardown()
})
it(`should return a pageData json on success`, async () => {
const prodLoader = new ProdLoader(null, [])
mockPageData(`/mypage`, 200, defaultPayload, true)
const expectation = {
status: `success`,
pagePath: `/mypage`,
payload: defaultPayload,
}
expect(await prodLoader.loadPageDataJson(`/mypage/`)).toEqual(expectation)
expect(prodLoader.pageDataDb.get(`/mypage`)).toEqual(expectation)
expect(xhrCount).toBe(1)
})
it(`should return a pageData json on success without contentType`, async () => {
const prodLoader = new ProdLoader(null, [])
mockPageData(`/mypage`, 200, defaultPayload)
const expectation = {
status: `success`,
pagePath: `/mypage`,
payload: defaultPayload,
}
expect(await prodLoader.loadPageDataJson(`/mypage/`)).toEqual(expectation)
expect(prodLoader.pageDataDb.get(`/mypage`)).toEqual(expectation)
expect(xhrCount).toBe(1)
})
it(`should return a pageData json with an empty compilation hash (gatsby develop)`, async () => {
const prodLoader = new ProdLoader(null, [])
const payload = { ...defaultPayload, webpackCompilationHash: `` }
mockPageData(`/mypage`, 200, payload)
const expectation = {
status: `success`,
pagePath: `/mypage`,
payload,
}
expect(await prodLoader.loadPageDataJson(`/mypage/`)).toEqual(expectation)
expect(prodLoader.pageDataDb.get(`/mypage`)).toEqual(expectation)
expect(xhrCount).toBe(1)
})
it(`should load a 404 page when page-path file is not a gatsby json`, async () => {
const prodLoader = new ProdLoader(null, [])
const payload = { ...defaultPayload, path: `/404.html/` }
mockPageData(`/unknown-page`, 200, { random: `string` }, true)
mockPageData(`/404.html`, 200, payload, true)
const expectation = {
status: `success`,
pagePath: `/404.html`,
notFound: true,
payload,
}
expect(await prodLoader.loadPageDataJson(`/unknown-page/`)).toEqual(
expectation
)
expect(prodLoader.pageDataDb.get(`/unknown-page`)).toEqual(expectation)
expect(xhrCount).toBe(2)
})
it(`should load a 404 page when page-path file is not a json`, async () => {
const prodLoader = new ProdLoader(null, [])
const payload = { ...defaultPayload, path: `/404.html/` }
mockPageData(`/unknown-page`, 200)
mockPageData(`/404.html`, 200, payload, true)
const expectation = {
status: `success`,
pagePath: `/404.html`,
notFound: true,
payload,
}
expect(await prodLoader.loadPageDataJson(`/unknown-page/`)).toEqual(
expectation
)
expect(prodLoader.pageDataDb.get(`/unknown-page`)).toEqual(expectation)
expect(xhrCount).toBe(2)
})
it(`should load a 404 page when path returns a 404`, async () => {
const prodLoader = new ProdLoader(null, [])
const payload = { ...defaultPayload, path: `/404.html/` }
mockPageData(`/unknown-page`, 200)
mockPageData(`/404.html`, 200, payload, true)
const expectation = {
status: `success`,
pagePath: `/404.html`,
notFound: true,
payload,
}
expect(await prodLoader.loadPageDataJson(`/unknown-page/`)).toEqual(
expectation
)
expect(prodLoader.pageDataDb.get(`/unknown-page`)).toEqual(expectation)
expect(xhrCount).toBe(2)
})
it(`should return a failure when status is 404 and 404 page is fetched`, async () => {
const prodLoader = new ProdLoader(null, [])
mockPageData(`/unknown-page`, 404)
mockPageData(`/404.html`, 404)
const expectation = {
status: `failure`,
pagePath: `/404.html`,
notFound: true,
}
expect(await prodLoader.loadPageDataJson(`/unknown-page/`)).toEqual(
expectation
)
expect(prodLoader.pageDataDb.get(`/unknown-page`)).toEqual(expectation)
expect(xhrCount).toBe(2)
})
it(`should return an error when status is 500`, async () => {
const prodLoader = new ProdLoader(null, [])
mockPageData(`/error-page`, 500)
const expectation = {
status: `error`,
pagePath: `/error-page`,
}
expect(await prodLoader.loadPageDataJson(`/error-page/`)).toEqual(
expectation
)
expect(prodLoader.pageDataDb.get(`/error-page`)).toEqual(expectation)
expect(xhrCount).toBe(1)
})
it(`should retry 3 times before returning an error`, async () => {
const prodLoader = new ProdLoader(null, [])
mockPageData(`/blocked-page`, 0)
const expectation = {
status: `error`,
retries: 3,
pagePath: `/blocked-page`,
}
expect(await prodLoader.loadPageDataJson(`/blocked-page/`)).toEqual(
expectation
)
expect(prodLoader.pageDataDb.get(`/blocked-page`)).toEqual(expectation)
expect(xhrCount).toBe(4)
})
it(`should recover if we get 1 failure`, async () => {
const prodLoader = new ProdLoader(null, [])
const payload = {
path: `/blocked-page/`,
}
let xhrCount = 0
mock.get(`/page-data/blocked-page/page-data.json`, (req, res) => {
if (xhrCount++ === 0) {
return res.status(0).body(``)
} else {
res.header(`content-type`, `application/json`)
return res.status(200).body(JSON.stringify(payload))
}
})
const expectation = {
status: `success`,
retries: 1,
pagePath: `/blocked-page`,
payload,
}
expect(await prodLoader.loadPageDataJson(`/blocked-page/`)).toEqual(
expectation
)
expect(prodLoader.pageDataDb.get(`/blocked-page`)).toEqual(expectation)
expect(xhrCount).toBe(2)
})
it(`shouldn't load pageData multiple times`, async () => {
const prodLoader = new ProdLoader(null, [])
mockPageData(`/mypage`, 200, defaultPayload, true)
const expectation = await prodLoader.loadPageDataJson(`/mypage/`)
expect(await prodLoader.loadPageDataJson(`/mypage/`)).toBe(expectation)
expect(xhrCount).toBe(1)
})
})
describe(`loadPage`, () => {
const createAsyncRequires = components => {
return {
components,
}
}
let originalPathPrefix
beforeEach(() => {
originalPathPrefix = global.__PATH_PREFIX__
global.__PATH_PREFIX__ = ``
mock.setup()
mock.get(`/app-data.json`, (req, res) =>
res
.status(200)
.header(`content-type`, `application/json`)
.body(
JSON.stringify({
webpackCompilationHash: `123`,
})
)
)
emitter.emit.mockReset()
})
afterEach(() => {
global.__PATH_PREFIX__ = originalPathPrefix
mock.teardown()
})
it(`should be successful when component can be loaded`, async () => {
const asyncRequires = createAsyncRequires({
chunk: () => Promise.resolve(`instance`),
})
const prodLoader = new ProdLoader(asyncRequires, [])
const pageData = {
path: `/mypage/`,
componentChunkName: `chunk`,
result: {
pageContext: `something something`,
},
}
prodLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: pageData,
status: `success`,
})
)
const expectation = await prodLoader.loadPage(`/mypage/`)
expect(expectation).toMatchSnapshot()
expect(Object.keys(expectation)).toEqual([`component`, `json`, `page`])
expect(prodLoader.pageDb.get(`/mypage`)).toEqual(
expect.objectContaining({
payload: expectation,
status: `success`,
})
)
expect(emitter.emit).toHaveBeenCalledTimes(1)
expect(emitter.emit).toHaveBeenCalledWith(`onPostLoadPageResources`, {
page: expectation,
pageResources: expectation,
})
})
it(`should set not found on finalResult`, async () => {
const asyncRequires = createAsyncRequires({
chunk: () => Promise.resolve(`instance`),
})
const prodLoader = new ProdLoader(asyncRequires, [])
const pageData = {
path: `/mypage/`,
componentChunkName: `chunk`,
}
prodLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: pageData,
status: `success`,
notFound: true,
})
)
await prodLoader.loadPage(`/mypage/`)
const expectation = prodLoader.pageDb.get(`/mypage`)
expect(expectation).toHaveProperty(`notFound`, true)
expect(emitter.emit).toHaveBeenCalledTimes(1)
expect(emitter.emit).toHaveBeenCalledWith(`onPostLoadPageResources`, {
page: expectation.payload,
pageResources: expectation.payload,
})
})
it(`should return an error when component cannot be loaded`, async () => {
const asyncRequires = createAsyncRequires({
chunk: () => Promise.resolve(false),
})
const prodLoader = new ProdLoader(asyncRequires, [])
const pageData = {
path: `/mypage/`,
componentChunkName: `chunk`,
}
prodLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: pageData,
status: `success`,
})
)
await prodLoader.loadPage(`/mypage/`)
const expectation = prodLoader.pageDb.get(`/mypage`)
expect(expectation).toHaveProperty(`status`, `error`)
expect(emitter.emit).toHaveBeenCalledTimes(0)
})
it(`should return an error pageData contains an error`, async () => {
const asyncRequires = createAsyncRequires({
chunk: () => Promise.resolve(`instance`),
})
const prodLoader = new ProdLoader(asyncRequires, [])
const pageData = {
path: `/mypage/`,
componentChunkName: `chunk`,
}
prodLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: pageData,
status: `error`,
})
)
expect(await prodLoader.loadPage(`/mypage/`)).toEqual({ status: `error` })
expect(prodLoader.pageDb.size).toBe(0)
expect(emitter.emit).toHaveBeenCalledTimes(0)
})
it(`should throw an error when 404 cannot be fetched`, async () => {
const prodLoader = new ProdLoader(null, [])
prodLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
status: `failure`,
})
)
try {
await prodLoader.loadPage(`/404.html/`)
} catch (err) {
expect(err.message).toEqual(
expect.stringContaining(`404 page could not be found`)
)
}
expect(prodLoader.pageDb.size).toBe(0)
expect(emitter.emit).toHaveBeenCalledTimes(0)
})
it(`should cache the result of loadPage`, async () => {
const asyncRequires = createAsyncRequires({
chunk: () => Promise.resolve(`instance`),
})
const prodLoader = new ProdLoader(asyncRequires, [])
prodLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: {
componentChunkName: `chunk`,
},
status: `success`,
})
)
const expectation = await prodLoader.loadPage(`/mypage/`)
expect(await prodLoader.loadPage(`/mypage/`)).toBe(expectation)
expect(prodLoader.loadPageDataJson).toHaveBeenCalledTimes(1)
})
it(`should only run 1 network request even when called multiple times`, async () => {
const asyncRequires = createAsyncRequires({
chunk: () => Promise.resolve(`instance`),
})
const prodLoader = new ProdLoader(asyncRequires, [])
prodLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: {
componentChunkName: `chunk`,
},
status: `success`,
})
)
const loadPagePromise = prodLoader.loadPage(`/test-page/`)
expect(prodLoader.inFlightDb.size).toBe(1)
expect(prodLoader.loadPage(`/test-page/`)).toBe(loadPagePromise)
expect(prodLoader.inFlightDb.size).toBe(1)
const expectation = await loadPagePromise
expect(prodLoader.inFlightDb.size).toBe(0)
expect(emitter.emit).toHaveBeenCalledTimes(1)
expect(emitter.emit).toHaveBeenCalledWith(`onPostLoadPageResources`, {
page: expectation,
pageResources: expectation,
})
})
})
describe(`loadPageSync`, () => {
it(`returns page resources when already fetched`, () => {
const prodLoader = new ProdLoader(null, [])
prodLoader.pageDb.set(`/mypage`, { payload: true })
expect(prodLoader.loadPageSync(`/mypage/`)).toBe(true)
})
it(`returns page resources when already fetched`, () => {
const prodLoader = new ProdLoader(null, [])
expect(prodLoader.loadPageSync(`/mypage/`)).toBeUndefined()
})
})
describe(`prefetch`, () => {
const flushPromises = () => new Promise(resolve => setImmediate(resolve))
it(`shouldn't prefetch when shouldPrefetch is false`, () => {
const prodLoader = new ProdLoader(null, [])
prodLoader.shouldPrefetch = jest.fn(() => false)
prodLoader.doPrefetch = jest.fn()
prodLoader.apiRunner = jest.fn()
expect(prodLoader.prefetch(`/mypath/`)).toBe(false)
expect(prodLoader.shouldPrefetch).toHaveBeenCalledWith(`/mypath/`)
expect(prodLoader.apiRunner).not.toHaveBeenCalled()
expect(prodLoader.doPrefetch).not.toHaveBeenCalled()
})
it(`should trigger custom prefetch logic when core is disabled`, () => {
const prodLoader = new ProdLoader(null, [])
prodLoader.shouldPrefetch = jest.fn(() => true)
prodLoader.doPrefetch = jest.fn()
prodLoader.apiRunner = jest.fn()
prodLoader.prefetchDisabled = true
expect(prodLoader.prefetch(`/mypath/`)).toBe(false)
expect(prodLoader.shouldPrefetch).toHaveBeenCalledWith(`/mypath/`)
expect(prodLoader.apiRunner).toHaveBeenCalledWith(`onPrefetchPathname`, {
pathname: `/mypath/`,
})
expect(prodLoader.doPrefetch).not.toHaveBeenCalled()
})
it(`should prefetch when not yet triggered`, async () => {
jest.useFakeTimers()
const prodLoader = new ProdLoader(null, [])
prodLoader.shouldPrefetch = jest.fn(() => true)
prodLoader.apiRunner = jest.fn()
prodLoader.doPrefetch = jest.fn(() => Promise.resolve({}))
expect(prodLoader.prefetch(`/mypath/`)).toBe(true)
// wait for doPrefetchPromise
await flushPromises()
expect(prodLoader.apiRunner).toHaveBeenCalledWith(`onPrefetchPathname`, {
pathname: `/mypath/`,
})
expect(prodLoader.apiRunner).toHaveBeenNthCalledWith(
2,
`onPostPrefetchPathname`,
{
pathname: `/mypath/`,
}
)
})
it(`should only run apis once`, async () => {
const prodLoader = new ProdLoader(null, [])
prodLoader.shouldPrefetch = jest.fn(() => true)
prodLoader.apiRunner = jest.fn()
prodLoader.doPrefetch = jest.fn(() => Promise.resolve({}))
expect(prodLoader.prefetch(`/mypath/`)).toBe(true)
expect(prodLoader.prefetch(`/mypath/`)).toBe(true)
// wait for doPrefetchPromise
await flushPromises()
expect(prodLoader.apiRunner).toHaveBeenCalledTimes(2)
expect(prodLoader.apiRunner).toHaveBeenNthCalledWith(
1,
`onPrefetchPathname`,
expect.anything()
)
expect(prodLoader.apiRunner).toHaveBeenNthCalledWith(
2,
`onPostPrefetchPathname`,
expect.anything()
)
})
})
})