Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ executors:
GATSBY_CPU_COUNT: 2
COMPILER_OPTIONS: GATSBY_MAJOR=<< parameters.gatsby_major >>
NODE_NO_WARNINGS: 1
BROWSERSLIST_IGNORE_OLD_DATA: true
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nondeterministic and was messing with some stdout test assertions

# See https://sharp.pixelplumbing.com/install/#custom-libvips
# libvips may or may not be installed in the CircleCI images we're using, but we
# don't want to use it regardless, for consistency (and because it can cause problems)
Expand Down Expand Up @@ -798,6 +799,8 @@ jobs:
no_output_timeout: 15m
environment:
NODE_OPTIONS: --max-old-space-size=2048
NODE_NO_WARNINGS: 1
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was missing from this Windows job (present in the others)

BROWSERSLIST_IGNORE_OLD_DATA: true
GENERATE_JEST_REPORT: "true"
COMPILER_OPTIONS: GATSBY_MAJOR=5
JEST_JUNIT_OUTPUT_DIR: ./test-results/jest-node/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ describe(`queries in packages`, () => {
})

it(`Should extract and run query from gatsby component`, () => {
// Note: in dev this may take a few ms to be populated due to the way the Gatsby Head API is
// implemented
cy.get("head > title").should(
`have.text`,
cy.title().should(
`eq`,
`Testing queries in packages | Gatsby Default Starter`
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ describe(`Head with wrapRootElement`, () => {
cy.getTestElement(`jsonLD`).should(`have.text`, data.static.jsonLD)
})

it(`updates document title`, () => {
cy.visit(
headFunctionExportSharedData.page.headWithWrapRooElement
).waitForRouteChange()

cy.title().should(`eq`, contextValue.posts[0].title)
})

it(`can use context values provided in wrapRootElement`, () => {
cy.visit(
headFunctionExportSharedData.page.headWithWrapRooElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe(`Head function export html insertion`, () => {
.invoke(`attr`, `href`)
.should(`equal`, data.static.base)
cy.getTestElement(`title`).should(`have.text`, data.static.title)
cy.title().should(`eq`, data.static.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.meta)
Expand All @@ -24,6 +25,7 @@ describe(`Head function export html insertion`, () => {
.invoke(`attr`, `href`)
.should(`equal`, data.queried.base)
cy.getTestElement(`title`).should(`have.text`, data.queried.title)
cy.title().should(`eq`, data.queried.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.queried.meta)
Expand All @@ -40,6 +42,7 @@ describe(`Head function export html insertion`, () => {
.invoke(`attr`, `href`)
.should(`equal`, data.static.base)
cy.getTestElement(`title`).should(`have.text`, data.static.title)
cy.title().should(`eq`, data.static.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.meta)
Expand All @@ -57,6 +60,7 @@ describe(`Head function export html insertion`, () => {
.invoke(`attr`, `href`)
.should(`equal`, data.queried.base)
cy.getTestElement(`title`).should(`have.text`, data.queried.title)
cy.title().should(`eq`, data.queried.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.queried.meta)
Expand All @@ -73,6 +77,7 @@ describe(`Head function export html insertion`, () => {
.invoke(`attr`, `href`)
.should(`equal`, data.dsg.base)
cy.getTestElement(`title`).should(`have.text`, data.dsg.title)
cy.title().should(`eq`, data.dsg.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.dsg.meta)
Expand All @@ -89,6 +94,7 @@ describe(`Head function export html insertion`, () => {
.invoke(`attr`, `href`)
.should(`equal`, data.ssr.base)
cy.getTestElement(`title`).should(`have.text`, data.ssr.title)
cy.title().should(`eq`, data.ssr.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.ssr.meta)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ describe(`Tsx Pages`, () => {
cy.visit(`/head-function-export/tsx-page`)

cy.getTestElement(`title`).should(`contain`, `TypeScript`)
cy.title().should(`eq`, `TypeScript`)

cy.getTestElement(`name`)
.invoke(`attr`, `content`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ for (const { descriptor, inlineScriptType } of typesOfInlineScripts) {
})
})

describe(`when navigation occurs`, () => {
// The back/forward navigation tests are flaky in CI, so we retry them a few times
// TODO(serhalp): Investigate. Maybe related to bfcache functionality in modern browsers?
describe(`when navigation occurs`, { retries: { runMode: 5 } }, () => {
it(`should load only once on initial page load`, () => {
cy.visit(page.target).waitForRouteChange()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ describe(`Head with wrapRootElement`, () => {
cy.getTestElement(`jsonLD`).should(`have.text`, data.static.jsonLD)
})

it(`updates document title`, () => {
cy.visit(
headFunctionExportSharedData.page.headWithWrapRooElement
).waitForRouteChange()

cy.title().should(`eq`, contextValue.posts[0].title)
})

it(`can use context values provided in wrapRootElement`, () => {
cy.visit(
headFunctionExportSharedData.page.headWithWrapRooElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ Cypress.on("uncaught:exception", err => {
describe(`Head function export html insertion`, () => {
it(`should work with static data`, () => {
cy.visit(page.basic).waitForRouteChange()

cy.getTestElement(`base`)
.invoke(`attr`, `href`)
.should(`equal`, data.static.base)
cy.getTestElement(`title`).should(`have.text`, data.static.title)
cy.title().should(`eq`, data.static.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.meta)
Expand All @@ -31,10 +33,12 @@ describe(`Head function export html insertion`, () => {

it(`should work with data from a page query`, () => {
cy.visit(page.pageQuery).waitForRouteChange()

cy.getTestElement(`base`)
.invoke(`attr`, `href`)
.should(`equal`, data.queried.base)
cy.getTestElement(`title`).should(`have.text`, data.queried.title)
cy.title().should(`eq`, data.queried.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.queried.meta)
Expand All @@ -47,10 +51,12 @@ describe(`Head function export html insertion`, () => {

it(`should work when a Head function with static data is re-exported from the page`, () => {
cy.visit(page.reExport).waitForRouteChange()

cy.getTestElement(`base`)
.invoke(`attr`, `href`)
.should(`equal`, data.static.base)
cy.getTestElement(`title`).should(`have.text`, data.static.title)
cy.title().should(`eq`, data.static.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.meta)
Expand All @@ -64,10 +70,12 @@ describe(`Head function export html insertion`, () => {

it(`should work when an imported Head component with queried data is used`, () => {
cy.visit(page.staticQuery).waitForRouteChange()

cy.getTestElement(`base`)
.invoke(`attr`, `href`)
.should(`equal`, data.queried.base)
cy.getTestElement(`title`).should(`have.text`, data.queried.title)
cy.title().should(`eq`, data.queried.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.queried.meta)
Expand All @@ -80,10 +88,12 @@ describe(`Head function export html insertion`, () => {

it(`should work in a DSG page (exporting function named config)`, () => {
cy.visit(page.dsg).waitForRouteChange()

cy.getTestElement(`base`)
.invoke(`attr`, `href`)
.should(`equal`, data.dsg.base)
cy.getTestElement(`title`).should(`have.text`, data.dsg.title)
cy.title().should(`eq`, data.dsg.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.dsg.meta)
Expand All @@ -96,10 +106,12 @@ describe(`Head function export html insertion`, () => {

it(`should work in an SSR page (exporting function named getServerData)`, () => {
cy.visit(page.ssr).waitForRouteChange()

cy.getTestElement(`base`)
.invoke(`attr`, `href`)
.should(`equal`, data.ssr.base)
cy.getTestElement(`title`).should(`have.text`, data.ssr.title)
cy.title().should(`eq`, data.ssr.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.ssr.meta)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ describe(`Tsx Pages`, () => {
cy.visit(`/head-function-export/tsx-page`)

cy.getTestElement(`title`).should(`contain`, `TypeScript`)
cy.title().should(`eq`, `TypeScript`)

cy.getTestElement(`name`)
.invoke(`attr`, `content`)
Expand Down
32 changes: 22 additions & 10 deletions packages/gatsby/cache-dir/head/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,15 @@ export function getValidHeadNodesAndAttributes(
const seenIds = new Map()
const validHeadNodes = []

// Filter out non-element nodes before looping since we don't care about them
for (const node of rootNode.childNodes) {
// Filter out non-element nodes before looping since we don't care about them
if (!isElementType(node)) continue

const nodeName =
node.attributes?.getNamedItem(`data-original-tag`)?.value ??
node.nodeName.toLowerCase()
const id = node.attributes?.id?.value

if (!isElementType(node)) continue

if (isValidNodeName(nodeName)) {
// <html> and <body> tags are treated differently, in that we don't render them, we only extract the attributes and apply them separetely
if (nodeName === `html` || nodeName === `body`) {
Expand Down Expand Up @@ -156,9 +156,16 @@ export function getValidHeadNodesAndAttributes(
let clonedNode = node.cloneNode(true)
clonedNode.setAttribute(`data-gatsby-head`, true)

// // This is hack to make script tags work
// This is a hack to make script tags work
// TODO(serhalp): Explain what this is solving
if (clonedNode.nodeName.toLowerCase() === `script`) {
clonedNode = massageScript(clonedNode)
clonedNode = cloneNodeWithoutNS(clonedNode)
}
// Recreate <title> elements in the HTML namespace to ensure `document.title` updates. When
// rendered inside an SVG (React 19 workaround), <title> elements inherit the SVG namespace
// (immutable on DOM nodes) and won't trigger an update of `document.title` when inserted in <head>.
if (clonedNode.nodeName.toLowerCase() === `title`) {
clonedNode = cloneNodeWithoutNS(clonedNode)
}
// Duplicate ids are not allowed in the head, so we need to dedupe them
if (id) {
Expand Down Expand Up @@ -195,14 +202,19 @@ export function getValidHeadNodesAndAttributes(
return { validHeadNodes, htmlAndBodyAttributes }
}

function massageScript(node) {
const script = document.createElement(`script`)
/**
* Recreate an element in the HTML namespace.
*
* This is similar to `cloneNode()` but reinitializes immutable properties like the namespace.
*/
function cloneNodeWithoutNS(node) {
const clonedNode = document.createElement(node.nodeName)
for (const attr of node.attributes) {
script.setAttribute(attr.name, attr.value)
clonedNode.setAttribute(attr.name, attr.value)
}
script.innerHTML = node.innerHTML
clonedNode.innerHTML = node.innerHTML

return script
return clonedNode
}

export function isValidNodeName(nodeName) {
Expand Down
Loading