Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: support Next 15 for Cypress Component Testing #30654

Merged
merged 1 commit into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ in this [GitHub issue](https://github.com/cypress-io/cypress/issues/30447). Addr
**Features:**

- Cypress Component Testing now supports `React` version 19. Cypress will allow detected use of the React 19 Release Candidate until React 19 is officially released. Addresses [#29470](https://github.com/cypress-io/cypress/issues/29470).
- Cypress Component Testing now supports `Next.js` version 15. Addresses [#30445](https://github.com/cypress-io/cypress/issues/30445).

**Bugfixes:**

Expand Down
156 changes: 81 additions & 75 deletions npm/webpack-dev-server/cypress/e2e/next.cy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/// <reference path="../support/e2e.ts" />
import type { ProjectFixtureDir } from '@tooling/system-tests/lib/fixtureDirs'

// keeping this structure for future versions of next.js. Next.js 15 has released but we have not yet added support
const NEXT_PROJECTS: ProjectFixtureDir[] = ['next-14']
// Add to this list to focus on a particular permutation
const ONLY_PROJECTS: ProjectFixtureDir[] = []
Expand Down Expand Up @@ -107,99 +106,106 @@ for (const project of NEXT_PROJECTS) {
})
}

// Since next-14-tsconfig-tailwind does not use the fixture directory we need to write our own test suite
// We want to specifically test typescript files with next-14 as there have been known problems with
// module: bundler and cypress compatibility
describe(`Working with next-14-tsconfig-tailwind`, () => {
beforeEach(() => {
cy.scaffoldProject('next-14-tsconfig-tailwind')
cy.openProject('next-14-tsconfig-tailwind', ['--component'])
cy.startAppServer('component')
})
const NEXT_PROJECTS_TSCONFIG_TAILWIND: ProjectFixtureDir[] = ['next-14-tsconfig-tailwind', 'next-15-tsconfig-tailwind']

it('should mount a passing test', () => {
cy.visitApp()
cy.specsPageIsVisible()
cy.contains('page.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })
})
for (const project of NEXT_PROJECTS_TSCONFIG_TAILWIND) {
// Since next-tsconfig-tailwind does not use the fixture directory we need to write our own test suite
// We want to specifically test typescript files with next as there have been known problems with
// module: bundler and cypress compatibility
describe(`Working with ${project}`, () => {
beforeEach(() => {
cy.scaffoldProject(project)
cy.openProject(project, ['--component'])
cy.startAppServer('component')
})

it('should mount a passing test', () => {
cy.visitApp()
cy.specsPageIsVisible()
cy.contains('page.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })
})

it('should live-reload on src changes', () => {
cy.visitApp()
cy.specsPageIsVisible()

it('should live-reload on src changes', () => {
cy.visitApp()
cy.specsPageIsVisible()
cy.contains('page.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })

cy.contains('page.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })
cy.withCtx(async (ctx) => {
const indexPath = ctx.path.join('app', 'page.tsx')

cy.withCtx(async (ctx) => {
const indexPath = ctx.path.join('app', 'page.tsx')
await ctx.actions.file.writeFileInProject(
indexPath,
(await ctx.file.readFileInProject(indexPath)).replace('Welcome to', 'Hello from'),
)
})

await ctx.actions.file.writeFileInProject(
indexPath,
(await ctx.file.readFileInProject(indexPath)).replace('Welcome to', 'Hello from'),
)
})
cy.waitForSpecToFinish({ failCount: 1 })
if (project !== 'next-15-tsconfig-tailwind') {
// code frames not fully working with next 15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something that we need to address?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likely. I need to test with the built binary and if its an issue I am going to file an issue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a problem. likely an issue with source maps that needs to be investigated. filed an issue to fix #30667

cy.get('.test-err-code-frame').should('be.visible')
}

cy.waitForSpecToFinish({ failCount: 1 })
cy.get('.test-err-code-frame').should('be.visible')
cy.withCtx(async (ctx) => {
const indexTestPath = ctx.path.join('app', 'page.cy.tsx')

cy.withCtx(async (ctx) => {
const indexTestPath = ctx.path.join('app', 'page.cy.tsx')
await ctx.actions.file.writeFileInProject(
indexTestPath,
(await ctx.file.readFileInProject(indexTestPath)).replace('Welcome to', 'Hello from'),
)
})

await ctx.actions.file.writeFileInProject(
indexTestPath,
(await ctx.file.readFileInProject(indexTestPath)).replace('Welcome to', 'Hello from'),
)
cy.waitForSpecToFinish({ passCount: 1 })
})

cy.waitForSpecToFinish({ passCount: 1 })
})
it('should show compilation errors on src changes', () => {
cy.visitApp()
cy.specsPageIsVisible()

it('should show compilation errors on src changes', () => {
cy.visitApp()
cy.specsPageIsVisible()
cy.contains('page.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })

cy.contains('page.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })
// Create compilation error
cy.withCtx(async (ctx) => {
const indexPath = ctx.path.join('app', 'page.tsx')

// Create compilation error
cy.withCtx(async (ctx) => {
const indexPath = ctx.path.join('app', 'page.tsx')
await ctx.actions.file.writeFileInProject(
indexPath,
(await ctx.file.readFileInProject(indexPath)).replace('export', 'expart'),
)
})

await ctx.actions.file.writeFileInProject(
indexPath,
(await ctx.file.readFileInProject(indexPath)).replace('export', 'expart'),
)
// The test should fail and the stack trace should appear in the command log
cy.waitForSpecToFinish({ failCount: 1 })
cy.contains('The following error originated from your test code, not from Cypress.').should('exist')
})

// The test should fail and the stack trace should appear in the command log
cy.waitForSpecToFinish({ failCount: 1 })
cy.contains('The following error originated from your test code, not from Cypress.').should('exist')
})
it('should detect new spec', { retries: 15 }, () => {
cy.visitApp()
cy.specsPageIsVisible()

it('should detect new spec', { retries: 15 }, () => {
cy.visitApp()
cy.specsPageIsVisible()
cy.withCtx(async (ctx) => {
const newTestPath = ctx.path.join('app', 'New.cy.tsx')
const indexTestPath = ctx.path.join('app', 'page.cy.tsx')

cy.withCtx(async (ctx) => {
const newTestPath = ctx.path.join('app', 'New.cy.tsx')
const indexTestPath = ctx.path.join('app', 'page.cy.tsx')
await ctx.actions.file.writeFileInProject(
newTestPath,
await ctx.file.readFileInProject(indexTestPath),
)
})

await ctx.actions.file.writeFileInProject(
newTestPath,
await ctx.file.readFileInProject(indexTestPath),
)
cy.contains('New.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })
})

cy.contains('New.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })
})

// Make sure tailwind styles are appearing in the test.
it('should allow import of global styles in support file', { retries: 15 }, () => {
cy.visitApp()
cy.specsPageIsVisible()
cy.contains('page-styles.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })
// Make sure tailwind styles are appearing in the test.
it('should allow import of global styles in support file', { retries: 15 }, () => {
cy.visitApp()
cy.specsPageIsVisible()
cy.contains('page-styles.cy.tsx').click()
cy.waitForSpecToFinish({ passCount: 1 })
})
})
})
}
2 changes: 1 addition & 1 deletion packages/scaffold-config/src/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const WIZARD_DEPENDENCY_NEXT = {
package: 'next',
installer: 'next',
description: 'The React Framework for Production',
minVersion: '^14.0.0',
minVersion: '^14.0.0 || ^15.0.0',
} as const

export const WIZARD_DEPENDENCY_ANGULAR_CLI = {
Expand Down
5 changes: 2 additions & 3 deletions packages/scaffold-config/test/unit/detect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,12 @@ describe('detectFramework', () => {
expect(actual.bundler).to.eq('vite')
})

// keeping array style as to make it easier to support future next versions
;['14.0.0'].forEach((v) => {
;['14.0.0', '15.0.0'].forEach((v) => {
it(`Next.js v${v}`, async () => {
const projectPath = await scaffoldMigrationProject('nextjs-unconfigured')

fakeDepsInNodeModules(projectPath, [
{ dependency: 'react', version: '18.0.0' },
{ dependency: 'react', version: v === '15.0.0' ? '19.0.0-rc.1' : '18.0.0' },
{ dependency: 'next', version: v },
])

Expand Down
2 changes: 1 addition & 1 deletion system-tests/projects/next-14/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "next-latest",
"name": "next-14",
"version": "0.0.0-test",
"scripts": {
"build": "next build",
Expand Down
40 changes: 40 additions & 0 deletions system-tests/projects/next-15-tsconfig-tailwind/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions system-tests/projects/next-15-tsconfig-tailwind/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
33 changes: 33 additions & 0 deletions system-tests/projects/next-15-tsconfig-tailwind/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}
22 changes: 22 additions & 0 deletions system-tests/projects/next-15-tsconfig-tailwind/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}

export default function RootLayout ({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react'
import Home from './page'

describe('<Home />', () => {
it('renders', () => {
cy.mount(<Home />)
cy.contains('h1', 'Welcome to Next.js!')

// verify tailwind classes are applied correctly via import from support file.
cy.get('main').should('have.css', 'background-color', 'rgb(245, 158, 11)')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react'
import Home from './page'

describe('<Home />', () => {
it('renders', () => {
cy.mount(<Home />)
cy.contains('h1', 'Welcome to Next.js!')
})
})
7 changes: 7 additions & 0 deletions system-tests/projects/next-15-tsconfig-tailwind/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Home () {
return (
<main className="bg-amber-500">
<h1> Welcome to Next.js! </h1>
</main>
)
}
Loading