mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-25 01:49:06 -05:00
fix(vite-dev-server): exclude CT specs from Vite 8 JSX refresh (#33751)
* fix(vite-dev-server): exclude CT specs from Vite 8 JSX refresh Prevents duplicate describe/it registration in headed mode when specs define local React components (HMR self-accept). Sets oxc.jsxRefreshExclude for Vite 8. Fixes #33750 Co-authored-by: Cursor <cursoragent@cursor.com> * fix(vite-dev-server): scope jsxRefreshInclude so CSS is not transformed by Oxc Pair jsxRefreshExclude with a script-only jsxRefreshInclude pattern; Vite's createFilter(undefined, exclude) otherwise matches all non-spec assets. Add unit tests including CSS path regression coverage. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
**Bugfixes:**
|
||||
|
||||
- Fixed an issue where component specs that defined local React components could register every `describe` / `it` block twice in `cypress open` when using Vite 8, because React refresh treated those specs as HMR self-accepting modules. `@cypress/vite-dev-server` now excludes component spec files from JSX refresh while leaving Fast Refresh enabled for application source. Fixes [#33750](https://github.com/cypress-io/cypress/issues/33750).
|
||||
- Fixed an issue where multi-origin tests using [`cy.origin`](https://docs.cypress.io/api/commands/origin) could fail to talk to a secondary origin after test isolation, when the spec-bridge iframe was already present, or when more than one secondary origin became ready around the same time. Cached spec-bridge window targets are now cleared at the correct lifecycle points, improving performance of specs with cy.origin calls. Addressed in [#33704](https://github.com/cypress-io/cypress/pull/33704).
|
||||
- Fixed an issue where a CSS selector built internally from element attributes could throw an uncaught `Syntax error, unrecognized expression` and crash the runner when an attribute value contained CSS-special characters (for example, an `<input>` with a `pattern` attribute containing regex metacharacters). Fixes [#26967](https://github.com/cypress-io/cypress/issues/26967) and [#29345](https://github.com/cypress-io/cypress/issues/29345).
|
||||
- Fixed an issue where transient HTTP 500 responses from Cypress Cloud were not retried for idempotent requests. Fixed in [#33718](https://github.com/cypress-io/cypress/pull/33718).
|
||||
|
||||
@@ -18,6 +18,12 @@ import type { Vite_7, Vite_8 } from './getVite.js'
|
||||
|
||||
const debug = debugFn('cypress:vite-dev-server:resolve-config')
|
||||
|
||||
// Limit jsxRefreshInclude/exclude matching to scripts. With only jsxRefreshExclude set, Vite builds
|
||||
// createFilter(undefined, exclude) which matches every non-excluded path — CSS would hit transformWithOxc and fail.
|
||||
// @see https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/oxc.ts (transform + jsxRefreshFilter)
|
||||
/** Passed as `oxc.jsxRefreshInclude` so JSX refresh excludes do not match CSS or other assets. */
|
||||
export const JSX_REFRESH_SCRIPT_RE = /\.(?:[cm]?js|[cm]?ts|[jt]sx)$/
|
||||
|
||||
export const isVite8 = (vite: Vite_7 | Vite_8): boolean => {
|
||||
const isVite8 = vite.version && semverGte(vite.version, '8.0.0') || false
|
||||
|
||||
@@ -138,6 +144,15 @@ function makeCypressViteConfig (config: ViteDevServerConfig, vite: Vite_7 | Vite
|
||||
const viteConfig: InlineConfig_7 | InlineConfig_8 = {
|
||||
root: projectRoot,
|
||||
base: `${devServerPublicPathRoute}/`,
|
||||
// Vite 8 Rolldown/react-plugin can wrap JSX specs with `import.meta.hot.accept`, re-evaluating
|
||||
// the module in headed mode and registering describe/it twice. Excluding CT specs from JSX refresh fixes it.
|
||||
// @see https://github.com/cypress-io/cypress/issues/33750
|
||||
...(isVite8(vite) ? {
|
||||
oxc: {
|
||||
jsxRefreshInclude: JSX_REFRESH_SCRIPT_RE,
|
||||
jsxRefreshExclude: specs.map((s) => s.absolute),
|
||||
},
|
||||
} : {}),
|
||||
optimizeDeps: {
|
||||
...options,
|
||||
entries: [
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import { EventEmitter } from 'events'
|
||||
import * as vite5 from 'vite-5'
|
||||
@@ -5,7 +7,7 @@ import * as vite6 from 'vite-6'
|
||||
import * as vite7 from 'vite-7'
|
||||
import * as vite8 from 'vite-8'
|
||||
import { scaffoldSystemTestProject } from './test-helpers/scaffoldProject'
|
||||
import { createViteDevServerConfig } from '../src/resolveConfig'
|
||||
import { createViteDevServerConfig, JSX_REFRESH_SCRIPT_RE } from '../src/resolveConfig'
|
||||
import type { ViteDevServerConfig } from '../src/devServer'
|
||||
|
||||
const getViteDevServerConfig = (projectRoot: string) => {
|
||||
@@ -115,4 +117,47 @@ describe('resolveConfig', function () {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vite 8 JSX refresh excludes component specs', () => {
|
||||
// Real package root so createRequire can resolve `vite` like a consumer project; inline viteConfig skips fixture scaffolding.
|
||||
const viteDevServerPackageRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
it('sets oxc.jsxRefreshInclude and jsxRefreshExclude from Cypress specs (Vite 8)', async () => {
|
||||
const specAbsolutes = [
|
||||
path.join(viteDevServerPackageRoot, 'src', 'Hello.cy.tsx'),
|
||||
path.join(viteDevServerPackageRoot, 'src', 'Other.cy.tsx'),
|
||||
]
|
||||
const viteDevServerConfig = {
|
||||
...getViteDevServerConfig(viteDevServerPackageRoot),
|
||||
viteConfig: {},
|
||||
specs: [
|
||||
{ absolute: specAbsolutes[0], relative: 'src/Hello.cy.tsx' },
|
||||
{ absolute: specAbsolutes[1], relative: 'src/Other.cy.tsx' },
|
||||
],
|
||||
} as unknown as ViteDevServerConfig
|
||||
|
||||
const viteConfig = await createViteDevServerConfig(viteDevServerConfig, vite8)
|
||||
|
||||
expect(viteConfig.oxc?.jsxRefreshInclude).to.equal(JSX_REFRESH_SCRIPT_RE)
|
||||
expect(viteConfig.oxc?.jsxRefreshExclude).to.eql(specAbsolutes)
|
||||
})
|
||||
|
||||
it('does not set oxc overrides for Vite 7', async () => {
|
||||
const specAbsolute = path.join(viteDevServerPackageRoot, 'components', 'Card.cy.tsx')
|
||||
const viteDevServerConfig = {
|
||||
...getViteDevServerConfig(viteDevServerPackageRoot),
|
||||
viteConfig: {},
|
||||
specs: [{ absolute: specAbsolute, relative: 'components/Card.cy.tsx' }],
|
||||
} as unknown as ViteDevServerConfig
|
||||
|
||||
const viteConfig = await createViteDevServerConfig(viteDevServerConfig, vite7)
|
||||
|
||||
expect(viteConfig.oxc).to.be.undefined
|
||||
})
|
||||
|
||||
it('matches only script-like paths so imported CSS (e.g. support files) is not run through transformWithOxc', () => {
|
||||
expect(JSX_REFRESH_SCRIPT_RE.test('/project/cypress/support/backgroundColor.css')).to.be.false
|
||||
expect(JSX_REFRESH_SCRIPT_RE.test('/project/src/App.cy.tsx')).to.be.true
|
||||
})
|
||||
})
|
||||
}, 1000 * 60)
|
||||
|
||||
Reference in New Issue
Block a user