breaking: remove support for React 16 and 17 for Cypress Component Testing. Additionally, remove the cypress/react18 testing harness and merge it upstream with cypress/react (#30590)

* breaking: remove support for react 16 and 17 for component testing and move cypress/react18 upstream into cypress/react [run ci]

* update tests / suggestions from code review [run ci]
This commit is contained in:
Bill Glesias
2024-11-15 16:14:48 -05:00
committed by GitHub
parent 53b24b1be2
commit 0c661b4f35
92 changed files with 633 additions and 2066 deletions

View File

@@ -1,3 +1,3 @@
# Bump this version to force CI to re-create the cache from scratch.
11-07-24-vue-cli-service-removal
11-07-24-react-16-17-removal

View File

@@ -30,7 +30,7 @@ mainBuildFilters: &mainBuildFilters
- /^release\/\d+\.\d+\.\d+$/
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- 'update-v8-snapshot-cache-on-develop'
- 'breaking/remove_vue_cli_service'
- 'breaking/remove_react_16_17_merge_react18_harness_upstream'
- 'publish-binary'
# usually we don't build Mac app - it takes a long time
@@ -42,7 +42,7 @@ macWorkflowFilters: &darwin-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'breaking/remove_vue_cli_service', << pipeline.git.branch >> ]
- equal: [ 'breaking/remove_react_16_17_merge_react18_harness_upstream', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -53,7 +53,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'breaking/remove_vue_cli_service', << pipeline.git.branch >> ]
- equal: [ 'breaking/remove_react_16_17_merge_react18_harness_upstream', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -76,7 +76,7 @@ windowsWorkflowFilters: &windows-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'breaking/remove_vue_cli_service', << pipeline.git.branch >> ]
- equal: [ 'breaking/remove_react_16_17_merge_react18_harness_upstream', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -152,7 +152,7 @@ commands:
name: Set environment variable to determine whether or not to persist artifacts
command: |
echo "Setting SHOULD_PERSIST_ARTIFACTS variable"
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "breaking/remove_vue_cli_service" ]]; then
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "breaking/remove_react_16_17_merge_react18_harness_upstream" ]]; then
export SHOULD_PERSIST_ARTIFACTS=true
fi' >> "$BASH_ENV"
# You must run `setup_should_persist_artifacts` command and be using bash before running this command

View File

