From b8378b331ca498505343ff5d74c11cf24deb3358 Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Mon, 4 Sep 2023 14:13:10 -0600 Subject: [PATCH] Update Jest and Vitest example for App Router. --- examples/with-jest/README.md | 10 +++++++-- examples/with-jest/__tests__/index.test.tsx | 3 +++ examples/with-jest/__tests__/snapshot.tsx | 3 +++ .../with-jest/app/blog/[slug]/page.test.tsx | 11 ++++++++++ examples/with-jest/app/blog/[slug]/page.tsx | 13 +++++++++++ examples/with-jest/app/client/page.test.tsx | 10 +++++++++ examples/with-jest/app/client/page.tsx | 5 +++++ examples/with-jest/app/component.test.tsx | 12 ++++++++++ examples/with-jest/app/component.tsx | 13 +++++++++++ examples/with-jest/app/layout.tsx | 11 ++++++++++ examples/with-jest/app/rsc/page.test.tsx | 10 +++++++++ examples/with-jest/app/rsc/page.tsx | 9 ++++++++ examples/with-jest/jest.setup.js | 6 +---- examples/with-jest/package.json | 15 +++++++------ examples/with-vitest/README.md | 12 ++++++++-- examples/with-vitest/__tests__/Home.test.tsx | 2 +- .../with-vitest/app/blog/[slug]/page.test.tsx | 10 +++++++++ examples/with-vitest/app/blog/[slug]/page.tsx | 13 +++++++++++ examples/with-vitest/app/client/page.test.tsx | 10 +++++++++ examples/with-vitest/app/client/page.tsx | 5 +++++ examples/with-vitest/app/component.test.tsx | 10 +++++++++ examples/with-vitest/app/component.tsx | 13 +++++++++++ examples/with-vitest/app/layout.tsx | 11 ++++++++++ examples/with-vitest/app/rsc/page.test.tsx | 10 +++++++++ examples/with-vitest/app/rsc/page.tsx | 10 +++++++++ examples/with-vitest/next.config.js | 6 ----- examples/with-vitest/package.json | 22 +++++++++++-------- 27 files changed, 233 insertions(+), 32 deletions(-) create mode 100644 examples/with-jest/app/blog/[slug]/page.test.tsx create mode 100644 examples/with-jest/app/blog/[slug]/page.tsx create mode 100644 examples/with-jest/app/client/page.test.tsx create mode 100644 examples/with-jest/app/client/page.tsx create mode 100644 examples/with-jest/app/component.test.tsx create mode 100644 examples/with-jest/app/component.tsx create mode 100644 examples/with-jest/app/layout.tsx create mode 100644 examples/with-jest/app/rsc/page.test.tsx create mode 100644 examples/with-jest/app/rsc/page.tsx create mode 100644 examples/with-vitest/app/blog/[slug]/page.test.tsx create mode 100644 examples/with-vitest/app/blog/[slug]/page.tsx create mode 100644 examples/with-vitest/app/client/page.test.tsx create mode 100644 examples/with-vitest/app/client/page.tsx create mode 100644 examples/with-vitest/app/component.test.tsx create mode 100644 examples/with-vitest/app/component.tsx create mode 100644 examples/with-vitest/app/layout.tsx create mode 100644 examples/with-vitest/app/rsc/page.test.tsx create mode 100644 examples/with-vitest/app/rsc/page.tsx delete mode 100644 examples/with-vitest/next.config.js diff --git a/examples/with-jest/README.md b/examples/with-jest/README.md index 7b05b6cdfb891..9eb09468833f9 100644 --- a/examples/with-jest/README.md +++ b/examples/with-jest/README.md @@ -2,7 +2,13 @@ This example shows how to configure Jest to work with Next.js. -This includes Next.js' built-in support for Global CSS, CSS Modules and TypeScript. +This includes Next.js' built-in support for Global CSS, CSS Modules and TypeScript. This example also shows how to use Jest with the App Router and React Server Components. + +> **Note:** Since tests can be co-located alongside other files inside the App Router, we have placed those tests in `app/` to demonstrate this behavior (which is different than `pages/`). You can still place all tests in `__tests__` if you prefer. + +## Deploy your own + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-jest&project-name=with-jest&repository-name=with-jest) ## How to Use @@ -22,7 +28,7 @@ yarn create next-app --example with-jest with-jest-app pnpm create next-app --example with-jest with-jest-app ``` -## Run Jest Tests +## Running Tests ```bash npm test diff --git a/examples/with-jest/__tests__/index.test.tsx b/examples/with-jest/__tests__/index.test.tsx index 8640dbfb6a124..89acbdb6f8762 100644 --- a/examples/with-jest/__tests__/index.test.tsx +++ b/examples/with-jest/__tests__/index.test.tsx @@ -1,3 +1,6 @@ +/** + * @jest-environment jsdom + */ import { render, screen } from '@testing-library/react' import Home from '@/pages/index' diff --git a/examples/with-jest/__tests__/snapshot.tsx b/examples/with-jest/__tests__/snapshot.tsx index 4c46eba354db5..7544f485a08c2 100644 --- a/examples/with-jest/__tests__/snapshot.tsx +++ b/examples/with-jest/__tests__/snapshot.tsx @@ -1,3 +1,6 @@ +/** + * @jest-environment jsdom + */ import { render } from '@testing-library/react' import Home from '@/pages/index' diff --git a/examples/with-jest/app/blog/[slug]/page.test.tsx b/examples/with-jest/app/blog/[slug]/page.test.tsx new file mode 100644 index 0000000000000..b621001461c03 --- /dev/null +++ b/examples/with-jest/app/blog/[slug]/page.test.tsx @@ -0,0 +1,11 @@ +/** + * @jest-environment jsdom + */ +import { render, screen } from '@testing-library/react' +import '@testing-library/jest-dom' +import Page from './page' + +it('App Router: Works with dynamic route segments', () => { + render() + expect(screen.getByRole('heading')).toHaveTextContent('Slug: Test') +}) diff --git a/examples/with-jest/app/blog/[slug]/page.tsx b/examples/with-jest/app/blog/[slug]/page.tsx new file mode 100644 index 0000000000000..df9575a49a6dc --- /dev/null +++ b/examples/with-jest/app/blog/[slug]/page.tsx @@ -0,0 +1,13 @@ +type Params = { + params: { + slug: string + } +} + +export async function generateMetadata({ params }: Params) { + return { title: `Post: ${params.slug}` } +} + +export default function Page({ params }: Params) { + return

