Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby): Gatsby Head API #35980

Merged
merged 69 commits into from
Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
194a690
initial work
marvinjude Jun 8, 2022
5beb48b
add latest
marvinjude Jun 8, 2022
bb56387
implement in page renderer
marvinjude Jun 16, 2022
40549d7
progress
marvinjude Jun 16, 2022
2597e9f
make static query work when used in export
marvinjude Jun 17, 2022
361ad85
cleanup
marvinjude Jun 21, 2022
69b39cc
don't warn for 'head' export
marvinjude Jun 21, 2022
36d296e
cleanup & append head nodes to head at once
marvinjude Jun 21, 2022
5357b5c
add warnings
marvinjude Jun 21, 2022
6b7bb4f
update warning
marvinjude Jun 22, 2022
e03a316
remove use less function
marvinjude Jun 22, 2022
923a08e
add TS Types
marvinjude Jun 23, 2022
eb33292
Merge branch 'master' into poc/metadata-management
marvinjude Jun 23, 2022
f48beff
test(gatsby): Metadata management e2e tests (#35979)
tyhopp Jun 23, 2022
f5d5288
fix test
marvinjude Jun 24, 2022
0751f0d
fix hydration issue
marvinjude Jun 24, 2022
3d7e8af
use react to render into head instead of parsing and appending result…
marvinjude Jun 26, 2022
cbc4324
fix test
marvinjude Jun 26, 2022
70dd9a9
don't invalid head element for HTML
marvinjude Jun 26, 2022
a8c2a9b
remove warning test in prod
marvinjude Jun 27, 2022
e3f2571
do react 18 checks for create root/ render
marvinjude Jun 27, 2022
940f02e
remove all props page test
marvinjude Jun 27, 2022
c7e80d3
wrap head element with server location and props provider
marvinjude Jun 27, 2022
c69941a
Merge branch 'master' into feat/metadata-management
marvinjude Jun 28, 2022
167fb4e
Fix borked yarn.lock
tyhopp Jun 28, 2022
c2acf0a
Update dev-loader test snapshot
tyhopp Jun 28, 2022
5efa140
Update packages/gatsby/cache-dir/dev-loader.js
marvinjude Jun 28, 2022
544e1d5
Update packages/gatsby/cache-dir/loader.js
marvinjude Jun 28, 2022
8dd4fa8
Update packages/gatsby/cache-dir/page-renderer.js
marvinjude Jun 28, 2022
42d72d6
Update packages/gatsby/cache-dir/static-entry.js
marvinjude Jun 28, 2022
4bab3eb
create shared function for render and hydrate
marvinjude Jun 28, 2022
56da964
Fix lint error
tyhopp Jun 29, 2022
f5969a6
Remove unused functions
tyhopp Jun 29, 2022
7169fa0
Ensure params are passed with props in page renderer
tyhopp Jun 29, 2022
6370a5f
Fix eslint test assertion
tyhopp Jun 29, 2022
2fe95f2
remove unnecessary spread operator
marvinjude Jun 29, 2022
f218a32
remove no-op setAttribute addition
marvinjude Jun 29, 2022
a2506d6
feat(head-function-export): Correct props (#36008)
LekoArts Jun 30, 2022
ee3c07a
test(head-function-export): Ensure head elements in SSR'ed HTML (#36006)
tyhopp Jun 30, 2022
664133d
test(head-function-export): More e2e tests (#36014)
tyhopp Jun 30, 2022
8fac6b6
create SSR and browser handler for head export
marvinjude Jul 4, 2022
4a50f2f
fix import and condition
marvinjude Jul 4, 2022
e1ca969
add head handler for DEV SSR
marvinjude Jul 4, 2022
1f80113
test(head-function-export): Test in DEV_SSR mode (#36007)
tyhopp Jul 4, 2022
b9c565e
feat(head-function-export): Hot update for Head export (#36060)
marvinjude Jul 12, 2022
a223b6d
Merge branch 'master' into feat/metadata-management
marvinjude Jul 12, 2022
9635085
fix(head-function-export): Make tsx pages work (#36114)
tyhopp Jul 13, 2022
4bfb485
Merge branch 'master' into feat/metadata-management
marvinjude Jul 13, 2022
fc53d38
Fix unit tests
tyhopp Jul 13, 2022
c01ffc2
cleaup tags on navigation
marvinjude Jul 13, 2022
a1bd8ec
remove test images
marvinjude Jul 13, 2022
ebd9ca4
Update e2e-tests/development-runtime/cypress/integration/head-functio…
marvinjude Jul 13, 2022
def1743
Update packages/gatsby/cache-dir/fast-refresh-overlay/components/runt…
marvinjude Jul 13, 2022
0836cdf
fixes from dot com testing
marvinjude Jul 13, 2022
e55541a
review update
marvinjude Jul 13, 2022
2117f0a
review update
marvinjude Jul 13, 2022
0732354
remove extra formatting
marvinjude Jul 13, 2022
ba1e5e8
review update
marvinjude Jul 13, 2022
0cb42a2
review update
marvinjude Jul 13, 2022
fa454d0
review updates
marvinjude Jul 13, 2022
9a6790d
remove all export query param
marvinjude Jul 13, 2022
8ccb9c5
fix duplicate tags added because we call onHeadRendered twice
marvinjude Jul 13, 2022
8486a93
.waitForRouteChange() everywhere
marvinjude Jul 13, 2022
4451b04
add additional e2e tests
marvinjude Jul 13, 2022
2a668ec
Fix dsg, ssr tests
tyhopp Jul 14, 2022
b649b2d
add Head type shorthand
LekoArts Jul 14, 2022
6f79729
Fix React 18 hydration error due to stringified all props
tyhopp Jul 14, 2022
4722284
update type name
LekoArts Jul 14, 2022
9df3cc2
warn once for same message
marvinjude Jul 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@ jobs:
test_path: integration-tests/functions
test_command: yarn test

integration_tests_head_function_export:
executor: node
steps:
- e2e-test:
test_path: integration-tests/head-function-export
test_command: yarn test

e2e_tests_path-prefix:
<<: *e2e-executor
environment:
Expand Down Expand Up @@ -626,6 +633,8 @@ workflows:
<<: *e2e-test-workflow
- integration_tests_functions:
<<: *e2e-test-workflow
- integration_tests_head_function_export:
<<: *e2e-test-workflow
- integration_tests_gatsby_cli:
requires:
- bootstrap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe(`limited-exports-page-templates`, () => {
it(`should initially not log to console`, () => {
cy.get(`@hmrConsoleLog`).should(
`not.be.calledWithMatch`,
/13:1 {2}warning {2}In page templates only a default export of a valid React component and the named exports of a page query, getServerData or config are allowed./i
/13:1 {2}warning {2}In page templates only a default export of a valid React component and the named exports of a page query, getServerData, Head or config are allowed./i
)
})
it(`should log warning to console for invalid export`, () => {
Expand All @@ -34,11 +34,11 @@ describe(`limited-exports-page-templates`, () => {

cy.get(`@hmrConsoleLog`).should(
`be.calledWithMatch`,
/13:1 {2}warning {2}In page templates only a default export of a valid React component and the named exports of a page query, getServerData or config are allowed./i
/13:1 {2}warning {2}In page templates only a default export of a valid React component and the named exports of a page query, getServerData, Head or config are allowed./i
)
cy.get(`@hmrConsoleLog`).should(
`not.be.calledWithMatch`,
/15:1 {2}warning {2}In page templates only a default export of a valid React component and the named exports of a page query, getServerData or config are allowed./i
/15:1 {2}warning {2}In page templates only a default export of a valid React component and the named exports of a page query, getServerData, Head or config are allowed./i
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import headFunctionExportSharedData from "../../../shared-data/head-function-export.js"

it(`Head function export receive correct props`, () => {
cy.visit(headFunctionExportSharedData.page.correctProps).waitForRouteChange()

const data = {
site: {
siteMetadata: {
headFunctionExport: {
...headFunctionExportSharedData.data.queried,
},
},
},
}
const location = {
pathname: headFunctionExportSharedData.page.correctProps,
}

const pageContext = headFunctionExportSharedData.data.context

cy.getTestElement(`pageContext`)
.invoke(`attr`, `content`)
.should(`equal`, JSON.stringify(pageContext, null, 2))

cy.getTestElement(`location`)
.invoke(`attr`, `content`)
.should(`equal`, JSON.stringify(location, null, 2))

cy.getTestElement(`data`)
.invoke(`attr`, `content`)
.should(`equal`, JSON.stringify(data, null, 2))
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { page, data } from "../../../shared-data/head-function-export.js"

it(`Head function export with FS Route API should work`, () => {
cy.visit(page.fsRouteApi).waitForRouteChange()
cy.getTestElement(`title`).should(`have.text`, data.fsRouteApi.slug)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { page, data } from "../../../shared-data/head-function-export.js"

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.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.static.noscript)
cy.getTestElement(`style`).should(`contain`, data.static.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.static.link)
})

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.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.queried.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.queried.noscript)
cy.getTestElement(`style`).should(`contain`, data.queried.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.queried.link)
})

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.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.static.noscript)
cy.getTestElement(`style`).should(`contain`, data.static.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.static.link)
})

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.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.queried.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.queried.noscript)
cy.getTestElement(`style`).should(`contain`, data.queried.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.queried.link)
})

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.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.dsg.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.dsg.noscript)
cy.getTestElement(`style`).should(`contain`, data.dsg.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.dsg.link)
})

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.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.ssr.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.ssr.noscript)
cy.getTestElement(`style`).should(`contain`, data.ssr.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.ssr.link)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { page, data } from "../../../shared-data/head-function-export.js"

it(`Head function export should not include invalid elements`, () => {
cy.visit(page.invalidElements).waitForRouteChange()

cy.get(`head > h1`).should(`not.exist`)
cy.get(`head > div`).should(`not.exist`)
cy.get(`head > audio`).should(`not.exist`)
cy.get(`head > video`).should(`not.exist`)
cy.get(`head > title`)
.should(`exist`)
.and(`have.text`, data.invalidElements.title)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { page, data } from "../../../shared-data/head-function-export.js"

// No need to test SSR navigation (anchor tags) because it's effectively covered in the html insertion tests

describe(`Head function export behavior during CSR navigation (Gatsby Link)`, () => {
it(`should remove tags not on next page`, () => {
cy.visit(page.basic).waitForRouteChange()

cy.getTestElement(`extra-meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.extraMeta)

cy.getTestElement(`gatsby-link`).click().waitForRouteChange()

cy.get(`[data-testid="extra-meta"]`).should(`not.exist`)
})

it(`should add tags not on next page`, () => {
cy.visit(page.basic).waitForRouteChange()

cy.get(`[data-testid="extra-meta-2"]`).should(`not.exist`)

cy.getTestElement(`gatsby-link`).click().waitForRouteChange()

cy.getTestElement(`extra-meta-2`)
.invoke(`attr`, `content`)
.should(`equal`, data.queried.extraMeta2)
})

it(`should not contain tags from old tags when we navigate to page without Head export`, () => {
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.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.static.noscript)
cy.getTestElement(`style`).should(`contain`, data.static.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.static.link)

cy.getTestElement(`navigate-to-page-without-head-export`)
.click()
.waitForRouteChange()

cy.getTestElement(`base`).should(`not.exist`)
cy.getTestElement(`title`).should(`not.exist`)
cy.getTestElement(`meta`).should(`not.exist`)
cy.getTestElement(`noscript`).should(`not.exist`)
cy.getTestElement(`style`).should(`not.exist`)
cy.getTestElement(`link`).should(`not.exist`)
})

/**
* Technically nodes are always removed from the DOM and new ones added (in other words nodes are not reused with different data),
* but since this is an implementation detail we'll still test the behavior we expect as if we didn't know that.
*/
it(`should change meta tag values`, () => {
// Initial load
cy.visit(page.basic).waitForRouteChange()

// Validate data from initial load
cy.getTestElement(`base`)
.invoke(`attr`, `href`)
.should(`equal`, data.static.base)
cy.getTestElement(`title`).should(`have.text`, data.static.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.static.noscript)
cy.getTestElement(`style`).should(`contain`, data.static.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.static.link)

// Navigate to a different page via Gatsby Link
cy.getTestElement(`gatsby-link`).click()

// Validate data on navigated-to page
cy.getTestElement(`base`)
.invoke(`attr`, `href`)
.should(`equal`, data.queried.base)
cy.getTestElement(`title`).should(`have.text`, data.queried.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.queried.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.queried.noscript)
cy.getTestElement(`style`).should(`contain`, data.queried.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.queried.link)

// Navigate back to original page via Gatsby Link
cy.getTestElement(`gatsby-link`).click().waitForRouteChange()

// Validate data is same as initial load
cy.getTestElement(`base`)
.invoke(`attr`, `href`)
.should(`equal`, data.static.base)
cy.getTestElement(`title`).should(`have.text`, data.static.title)
cy.getTestElement(`meta`)
.invoke(`attr`, `content`)
.should(`equal`, data.static.meta)
cy.getTestElement(`noscript`).should(`have.text`, data.static.noscript)
cy.getTestElement(`style`).should(`contain`, data.static.style)
cy.getTestElement(`link`)
.invoke(`attr`, `href`)
.should(`equal`, data.static.link)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
describe(`Tsx Pages`, () => {
it(`Works with Head export`, () => {
cy.visit(`/head-function-export/tsx-page`)

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

cy.getTestElement(`name`)
.invoke(`attr`, `content`)
.should(`equal`, `TypeScript`)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { VALID_NODE_NAMES } from "gatsby/cache-dir/head/constants"
import { page } from "../../../shared-data/head-function-export.js"

describe(`Head function export should warn`, () => {
beforeEach(() => {
cy.visit(page.warnings, {
onBeforeLoad(win) {
cy.stub(win.console, `warn`).as(`consoleWarn`)
},
}).waitForRouteChange()
})

it(`for elements that belong in the body`, () => {
cy.get(`@consoleWarn`).should(
`be.calledWith`,
`<h1> is not a valid head element. Please use one of the following: ${VALID_NODE_NAMES.join(
`, `
)}`
)
})

it(`for scripts that could use the script component`, () => {
cy.get(`@consoleWarn`).should(
`be.calledWith`,
`Do not add scripts here. Please use the <Script> component in your page template instead. For more info see: https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-script/`
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { data } = require("../../../shared-data/head-function-export")

const TEST_ID = `extra-meta-for-hot-reloading`

describe(`hot reloading Head export`, () => {
beforeEach(() => {
cy.visit(`/head-function-export/basic`).waitForRouteChange()
})

it(`displays placeholder content on launch`, () => {
cy.getTestElement(TEST_ID)
.invoke(`attr`, `content`)
.should(`contain.equal`, "%SOME_EXTRA_META%")
})

it(`hot reloads with new content`, () => {
const text = `New Title by HMR`
cy.exec(
`npm run update -- --file src/pages/head-function-export/basic.js --replacements "SOME_EXTRA_META:${text}"`
)

cy.waitForHmr()

cy.getTestElement(TEST_ID)
.invoke(`attr`, `content`)
.should(`contain.equal`, text)
})
})