Skip to content

Commit 85d9c8a

Browse files
committed
feat: support React 19
1 parent 451e425 commit 85d9c8a

File tree

40 files changed

+624
-76
lines changed

40 files changed

+624
-76
lines changed

.circleci/config.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,20 @@ jobs:
568568
- e2e-test:
569569
test_path: e2e-tests/development-runtime
570570

571+
e2e_tests_development_runtime_with_react_19:
572+
parameters:
573+
node_version:
574+
type: string
575+
e2e_executor_suffix:
576+
type: string
577+
executor: e2e<< parameters.e2e_executor_suffix >>
578+
steps:
579+
# We use curl in this test suite but it isn't available in all images we use
580+
- run: apt-get update && apt-get install -y curl
581+
- e2e-test:
582+
test_path: e2e-tests/development-runtime
583+
react_version: "19.1.1"
584+
571585
e2e_tests_production_runtime_with_react_18:
572586
parameters:
573587
node_version:
@@ -580,6 +594,30 @@ jobs:
580594
test_path: e2e-tests/production-runtime
581595
test_command: yarn test && yarn test:offline
582596

597+
e2e_tests_production_runtime_with_react_19:
598+
parameters:
599+
node_version:
600+
type: string
601+
e2e_executor_suffix:
602+
type: string
603+
executor: e2e<< parameters.e2e_executor_suffix >>
604+
steps:
605+
- e2e-test:
606+
test_path: e2e-tests/production-runtime
607+
test_command: yarn test && yarn test:offline
608+
react_version: "19.1.1"
609+
610+
e2e_tests_react_19:
611+
parameters:
612+
node_version:
613+
type: string
614+
e2e_executor_suffix:
615+
type: string
616+
executor: e2e<< parameters.e2e_executor_suffix >>
617+
steps:
618+
- e2e-test:
619+
test_path: e2e-tests/react-19
620+
583621
themes_e2e_tests_development_runtime:
584622
parameters:
585623
node_version:
@@ -902,6 +940,12 @@ workflows:
902940
<<: *e2e-test-workflow
903941
- e2e_tests_production_runtime_with_react_18:
904942
<<: *e2e-test-workflow
943+
- e2e_tests_development_runtime_with_react_19:
944+
<<: *e2e-test-workflow
945+
- e2e_tests_production_runtime_with_react_19:
946+
<<: *e2e-test-workflow
947+
- e2e_tests_react_19:
948+
<<: *e2e-test-workflow
905949
- themes_e2e_tests_production_runtime:
906950
<<: *e2e-test-workflow
907951
- themes_e2e_tests_development_runtime:

docs/contributing/code-contributions.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,24 @@ If you're adding e2e tests and want to run them against local changes:
6464
- Run `gatsby-dev` inside your specific e2e test directory, for example `e2e-tests/themes/development-runtime`.
6565
- While the previous step is running, open a new terminal window and run `yarn test` in that same e2e test directory.
6666

67+
### Testing with different React versions
68+
69+
To test compatibility with different React versions, you can use the `upgrade-react.js` script to update the React version in a test directory, then run the tests:
70+
71+
```shell
72+
REACT_VERSION=19.0.0 TEST_PATH=e2e-tests/production-runtime node ./scripts/upgrade-react.js
73+
cd e2e-tests/production-runtime
74+
yarn test
75+
```
76+
77+
This will update the `package.json` file in the test directory to use the specified React version. This is useful for:
78+
79+
- Testing React 19 compatibility
80+
- Verifying backwards compatibility with older React versions
81+
- Ensuring features work across React version boundaries
82+
83+
The same approach works for any e2e test directory. The CI system automatically runs e2e tests against both React 18 and React 19 to catch compatibility issues.
84+
6785
### Troubleshooting
6886