Slug: {params.slug}

+} diff --git a/examples/with-jest/app/client/page.test.tsx b/examples/with-jest/app/client/page.test.tsx new file mode 100644 index 0000000000000..5120843e0a0a7 --- /dev/null +++ b/examples/with-jest/app/client/page.test.tsx @@ -0,0 +1,10 @@ +/** + * @jest-environment jsdom + */ +import { render, screen } from '@testing-library/react' +import ClientComponent from './page' + +it('App Router: Works with Client Components', () => { + render() + expect(screen.getByRole('heading')).toHaveTextContent('Client Component') +}) diff --git a/examples/with-jest/app/client/page.tsx b/examples/with-jest/app/client/page.tsx new file mode 100644 index 0000000000000..63a6627147bce --- /dev/null +++ b/examples/with-jest/app/client/page.tsx @@ -0,0 +1,5 @@ +'use client' + +export default function ClientComponent() { + return

Client Component

+} diff --git a/examples/with-jest/app/component.test.tsx b/examples/with-jest/app/component.test.tsx new file mode 100644 index 0000000000000..afdf088e3f0ea --- /dev/null +++ b/examples/with-jest/app/component.test.tsx @@ -0,0 +1,12 @@ +/** + * @jest-environment jsdom + */ +import { fireEvent, render, screen } from '@testing-library/react' +import Component from './component' + +it('App Router: Works with Client Components (React State)', () => { + render() + expect(screen.getByRole('heading')).toHaveTextContent('0') + fireEvent.click(screen.getByRole('button')) + expect(screen.getByRole('heading')).toHaveTextContent('1') +}) diff --git a/examples/with-jest/app/component.tsx b/examples/with-jest/app/component.tsx new file mode 100644 index 0000000000000..8430160b348a9 --- /dev/null +++ b/examples/with-jest/app/component.tsx @@ -0,0 +1,13 @@ +import { useState } from 'react' + +export default function Counter() { + const [count, setCount] = useState(0) + return ( + <> +

{count}

+ + + ) +} diff --git a/examples/with-jest/app/layout.tsx b/examples/with-jest/app/layout.tsx new file mode 100644 index 0000000000000..dbce4ea8e3aeb --- /dev/null +++ b/examples/with-jest/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/examples/with-jest/app/rsc/page.test.tsx b/examples/with-jest/app/rsc/page.test.tsx new file mode 100644 index 0000000000000..ad9f208355307 --- /dev/null +++ b/examples/with-jest/app/rsc/page.test.tsx @@ -0,0 +1,10 @@ +/** + * @jest-environment jsdom + */ +import { render, screen } from '@testing-library/react' +import Page from './page' + +it('App Router: Works with Server Components', () => { + render() + expect(screen.getByRole('heading')).toHaveTextContent('App Router') +}) diff --git a/examples/with-jest/app/rsc/page.tsx b/examples/with-jest/app/rsc/page.tsx new file mode 100644 index 0000000000000..cf421efa0c3cf --- /dev/null +++ b/examples/with-jest/app/rsc/page.tsx @@ -0,0 +1,9 @@ +import 'server-only' + +export const metdata = { + title: 'App Router', +} + +export default function Page() { + return

App Router

+} diff --git a/examples/with-jest/jest.setup.js b/examples/with-jest/jest.setup.js index 996e74a2ba7bf..d5dc35681c6a1 100644 --- a/examples/with-jest/jest.setup.js +++ b/examples/with-jest/jest.setup.js @@ -1,6 +1,2 @@ -// Optional: configure or set up a testing framework before each test. -// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` - -// Used for __tests__/testing-library.js // Learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom/extend-expect' +import '@testing-library/jest-dom' diff --git a/examples/with-jest/package.json b/examples/with-jest/package.json index 978d6d25686b6..3d775c2ff6d3b 100644 --- a/examples/with-jest/package.json +++ b/examples/with-jest/package.json @@ -10,16 +10,17 @@ "dependencies": { "next": "latest", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "server-only": "^0.0.1" }, "devDependencies": { - "@testing-library/jest-dom": "5.16.4", + "@testing-library/jest-dom": "6.1.2", "@testing-library/react": "14.0.0", "@testing-library/user-event": "14.4.3", - "@types/react": "18.0.28", - "@types/testing-library__jest-dom": "5.14.5", - "jest": "29.5.0", - "jest-environment-jsdom": "29.5.0", - "typescript": "4.9.5" + "@types/react": "18.2.21", + "@types/testing-library__jest-dom": "6.0.0", + "jest": "29.6.4", + "jest-environment-jsdom": "29.6.4", + "typescript": "5.2.2" } } diff --git a/examples/with-vitest/README.md b/examples/with-vitest/README.md index 5ec5810a61a30..d1715cea057b0 100644 --- a/examples/with-vitest/README.md +++ b/examples/with-vitest/README.md @@ -2,9 +2,11 @@ This example shows how to use [Vitest](https://github.com/vitest-dev/vitest) with Next.js. -## Deploy your own +This includes Next.js' built-in support for Global CSS, CSS Modules and TypeScript. This example also shows how to use Vitest with the App Router and React Server Components. + +> **Note:** Since tests can be co-located alongside other files inside the App Router, we have placed those tests in `app/` to demonstrate this behavior (which is different than `pages/`). You can still place all tests in `__tests__` if you prefer. -Deploy the example using [Vercel](https://vercel.com/) or preview live with [StackBlitz](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-vitest) +## Deploy your own [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-vitest&project-name=with-vitest&repository-name=with-vitest) @@ -25,3 +27,9 @@ pnpm create next-app --example with-vitest with-vitest-app ``` Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). + +## Running Tests + +```bash +npm test +``` diff --git a/examples/with-vitest/__tests__/Home.test.tsx b/examples/with-vitest/__tests__/Home.test.tsx index c6f8dc2303427..3b6c636d07919 100644 --- a/examples/with-vitest/__tests__/Home.test.tsx +++ b/examples/with-vitest/__tests__/Home.test.tsx @@ -2,7 +2,7 @@ import { expect, test } from 'vitest' import { render, screen, within } from '@testing-library/react' import Home from '../pages' -test('home', () => { +test('Pages Router', () => { render() const main = within(screen.getByRole('main')) expect( diff --git a/examples/with-vitest/app/blog/[slug]/page.test.tsx b/examples/with-vitest/app/blog/[slug]/page.test.tsx new file mode 100644 index 0000000000000..c83cda0b37403 --- /dev/null +++ b/examples/with-vitest/app/blog/[slug]/page.test.tsx @@ -0,0 +1,10 @@ +import { expect, test } from 'vitest' +import { render, screen } from '@testing-library/react' +import Page from './page' + +test('App Router: Works with dynamic route segments', () => { + render() + expect( + screen.getByRole('heading', { level: 1, name: 'Slug: Test' }) + ).toBeDefined() +}) diff --git a/examples/with-vitest/app/blog/[slug]/page.tsx b/examples/with-vitest/app/blog/[slug]/page.tsx new file mode 100644 index 0000000000000..df9575a49a6dc --- /dev/null +++ b/examples/with-vitest/app/blog/[slug]/page.tsx @@ -0,0 +1,13 @@ +type Params = { + params: { + slug: string + } +} + +export async function generateMetadata({ params }: Params) { + return { title: `Post: ${params.slug}` } +} + +export default function Page({ params }: Params) { + return

