diff --git a/e2e/react-extensions/src/cypress-component-tests.test.ts b/e2e/react-extensions/src/cypress-component-tests.test.ts index 1f1ffe073683d..134889ffd9b8e 100644 --- a/e2e/react-extensions/src/cypress-component-tests.test.ts +++ b/e2e/react-extensions/src/cypress-component-tests.test.ts @@ -4,6 +4,7 @@ import { ensureCypressInstallation, newProject, runCLI, + runCypressTests, uniq, updateFile, updateJson, @@ -146,18 +147,22 @@ export default Input; runCLI( `generate @nx/react:cypress-component-configuration --project=${appName} --generate-tests` ); - expect(runCLI(`component-test ${appName} --no-watch`)).toContain( - 'All specs passed!' - ); + if (runCypressTests()) { + expect(runCLI(`component-test ${appName} --no-watch`)).toContain( + 'All specs passed!' + ); + } }, 300_000); it('should successfully component test lib being used in app', () => { runCLI( `generate @nx/react:cypress-component-configuration --project=${usedInAppLibName} --generate-tests` ); - expect(runCLI(`component-test ${usedInAppLibName} --no-watch`)).toContain( - 'All specs passed!' - ); + if (runCypressTests()) { + expect(runCLI(`component-test ${usedInAppLibName} --no-watch`)).toContain( + 'All specs passed!' + ); + } }, 300_000); it('should test buildable lib not being used in app', () => { @@ -184,9 +189,12 @@ describe(Input.name, () => { runCLI( `generate @nx/react:cypress-component-configuration --project=${buildableLibName} --generate-tests --build-target=${appName}:build` ); - expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain( - 'All specs passed!' - ); + + if (runCypressTests()) { + expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain( + 'All specs passed!' + ); + } // add tailwind runCLI(`generate @nx/react:setup-tailwind --project=${buildableLibName}`); @@ -213,9 +221,11 @@ ${content}`; } ); - expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain( - 'All specs passed!' - ); + if (runCypressTests()) { + expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain( + 'All specs passed!' + ); + } }, 300_000); it('should work with async webpack config', () => { @@ -250,8 +260,41 @@ ${content}`; return config; }); - const results = runCLI(`component-test ${appName}`); - expect(results).toContain('I am from the custom async Webpack config'); - expect(results).toContain('All specs passed!'); + if (runCypressTests()) { + const results = runCLI(`component-test ${appName}`); + expect(results).toContain('I am from the custom async Webpack config'); + expect(results).toContain('All specs passed!'); + } + }); + + // flaky bc of upstream issue https://github.com/cypress-io/cypress/issues/25913 + it.skip('should CT vite projects importing other projects', () => { + const viteLibName = uniq('vite-lib'); + runCLI( + `generate @nrwl/react:lib ${viteLibName} --bundler=vite --no-interactive` + ); + + updateFile(`libs/${viteLibName}/src/lib/${viteLibName}.tsx`, () => { + return `import { Btn } from '@${projectName}/${usedInAppLibName}'; + +export function MyComponent() { + return ( + <> + +

hello

+ + ); +} +export default MyComponent;`; + }); + + runCLI( + `generate @nrwl/react:cypress-component-configuration --project=${viteLibName} --generate-tests --bundler=vite --build-target=${appName}:build` + ); + if (runCypressTests()) { + expect(runCLI(`component-test ${viteLibName}`)).toContain( + 'All specs passed!' + ); + } }); }); diff --git a/packages/react/plugins/component-testing/index.ts b/packages/react/plugins/component-testing/index.ts index 300f1a17eb33e..0c55692280dab 100644 --- a/packages/react/plugins/component-testing/index.ts +++ b/packages/react/plugins/component-testing/index.ts @@ -5,6 +5,7 @@ import { import type { CypressExecutorOptions } from '@nx/cypress/src/executors/cypress/cypress.impl'; import { ExecutorContext, + joinPathFragments, logger, parseTargetString, ProjectGraph, @@ -19,7 +20,8 @@ import { getProjectConfigByPath, } from '@nx/cypress/src/utils/ct-helpers'; -import type { Configuration } from 'webpack'; +import { existsSync, lstatSync } from 'fs'; +import { dirname, join } from 'path'; type ViteDevServer = { framework: 'react'; bundler: 'vite'; @@ -66,6 +68,36 @@ export function nxComponentTestingPreset( specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}', devServer: { ...({ framework: 'react', bundler: 'vite' } as const), + viteConfig: async () => { + const normalizedPath = ['.ts', '.js'].some((ext) => + pathToConfig.endsWith(ext) + ) + ? pathToConfig + : dirname(pathToConfig); + const viteConfigPath = findViteConfig(normalizedPath); + + const { mergeConfig, loadConfigFromFile, searchForWorkspaceRoot } = + (await import('vite')) as typeof import('vite'); + + const resolved = await loadConfigFromFile( + { + mode: 'watch', + command: 'serve', + }, + viteConfigPath + ); + return mergeConfig(resolved.config, { + server: { + fs: { + allow: [ + searchForWorkspaceRoot(normalizedPath), + workspaceRoot, + joinPathFragments(workspaceRoot, 'node_modules/vite'), + ], + }, + }, + }); + }, }, }; } @@ -232,3 +264,12 @@ function buildTargetWebpack( }; } } +function findViteConfig(projectRootFullPath: string): string { + const allowsExt = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts']; + + for (const ext of allowsExt) { + if (existsSync(join(projectRootFullPath, `vite.config.${ext}`))) { + return join(projectRootFullPath, `vite.config.${ext}`); + } + } +} diff --git a/packages/react/src/generators/cypress-component-configuration/lib/add-files.ts b/packages/react/src/generators/cypress-component-configuration/lib/add-files.ts index 0b90fd8b61d09..aae5fb9125c79 100644 --- a/packages/react/src/generators/cypress-component-configuration/lib/add-files.ts +++ b/packages/react/src/generators/cypress-component-configuration/lib/add-files.ts @@ -56,6 +56,13 @@ export async function addFiles( addDependenciesToPackageJson(tree, {}, { '@nx/webpack': nxVersion }); } + if ( + options.bundler === 'vite' || + (!options.bundler && actualBundler === 'vite') + ) { + addDependenciesToPackageJson(tree, {}, { '@nx/vite': nxVersion }); + } + if (options.generateTests) { const filePaths = []; visitNotIgnoredFiles(tree, projectConfig.sourceRoot, (filePath) => { diff --git a/scripts/depcheck/missing.ts b/scripts/depcheck/missing.ts index 5382ecc6c8090..e742883d3c018 100644 --- a/scripts/depcheck/missing.ts +++ b/scripts/depcheck/missing.ts @@ -114,6 +114,8 @@ const IGNORE_MATCHES_IN_PACKAGE = { 'url-loader', 'webpack', 'webpack-merge', + // used via the CT react plugin installed via vite plugin + 'vite', ], 'react-native': ['@nx/storybook'], rollup: ['@swc/core'],