6987
At any point during the contributing process the Gatsby team would love to help! For help with a specific problem you can [open an Discussion on GitHub](https://github.com/gatsbyjs/gatsby/discussions/categories/help).

e2e-tests/react-19/.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Project dependencies
2+
.cache
3+
node_modules
4+
yarn-error.log
5+
6+
# Build assets
7+
/public
8+
.DS_Store
9+
/assets
10+
11+
# Cypress output
12+
cypress/videos/
13+
cypress/screenshots/

e2e-tests/react-19/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# React 19 Compatibility Test
2+
3+
This directory contains end-to-end tests to verify Gatsby's compatibility with React 19.
4+
5+
## Running the Tests
6+
7+
1. Install dependencies:
8+
9+
```bash
10+
npm install
11+
```
12+
13+
2. Run the tests:
14+
```bash
15+
npm test
16+
```
17+
18+
## Test Cases
19+
20+
1. **Development Server**
21+
22+
- Verifies that the development server starts with React 19
23+
- Tests React 19 state updates and hooks
24+
- Tests error boundaries with React 19
25+
26+
2. **Production Build**
27+
- Verifies that the production build completes successfully with React 19
28+
- Checks for the existence of expected output files
29+
30+
## Dependencies
31+
32+
- React 19.0.0 or later
33+
- React DOM 19.0.0 or later
34+
- Gatsby (linked to local development version)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { defineConfig } from "cypress"
2+
3+
export default defineConfig({
4+
e2e: {
5+
baseUrl: "http://localhost:8000",
6+
supportFile: false,
7+
specPattern: "cypress/integration/**/*.{js,jsx,ts,tsx}",
8+
},
9+
component: {
10+
devServer: {
11+
framework: "create-react-app",
12+
bundler: "webpack",
13+
},
14+
},
15+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/// <reference types="cypress" />
2+
3+
describe("React 19 specific tests", () => {
4+
beforeEach(() => {
5+
cy.visit("/")
6+
})
7+
8+
it("renders the home page", () => {
9+
cy.get("h1").should("contain", "Gatsby + React 19 Test")
10+
})
11+
12+
it("handles React 19 state updates", () => {
13+
cy.get("p").should("contain", "Count: 0")
14+
15+
cy.get('[data-testid="increment"]').click()
16+
cy.get("p").should("contain", "Count: 1")
17+
})
18+
19+
it("calls provided React 19 onCaughtError callback", () => {
20+
cy.get('[data-testid="throwing-component"]').should(
21+
"contain",
22+
"Component rendered successfully"
23+
)
24+
25+
cy.get('[data-testid="trigger-caught-error"]').click()
26+
27+
cy.get('[data-testid="error-boundary-fallback"]').should(
28+
"contain",
29+
"Error boundary caught: Caught error from component"
30+
)
31+
})
32+
33+
it("passes uncaught errors along to React Fast Refresh error overlay", () => {
34+
cy.on("uncaught:exception", err => {
35+
if (err.message.includes("Uncaught error from event handler")) {
36+
return false
37+
}
38+
})
39+
40+
cy.get('[data-testid="trigger-uncaught-error"]').click()
41+
42+
cy.get(`gatsby-fast-refresh`)
43+
.shadow()
44+
.find(`[data-gatsby-overlay="body__error-message"]`)
45+
.should("have.text", "Uncaught error from event handler")
46+
})
47+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "gatsby-cypress"
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Test React 19 error callbacks by updating DOM elements
2+
export const onCaughtError = ({ error, errorInfo }) => {
3+
console.log("[Test] onCaughtError callback called!", error?.message || error)
4+
5+
// Update DOM to show caught error
6+
if (typeof document !== "undefined") {
7+
console.log("[Test] Updating DOM for caught error")
8+
const errorDisplay = document.getElementById("caught-error-display")
9+
if (errorDisplay) {
10+
errorDisplay.textContent = `Caught Error: ${error?.message || error}`
11+
errorDisplay.style.display = "block"
12+
console.log("[Test] Updated caught error display")
13+
} else {
14+
console.log("[Test] Could not find caught-error-display element")
15+
}
16+
17+
// Increment counter
18+
const counter = document.getElementById("caught-error-count")
19+
if (counter) {
20+
const currentCount = parseInt(counter.textContent) || 0
21+
counter.textContent = (currentCount + 1).toString()
22+
console.log("[Test] Updated caught error counter to:", currentCount + 1)
23+
} else {
24+
console.log("[Test] Could not find caught-error-count element")
25+
}
26+
}
27+
}
28+
29+
export const onUncaughtError = ({ error, errorInfo }) => {
30+
console.log(
31+
"[Test] onUncaughtError callback called!",
32+
error?.message || error
33+
)
34+
35+
// Update DOM to show uncaught error
36+
if (typeof document !== "undefined") {
37+
console.log("[Test] Updating DOM for uncaught error")
38+
const errorDisplay = document.getElementById("uncaught-error-display")
39+
if (errorDisplay) {
40+
errorDisplay.textContent = `Uncaught Error: ${error?.message || error}`
41+
errorDisplay.style.display = "block"
42+
console.log("[Test] Updated uncaught error display")
43+
} else {
44+
console.log("[Test] Could not find uncaught-error-display element")
45+
}
46+
47+
// Increment counter
48+
const counter = document.getElementById("uncaught-error-count")
49+
if (counter) {
50+
const currentCount = parseInt(counter.textContent) || 0
51+
counter.textContent = (currentCount + 1).toString()
52+
console.log("[Test] Updated uncaught error counter to:", currentCount + 1)
53+
} else {
54+
console.log("[Test] Could not find uncaught-error-count element")
55+
}
56+
}
57+
}
58+
59+
export const onClientEntry = () => {
60+
console.log("[Test] onClientEntry called")
61+
// Try to access React to check version
62+
try {
63+
const React = require("react")
64+
console.log("[Test] React version from require:", React.version)
65+
} catch (e) {
66+
console.log("[Test] Could not require React:", e)
67+
}
68+
}
69+
70+
// Debug: Log when this file is loaded
71+
console.log("[Test] gatsby-browser.js loaded, React version check...")
72+
if (typeof window !== "undefined" && window.React) {
73+
console.log("[Test] React version:", window.React.version)
74+
} else {
75+
console.log("[Test] React not available on window")
76+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
siteMetadata: {
3+
title: `Gatsby React 19 Test`,
4+
},
5+
plugins: [],
6+
}

e2e-tests/react-19/package.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "gatsby-react-19",
3+
"private": true,
4+
"description": "Test React 19",
5+
"version": "1.0.0",
6+
"author": "Gatsby Team",
7+
"dependencies": {
8+
"gatsby": "5.16.0-next.0-dev-1763762112311",
9+
"react": "^19.0.0",
10+
"react-dom": "^19.0.0"
11+
},
12+
"devDependencies": {
13+
"@testing-library/cypress": "^9.0.0",
14+
"cypress": "^12.0.0",
15+
"gatsby-cypress": "next",
16+
"start-server-and-test": "2.1.1",
17+
"typescript": "^4.9.5"
18+
},
19+
"keywords": [
20+
"gatsby"
21+
],
22+
"license": "MIT",
23+
"scripts": {
24+
"develop": "CYPRESS_SUPPORT=y gatsby develop",
25+
"build": "gatsby build",
26+
"clean": "gatsby clean",
27+
"serve": "gatsby serve",
28+
"test": "start-server-and-test develop http://localhost:8000 'cypress run'",
29+
"test:watch": "start-server-and-test develop http://localhost:8000 'cypress open'"
30+
},
31+
"engines": {
32+
"node": ">=18.0.0"
33+
}
34+
}

0 commit comments

Comments
 (0)