Slug: {params.slug}

+} diff --git a/examples/with-vitest/app/client/page.test.tsx b/examples/with-vitest/app/client/page.test.tsx new file mode 100644 index 0000000000000..abb2cdb8c494f --- /dev/null +++ b/examples/with-vitest/app/client/page.test.tsx @@ -0,0 +1,10 @@ +import { expect, test } from 'vitest' +import { render, screen } from '@testing-library/react' +import ClientComponent from './page' + +test('App Router: Works with Client Components', () => { + render() + expect( + screen.getByRole('heading', { level: 1, name: 'Client Component' }) + ).toBeDefined() +}) diff --git a/examples/with-vitest/app/client/page.tsx b/examples/with-vitest/app/client/page.tsx new file mode 100644 index 0000000000000..63a6627147bce --- /dev/null +++ b/examples/with-vitest/app/client/page.tsx @@ -0,0 +1,5 @@ +'use client' + +export default function ClientComponent() { + return

Client Component

+} diff --git a/examples/with-vitest/app/component.test.tsx b/examples/with-vitest/app/component.test.tsx new file mode 100644 index 0000000000000..8871a7e5ff4f3 --- /dev/null +++ b/examples/with-vitest/app/component.test.tsx @@ -0,0 +1,10 @@ +import { expect, test } from 'vitest' +import { render, screen, fireEvent } from '@testing-library/react' +import Component from './component' + +test('App Router: Works with Client Components (React State)', () => { + render() + expect(screen.getByRole('heading', { level: 2, name: '0' })).toBeDefined() + fireEvent.click(screen.getByRole('button')) + expect(screen.getByRole('heading', { level: 2, name: '1' })).toBeDefined() +}) diff --git a/examples/with-vitest/app/component.tsx b/examples/with-vitest/app/component.tsx new file mode 100644 index 0000000000000..8430160b348a9 --- /dev/null +++ b/examples/with-vitest/app/component.tsx @@ -0,0 +1,13 @@ +import { useState } from 'react' + +export default function Counter() { + const [count, setCount] = useState(0) + return ( + <> +

{count}

+ + + ) +} diff --git a/examples/with-vitest/app/layout.tsx b/examples/with-vitest/app/layout.tsx new file mode 100644 index 0000000000000..dbce4ea8e3aeb --- /dev/null +++ b/examples/with-vitest/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/examples/with-vitest/app/rsc/page.test.tsx b/examples/with-vitest/app/rsc/page.test.tsx new file mode 100644 index 0000000000000..1cd07a2ece43d --- /dev/null +++ b/examples/with-vitest/app/rsc/page.test.tsx @@ -0,0 +1,10 @@ +import { expect, test } from 'vitest' +import { render, screen } from '@testing-library/react' +import Page from './page' + +test('App Router: Works with Server Components', () => { + render() + expect( + screen.getByRole('heading', { level: 1, name: 'App Router' }) + ).toBeDefined() +}) diff --git a/examples/with-vitest/app/rsc/page.tsx b/examples/with-vitest/app/rsc/page.tsx new file mode 100644 index 0000000000000..3947bc54e94f1 --- /dev/null +++ b/examples/with-vitest/app/rsc/page.tsx @@ -0,0 +1,10 @@ +// import 'server-only' does not currently +// work with Vitest + +export const metdata = { + title: 'App Router', +} + +export default function Page() { + return

