Files
discordeno/docs/.cache/__tests__/dev-loader.js
Skillz 4cd76db969 fixes
2020-11-19 23:10:14 -05:00

558 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 DevLoader from "../dev-loader";
import emitter from "../emitter";
jest.mock(`../emitter`);
jest.mock(`../socketIo`, () => {
return {
default: jest.fn(),
getPageData: jest.fn().mockResolvedValue(),
};
});
describe(`Dev loader`, () => {
let originalBasePath;
let originalPathPrefix;
beforeEach(() => {
originalBasePath = global.__BASE_PATH__;
originalPathPrefix = global.__PATH_PREFIX__;
global.__BASE_PATH__ = ``;
global.__PATH_PREFIX__ = ``;
});
afterEach(() => {
global.__BASE_PATH__ = originalBasePath;
global.__PATH_PREFIX__ = originalPathPrefix;
});
describe(`loadPageDataJson`, () => {
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(() => {
xhrCount = 0;
mock.setup();
});
// put the real XHR object back and clear the mocks after each test
afterEach(() => {
mock.teardown();
});
it(`should return a pageData json on success`, async () => {
const devLoader = new DevLoader(null, []);
mockPageData(`/mypage`, 200, defaultPayload, true);
const expectation = {
status: `success`,
pagePath: `/mypage`,
payload: defaultPayload,
};
expect(await devLoader.loadPageDataJson(`/mypage/`)).toEqual(expectation);
expect(devLoader.pageDataDb.get(`/mypage`)).toEqual(expectation);
expect(xhrCount).toBe(1);
});
it(`should return a pageData json on success without contentType`, async () => {
const devLoader = new DevLoader(null, []);
mockPageData(`/mypage`, 200, defaultPayload);
const expectation = {
status: `success`,
pagePath: `/mypage`,
payload: defaultPayload,
};
expect(await devLoader.loadPageDataJson(`/mypage/`)).toEqual(expectation);
expect(devLoader.pageDataDb.get(`/mypage`)).toEqual(expectation);
expect(xhrCount).toBe(1);
});
it(`should return a pageData json with an empty compilation hash (gatsby develop)`, async () => {
const devLoader = new DevLoader(null, []);
const payload = { ...defaultPayload, webpackCompilationHash: `` };
mockPageData(`/mypage`, 200, payload);
const expectation = {
status: `success`,
pagePath: `/mypage`,
payload,
};
expect(await devLoader.loadPageDataJson(`/mypage/`)).toEqual(expectation);
expect(devLoader.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 devLoader = new DevLoader(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 devLoader.loadPageDataJson(`/unknown-page/`)).toEqual(
expectation,
);
expect(devLoader.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 devLoader = new DevLoader(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 devLoader.loadPageDataJson(`/unknown-page/`)).toEqual(
expectation,
);
expect(devLoader.pageDataDb.get(`/unknown-page`)).toEqual(expectation);
expect(xhrCount).toBe(2);
});
it(`should load a 404 page when path returns a 404`, async () => {
const devLoader = new DevLoader(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 devLoader.loadPageDataJson(`/unknown-page/`)).toEqual(
expectation,
);
expect(devLoader.pageDataDb.get(`/unknown-page`)).toEqual(expectation);
expect(xhrCount).toBe(2);
});
it(`should return the dev-404-page when no 404 page can be found`, async () => {
const devLoader = new DevLoader(null, []);
const payload = { ...defaultPayload, path: `/dev-404-page/` };
mockPageData(`/unknown-page`, 404);
mockPageData(`/404.html`, 404);
mockPageData(`/dev-404-page`, 200, payload, true);
const expectation = {
status: `success`,
pagePath: `/dev-404-page`,
notFound: true,
payload,
};
expect(await devLoader.loadPageDataJson(`/unknown-page/`)).toEqual(
expectation,
);
expect(devLoader.pageDataDb.get(`/unknown-page`)).toEqual({
notFound: true,
pagePath: `/404.html`,
status: `error`,
});
expect(xhrCount).toBe(3);
});
it(`should return an error when status is 500`, async () => {
const devLoader = new DevLoader(null, []);
mockPageData(`/error-page`, 500);
const expectation = {
status: `error`,
pagePath: `/error-page`,
};
expect(await devLoader.loadPageDataJson(`/error-page/`)).toEqual({
status: `error`,
pagePath: `/dev-404-page`,
retries: 3,
});
expect(devLoader.pageDataDb.get(`/error-page`)).toEqual(expectation);
expect(xhrCount).toBe(1);
});
it(`should retry 3 times before returning an error`, async () => {
const devLoader = new DevLoader(null, []);
mockPageData(`/blocked-page`, 0);
const expectation = {
status: `error`,
retries: 3,
pagePath: `/blocked-page`,
};
expect(await devLoader.loadPageDataJson(`/blocked-page/`)).toEqual({
status: `error`,
retries: 3,
pagePath: `/dev-404-page`,
});
expect(devLoader.pageDataDb.get(`/blocked-page`)).toEqual(expectation);
expect(xhrCount).toBe(4);
});
it(`should recover if we get 1 failure`, async () => {
const devLoader = new DevLoader(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 devLoader.loadPageDataJson(`/blocked-page/`)).toEqual(
expectation,
);
expect(devLoader.pageDataDb.get(`/blocked-page`)).toEqual(expectation);
expect(xhrCount).toBe(2);
});
it(`shouldn't load pageData multiple times`, async () => {
const devLoader = new DevLoader(null, []);
mockPageData(`/mypage`, 200, defaultPayload, true);
const expectation = await devLoader.loadPageDataJson(`/mypage/`);
expect(await devLoader.loadPageDataJson(`/mypage/`)).toBe(expectation);
expect(xhrCount).toBe(1);
});
});
describe(`loadPage`, () => {
const createSyncRequires = (components) => {
return {
components,
};
};
let originalPathPrefix;
beforeEach(() => {
originalPathPrefix = global.__PATH_PREFIX__;
global.__PATH_PREFIX__ = ``;
mock.setup();
mock.get(`/page-data/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 syncRequires = createSyncRequires({
chunk: `instance`,
});
const devLoader = new DevLoader(syncRequires, []);
const pageData = {
path: `/mypage/`,
componentChunkName: `chunk`,
result: {
pageContext: `something something`,
},
staticQueryHashes: [],
};
devLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: pageData,
status: `success`,
})
);
const expectation = await devLoader.loadPage(`/mypage/`);
expect(expectation).toMatchSnapshot();
expect(Object.keys(expectation)).toEqual([
`component`,
`json`,
`page`,
`staticQueryResults`,
]);
expect(devLoader.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 syncRequires = createSyncRequires({
chunk: `instance`,
});
const devLoader = new DevLoader(syncRequires, []);
const pageData = {
path: `/mypage/`,
componentChunkName: `chunk`,
};
devLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: pageData,
status: `success`,
notFound: true,
})
);
await devLoader.loadPage(`/mypage/`);
const expectation = devLoader.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 syncRequires = createSyncRequires({
chunk: false,
});
const devLoader = new DevLoader(syncRequires, []);
const pageData = {
path: `/mypage/`,
componentChunkName: `chunk`,
staticQueryHashes: [],
};
devLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: pageData,
status: `success`,
})
);
await devLoader.loadPage(`/mypage/`);
const expectation = devLoader.pageDb.get(`/mypage`);
expect(expectation).toHaveProperty(`status`, `error`);
expect(emitter.emit).toHaveBeenCalledTimes(0);
});
it(`should return an error pageData contains an error`, async () => {
const syncRequires = createSyncRequires({
chunk: `instance`,
});
const devLoader = new DevLoader(syncRequires, []);
const pageData = {
path: `/mypage/`,
componentChunkName: `chunk`,
staticQueryHashes: [],
};
devLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: pageData,
status: `error`,
})
);
expect(await devLoader.loadPage(`/mypage/`)).toEqual({ status: `error` });
expect(devLoader.pageDb.size).toBe(0);
expect(emitter.emit).toHaveBeenCalledTimes(0);
});
it(`should log an error when 404 cannot be fetched`, async () => {
const devLoader = new DevLoader(null, []);
const consoleErrorSpy = jest.spyOn(console, `error`);
const defaultXHRMockErrorHandler = XMLHttpRequest.errorCallback;
mock.error(() => {});
await devLoader.loadPage(`/404.html/`);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toHaveBeenCalledWith(
`404 page could not be found. Checkout https://www.gatsbyjs.org/docs/add-404-page/`,
);
mock.error(defaultXHRMockErrorHandler);
consoleErrorSpy.mockRestore();
expect(devLoader.pageDb.size).toBe(0);
expect(emitter.emit).toHaveBeenCalledTimes(0);
});
it(`should cache the result of loadPage`, async () => {
const syncRequires = createSyncRequires({
chunk: `instance`,
});
const devLoader = new DevLoader(syncRequires, []);
devLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
payload: {
componentChunkName: `chunk`,
staticQueryHashes: [],
},
status: `success`,
})
);
const expectation = await devLoader.loadPage(`/mypage/`);
expect(await devLoader.loadPage(`/mypage/`)).toBe(expectation);
expect(devLoader.loadPageDataJson).toHaveBeenCalledTimes(1);
});
});
describe(`loadPageSync`, () => {
it(`returns page resources when already fetched`, () => {
const devLoader = new DevLoader(null, []);
devLoader.pageDb.set(`/mypage`, { payload: true });
expect(devLoader.loadPageSync(`/mypage/`)).toBe(true);
});
it(`returns page resources when already fetched`, () => {
const devLoader = new DevLoader(null, []);
expect(devLoader.loadPageSync(`/mypage/`)).toBeUndefined();
});
});
describe(`prefetch`, () => {
const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
it(`shouldn't prefetch when shouldPrefetch is false`, () => {
const devLoader = new DevLoader(null, []);
devLoader.shouldPrefetch = jest.fn(() => false);
devLoader.doPrefetch = jest.fn();
devLoader.apiRunner = jest.fn();
expect(devLoader.prefetch(`/mypath/`)).toBe(false);
expect(devLoader.shouldPrefetch).toHaveBeenCalledWith(`/mypath/`);
expect(devLoader.apiRunner).not.toHaveBeenCalled();
expect(devLoader.doPrefetch).not.toHaveBeenCalled();
});
it(`should trigger custom prefetch logic when core is disabled`, () => {
const devLoader = new DevLoader(null, []);
devLoader.shouldPrefetch = jest.fn(() => true);
devLoader.doPrefetch = jest.fn();
devLoader.apiRunner = jest.fn();
devLoader.prefetchDisabled = true;
expect(devLoader.prefetch(`/mypath/`)).toBe(false);
expect(devLoader.shouldPrefetch).toHaveBeenCalledWith(`/mypath/`);
expect(devLoader.apiRunner).toHaveBeenCalledWith(`onPrefetchPathname`, {
pathname: `/mypath/`,
});
expect(devLoader.doPrefetch).not.toHaveBeenCalled();
});
it(`should prefetch when not yet triggered`, async () => {
jest.useFakeTimers();
const devLoader = new DevLoader(null, []);
devLoader.shouldPrefetch = jest.fn(() => true);
devLoader.apiRunner = jest.fn();
devLoader.doPrefetch = jest.fn(() => Promise.resolve({}));
expect(devLoader.prefetch(`/mypath/`)).toBe(true);
// wait for doPrefetchPromise
await flushPromises();
expect(devLoader.apiRunner).toHaveBeenCalledWith(`onPrefetchPathname`, {
pathname: `/mypath/`,
});
expect(devLoader.apiRunner).toHaveBeenNthCalledWith(
2,
`onPostPrefetchPathname`,
{
pathname: `/mypath/`,
},
);
});
it(`should only run apis once`, async () => {
const devLoader = new DevLoader(null, []);
devLoader.shouldPrefetch = jest.fn(() => true);
devLoader.apiRunner = jest.fn();
devLoader.doPrefetch = jest.fn(() => Promise.resolve({}));
expect(devLoader.prefetch(`/mypath/`)).toBe(true);
expect(devLoader.prefetch(`/mypath/`)).toBe(true);
// wait for doPrefetchPromise
await flushPromises();
expect(devLoader.apiRunner).toHaveBeenCalledTimes(2);
expect(devLoader.apiRunner).toHaveBeenNthCalledWith(
1,
`onPrefetchPathname`,
expect.anything(),
);
expect(devLoader.apiRunner).toHaveBeenNthCalledWith(
2,
`onPostPrefetchPathname`,
expect.anything(),
);
});
});
});