Skip to content
Permalink
Browse files

fix: runtime network/resource loading resilience fixes + restore e2e …

…tests for it (#18051)

* Blocking resources with before all it statements and then running

* Only making CircleCI to test production runtime for speed up in local development test

* Adding missing line

* Removing requirements

* Fixing typo on added test

* Bringing back old circleCI config

* Updating Cypress tests with some conditionals to handle page redirections and safe resources like 404

* Avoid CircleCI to only test production runtime to check CI behavior

* Adding rest of tests to run

* Adding CircleCI configuration separate

* Fixing for offline plugin so far

* Fixing tests for offline flag

* Adding location and should back dynamic depending on blocked paths

* Adding helper function to assert on visit with click on an element being optional

* Bringing back Circle CI full configuration

* we don't want to assert broken behavior - we want to assert correct one, that just means we need to fix runtime

* wip fixes for runtime

* ugh circleCi  - please run tests

* dummy change

* skip failing unit tests (for now)

* Revert "skip failing unit tests (for now)"

This reverts commit 892a378.

* - make page resource status an "enum"
- get rid of "failure" status (it's not clear what's the difference between failure and error)
- skip some tests (they seem to enter infinite recursion and causing OOM - need to investigate)

Co-authored-by: Blaine Kasten <blainekasten@gmail.com>

* restore error, don't fall into infinite recursion, fix unit tests

* fixup 404 loadPage unit test

* check if ___chunkMapping has componentChunkName

* remove some debug console.logs and add more detailed comments

* uncomment hovering

* remove console.log

* Update packages/gatsby/cache-dir/__tests__/loader.js

Co-Authored-By: Ward Peeters <ward@coding-tech.com>

Co-authored-by: gatsbybot <mathews.kyle+gatsbybot@gmail.com>
Co-authored-by: Michal Piechowiak <misiek.piechowiak@gmail.com>
Co-authored-by: Blaine Kasten <blainekasten@gmail.com>
Co-authored-by: Ward Peeters <ward@coding-tech.com>
  • Loading branch information
5 people committed Mar 12, 2020
1 parent 94f789e commit 030d927cddbdc64f8d93d409a5ada7442d5e62bf
@@ -9,66 +9,83 @@ const waitForAPIOptions = {
timeout: 3000,
}

const runTests = () => {
it(`Loads index`, () => {
function assertOnNavigate(
page = null,
locationAssert,
shouldLocationAssert,
assertShouldBe
) {
if (page) {
cy.getTestElement(page).click()
}
cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
.location(`pathname`)
.should(locationAssert, shouldLocationAssert)
cy.getTestElement(`dom-marker`).contains(assertShouldBe)
}

const runTests = (testNameSuffix = `Unknown scenario`) => {
it(`Loads index - ${testNameSuffix}`, () => {
cy.visit(`/`).waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
cy.getTestElement(`dom-marker`).contains(`index`)
})

it(`Navigates to second page`, () => {
it(`Navigates to second page - ${testNameSuffix}`, () => {
cy.getTestElement(`page2`).click()
cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
.location(`pathname`)
.should(`equal`, `/page-2/`)

cy.location(`pathname`).should(`equal`, `/page-2/`)
cy.getTestElement(`dom-marker`).contains(`page-2`)
})

it(`Navigates to 404 page`, () => {
it(`Navigates to 404 page - ${testNameSuffix}`, () => {
cy.getTestElement(`404`).click()
cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
.location(`pathname`)
.should(`equal`, `/page-3/`)
cy.location(`pathname`).should(`equal`, `/page-3/`)
cy.getTestElement(`dom-marker`).contains(`404`)
})

it(`Loads 404`, () => {
it(`Loads 404 - ${testNameSuffix}`, () => {
cy.visit(`/page-3/`, {
failOnStatusCode: false,
}).waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)

cy.location(`pathname`).should(`equal`, `/page-3/`)
cy.getTestElement(`dom-marker`).contains(`404`)
})

it(`Can navigate from 404 to index`, () => {
it(`Can navigate from 404 to index - ${testNameSuffix}`, () => {
cy.getTestElement(`index`).click()
cy.waitForAPIorTimeout(`onRouteUpdate`, waitForAPIOptions)
.location(`pathname`)
.should(`equal`, `/`)

cy.location(`pathname`).should(`equal`, `/`)
cy.getTestElement(`dom-marker`).contains(`index`)
})
}

describe(`Every resources available`, () => {
it(`Restore resources`, () => {
cy.task(`restoreAllBlockedResources`)
})
runTests()
})
const getTestNameSuffix = (scenario, args) => {
if (scenario === `blockAssetsForChunk` && args.chunk === `app`) {
return `Blocked "app" chunk`
} else if (scenario === `blockAssetsForPage`) {
return `Blocked "${args.filter}" for "${args.pagePath}"`
}

return undefined
}

const runBlockedScenario = (scenario, args) => {
it(`Block resources`, () => {
cy.task(`restoreAllBlockedResources`).then(() => {
cy.task(scenario, args).then(() => {
runTests()
describe(`Block resources`, () => {
before(done => {
cy.task(`restoreAllBlockedResources`).then(() => {
cy.task(scenario, args).then(() => {
done()
})
})
})
})
}

describe(`Missing top level resources`, () => {
describe(`Deleted app chunk assets`, () => {
runBlockedScenario(`blockAssetsForChunk`, { chunk: `app` })
runTests(getTestNameSuffix(scenario, args))
})
})
}

const runSuiteForPage = (label, pagePath) => {
describe(`Missing "${label}" resources`, () => {
@@ -99,6 +116,19 @@ const runSuiteForPage = (label, pagePath) => {
})
}

describe(`Every resources available`, () => {
it(`Restore resources`, () => {
cy.task(`restoreAllBlockedResources`)
})
runTests(`Every resource available`)
})

describe(`Missing top level resources`, () => {
describe(`Deleted app chunk assets`, () => {
runBlockedScenario(`blockAssetsForChunk`, { chunk: `app` })
})
})

runSuiteForPage(`Index`, `/`)
runSuiteForPage(`Page-2`, `/page-2/`)
runSuiteForPage(`404`, `/404.html`)
@@ -12,9 +12,21 @@ jest.mock(`../socketIo`, () => {
})

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 originalBasePath
let originalPathPrefix
let xhrCount

/**
@@ -46,18 +58,12 @@ describe(`Dev loader`, () => {

// 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()
})

@@ -188,7 +194,7 @@ describe(`Dev loader`, () => {
expect(devLoader.pageDataDb.get(`/unknown-page`)).toEqual({
notFound: true,
pagePath: `/404.html`,
status: `failure`,
status: `error`,
})
expect(xhrCount).toBe(3)
})
@@ -202,9 +208,11 @@ describe(`Dev loader`, () => {
status: `error`,
pagePath: `/error-page`,
}
expect(await devLoader.loadPageDataJson(`/error-page/`)).toEqual(
expectation
)
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)
})
@@ -219,9 +227,11 @@ describe(`Dev loader`, () => {
retries: 3,
pagePath: `/blocked-page`,
}
expect(await devLoader.loadPageDataJson(`/blocked-page/`)).toEqual(
expectation
)
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)
})
@@ -402,22 +412,22 @@ describe(`Dev loader`, () => {
expect(emitter.emit).toHaveBeenCalledTimes(0)
})

it(`should throw an error when 404 cannot be fetched`, async () => {
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(() => {})

devLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
status: `failure`,
})
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/`
)

try {
await devLoader.loadPage(`/404.html/`)
} catch (err) {
expect(err.message).toEqual(
expect.stringContaining(`404 page could not be found`)
)
}
mock.error(defaultXHRMockErrorHandler)
consoleErrorSpy.mockRestore()

expect(devLoader.pageDb.size).toBe(0)
expect(emitter.emit).toHaveBeenCalledTimes(0)
})
@@ -6,9 +6,23 @@ import emitter from "../emitter"
jest.mock(`../emitter`)

describe(`Production loader`, () => {
let originalBasePath
let originalPathPrefix

beforeEach(() => {
originalBasePath = global.__BASE_PATH__
originalPathPrefix = global.__PATH_PREFIX__
global.__BASE_PATH__ = ``
global.__PATH_PREFIX__ = ``
})

// put the real XHR object back and clear the mocks after each test
afterEach(() => {
global.__BASE_PATH__ = originalBasePath
global.__PATH_PREFIX__ = originalPathPrefix
})

describe(`loadPageDataJson`, () => {
let originalBasePath
let originalPathPrefix
let xhrCount

/**
@@ -40,18 +54,12 @@ describe(`Production loader`, () => {

// 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()
})

@@ -168,7 +176,7 @@ describe(`Production loader`, () => {
mockPageData(`/404.html`, 404)

const expectation = {
status: `failure`,
status: `error`,
pagePath: `/404.html`,
notFound: true,
}
@@ -259,11 +267,7 @@ describe(`Production loader`, () => {
}
}

let originalPathPrefix

beforeEach(() => {
originalPathPrefix = global.__PATH_PREFIX__
global.__PATH_PREFIX__ = ``
mock.setup()
mock.get(`/page-data/app-data.json`, (req, res) =>
res
@@ -279,7 +283,6 @@ describe(`Production loader`, () => {
})

afterEach(() => {
global.__PATH_PREFIX__ = originalPathPrefix
mock.teardown()
})

@@ -388,22 +391,18 @@ describe(`Production loader`, () => {
expect(emitter.emit).toHaveBeenCalledTimes(0)
})

it(`should throw an error when 404 cannot be fetched`, async () => {
it(`should return an error when 404 cannot be fetched`, async () => {
const prodLoader = new ProdLoader(null, [])

prodLoader.loadPageDataJson = jest.fn(() =>
Promise.resolve({
status: `failure`,
status: `error`,
})
)

try {
await prodLoader.loadPage(`/404.html/`)
} catch (err) {
expect(err.message).toEqual(
expect.stringContaining(`404 page could not be found`)
)
}
expect(await prodLoader.loadPage(`/404.html/`)).toEqual({
status: `error`,
})
expect(prodLoader.pageDb.size).toBe(0)
expect(emitter.emit).toHaveBeenCalledTimes(0)
})
@@ -1,4 +1,4 @@
import { BaseLoader } from "./loader"
import { BaseLoader, PageResourceStatus } from "./loader"
import { findPath } from "./find-path"

class DevLoader extends BaseLoader {
@@ -21,7 +21,13 @@ class DevLoader extends BaseLoader {
return super.loadPageDataJson(rawPath).then(data => {
// when we can't find a proper 404.html we fallback to dev-404-page
// we need to make sure to mark it as not found.
if (data.status === `failure`) {
if (
data.status === PageResourceStatus.Error &&
rawPath !== `/dev-404-page/`
) {
console.error(
`404 page could not be found. Checkout https://www.gatsbyjs.org/docs/add-404-page/`
)
return this.loadPageDataJson(`/dev-404-page/`).then(result =>
Object.assign({}, data, result)
)
@@ -1,5 +1,5 @@
import React from "react"
import loader from "./loader"
import loader, { PageResourceStatus } from "./loader"
import shallowCompare from "shallow-compare"

class EnsureResources extends React.Component {
@@ -28,7 +28,7 @@ class EnsureResources extends React.Component {

loadResources(rawPath) {
loader.loadPage(rawPath).then(pageResources => {
if (pageResources && pageResources.status !== `error`) {
if (pageResources && pageResources.status !== PageResourceStatus.Error) {
this.setState({
location: { ...window.location },
pageResources,

0 comments on commit 030d927

Please sign in to comment.
You can’t perform that action at this time.