App Router

+} diff --git a/examples/with-vitest/next.config.js b/examples/with-vitest/next.config.js deleted file mode 100644 index a843cbee09afa..0000000000000 --- a/examples/with-vitest/next.config.js +++ /dev/null @@ -1,6 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, -} - -module.exports = nextConfig diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json index 973a91ffeed55..6a5d1b84c7c0a 100644 --- a/examples/with-vitest/package.json +++ b/examples/with-vitest/package.json @@ -8,16 +8,20 @@ }, "dependencies": { "next": "latest", - "react": "18.0.0", - "react-dom": "18.0.0" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "server-only": "^0.0.1" }, "devDependencies": { - "@testing-library/react": "^12.1.4", - "@types/node": "17.0.23", - "@types/react": "17.0.43", - "@vitejs/plugin-react": "1.3.0", - "jsdom": "^19.0.0", - "typescript": "4.6.3", - "vitest": "0.8.4" + "@testing-library/jest-dom": "6.1.2", + "@testing-library/react": "14.0.0", + "@testing-library/user-event": "14.4.3", + "@types/node": "20.5.9", + "@types/react": "18.2.21", + "@types/testing-library__jest-dom": "6.0.0", + "@vitejs/plugin-react": "4.0.4", + "jsdom": "^22.1.0", + "typescript": "5.2.2", + "vitest": "0.34.3" } }