@@ -5,7 +5,6 @@
- [`@cypress/eslint-plugin-dev`](https://github.com/cypress-io/cypress/blob/develop/npm/eslint-plugin-dev/CHANGELOG.md)
- [`@cypress/mount-utils`](https://github.com/cypress-io/cypress/blob/develop/npm/mount-utils/CHANGELOG.md)
- [`@cypress/react`](https://github.com/cypress-io/cypress/blob/develop/npm/react/CHANGELOG.md)
- [`@cypress/react18`](https://github.com/cypress-io/cypress/blob/develop/npm/react18/CHANGELOG.md)
- [`@cypress/svelte`](https://github.com/cypress-io/cypress/blob/develop/npm/svelte/CHANGELOG.md)
- [`@cypress/vite-dev-server`](https://github.com/cypress-io/cypress/blob/develop/npm/vite-dev-server/CHANGELOG.md)
- [`@cypress/vue`](https://github.com/cypress-io/cypress/blob/develop/npm/vue/CHANGELOG.md)

View File

@@ -186,7 +186,6 @@ Here is a list of the npm packages in this repository:
| [grep](./npm/grep) | `@cypress/grep` | Filter tests using substring |
| [mount-utils](./npm/mount-utils) | `@cypress/mount-utils` | Common functionality for Vue/React/Angular adapters. |
| [react](./npm/react) | `@cypress/react` | Cypress component testing for React. |
| [react18](./npm/react18) | `@cypress/react18` | Cypress component testing for React 18. |
| [schematic](./npm/cypress-schematic) | `@cypress/schematic` | Official Angular Schematic and Builder for the Angular CLI.|
| [svelte](./npm/svelte) | `@cypress/svelte` | Cypress component testing for Svelte. |
| [vite-dev-server](./npm/vite-dev-server) | `@cypress/vite-dev-server` | Vite powered dev server for Component Testing. |

View File

@@ -11,7 +11,6 @@ package.json
# these are all copied from dist'd builds from the individual libs
/angular
/react
/react18
/vue
/svelte
/mount-utils

View File

@@ -17,11 +17,13 @@ _Released 12/3/2024 (PENDING)_
- It is no longer possible to make a `fetch` or `XMLHttpRequest` request from the `about:blank` page in Electron (i.e. `cy.window().then((win) => win.fetch('<some-url>')`). You must use `cy.request` instead or perform some form of initial navigation via `cy.visit()`. Addressed in [#29547](https://github.com/cypress-io/cypress/pull/30394).
- `@cypress/webpack-dev-server` no longer supports `webpack-dev-server` version 3. Additionally, `@cypress/webpack-dev-server` now ships with `webpack-dev-server` version 5 by default. `webpack-dev-server` version 4 will need to be installed along side Cypress if you are still using `webpack` version 4. Addresses [#29308](https://github.com/cypress-io/cypress/issues/29308), [#30347](https://github.com/cypress-io/cypress/issues/30347), and [#30141](https://github.com/cypress-io/cypress/issues/30141).
- `@cypress/vite-dev-server` no longer supports `vite` versions 2 and 3. Addresses [#29377](https://github.com/cypress-io/cypress/issues/29377) and [#29378](https://github.com/cypress-io/cypress/issues/29378).
- Cypress Component Testing no longer supports `Nuxt.js` version 2. Addresses [#30468](https://github.com/cypress-io/cypress/issues/30468).
- Cypress Component Testing no longer supports `Vue` version 2. Addresses [#30295](https://github.com/cypress-io/cypress/issues/30295).
- Cypress Component Testing no longer supports `React` versions 16 and 17. Addresses [#29607](https://github.com/cypress-io/cypress/issues/29607).
- Cypress Component Testing no longer supports `Next.js` versions 10, 11, 12, and 13. Addresses [#29583](https://github.com/cypress-io/cypress/issues/29583).
- Cypress Component Testing no longer supports `Vue` version 2. Addresses [#30295](https://github.com/cypress-io/cypress/issues/30295).
- Cypress Component Testing no longer supports `Nuxt.js` version 2. Addresses [#30468](https://github.com/cypress-io/cypress/issues/30468).
- Cypress Component Testing no longer supports `Angular` versions 13, 14, 15, and 16. The minimum supported version is now `17.2.0` in order to fully support Angular [signals](https://angular.dev/guide/signals). Addresses [#29582](https://github.com/cypress-io/cypress/issues/29582). Addressed in [#30539](https://github.com/cypress-io/cypress/pull/30539).
- Cypress Component Testing no longer supports `Svelte` version 3. Addresses [#30492](https://github.com/cypress-io/cypress/issues/30492).
- The `cypress/react18` test harness is no longer included in the Cypress binary. Instead, React 18 support is now shipped with `cypress/react`! Addresses [#29607](https://github.com/cypress-io/cypress/issues/29607).
- The `cypress/angular-signals` test harness is no longer included in the Cypress binary. Instead, signals support is now shipped with `cypress/angular`! This requires `rxjs` to be installed as a `peerDependency`. Addresses [#29606](https://github.com/cypress-io/cypress/issues/29606).
- Cypress Component Testing no longer supports `create-react-app`. Addresses [#30028](https://github.com/cypress-io/cypress/issues/30028).
- Cypress Component Testing no longer supports `@vue/cli-service`. Addresses [#30481](https://github.com/cypress-io/cypress/issues/30481).

View File

@@ -71,7 +71,6 @@
"@cypress/grep": "0.0.0-development",
"@cypress/mount-utils": "0.0.0-development",
"@cypress/react": "0.0.0-development",
"@cypress/react18": "0.0.0-development",
"@cypress/sinon-chai": "2.9.1",
"@cypress/svelte": "0.0.0-development",
"@cypress/vue": "0.0.0-development",
@@ -114,7 +113,6 @@
"mount-utils",
"vue",
"react",
"react18",
"angular",
"svelte"
],
@@ -145,11 +143,6 @@
"import": "./react/dist/cypress-react.esm-bundler.js",
"require": "./react/dist/cypress-react.cjs.js"
},
"./react18": {
"types": "./react18/dist/index.d.ts",
"import": "./react18/dist/cypress-react.esm-bundler.js",
"require": "./react18/dist/cypress-react.cjs.js"
},
"./mount-utils": {
"types": "./mount-utils/dist/index.d.ts",
"require": "./mount-utils/dist/index.js"

View File

@@ -9,7 +9,6 @@ shell.set('-e') // any error is fatal
const npmModulesToCopy = [
'mount-utils',
'react',
'react18',
'vue',
'angular',
'svelte',

View File

@@ -20,15 +20,13 @@ describe('Counter with access', () => {
// the window.counter was set from the Counter's constructor
cy.window()
.should('have.property', 'counter')
.its('state')
.should('deep.equal', { count: 0 })
.its('count')
.should('equal', 0)
// let's change the state of the component
cy.window()
.its('counter')
.invoke('setState', {
count: 101,
})
.invoke('setCount', 101)
// the UI should update to reflect the new count
cy.contains('count: 101').should('be.visible')

View File

@@ -1,34 +1,28 @@
import React from 'react'
import React, { useState } from 'react'
export class Counter extends React.Component {
constructor (props) {
super(props)
this.state = {
count: 0,
}
if (window.Cypress) {
// if this component is mounted from inside Cypress Test Runner
// then expose the reference to this instance
// to allow controlling it from tests
console.log(
'set window.counter to this component in window',
window.location.pathname,
)
window.counter = this
} else {
console.log('running outside Cypress')
export function Counter () {
const [count, setCount] = useState(0)
if (window.Cypress) {
// if this component is mounted from inside Cypress Test Runner
// then expose the reference to this instance
// to allow controlling it from tests
console.log(
'set window.counter to this component in window',
window.location.pathname,
)
window.counter = {
count,
setCount,
}
} else {
console.log('running outside Cypress')
}
click = () => {
this.setState({
count: this.state.count + 1,
})
function click () {
setCount(count + 1)
}
render () {
return <p onClick={this.click}>count: {this.state.count}</p>
}
return <p onClick={click}>count: {count}</p>
}

View File

@@ -2,15 +2,13 @@ import React from 'react'
import { ThemeContext } from './context'
import { Toolbar } from './Toolbar.jsx'
export default class App extends React.Component {
render () {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
)
}
export default function App () {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
)
}

View File

@@ -6,7 +6,7 @@ export default function Counter2WithHooks () {
useEffect(() => {
document.title = `You clicked ${count} times`
})
}, [count])
return (
<div>

View File

@@ -1,36 +1,28 @@
import React from 'react'
import React, { useState, useEffect } from 'react'
// import the entire axios module
// later we can use axios.get to make requests
import axios from 'axios'
export class Users extends React.Component {
constructor (props) {
super(props)
this.state = {
users: [],
}
export function Users () {
const [users, setUsers] = useState([])
const getUsers = async () => {
const response = await axios.get('https://jsonplaceholder.cypress.io/users?_limit=3')
setUsers(response.data)
}
componentDidMount () {
axios
.get('https://jsonplaceholder.cypress.io/users?_limit=3')
.then((response) => {
// JSON responses are automatically parsed.
this.setState({
users: response.data,
})
})
}
useEffect(() => {
getUsers()
}, [])
render () {
return (
<div>
{this.state.users.map((user) => (
<li key={user.id}>
<strong>{user.id}</strong> - {user.name}
</li>
))}
</div>
)
}
return (
<div>
{users.map((user) => (
<li key={user.id}>
<strong>{user.id}</strong> - {user.name}
</li>
))}
</div>
)
}

View File

@@ -1,8 +1,8 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from '@cypress/react'
import { Users } from './3-users-api.jsx'
import * as axios from './axios-api'
import { Users } from './2-users-api.jsx'
import * as axios from './axios-api.jsx'
describe('Mocking wrapped Axios', () => {
it('shows real users', () => {

View File

@@ -0,0 +1,28 @@
import React, { useState, useEffect } from 'react'
// import wrapped Axios method
import axiosApi from './axios-api.jsx'
export function Users () {
const [users, setUsers] = useState([])
const getUsers = async () => {
console.log({ axiosApi })
const response = await axiosApi.get('https://jsonplaceholder.cypress.io/users?_limit=3')
setUsers(response.data)
}
useEffect(() => {
getUsers()
}, [])
return (
<div>
{users.map((user) => (
<li key={user.id}>
<strong>{user.id}</strong> - {user.name}
</li>
))}
</div>
)
}

View File

@@ -1,36 +0,0 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from '@cypress/react'
import { Users } from './2-users-named.jsx'
import axios from 'axios'
describe('Mocking Axios named import get', () => {
it('shows real users', () => {
mount(<Users />)
cy.get('li').should('have.length', 3)
})
// TODO: Support stubbing ES Modules
it.skip('mocks get', () => {
cy.stub(axios, 'get')
.resolves({
data: [
{
id: 101,
name: 'Test User',
},
],
})
.as('get')
mount(<Users />)
// only the test user should be shown
cy.get('li').should('have.length', 1)
cy.get('@get').should('have.been.called')
})
it('restores the original method', () => {
mount(<Users />)
cy.get('li').should('have.length', 3)
})
})

View File

@@ -1,33 +0,0 @@
import React from 'react'
// use named import "get" from the module
import { get } from 'axios'
export class Users extends React.Component {
constructor (props) {
super(props)
this.state = {
users: [],
}
}
componentDidMount () {
get('https://jsonplaceholder.cypress.io/users?_limit=3').then((response) => {
// JSON responses are automatically parsed.
this.setState({
users: response.data,
})
})
}
render () {
return (
<div>
{this.state.users.map((user) => (
<li key={user.id}>
<strong>{user.id}</strong> - {user.name}
</li>
))}
</div>
)
}
}

View File

@@ -1,34 +0,0 @@
import React from 'react'
// import wrapped Axios method
import axiosApi from './axios-api'
export class Users extends React.Component {
constructor (props) {
super(props)
this.state = {
users: [],
}
}
componentDidMount () {
console.log({ axiosApi })
axiosApi.get('https://jsonplaceholder.cypress.io/users?_limit=3').then((response) => {
// JSON responses are automatically parsed.
this.setState({
users: response.data,
})
})
}
render () {
return (
<div>
{this.state.users.map((user) => (
<li key={user.id}>
<strong>{user.id}</strong> - {user.name}
</li>
))}
</div>
)
}
}

View File

@@ -1,52 +1,44 @@
// https://medium.com/@pierrehedkvist/renderless-components-in-react-8d663746314c
import React from 'react'
// eslint-disable-next-line no-unused-vars
import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
export default class MouseMovement extends React.Component {
constructor (props) {
console.log('MouseMovement constructor')
super(props)
this.state = {
timer: undefined,
}
export default function MouseMovement ({ onMoved }) {
const [timer, setTimer] = useState(undefined)
this.timeout = this.timeout.bind(this)
this.onMouseMove = this.onMouseMove.bind(this)
}
componentWillMount () {
console.log('MouseMovement componentWillMount')
document.addEventListener('mousemove', this.onMouseMove)
const timer = setTimeout(this.timeout, 4000)
this.setState({ timer })
}
componentWillUnmount () {
console.log('MouseMovement componentWillUnmount')
document.removeEventListener('mousemove', this.onMouseMove)
clearTimeout(this.state.timer)
this.setState({ timer: undefined })
}
onMouseMove () {
function onMouseMove () {
console.log('MouseMovement onMouseMove')
clearTimeout(this.state.timer)
const timer = setTimeout(this.timeout, 4000)
clearTimeout(timer)
const timerNew = setTimeout(timeout, 4000)
this.setState({ timer })
this.props.onMoved(true)
setTimer(timerNew)
onMoved(true)
}
timeout () {
function timeout () {
console.log('timeout')
clearTimeout(this.state.timer)
this.props.onMoved(false)
clearTimeout(timer)
onMoved(false)
}
render () {
return null
}
useEffect(() => {
// Anything in here is fired on component mount.
console.log('MouseMovement componentWillMount')
document.addEventListener('mousemove', onMouseMove)
const timerNew = setTimeout(timeout, 4000)
setTimer(timerNew)
return () => {
// Anything in here is fired on component unmount.
console.log('MouseMovement componentWillUnmount')
document.removeEventListener('mousemove', onMouseMove)
clearTimeout(timer)
setTimer(undefined)
}
}, [])
return null
}
MouseMovement.propTypes = {

View File

@@ -5,14 +5,6 @@ import MouseMovement from './mouse-movement'
describe('Renderless component', () => {
it('works', () => {
// let's also spy on "console.log" calls
// to make sure the entire sequence of calls happens
cy.window()
.its('console')
.then((console) => {
cy.spy(console, 'log').as('log')
})
const onMoved = cy.stub()
mount(<MouseMovement onMoved={onMoved} />)
@@ -23,27 +15,7 @@ describe('Renderless component', () => {
expect(onMoved).to.have.been.calledWith(true)
})
// mount something else to trigger unmount
// mount something else to trigger unmount and stop log flow
mount(<div>Test Component</div>)
cy.get('@log')
.its('callCount')
.should('equal', 4)
cy.get('@log')
.invoke('getCalls')
.then((calls) => {
return calls.map((call) => {
console.log('one', call.args[0])
return call.args[0]
})
})
.should('deep.equal', [
'MouseMovement constructor',
'MouseMovement componentWillMount',
'MouseMovement onMouseMove',
'MouseMovement componentWillUnmount',
])
})
})

View File

@@ -1,35 +1,31 @@
import React, { Component } from 'react'
import React, { useState, useEffect } from 'react'
import './App.css'
import logo from './logo.svg'
import LoadingIndicator from './LoadingIndicator'
class App extends Component {
state = {
isLoading: true,
}
function App () {
const [isLoading, setIsLoading] = useState(true)
componentDidMount () {
this._timer = setTimeout(() => this.setState({ isLoading: false }), 2000)
}
useEffect(() => {
const _timer = setTimeout(() => setIsLoading(false), 2000)
componentWillUnmount () {
clearTimeout(this._timer)
}
return () => {
clearTimeout(_timer)
}
}, [])
render () {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<pre>isLoading: {String(this.state.isLoading)}</pre>
<LoadingIndicator isLoading={this.state.isLoading}>
<div>ahoy!</div>
</LoadingIndicator>
</div>
)
}
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<pre>isLoading: {String(isLoading)}</pre>
<LoadingIndicator isLoading={isLoading}>
<div>ahoy!</div>
</LoadingIndicator>
</div>
)
}
export default App

View File

@@ -1,37 +1,26 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import React, { useState, useEffect } from 'react'
export default class LoadingIndicator extends Component {
static propTypes = {
isLoading: PropTypes.bool.isRequired,
}
export default function LoadingIndicator ({ isLoading, children }) {
const [isPastDelay, setIsPastDelay] = useState(false)
state = {
isPastDelay: false,
}
componentDidMount () {
console.log('component did mount')
this._delayTimer = setTimeout(() => {
useEffect(() => {
const _delayTimer = setTimeout(() => {
console.log('2000ms passed')
this.setState({ isPastDelay: true })
setIsPastDelay(true)
}, 2000)
}
componentWillUnmount () {
console.log('componentWillUnmount')
clearTimeout(this._delayTimer)
}
return () => {
clearTimeout(_delayTimer)
}
}, [])
render () {
if (this.props.isLoading) {
if (!this.state.isPastDelay) {
return null
}
return <div>loading...</div>
if (isLoading) {
if (!isPastDelay) {
return null
}
return this.props.children
return <div>loading...</div>
}
return children
}

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react'
// Class components will be removed in a future release of React 18+. Until then, this example will serve as a class component example
export default class Card extends Component {
componentDidMount () {
this._timeoutID = setTimeout(() => {

View File

@@ -1,16 +1,14 @@
import React from 'react'
export default class ShoppingList extends React.Component {
render () {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
)
}
export default function ShoppingList ({ name }) {
return (
<div className="shopping-list">
<h1>Shopping List for {name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
)
}

View File

@@ -1,27 +1,17 @@
/// <reference types="cypress" />
import React from 'react'
import React, { useState } from 'react'
import { mount } from '@cypress/react'
import './tic-tac-toe.css'
// let's put React component right in the spec file
class Square extends React.Component {
constructor (props) {
super(props)
this.state = {
value: null,
}
}
export default function Square ({ value: valueAsProp }) {
const [valueAsState, setValueAsState] = useState(null)
render () {
return (
<button
className="square"
onClick={() => this.setState({ value: this.props.value })}
>
{this.state.value}
</button>
)
}
return (
<button className="square" onClick={() => setValueAsState(valueAsProp)}>
{valueAsState}
</button>
)
}
describe('Square', () => {

View File

@@ -3,7 +3,7 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from '@cypress/react'
import { Game } from './tic-tac-toe.jsx'
import Game from './tic-tac-toe.jsx'
import './tic-tac-toe.css'
describe('Tic Tac Toe', () => {

View File

@@ -1,6 +1,105 @@
// entire game from the tutorial inside the spec for simplicity
// the code taken from https://codepen.io/gaearon/pen/LyyXgK
import React from 'react'
import React, { useState } from 'react'
function Square ({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
)
}
export function Board ({ xIsNext, squares, onPlay }) {
function handleClick (i) {
if (calculateWinner(squares) || squares[i]) {
return
}
const nextSquares = squares.slice()
if (xIsNext) {
nextSquares[i] = 'X'
} else {
nextSquares[i] = 'O'
}
onPlay(nextSquares)
}
const winner = calculateWinner(squares)
let status
if (winner) {
status = `Winner: ${ winner}`
} else {
status = `Next player: ${ xIsNext ? 'X' : 'O'}`
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
)
}
export default function Game () {
const [history, setHistory] = useState([Array(9).fill(null)])
const [currentMove, setCurrentMove] = useState(0)
const xIsNext = currentMove % 2 === 0
const currentSquares = history[currentMove]
function handlePlay (nextSquares) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]
setHistory(nextHistory)
setCurrentMove(nextHistory.length - 1)
}
function jumpTo (nextMove) {
setCurrentMove(nextMove)
}
const moves = history.map((squares, move) => {
let description
if (move > 0) {
description = `Go to move #${ move}`
} else {
description = 'Go to game start'
}
return (
<li key={move}>
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
)
})
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{moves}</ol>
</div>
</div>
)
}
export function calculateWinner (squares) {
const lines = [
@@ -24,92 +123,3 @@ export function calculateWinner (squares) {
return null
}
function Square (props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
)
}
export class Board extends React.Component {
constructor (props) {
super(props)
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
}
}
handleClick (i) {
const squares = this.state.squares.slice()
if (calculateWinner(squares) || squares[i]) {
return
}
squares[i] = this.state.xIsNext ? 'X' : 'O'
this.setState({
squares,
xIsNext: !this.state.xIsNext,
})
}
renderSquare (i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
)
}
render () {
const winner = calculateWinner(this.state.squares)
let status
if (winner) {
status = `Winner: ${winner}`
} else {
status = `Next player: ${this.state.xIsNext ? 'X' : 'O'}`
}
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
)
}
}
export class Game extends React.Component {
render () {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
)
}
}

View File

@@ -1,22 +1,20 @@
import React from 'react'
import './Button.css'
export class Button extends React.Component {
handleClick () {
this.props.clickHandler(this.props.name)
export function Button ({ name, orange, wide, clickHandler }) {
const className = [
'component-button',
orange ? 'orange' : '',
wide ? 'wide' : '',
]
function handleClick () {
clickHandler(name)
}
render () {
const className = [
'component-button',
this.props.orange ? 'orange' : '',
this.props.wide ? 'wide' : '',
]
return (
<div className={className.join(' ').trim()}>
<button onClick={this.handleClick.bind(this)}>{this.props.name}</button>
</div>
)
}
return (
<div className={className.join(' ').trim()}>
<button onClick={handleClick.bind(this)}>{name}</button>
</div>
)
}

View File

@@ -1,98 +0,0 @@
# Enzyme examples
This folder shows several examples from [Enzyme docs](https://enzymejs.github.io/enzyme/).
In general if you are migrating from Enzyme to `@cypress/react`:
- there is no shallow mounting, only the full mounting. Thus `@cypress/react` has `mount` which is similar to the Enzyme's `render`. It renders the full HTML and CSS output of your component.
- you can mock [children components](https://github.com/bahmutov/cypress-react-unit-test/tree/main/cypress/component/advanced/mocking-component) if you want to avoid running "expensive" components during tests
- the test is running as a "mini" web application. Thus if you want to set a context around component, then set the [context around the component](https://github.com/bahmutov/cypress-react-unit-test/tree/main/cypress/component/advanced/context)
## setState
If you want to change the component's internal state, use the component reference. You can get it by using the special property `ref` when mounting.
```js
// get the component reference using "ref" prop
// and place it into the object for Cypress to "wait" for it
let c = {}
mount(<Foo id="foo" foo="initial" ref={i => (c.instance = i)} />)
cy.wrap(c)
.its('instance')
.invoke('setState', { count: 10 })
```
See [state-spec.js](state-spec.js) file.
## setProps
There is no direct implementation of `setProps`. If you want to see how the component behaves with different props:
```js
it('mounts component with new props', () => {
mount(<Foo id="foo" foo="initial" />)
cy.contains('initial').should('be.visible')
mount(<Foo id="foo" foo="second" />)
cy.contains('second').should('be.visible')
})
```
If you want to reuse properties, you can even clone the component
```js
it('mounts cloned component', () => {
const cmp = <Foo id="foo" foo="initial" />
mount(cmp)
cy.contains('initial').should('be.visible')
const cloned = Cypress._.cloneDeep(cmp)
// change a property, leaving the rest unchanged
cloned.props.foo = 'second'
mount(cloned)
cy.contains('.foo', 'second').should('be.visible')
})
```
See [props-spec.js](props-spec.js) file.
## context
Enzyme's `mount` method allows passing the [React context](https://reactjs.org/docs/context.html) as the second argument to the JSX component like `SimpleComponent` below.
```js
function SimpleComponent(props, context) {
const { name } = context
return <div>{name || 'not set'}</div>
}
```
Since the above syntax is [deprecated](https://reactjs.org/docs/legacy-context.html), `@cypress/react` does not support it. Instead use `createContext` and `Context.Provider` to surround the mounted component, just like you would do in a regular application code.
```js
mount(
<SimpleContext.Provider value={{ name: 'test context' }}>
<SimpleComponent />
</SimpleContext.Provider>,
)
```
Instead of setting a new context, mount the same component but surround it with a different context provider
```js
const cmp = <SimpleComponent id="0x123" />
mount(
<SimpleContext.Provider value={{ name: 'first context' }}>
{cmp}
</SimpleContext.Provider>,
)
// same component, different provider
mount(
<SimpleContext.Provider value={{ name: 'second context' }}>
{cmp}
</SimpleContext.Provider>,
)
```
See [context-spec.js](context-spec.js) for more examples.

View File

@@ -1,53 +0,0 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from '@cypress/react'
import { SimpleContext } from './simple-context'
import { SimpleComponent } from './simple-component.jsx'
// testing components that use Context React API
// https://reactjs.org/docs/context.html
describe('Enzyme', () => {
context('setContext', () => {
it('does not provide the context', () => {
mount(<SimpleComponent />)
cy.contains('context not set').should('be.visible')
})
it('provides the context', () => {
// surround the component with the real provider but
// set the value prop to whatever the test requires
mount(
<SimpleContext.Provider value={{ name: 'test context' }}>
<SimpleComponent />
</SimpleContext.Provider>,
)
cy.contains('test context').should('be.visible')
})
it('mounts new context', () => {
// instead of setting the context from the test
// just mount the component again with a different provider around it
const cmp = <SimpleComponent id="0x123" />
mount(
<SimpleContext.Provider value={{ name: 'first context' }}>
{cmp}
</SimpleContext.Provider>,
)
cy.contains('first context').should('be.visible')
cy.contains('.id', '0x123').should('be.visible')
// same component, different provider
mount(
<SimpleContext.Provider value={{ name: 'second context' }}>
{cmp}
</SimpleContext.Provider>,
)
cy.contains('second context').should('be.visible')
cy.contains('.id', '0x123').should('be.visible')
})
})
})

View File

@@ -1,82 +0,0 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from '@cypress/react'
class Foo extends React.Component {
constructor (props) {
super(props)
this.state = {
count: 0,
}
}
componentDidMount () {
console.log('componentDidMount called')
}
componentDidUpdate () {
console.log('componentDidUpdate called')
}
render () {
const { id, foo } = this.props
return (
<div className={id}>
{foo} count {this.state.count}
</div>
)
}
}
describe('Enzyme', () => {
// example test copied from
// https://github.com/enzymejs/enzyme/blob/master/packages/enzyme-test-suite/test/shared/methods/setProps.jsx
context('setProps', () => {
it('gets props from the component', () => {
mount(<Foo id="foo" foo="initial" />).as('Foo')
cy.contains('initial').should('be.visible')
cy.get('@Foo')
.its('component')
.its('props')
.then((props) => {
console.log('current props', props)
expect(props).to.deep.equal({
id: 'foo',
foo: 'initial',
})
// you can get current props of the component
// but not change them - they are read-only
expect(() => {
props.foo = 'change 1'
}).to.throw()
})
})
it('mounts component with new props', () => {
mount(<Foo id="foo" foo="initial" />)
cy.contains('initial').should('be.visible')
mount(<Foo id="foo" foo="second" />)
cy.contains('second').should('be.visible')
})
it('mounts cloned component', () => {
const cmp = <Foo id="foo" foo="initial" />
mount(cmp)
cy.contains('initial').should('be.visible')
const cloned = Cypress._.cloneDeep(cmp)
// change a property, leaving the rest unchanged
cloned.props.foo = 'second'
mount(cloned)
cy.contains('.foo', 'second').should('be.visible')
})
})
})

View File

@@ -1,24 +0,0 @@
import React from 'react'
import { SimpleContext } from './simple-context'
export class SimpleComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
id: props.id || 'unknown id',
}
}
render () {
console.log('context %o', this.context)
return (
<>
<div>{this.context.name || 'context not set'}</div>
<div className="id">{this.state.id}</div>
</>
)
}
}
SimpleComponent.contextType = SimpleContext

View File

@@ -1,4 +0,0 @@
// https://reactjs.org/docs/context.html
import { createContext } from 'react'
export const SimpleContext = createContext({ name: '' })

View File

@@ -1,60 +0,0 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from '@cypress/react'
class Foo extends React.Component {
constructor (props) {
super(props)
this.state = {
count: 0,
}
}
componentDidMount () {
console.log('componentDidMount called')
}
componentDidUpdate () {
console.log('componentDidUpdate called')
}
render () {
const { id, foo } = this.props
return (
<div className={id}>
{foo} count {this.state.count}
</div>
)
}
}
describe('Enzyme', () => {
context('setState', () => {
it('sets component state', () => {
// get the component reference using "ref" prop
// and place it into the object for Cypress to "wait" for it
let c = {}
mount(<Foo id="foo" foo="initial" ref={(i) => (c.instance = i)} />)
cy.contains('initial').should('be.visible')
cy.log('**check state**')
cy.wrap(c)
.its('instance.state')
.should('deep.equal', { count: 0 })
cy.log('**setState**')
cy.wrap(c)
.its('instance')
.invoke('setState', { count: 10 })
cy.wrap(c)
.its('instance.state')
.should('deep.equal', { count: 10 })
cy.contains('initial count 10')
})
})
})

View File

@@ -1,34 +1,26 @@
import React from 'react'
import React, { useState, useEffect } from 'react'
import axios from 'axios'
export class Users extends React.Component {
constructor (props) {
super(props)
this.state = {
users: [],
}
export function Users () {
const [users, setUsers] = useState([])
const getUsers = async () => {
const response = await axios.get('https://jsonplaceholder.cypress.io/users?_limit=3')
setUsers(response.data)
}
componentDidMount () {
axios
.get('https://jsonplaceholder.cypress.io/users?_limit=3')
.then((response) => {
// JSON responses are automatically parsed.
this.setState({
users: response.data,
})
})
}
useEffect(() => {
getUsers()
}, [])
render () {
return (
<div>
{this.state.users.map((user) => (
<li key={user.id}>
<strong>{user.id}</strong> - {user.name}
</li>
))}
</div>
)
}
return (
<div>
{users.map((user) => (
<li key={user.id}>
<strong>{user.id}</strong> - {user.name}
</li>
))}
</div>
)
}

View File

@@ -1,34 +1,26 @@
import React from 'react'
import React, { useState, useEffect } from 'react'
export class Users extends React.Component {
constructor (props) {
super(props)
this.state = {
users: [],
}
export function Users () {
const [users, setUsers] = useState([])
const getUsers = async () => {
const response = await fetch('https://jsonplaceholder.cypress.io/users?_limit=3')
const list = await response.json()
setUsers(list)
}
componentDidMount () {
fetch('https://jsonplaceholder.cypress.io/users?_limit=3')
.then((response) => {
return response.json()
})
.then((list) => {
this.setState({
users: list,
})
})
}
useEffect(() => {
getUsers()
}, [])
render () {
return (
<div>
{this.state.users.map((user) => (
<li key={user.id}>
<strong>{user.id}</strong> - {user.name}
</li>
))}
</div>
)
}
return (
<div>
{users.map((user) => (
<li key={user.id}>
<strong>{user.id}</strong> - {user.name}
</li>
))}
</div>
)
}

View File

@@ -5,11 +5,11 @@ import { mount } from '@cypress/react'
import './tic-tac-toe.css'
// for now need a constructor, otherwise getting "Weak map" key
const BoardWrap = ({ squares, onClick }) => {
const BoardWrap = ({ squares, onPlay }) => {
return (
<div className="game">
<div className="game-board">
<Board squares={squares} onClick={onClick} />
<Board squares={squares} onPlay={onPlay} />
</div>
</div>
)
@@ -21,16 +21,16 @@ beforeEach(() => {
it('renders empty Board', () => {
const squares = Array(9).fill(null)
const onClick = cy.stub()
const onPlay = cy.stub()
mount(<BoardWrap squares={squares} onClick={onClick} />)
mount(<BoardWrap squares={squares} onPlay={onPlay} />)
cy.get('.board-row')
.eq(0)
.find('.square')
.eq(0)
.click()
.then(() => {
expect(onClick).to.have.been.calledWith(0)
expect(onPlay).to.have.been.called
})
})
@@ -55,7 +55,7 @@ it('renders Board with a few squares filled', () => {
it('plays the game', () => {
mount(<Game />)
cy.contains('.game-info', 'Next player: X').should('be.visible')
cy.contains('.game-board > .status', 'Next player: X').should('be.visible')
cy.get('.board-row')
.eq(0)
.find('.square')
@@ -87,7 +87,7 @@ it('plays the game', () => {
.eq(2)
.click()
cy.contains('.game-info', 'Winner: X').should('be.visible')
cy.contains('.game-board > .status', 'Winner: X').should('be.visible')
// history of moves
cy.get('ol li')
.should('have.length', 6)

View File

@@ -1,130 +1,106 @@
// from https://codepen.io/gaearon/pen/gWWZgR
import React from 'react'
import React, { useState } from 'react'
function Square (props) {
function Square ({ value, onSquareClick }) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
<button className="square" onClick={onSquareClick}>
{value}
</button>
)
}
export class Board extends React.Component {
renderSquare (i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
)
}
render () {
return (
<div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
)
}
}
export default class Game extends React.Component {
constructor (props) {
super(props)
this.state = {
history: [
{
squares: Array(9).fill(null),
},
],
stepNumber: 0,
xIsNext: true,
}
}
handleClick (i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1)
const current = history[history.length - 1]
const squares = current.squares.slice()
export function Board ({ xIsNext, squares, onPlay }) {
function handleClick (i) {
if (calculateWinner(squares) || squares[i]) {
return
}
squares[i] = this.state.xIsNext ? 'X' : 'O'
this.setState({
history: history.concat([
{
squares,
},
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext,
})
}
const nextSquares = squares.slice()
jumpTo (step) {
this.setState({
stepNumber: step,
xIsNext: step % 2 === 0,
})
}
render () {
const history = this.state.history
const current = history[this.state.stepNumber]
const winner = calculateWinner(current.squares)
const moves = history.map((step, move) => {
const desc = move ? `Go to move #${move}` : 'Go to game start'
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
)
})
let status
if (winner) {
status = `Winner: ${winner}`
if (xIsNext) {
nextSquares[i] = 'X'
} else {
status = `Next player: ${this.state.xIsNext ? 'X' : 'O'}`
nextSquares[i] = 'O'
}
onPlay(nextSquares)
}
const winner = calculateWinner(squares)
let status
if (winner) {
status = `Winner: ${ winner}`
} else {
status = `Next player: ${ xIsNext ? 'X' : 'O'}`
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
)
}
export default function Game () {
const [history, setHistory] = useState([Array(9).fill(null)])
const [currentMove, setCurrentMove] = useState(0)
const xIsNext = currentMove % 2 === 0
const currentSquares = history[currentMove]
function handlePlay (nextSquares) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]
setHistory(nextHistory)
setCurrentMove(nextHistory.length - 1)
}
function jumpTo (nextMove) {
setCurrentMove(nextMove)
}
const moves = history.map((squares, move) => {
let description
if (move > 0) {
description = `Go to move #${ move}`
} else {
description = 'Go to game start'
}
return (
<div className="game">
<div className="game-board">
<Board squares={current.squares} onClick={(i) => this.handleClick(i)} />
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
<li key={move}>
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
)
}
})
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{moves}</ol>
</div>
</div>
)
}
// ========================================
// ReactDOM.render(<Game />, document.getElementById('root'))
export function calculateWinner (squares) {
const lines = [
[0, 1, 2],

View File

@@ -1,18 +1,16 @@
import React from 'react'
export default class ShoppingList extends React.Component {
render () {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
)
}
export default function ShoppingList ({ name }) {
return (
<div className="shopping-list">
<h1>Shopping List for {name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
)
}
// Example usage: <ShoppingList name="Mark" />

View File

@@ -1,11 +1,9 @@
import React from 'react'
export default class Square extends React.Component {
render () {
return (
<button className="square" onClick={() => alert('click')}>
{this.props.value}
</button>
)
}
export default function Square ({ value }) {
return (
<button className="square" onClick={() => alert('click')}>
{value}
</button>
)
}

View File

@@ -1,18 +1,9 @@
import React from 'react'
export default class Square extends React.Component {
constructor (props) {
super(props)
this.state = {
value: null,
}
}
render () {
return (
<button className="square" onClick={() => alert('click')}>
{this.props.value}
</button>
)
}
export default function Square ({ value }) {
return (
<button className="square" onClick={() => alert('click')}>
{value || null}
</button>
)
}

View File

@@ -1,18 +1,11 @@
import React from 'react'
import React, { useState } from 'react'
export default class Square extends React.Component {
constructor (props) {
super(props)
this.state = {
value: null,
}
}
export default function Square () {
const [value, setValue] = useState(null)
render () {
return (
<button className="square" onClick={() => this.setState({ value: 'X' })}>
{this.state.value}
</button>
)
}
return (
<button className="square" onClick={() => setValue('X')}>
{value}
</button>
)
}

View File

@@ -1,9 +1,9 @@
import React from 'react'
export default function Square (props) {
export default function Square ({ value, onClick }) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
<button className="square" onClick={onClick}>
{value}
</button>
)
}

View File

@@ -1,7 +1,6 @@
/// <reference types="cypress" />
import React, { useLayoutEffect, useEffect } from 'react'
import ReactDom from 'react-dom'
import { mount, getContainerEl } from '@cypress/react'
import { mount } from '@cypress/react'
it('should not run unmount effect cleanup when rerendering', () => {
const layoutEffectCleanup = cy.stub()
@@ -60,8 +59,8 @@ it('should run unmount effect cleanup when unmounting', () => {
expect(effectCleanup).to.have.been.callCount(0)
})
cy
.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))
// mount something else to trigger an unmount event
cy.mount(<div>Hello </div>)
.then(async () => {
// does not call useEffect in react 17 unmount synchronously.
// @see https://github.com/facebook/react/issues/20263

View File

@@ -1,5 +1,5 @@
/// <reference types="cypress" />
import Comp from './comp.jsx'
import { Comp } from './comp.jsx'
import React from 'react'
import { mount, unmount } from '@cypress/react'
@@ -11,7 +11,6 @@ it('calls callbacks on mount and unmount', () => {
mount(<Comp onMount={onMount} onUnmount={onUnmount} />)
cy.then(() => {
expect(onMount).to.have.been.calledOnce
expect(onUnmount).to.have.not.been.called
})
cy.contains('Component with').should('be.visible')

View File

@@ -1,15 +1,9 @@
import React, { Component } from 'react'
import React, { useEffect } from 'react'
export default class Comp extends Component {
componentDidMount () {
this.props.onMount()
}
export const Comp = ({ onMount }) => {
useEffect(() => {
onMount()
}, [])
componentWillUnmount () {
this.props.onUnmount()
}
render () {
return <div>Component with mount and unmount calls</div>
}
return <div>Component with mount and unmount calls</div>
}

View File

@@ -1,45 +0,0 @@
/// <reference types="cypress" />
import React, { Component } from 'react'
import { getContainerEl } from '@cypress/mount-utils'
import ReactDom from 'react-dom'
import { mount } from '@cypress/react'
class Comp extends Component {
componentWillUnmount () {
// simply calls the prop
this.props.onUnmount()
}
render () {
return <div>My component</div>
}
}
describe('Comp with componentWillUnmount', () => {
it('calls the prop', () => {
mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
// after we have confirmed the component exists let's remove it
// unmount() command is automatically enqueued
cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))
// the component is gone from the DOM
cy.contains('My component').should('not.exist')
// the component has called the prop on unmount
cy.get('@onUnmount').should('have.been.calledOnce')
})
it('can be called using then', () => {
mount(<Comp onUnmount={cy.stub().as('onUnmount')} />)
cy.contains('My component')
// still works, should probably be removed in v5
cy.then(() => ReactDom.unmountComponentAtNode(getContainerEl()))
// the component is gone from the DOM
cy.contains('My component').should('not.exist')
// the component has called the prop on unmount
cy.get('@onUnmount').should('have.been.calledOnce')
})
})

View File

@@ -1,10 +1,7 @@
import React from 'react'
class MyComponent extends React.Component {
state = {}
render () {
return <div>Hello</div>
}
export function MyComponent () {
return <div>Hello</div>
}
export default MyComponent

View File

@@ -17,25 +17,26 @@
},
"devDependencies": {
"@cypress/mount-utils": "0.0.0-development",
"@types/semver": "7.5.0",
"@vitejs/plugin-react": "4.3.0",
"axios": "0.21.2",
"@types/semver": "7.5.8",
"@vitejs/plugin-react": "4.3.3",
"axios": "1.7.7",
"cypress": "0.0.0-development",
"prop-types": "15.7.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-router": "6.10.0",
"react-router-dom": "6.10.0",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router": "6.28.0",
"react-router-dom": "6.28.0",
"semver": "^7.5.3",
"typescript": "~5.4.5",
"vite": "5.2.11",
"vite": "5.4.10",
"vite-plugin-require-transform": "1.0.12"
},
"peerDependencies": {
"@types/react": "^16.9.16 || ^17.0.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"cypress": "*",
"react": "^=16.x || ^=17.x",
"react-dom": "^=16.x || ^=17.x"
"react": "^18",
"react-dom": "^18"
},
"files": [
"dist"
@@ -90,9 +91,6 @@
"nx": {
"targets": {
"build": {
"dependsOn": [
"!@cypress/react18:build"
],
"outputs": [
"{workspaceRoot}/cli/react",
"{projectRoot}/dist"

View File

@@ -1,23 +1,23 @@
import { getContainerEl } from '@cypress/mount-utils'
import React from 'react'
import ReactDOM from 'react-dom'
import major from 'semver/functions/major'
import ReactDOM from 'react-dom/client'
import { getContainerEl } from '@cypress/mount-utils'
import {
makeMountFn,
makeUnmountFn,
} from './createMount'
} from './index'
import type {
MountOptions,
InternalMountOptions,
} from './types'
UnmountArgs,
} from './index'
let lastReactDom: typeof ReactDOM
let root: ReactDOM.Root | null
const cleanup = () => {
if (lastReactDom) {
const root = getContainerEl()
if (root) {
root.unmount()
lastReactDom.unmountComponentAtNode(root)
root = null
return true
}
@@ -27,10 +27,10 @@ const cleanup = () => {
/**
* Mounts a React component into the DOM.
* @param jsx {React.ReactNode} The React component to mount.
* @param options {MountOptions} [options={}] options to pass to the mount function.
* @param rerenderKey {string} [rerenderKey] A key to use to force a rerender.
* @see {@link https://on.cypress.io/mounting-react} for more details.
* @param {import('react').JSX.Element} jsx The React component to mount.
* @param {MountOptions} options Options to pass to the mount function.
* @param {string} rerenderKey A key to use to force a rerender.
*
* @example
* import { mount } from '@cypress/react'
* import { Stepper } from './Stepper'
@@ -40,24 +40,24 @@ const cleanup = () => {
* cy.get('[data-cy=increment]').click()
* cy.get('[data-cy=counter]').should('have.text', '1')
* }
*
* @see {@link https://on.cypress.io/mounting-react} for more details.
*
* @returns {Cypress.Chainable<MountReturn>} The mounted component.
*/
export function mount (jsx: React.ReactNode, options: MountOptions = {}, rerenderKey?: string) {
if (major(React.version) === 18) {
const message = '[cypress/react]: You are using `cypress/react`, which is designed for React <= 17. Consider changing to `cypress/react18`, which is designed for React 18.'
console.error(message)
Cypress.log({ name: 'warning', message })
}
// Remove last mounted component if cy.mount is called more than once in a test
// React by default removes the last component when calling render, but we should remove the root
// to wipe away any state
cleanup()
const internalOptions: InternalMountOptions = {
reactDom: ReactDOM,
render: (reactComponent: ReturnType<typeof React.createElement>, el: HTMLElement, reactDomToUse: typeof ReactDOM) => {
lastReactDom = (reactDomToUse || ReactDOM)
render: (reactComponent: ReturnType<typeof React.createElement>, el: HTMLElement) => {
if (!root) {
root = ReactDOM.createRoot(el)
}
return lastReactDom.render(reactComponent, el)
return root.render(reactComponent)
},
unmount: internalUnmount,
cleanup,
@@ -66,20 +66,14 @@ export function mount (jsx: React.ReactNode, options: MountOptions = {}, rerende
return makeMountFn('mount', jsx, { ReactDom: ReactDOM, ...options }, rerenderKey, internalOptions)
}
/**
* Unmounts the component from the DOM.
* @internal
* @param options - Options for unmounting.
*/
function internalUnmount (options = { log: true }) {
return makeUnmountFn(options)
}
/**
* Removed as of Cypress 11.0.0.
* @see https://on.cypress.io/migration-11-0-0-component-testing-updates
*/
export function unmount (options = { log: true }) {
export function unmount (options: UnmountArgs = { log: true }) {
// @ts-expect-error - undocumented API
Cypress.utils.throwErrByPath('mount.unmount')
}

View File

@@ -8,7 +8,7 @@ export interface UnmountArgs {
export type MountOptions = Partial<MountReactComponentOptions>
export interface MountReactComponentOptions {
ReactDom: typeof import('react-dom')
ReactDom: typeof import('react-dom/client')
/**
* Log the mounting command into Cypress Command Log,
* true by default.
@@ -22,11 +22,11 @@ export interface MountReactComponentOptions {
}
export interface InternalMountOptions {
reactDom: typeof import('react-dom')
reactDom: typeof import('react-dom/client')
render: (
reactComponent: ReturnType<typeof React.createElement>,
el: HTMLElement,
reactDomToUse: typeof import('react-dom')
reactDomToUse: typeof import('react-dom/client')
) => void
unmount: (options: UnmountArgs) => void
cleanup: () => boolean

View File

@@ -1,5 +0,0 @@
**/dist
**/*.d.ts
**/package-lock.json
**/tsconfig.json
**/cypress/fixtures

View File

@@ -1,3 +0,0 @@
module.exports = {
...require('../../.releaserc'),
}

View File

@@ -1,47 +0,0 @@
# [@cypress/react18-v2.0.1](https://github.com/cypress-io/cypress/compare/@cypress/react18-v2.0.0...@cypress/react18-v2.0.1) (2024-06-07)
### Bug Fixes
* update cypress to Typescript 5 ([#29568](https://github.com/cypress-io/cypress/issues/29568)) ([f3b6766](https://github.com/cypress-io/cypress/commit/f3b67666a5db0438594339c379cf27e1fd1e4abc))
# [@cypress/react18-v2.0.0](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.1.1...@cypress/react18-v2.0.0) (2022-11-07)
### Bug Fixes
* remove last mounted component upon subsequent mount calls ([#24470](https://github.com/cypress-io/cypress/issues/24470)) ([f39eb1c](https://github.com/cypress-io/cypress/commit/f39eb1c19e0923bda7ae263168fc6448da942d54))
* remove some CT functions and props ([#24419](https://github.com/cypress-io/cypress/issues/24419)) ([294985f](https://github.com/cypress-io/cypress/commit/294985f8b3e0fa00ed66d25f88c8814603766074))
### BREAKING CHANGES
* remove last mounted component upon subsequent mount calls of mount
# [@cypress/react18-v1.1.1](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.1.0...@cypress/react18-v1.1.1) (2022-10-13)
### Bug Fixes
* cypress/react18 rerender ([#23360](https://github.com/cypress-io/cypress/issues/23360)) ([8b8f20e](https://github.com/cypress-io/cypress/commit/8b8f20eec77d4c0a704aee7f7077dc92dbafb93f))
# [@cypress/react18-v1.1.0](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.0.1...@cypress/react18-v1.1.0) (2022-08-30)
### Features
* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8))
# [@cypress/react18-v1.0.1](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.0.0...@cypress/react18-v1.0.1) (2022-08-15)
### Bug Fixes
* **react18:** unmount component with react18 API ([#23204](https://github.com/cypress-io/cypress/issues/23204)) ([eab950b](https://github.com/cypress-io/cypress/commit/eab950bec013f9caf5836e3fa58670fde25e2684))
# @cypress/react18-v1.0.0 (2022-08-11)
### Features
* React 18 support ([#22876](https://github.com/cypress-io/cypress/issues/22876)) ([f0d3a48](https://github.com/cypress-io/cypress/commit/f0d3a4867907bf6e60468510daa883ccc8dcfb63))

View File

@@ -1,7 +0,0 @@
# @cypress/react18
Mount React 18 components in the open source [Cypress.io](https://www.cypress.io/) test runner
> **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [React Component Testing Docs](https://docs.cypress.io/guides/component-testing/react/overview) for mounting React components. Installing and importing `mount` from `@cypress/react18` should only be done for advanced use-cases.
## [Changelog](./CHANGELOG.md)

View File

@@ -1,71 +0,0 @@
{
"name": "@cypress/react18",
"version": "0.0.0-development",
"description": "Test React components using Cypress",
"main": "dist/cypress-react.cjs.js",
"scripts": {
"build": "rimraf dist && rollup -c rollup.config.mjs",
"postbuild": "node ../../scripts/sync-exported-npm-with-cli.js",
"check-ts": "tsc --noEmit",
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.json, .",
"watch": "yarn build --watch --watch.exclude ./dist/**/*"
},
"devDependencies": {
"@cypress/mount-utils": "0.0.0-development",
"@cypress/react": "0.0.0-development",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.1.1",
"@types/react": "17.0.83",
"@types/react-dom": "17.0.25",
"cypress": "0.0.0-development",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rollup": "3.7.3",
"rollup-plugin-typescript2": "^0.29.0",
"typescript": "~5.4.5"
},
"peerDependencies": {
"@types/react": "^18",
"@types/react-dom": "^18",
"cypress": "*",
"react": "^18",
"react-dom": "^18"
},
"files": [
"dist"
],
"types": "dist/index.d.ts",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/cypress-io/cypress.git"
},
"homepage": "https://github.com/cypress-io/cypress/blob/develop/npm/react18/#readme",
"bugs": "https://github.com/cypress-io/cypress/issues/new?assignees=&labels=npm%3A%20%40cypress%2Freact18&template=1-bug-report.md&title=",
"keywords": [
"react",
"cypress",
"cypress-io",
"test",
"testing"
],
"module": "dist/cypress-react.esm-bundler.js",
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
},
"publishConfig": {
"access": "public"
},
"nx": {
"targets": {
"build": {
"outputs": [
"{workspaceRoot}/cli/react18",
"{projectRoot}/dist"
]
}
}
}
}

View File

@@ -1,3 +0,0 @@
import rollupConfig from '@cypress/react/rollup.config.mjs'
export default rollupConfig

View File

@@ -1,92 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { getContainerEl } from '@cypress/mount-utils'
import {
makeMountFn,
makeUnmountFn,
} from '@cypress/react'
import type {
MountOptions,
MountReturn,
InternalMountOptions,
UnmountArgs,
} from '@cypress/react'
let root: ReactDOM.Root | null
const cleanup = () => {
if (root) {
root.unmount()
root = null
return true
}
return false
}
/**
* Mounts a React component into the DOM.
* @param {import('react').JSX.Element} jsx The React component to mount.
* @param {MountOptions} options Options to pass to the mount function.
* @param {string} rerenderKey A key to use to force a rerender.
*
* @example
* import { mount } from '@cypress/react'
* import { Stepper } from './Stepper'
*
* it('mounts', () => {
* mount(<StepperComponent />)
* cy.get('[data-cy=increment]').click()
* cy.get('[data-cy=counter]').should('have.text', '1')
* }
*
* @see {@link https://on.cypress.io/mounting-react} for more details.
*
* @returns {Cypress.Chainable<MountReturn>} The mounted component.
*/
export function mount (jsx: React.ReactNode, options: MountOptions = {}, rerenderKey?: string) {
// Remove last mounted component if cy.mount is called more than once in a test
// React by default removes the last component when calling render, but we should remove the root
// to wipe away any state
cleanup()
const internalOptions: InternalMountOptions = {
// @ts-expect-error
reactDom: ReactDOM,
render: (reactComponent: ReturnType<typeof React.createElement>, el: HTMLElement) => {
if (!root) {
root = ReactDOM.createRoot(el)
}
return root.render(reactComponent)
},
unmount: internalUnmount,
cleanup,
}
// @ts-expect-error
return makeMountFn('mount', jsx, { ReactDom: ReactDOM, ...options }, rerenderKey, internalOptions)
}
function internalUnmount (options = { log: true }) {
return makeUnmountFn(options)
}
/**
* Removed as of Cypress 11.0.0.
* @see https://on.cypress.io/migration-11-0-0-component-testing-updates
*/
export function unmount (options: UnmountArgs = { log: true }) {
// @ts-expect-error - undocumented API
Cypress.utils.throwErrByPath('mount.unmount')
}
// Re-export this to help with migrating away from `unmount`
export {
getContainerEl,
}
export type {
MountOptions,
MountReturn,
}

View File

@@ -1,22 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"es2015",
"dom"
],
"rootDir": "src",
"outDir": "dist",
"declaration": true,
"strict": true,
"types": [
"cypress"
],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"resolveJsonModule": false
},
"include": ["src/**/*.ts"]
}

View File

@@ -19,7 +19,7 @@ describe('Config options', () => {
await ctx.actions.file.writeFileInProject(
'src/App.cy.jsx', `
import React from 'react'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
export const App = () => {
return (

View File

@@ -10,8 +10,8 @@ Run Cypress with `DEBUG=cypress:vite-plugin-cypress-esm`. You will get logs in t
## Compatibility
| @cypress/vite-plugin-cypress-esm | cypress |
| ------------------------ | ------- |
| >= v1 | >= v12 |
| -------------------------------- | ------- |
| >= v1 | >= v12 |
## Usage
@@ -80,6 +80,14 @@ CypressEsm({
})
```
If using the `@cypress/react` test harness, you may need to ignore the `react-dom/client` module by configuring as such:
```ts
CypressEsm({
ignoreImportList: ['**/react-dom/client']
})
```
## Known Issues
### Import Syntax

View File

@@ -13,12 +13,11 @@ export default defineConfig({
viteConfig: () => {
return {
plugins: [
react({
jsxRuntime: 'classic',
}),
react(),
CypressEsm({
ignoreModuleList: ['**/ignoreModuleList.cy.ts', '*MyAsync*'],
ignoreImportList: ['**/ImmutableModuleB*'],
// For `cypress/react` on react 18+, we need to ignore transforming the react-dom/client library
ignoreImportList: ['**/ImmutableModuleB*', '**/react-dom/client'],
}),
],
}

View File

@@ -19,11 +19,11 @@
"devDependencies": {
"@tanstack/react-query": "4.36.1",
"@types/picomatch": "2.3.0",
"@vitejs/plugin-react": "4.3.0",
"react": "16.8.6",
"react-dom": "16.8.6",
"react-router": "6.10.0",
"react-router-dom": "6.10.0",
"@vitejs/plugin-react": "4.3.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router": "6.28.0",
"react-router-dom": "6.28.0",
"vite": "5.2.11"
},
"files": [

View File

@@ -82,9 +82,6 @@
"nx": {
"targets": {
"build": {
"dependsOn": [
"!@cypress/react18:build"
],
"outputs": [
"{workspaceRoot}/cli/vue",
"{projectRoot}/dist"

View File

@@ -70,9 +70,6 @@
"nx": {
"targets": {
"build": {
"dependsOn": [
"!@cypress/react18:build"
],
"outputs": [
"{projectRoot}/dist"
]

View File

@@ -203,8 +203,8 @@ describe('component testing dependency warnings', () => {
cy.get('[data-cy="warning-alert"]', { timeout: 12000 }).should('exist')
.should('contain.text', 'Warning: Component Testing Mismatched Dependencies')
.should('contain.text', 'vite. Expected ^4.0.0 || ^5.0.0, found 3.2.11')
.should('contain.text', 'react. Expected ^16.0.0 || ^17.0.0 || ^18.0.0, found 15.6.2.')
.should('contain.text', 'react-dom. Expected ^16.0.0 || ^17.0.0 || ^18.0.0 but dependency was not found.')
.should('contain.text', 'react. Expected ^18.0.0, found 15.6.2.')
.should('contain.text', 'react-dom. Expected ^18.0.0 but dependency was not found.')
cy.get('.warning-markdown').find('li').should('have.length', 3)
})

View File

@@ -1,4 +1,4 @@
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
import 'cypress-real-events/support'
import { installCustomPercyCommand } from '@packages/frontend-shared/cypress/support/customPercyCommand'

View File

@@ -22,7 +22,7 @@ export const WIZARD_DEPENDENCY_REACT = {
package: 'react',
installer: 'react',
description: 'A JavaScript library for building user interfaces',
minVersion: '^16.0.0 || ^17.0.0 || ^18.0.0',
minVersion: '^18.0.0',
} as const
export const WIZARD_DEPENDENCY_REACT_DOM = {
@@ -31,7 +31,7 @@ export const WIZARD_DEPENDENCY_REACT_DOM = {
package: 'react-dom',
installer: 'react-dom',
description: 'This package serves as the entry point to the DOM and server renderers for React',
minVersion: '^16.0.0 || ^17.0.0 || ^18.0.0',
minVersion: '^18.0.0',
} as const
export const WIZARD_DEPENDENCY_TYPESCRIPT = {

View File

@@ -101,16 +101,6 @@ export function getBundler (bundler: WizardBundler['type']): WizardBundler {
const mountModule = <T extends string>(mountModule: T) => (projectPath: string) => Promise.resolve(mountModule)
const reactMountModule = async (projectPath: string) => {
const reactPkg = await isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_REACT, projectPath)
if (!reactPkg.detectedVersion || !semver.valid(reactPkg.detectedVersion)) {
return 'cypress/react'
}
return semver.major(reactPkg.detectedVersion) === 18 ? 'cypress/react18' : 'cypress/react'
}
export const SUPPORT_STATUSES: Readonly<Cypress.ResolvedComponentFrameworkDefinition['supportStatus'][]> = ['alpha', 'beta', 'full', 'community'] as const
export const CT_FRAMEWORKS: Cypress.ComponentFrameworkDefinition[] = [
@@ -130,7 +120,7 @@ export const CT_FRAMEWORKS: Cypress.ComponentFrameworkDefinition[] = [
},
codeGenFramework: 'react',
glob: '*.{js,jsx,tsx}',
mountModule: reactMountModule,
mountModule: mountModule('cypress/react'),
supportStatus: 'full',
/**
* Next.js uses style-loader to inject CSS and requires this element to exist in the HTML.
@@ -176,7 +166,7 @@ export const CT_FRAMEWORKS: Cypress.ComponentFrameworkDefinition[] = [
},
codeGenFramework: 'react',
glob: '*.{js,jsx,tsx}',
mountModule: reactMountModule,
mountModule: mountModule('cypress/react'),
supportStatus: 'full',
componentIndexHtml: componentIndexHtmlGenerator(),
},

View File

@@ -4,11 +4,12 @@ import { expect } from 'chai'
describe('supportFileComponent', () => {
context('react', () => {
for (const mountModule of ['cypress/react', 'cypress/react18'] as const) {
it(`handles ${mountModule} and JS`, () => {
const actual = supportFileComponent('js', mountModule)
const mountModule = 'cypress/react'
expect(actual).to.eq(dedent`
it(`handles ${mountModule} and JS`, () => {
const actual = supportFileComponent('js', mountModule)
expect(actual).to.eq(dedent`
// ***********************************************************
// This example support/component.js is processed and
// loaded automatically before your test files.
@@ -37,12 +38,12 @@ describe('supportFileComponent', () => {
// Example use:
// cy.mount(<MyComponent />)
`)
})
})
it(`handles ${mountModule} and TS`, () => {
const actual = supportFileComponent('ts', mountModule)
it(`handles ${mountModule} and TS`, () => {
const actual = supportFileComponent('ts', mountModule)
expect(actual).to.eq(dedent`
expect(actual).to.eq(dedent`
// ***********************************************************
// This example support/component.ts is processed and
// loaded automatically before your test files.
@@ -83,8 +84,7 @@ describe('supportFileComponent', () => {
// Example use:
// cy.mount(<MyComponent />)
`)
})
}
})
})
context('vue', () => {

View File

@@ -7,16 +7,14 @@ exports['React major versions with Webpack executes all of the tests for React v
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 5 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx, Rerendering.cy.jsx,
mount.cy.jsx)
│ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx, src/Rerendering.c │
│ y.jsx, src/mount.cy.jsx │
│ Specs: 4 found (App.cy.jsx, Unmount.cy.jsx, Rerendering.cy.jsx, mount.cy.jsx)
Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/Rerendering.cy.jsx, src/mount.cy.jsx
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: App.cy.jsx (1 of 5)
Running: App.cy.jsx (1 of 4)
✓ renders hello world
@@ -42,7 +40,7 @@ exports['React major versions with Webpack executes all of the tests for React v
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: Unmount.cy.jsx (2 of 5)
Running: Unmount.cy.jsx (2 of 4)
Comp with componentWillUnmount
@@ -73,34 +71,7 @@ exports['React major versions with Webpack executes all of the tests for React v
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: UsingLegacyMount.cy.jsx (3 of 5)
using legacy mount
✓ issues a warning encouraging user to update
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: UsingLegacyMount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: Rerendering.cy.jsx (4 of 5)
Running: Rerendering.cy.jsx (3 of 4)
re-render
@@ -127,7 +98,7 @@ exports['React major versions with Webpack executes all of the tests for React v
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: mount.cy.jsx (5 of 5)
Running: mount.cy.jsx (4 of 4)
mount
@@ -166,372 +137,11 @@ exports['React major versions with Webpack executes all of the tests for React v
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ UsingLegacyMount.cy.jsx XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ mount.cy.jsx XX:XX 3 3 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 10 10 - - -
`
exports['React major versions with Webpack executes all of the tests for React v17 with Webpack 1'] = `
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 5 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx, Rerendering.cy.jsx, │
│ mount.cy.jsx) │
│ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx, src/Rerendering.c │
│ y.jsx, src/mount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: App.cy.jsx (1 of 5)
50 modules
✓ renders hello world
✓ renders background
2 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 2 │
│ Passing: 2 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: App.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: Unmount.cy.jsx (2 of 5)
Comp with componentWillUnmount
✓ calls the prop
mount cleanup
✓ mount 1
✓ mount 2
3 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 3 │
│ Passing: 3 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: Unmount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: UsingLegacyMount.cy.jsx (3 of 5)
using legacy mount
✓ does not warning or log
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: UsingLegacyMount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: Rerendering.cy.jsx (4 of 5)
re-render
✓ maintains component state across re-renders
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: Rerendering.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: mount.cy.jsx (5 of 5)
mount
✓ does not error when rendering primitives
teardown
✓ should mount
✓ should remove previous mounted component
3 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 3 │
│ Passing: 3 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: mount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ App.cy.jsx XX:XX 2 2 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ UsingLegacyMount.cy.jsx XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ mount.cy.jsx XX:XX 3 3 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 10 10 - - -
`
exports['React major versions with Vite executes all of the tests for React v17 with Vite 1'] = `
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 5 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx, Rerendering.cy.jsx, │
│ mount.cy.jsx) │
│ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx, src/Rerendering.c │
│ y.jsx, src/mount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: App.cy.jsx (1 of 5)
✓ renders hello world
✓ renders background
2 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 2 │
│ Passing: 2 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: App.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: Unmount.cy.jsx (2 of 5)
Comp with componentWillUnmount
✓ calls the prop
mount cleanup
✓ mount 1
✓ mount 2
3 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 3 │
│ Passing: 3 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: Unmount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: UsingLegacyMount.cy.jsx (3 of 5)
using legacy mount
✓ does not warning or log
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: UsingLegacyMount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: Rerendering.cy.jsx (4 of 5)
re-render
✓ maintains component state across re-renders
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: Rerendering.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: mount.cy.jsx (5 of 5)
mount
✓ does not error when rendering primitives
teardown
✓ should mount
✓ should remove previous mounted component
3 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 3 │
│ Passing: 3 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: mount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ App.cy.jsx XX:XX 2 2 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ UsingLegacyMount.cy.jsx XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ mount.cy.jsx XX:XX 3 3 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 10 10 - - -
✔ All specs passed! XX:XX 9 9 - - -
`
@@ -545,16 +155,14 @@ exports['React major versions with Vite executes all of the tests for React v18
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 5 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx, Rerendering.cy.jsx,
mount.cy.jsx)
│ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx, src/Rerendering.c │
│ y.jsx, src/mount.cy.jsx │
│ Specs: 4 found (App.cy.jsx, Unmount.cy.jsx, Rerendering.cy.jsx, mount.cy.jsx)
Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/Rerendering.cy.jsx, src/mount.cy.jsx
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: App.cy.jsx (1 of 5)
Running: App.cy.jsx (1 of 4)
✓ renders hello world
@@ -580,7 +188,7 @@ exports['React major versions with Vite executes all of the tests for React v18
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: Unmount.cy.jsx (2 of 5)
Running: Unmount.cy.jsx (2 of 4)
Comp with componentWillUnmount
@@ -611,34 +219,7 @@ exports['React major versions with Vite executes all of the tests for React v18
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: UsingLegacyMount.cy.jsx (3 of 5)
using legacy mount
✓ issues a warning encouraging user to update
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: UsingLegacyMount.cy.jsx │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: Rerendering.cy.jsx (4 of 5)
Running: Rerendering.cy.jsx (3 of 4)
re-render
@@ -665,7 +246,7 @@ exports['React major versions with Vite executes all of the tests for React v18
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: mount.cy.jsx (5 of 5)
Running: mount.cy.jsx (4 of 4)
mount
@@ -704,13 +285,11 @@ exports['React major versions with Vite executes all of the tests for React v18
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ UsingLegacyMount.cy.jsx XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ mount.cy.jsx XX:XX 3 3 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 10 10 - - -
✔ All specs passed! XX:XX 9 9 - - -
`

View File

@@ -1,4 +1,4 @@
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
import './backgroundColor.css'

View File

@@ -1,3 +1,3 @@
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
Cypress.Commands.add('mount', mount)

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
describe('NewComponent', () => {
it('renders the new component', () => {

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
describe('TestComponent', () => {
it('renders the test component', () => {

View File

@@ -1,3 +1,3 @@
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
Cypress.Commands.add('mount', mount)

View File

@@ -1,3 +1,3 @@
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
Cypress.Commands.add('mount', mount)

View File

@@ -1,6 +1,6 @@
import 'tailwindcss/tailwind.css'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
declare global {
namespace Cypress {

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
import { Button } from './button'
it('works', () => {

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
import { Button } from './button'
it('works', () => {

View File

@@ -19,7 +19,7 @@ import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
Cypress.Commands.add('mount', mount)

View File

@@ -1,4 +1,4 @@
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
// Augment the Cypress namespace to include type definitions for
// your custom command.

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
import App from './App.tsx'
it('works', () => {

View File

@@ -1,4 +1,4 @@
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
import './backgroundColor.css'

View File

@@ -1,23 +0,0 @@
import React from 'react'
import { mount } from 'cypress/react'
function App () {
return <h1>Hello world</h1>
}
describe('using legacy mount', () => {
it('issues a warning encouraging user to update', () => {
// dont log else we create an endless loop!
const log = cy.spy(Cypress, 'log').log(false)
const err = cy.spy(console, 'error')
mount(<App />).get('h1').contains('Hello world')
.then(() => {
const msg = '[cypress/react]: You are using `cypress/react`, which is designed for React <= 17. Consider changing to `cypress/react18`, which is designed for React 18.'
const warning = log.getCalls().find((call) => call.args[0].name === 'warning')
expect(warning.lastArg.message).to.eq(msg)
expect(err).to.have.been.calledWith(msg)
})
})
})

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
export const App = () => {
return (

View File

@@ -1,3 +1,3 @@
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
Cypress.Commands.add('mount', mount)

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
describe('webpack-dev-server', () => {
it('image with relative path should load', () => {

View File

@@ -1,6 +1,6 @@
import React from 'react'
import { App } from './App'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
it('renders hello world', () => {
mount(<App />)

View File

@@ -1,6 +1,6 @@
import React from 'react'
import { App } from './App'
import { mount } from 'cypress/react18'
import { mount } from 'cypress/react'
it('renders hello world', () => {
mount(<App />)

View File

@@ -47,7 +47,7 @@ describe(`React major versions with Vite`, function () {
return systemTests.exec(this, {
project: `react${majorVersion}`,
configFile: 'cypress-vite-default.config.ts',
spec: 'src/App.cy.jsx,src/Unmount.cy.jsx,src/UsingLegacyMount.cy.jsx,src/Rerendering.cy.jsx,src/mount.cy.jsx',
spec: 'src/App.cy.jsx,src/Unmount.cy.jsx,src/Rerendering.cy.jsx,src/mount.cy.jsx',
testingType: 'component',
browser: 'chrome',
snapshot: true,
@@ -65,7 +65,7 @@ describe(`React major versions with Webpack`, function () {
return systemTests.exec(this, {
project: `react${majorVersion}`,
configFile: 'cypress-webpack.config.ts',
spec: 'src/App.cy.jsx,src/Unmount.cy.jsx,src/UsingLegacyMount.cy.jsx,src/Rerendering.cy.jsx,src/mount.cy.jsx',
spec: 'src/App.cy.jsx,src/Unmount.cy.jsx,src/Rerendering.cy.jsx,src/mount.cy.jsx',
testingType: 'component',
browser: 'chrome',
snapshot: true,

217
yarn.lock
View File

@@ -1562,10 +1562,10 @@
dependencies:
"@babel/types" "^7.24.7"
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.6", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878"
integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
version "7.25.9"
resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46"
integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==
"@babel/helper-remap-async-to-generator@^7.24.7", "@babel/helper-remap-async-to-generator@^7.25.0":
version "7.25.0"
@@ -2229,19 +2229,19 @@
dependencies:
"@babel/plugin-transform-react-jsx" "^7.24.7"
"@babel/plugin-transform-react-jsx-self@^7.24.5":
version "7.24.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.6.tgz#4fa4870d594d6840d724d2006d0f98b19be6f502"
integrity sha512-FfZfHXtQ5jYPQsCRyLpOv2GeLIIJhs8aydpNh39vRDjhD411XcfWDni5i7OjP/Rs8GAtTn7sWFFELJSHqkIxYg==
"@babel/plugin-transform-react-jsx-self@^7.24.7":
version "7.25.9"
resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz#c0b6cae9c1b73967f7f9eb2fca9536ba2fad2858"
integrity sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==
dependencies:
"@babel/helper-plugin-utils" "^7.24.6"
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-react-jsx-source@^7.24.1":
version "7.24.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.6.tgz#4e1503f24ca5fccb1fc7f20c57426899d5ce5c1f"
integrity sha512-BQTBCXmFRreU3oTUXcGKuPOfXAGb1liNY4AvvFKsOBAJ89RKcTsIrSsnMYkj59fNa66OFKnSa4AJZfy5Y4B9WA==
"@babel/plugin-transform-react-jsx-source@^7.24.7":
version "7.25.9"
resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz#4c6b8daa520b5f155b5fb55547d7c9fa91417503"
integrity sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==
dependencies:
"@babel/helper-plugin-utils" "^7.24.6"
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.24.7":
version "7.25.2"
@@ -6231,10 +6231,10 @@
tslib "^2.0.0"
warning "^4.0.3"
"@remix-run/router@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.5.0.tgz#57618e57942a5f0131374a9fdb0167e25a117fdc"
integrity sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==
"@remix-run/router@1.21.0":
version "1.21.0"
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz#c65ae4262bdcfe415dbd4f64ec87676e4a56e2b5"
integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==
"@rollup/plugin-commonjs@^17.1.0":
version "17.1.0"
@@ -8195,13 +8195,6 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/react-dom@17.0.25":
version "17.0.25"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz#e0e5b3571e1069625b3a3da2b279379aa33a0cb5"
integrity sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==
dependencies:
"@types/react" "^17"
"@types/react-dom@18.3.1":
version "18.3.1"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz#1e4654c08a9cdcfb6594c780ac59b55aad42fe07"
@@ -8217,15 +8210,6 @@
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/react@17.0.83", "@types/react@^17":
version "17.0.83"
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz#b477c56387b74279281149dcf5ba2a1e2216d131"
integrity sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "^0.16"
csstype "^3.0.2"
"@types/react@^16":
version "16.14.62"
resolved "https://registry.npmjs.org/@types/react/-/react-16.14.62.tgz#449e4e81caaf132d0c2c390644e577702db1dd9e"
@@ -8295,12 +8279,7 @@
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff"
integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==
"@types/semver@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a"
integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==
"@types/semver@^7.5.0":
"@types/semver@7.5.8", "@types/semver@^7.5.0":
version "7.5.8"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
@@ -8770,14 +8749,14 @@
regenerator-runtime "^0.14.1"
systemjs "^6.15.1"
"@vitejs/plugin-react@4.3.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.0.tgz#f20ec2369a92d8abaaefa60da8b7157819d20481"
integrity sha512-KcEbMsn4Dpk+LIbHMj7gDPRKaTMStxxWRkRmxsg/jVdFdJCZWt1SchZcf0M4t8lIKdwwMsEyzhrcOXRrDPtOBw==
"@vitejs/plugin-react@4.3.3":
version "4.3.3"
resolved "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz#28301ac6d7aaf20b73a418ee5c65b05519b4836c"
integrity sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==
dependencies:
"@babel/core" "^7.24.5"
"@babel/plugin-transform-react-jsx-self" "^7.24.5"
"@babel/plugin-transform-react-jsx-source" "^7.24.1"
"@babel/core" "^7.25.2"
"@babel/plugin-transform-react-jsx-self" "^7.24.7"
"@babel/plugin-transform-react-jsx-source" "^7.24.7"
"@types/babel__core" "^7.20.5"
react-refresh "^0.14.2"
@@ -10624,7 +10603,7 @@ axios@0.21.2:
dependencies:
follow-redirects "^1.14.0"
axios@^1.7.4:
axios@1.7.7, axios@^1.7.4:
version "1.7.7"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f"
integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==
@@ -22513,7 +22492,7 @@ mobx@6.13.5:
resolved "https://registry.npmjs.org/mobx/-/mobx-6.13.5.tgz#957d9df88c7f8b4baa7c6f8bdcb6d68b432a6ed5"
integrity sha512-/HTWzW2s8J1Gqt+WmUj5Y0mddZk+LInejADc79NJadrWla3rHzmRHki/mnEUH1AvOmbNTZ1BRbKxr8DSgfdjMA==
"mocha-7.0.1@npm:mocha@7.0.1":
"mocha-7.0.1@npm:mocha@7.0.1", mocha@7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.0.1.tgz#276186d35a4852f6249808c6dd4a1376cbf6c6ce"
integrity sha512-9eWmWTdHLXh72rGrdZjNbG3aa1/3NRPpul1z0D979QpEnFdCG0Q5tv834N+94QEN2cysfV72YocQ3fn87s70fg==
@@ -22630,36 +22609,6 @@ mocha@6.2.2:
yargs-parser "13.1.1"
yargs-unparser "1.6.0"
mocha@7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.0.1.tgz#276186d35a4852f6249808c6dd4a1376cbf6c6ce"
integrity sha512-9eWmWTdHLXh72rGrdZjNbG3aa1/3NRPpul1z0D979QpEnFdCG0Q5tv834N+94QEN2cysfV72YocQ3fn87s70fg==
dependencies:
ansi-colors "3.2.3"
browser-stdout "1.3.1"
chokidar "3.3.0"
debug "3.2.6"
diff "3.5.0"
escape-string-regexp "1.0.5"
find-up "3.0.0"
glob "7.1.3"
growl "1.10.5"
he "1.2.0"
js-yaml "3.13.1"
log-symbols "2.2.0"
minimatch "3.0.4"
mkdirp "0.5.1"
ms "2.1.1"
node-environment-flags "1.0.6"
object.assign "4.1.0"
strip-json-comments "2.0.1"
supports-color "6.0.0"
which "1.3.1"
wide-align "1.1.3"
yargs "13.3.0"
yargs-parser "13.1.1"
yargs-unparser "1.6.0"
mocha@7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.0.tgz#c784f579ad0904d29229ad6cb1e2514e4db7d249"
@@ -25908,7 +25857,7 @@ prop-types@15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@15.8.1, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -26335,25 +26284,6 @@ react-docgen@6.0.4:
object-assign "^4.1.1"
prop-types "^15.6.0"
react-dom@16.8.6:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f"
integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.13.6"
react-dom@17.0.2, react-dom@^17.0.2:
version "17.0.2"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler "^0.20.2"
react-dom@18.3.1:
version "18.3.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
@@ -26423,20 +26353,20 @@ react-remove-scroll@^2.3.0:
use-callback-ref "^1.2.3"
use-sidecar "^1.0.1"
react-router-dom@6.10.0:
version "6.10.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.10.0.tgz#090ddc5c84dc41b583ce08468c4007c84245f61f"
integrity sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==
react-router-dom@6.28.0:
version "6.28.0"
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz#f73ebb3490e59ac9f299377062ad1d10a9f579e6"
integrity sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==
dependencies:
"@remix-run/router" "1.5.0"
react-router "6.10.0"
"@remix-run/router" "1.21.0"
react-router "6.28.0"
react-router@6.10.0:
version "6.10.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.10.0.tgz#230f824fde9dd0270781b5cb497912de32c0a971"
integrity sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==
react-router@6.28.0:
version "6.28.0"
resolved "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz#29247c86d7ba901d7e5a13aa79a96723c3e59d0d"
integrity sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==
dependencies:
"@remix-run/router" "1.5.0"
"@remix-run/router" "1.21.0"
react-style-singleton@^2.1.0:
version "2.1.1"
@@ -26457,24 +26387,6 @@ react-test-renderer@^16.0.0-0:
react-is "^16.8.6"
scheduler "^0.19.1"
react@16.8.6:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.13.6"
react@17.0.2, react@^17.0.2:
version "17.0.2"
resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
react@18.3.1:
version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
@@ -27597,14 +27509,6 @@ sax@>=0.6.0, sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
scheduler@^0.13.6:
version "0.13.6"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889"
integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
@@ -27613,14 +27517,6 @@ scheduler@^0.19.1:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
@@ -29282,7 +29178,7 @@ string-template@~0.2.1:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=
"string-width-cjs@npm:string-width@^4.2.0":
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -29308,15 +29204,6 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^3.0.0, string-width@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
@@ -29427,7 +29314,7 @@ stringify-object@^3.0.0, stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -29448,13 +29335,6 @@ strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"
strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -31785,7 +31665,7 @@ vite@5.2.11:
optionalDependencies:
fsevents "~2.3.3"
vite@^5.0.0:
vite@5.4.10, vite@^5.0.0:
version "5.4.10"
resolved "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz#d358a7bd8beda6cf0f3b7a450a8c7693a4f80c18"
integrity sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==
@@ -32459,7 +32339,7 @@ workerpool@6.2.0:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -32502,15 +32382,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"