Skip to content
This repository was archived by the owner on Mar 5, 2022. It is now read-only.

Commit bf94dea

Browse files
feat: Nextjs support plugin (#422)
- closes #155 Co-authored-by: Gleb Bahmutov <[email protected]>
1 parent 54857cd commit bf94dea

File tree

23 files changed

+5835
-2161
lines changed

23 files changed

+5835
-2161
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ package-lock.json
88
.nyc_output
99
coverage
1010
*.generated.css
11+
.next

circle.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ workflows:
8585
npm run only-covered
8686
working_directory: examples/react-scripts
8787

88+
- cypress/run:
89+
name: Example Next.js
90+
requires:
91+
- Install
92+
executor: cypress/base-12
93+
# each example installs "cypress-react-unit-test" as a local dependency (symlink)
94+
install-command: npm install
95+
verify-command: echo 'Already verified'
96+
no-workspace: true
97+
working_directory: examples/nextjs
98+
command: npm test
99+
store_artifacts: true
100+
post-steps:
101+
- run:
102+
name: Check coverage 📈
103+
command: |
104+
npm run check-coverage
105+
npm run only-covered
106+
working_directory: examples/nextjs
107+
88108
- cypress/run:
89109
# react-scripts example with component tests not in "src" folder
90110
# but in "cypress/component" folder

cypress/component/advanced/framer-motion/Motion.spec.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ describe('framer-motion', () => {
1010
cy.get("[data-testid='motion']").should('have.css', 'border-radius', '20%')
1111
})
1212

13-
it('Mocks setTimeout and requestAnimationFrame', () => {
13+
// looks like cy.tick issue. Refer to the https://github.com/bahmutov/cypress-react-unit-test/issues/420
14+
it.skip('Mocks setTimeout and requestAnimationFrame', () => {
1415
cy.clock()
1516
mount(<Motion />)
1617

examples/nextjs/.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

examples/nextjs/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# example: Next.js
2+
3+
> A typical project using [next.js](https://nextjs.org/)
4+
5+
## Configuration
6+
7+
In order to reuse next's webpack configuration and all the custom configuration defined in `next.config.js` connect special plugin in [plugin file](./cypress/plugins/index.js)
8+
9+
```js
10+
const preprocessor = require('cypress-react-unit-test/plugins/next')
11+
12+
module.exports = (on, config) => {
13+
preprocessor(on, config)
14+
15+
return config
16+
}
17+
```
18+
19+
## Usage
20+
21+
1. Run `npm install` in this folder to install dependencies.
22+
23+
```bash
24+
# in this folder
25+
npm install
26+
```
27+
28+
3. Start Cypress
29+
30+
```bash
31+
npm run cy:open
32+
# or just run headless tests
33+
npm test
34+
```
35+
36+
## Server side props
37+
38+
⚠️⚠️ **Important:** ⚠️⚠️ Please do not test the page components using component testing. These components have too much responsibility and need to be tested as a part of your application flow. Consider using cypress `integration` tests.
39+
40+
But if you still want to test the page component, make sure that it will be mounted as any other pure `React` component. It means that next's specific functions like `getInitialProps` or `getStaticProps` **won't be called**.
41+
42+
But still you can call them manually:
43+
44+
```js
45+
IndexPage.getInitialProps().then(props => {
46+
mount(<IndexPage {...props} />)
47+
})
48+
49+
cy.contains(
50+
'`.getInitialProps()` was called and passed props to this component',
51+
)
52+
```
53+
54+
Find more examples in [Page.spec.jsx](./cypress/components/Page.spec.jsx).
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Hello, *world*!
2+
3+
Below is an example of JSX embedded in Markdown. <br /> **Try and change
4+
the background color!**
5+
6+
<div style={{ padding: '20px', backgroundColor: 'tomato' }}>
7+
<h3>This is JSX</h3>
8+
</div>

examples/nextjs/components/Search.jsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as React from 'react'
2+
3+
export function Search() {
4+
const [value, setValue] = React.useState('')
5+
return (
6+
<div>
7+
<input
8+
aria-label="search"
9+
value={value}
10+
onChange={e => setValue(e.currentTarget.value)}
11+
/>
12+
13+
<p className="search-text">You are searching for: {value}</p>
14+
15+
<style jsx>{`
16+
input {
17+
border-radius: 20px;
18+
}
19+
div {
20+
padding: 16px;
21+
background: tomato;
22+
}
23+
`}</style>
24+
</div>
25+
)
26+
}

examples/nextjs/cypress.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"testFiles": "**/*.spec.{js,jsx}",
3+
"viewportWidth": 500,
4+
"viewportHeight": 800,
5+
"experimentalComponentTesting": true,
6+
"experimentalFetchPolyfill": true,
7+
"componentFolder": "cypress/components"
8+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/// <reference types="cypress" />
2+
import * as React from 'react'
3+
import IndexPage from '../../pages/index'
4+
import { mount } from 'cypress-react-unit-test'
5+
6+
describe('NextJS page', () => {
7+
it('Renders page component', () => {
8+
mount(<IndexPage />)
9+
10+
cy.contains('Welcome to Next.js')
11+
})
12+
13+
it("It doesn't run the `.getInitialProps()`", () => {
14+
mount(<IndexPage />)
15+
16+
cy.get('[data-testid="server-result"').should(
17+
'not.contain',
18+
'`.getInitialProps()` was called and passed props to this component',
19+
)
20+
})
21+
22+
it('Allows to manually mock the server side props', () => {
23+
mount(<IndexPage asyncProp />)
24+
25+
cy.contains(
26+
'`.getInitialProps()` was called and passed props to this component',
27+
)
28+
})
29+
30+
it('can be tested with real .getInitialProps call', () => {
31+
IndexPage.getInitialProps().then(props => {
32+
mount(<IndexPage {...props} />)
33+
})
34+
35+
cy.contains(
36+
'`.getInitialProps()` was called and passed props to this component',
37+
)
38+
})
39+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference types="cypress" />
2+
import * as React from 'react'
3+
import { mount } from 'cypress-react-unit-test'
4+
import { Search } from '../../components/Search'
5+
import HelloWorld from '../../components/HelloWorld.mdx'
6+
7+
describe('<Search /> NextJS component', () => {
8+
it('Renders component', () => {
9+
mount(<Search />)
10+
11+
cy.get('input').type('124152')
12+
cy.contains('.search-text', '124152').should('be.visible')
13+
})
14+
15+
it('Renders mdx component using custom next.config.js', () => {
16+
mount(<HelloWorld />)
17+
18+
cy.contains('Hello').should('have.css', 'fontWeight', '700')
19+
cy.contains('This is JSX')
20+
.parent()
21+
.should('have.css', 'background-color', 'rgb(255, 99, 71)')
22+
})
23+
})
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "Using fixtures to represent data",
3+
"email": "[email protected]",
4+
"body": "Fixtures are a great way to mock data for responses to routes"
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference types="cypress" />
2+
describe('integration spec', () => {
3+
it('works', () => {
4+
expect(1).to.equal(1)
5+
})
6+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const preprocessor = require('cypress-react-unit-test/plugins/next')
2+
3+
/**
4+
* @type {Cypress.PluginConfig}
5+
*/
6+
module.exports = (on, config) => {
7+
preprocessor(on, config)
8+
9+
return config
10+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import 'cypress-react-unit-test/support'
2+
import '@cypress/code-coverage/support'

examples/nextjs/next.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const withMDX = require('@next/mdx')()
2+
const withSass = require('@zeit/next-sass')
3+
4+
module.exports = withSass(withMDX())

examples/nextjs/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "example-nextjs",
3+
"version": "0.1.0",
4+
"license": "MIT",
5+
"description": "Fancy Next.js app",
6+
"scripts": {
7+
"dev": "next",
8+
"build": "next build",
9+
"start": "next start",
10+
"test": "node ../../scripts/cypress-expect run --passing 7",
11+
"cy:open": "../../node_modules/.bin/cypress open",
12+
"build:static": "next build && next out",
13+
"check-coverage": "echo no code coverage yet",
14+
"only-covered": "echo no code coverage yet"
15+
},
16+
"devDependencies": {
17+
"cypress-react-unit-test": "file:../.."
18+
}
19+
}

examples/nextjs/pages/index.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Search } from '../components/Search'
2+
import HelloWorld from '../components/HelloWorld.mdx'
3+
4+
function IndexPage({ asyncProp }) {
5+
return (
6+
<main>
7+
<h1>Welcome to Next.js</h1>
8+
9+
{asyncProp && (
10+
<p data-testid="server-result">
11+
`.getInitialProps()` was called and passed props to this component
12+
</p>
13+
)}
14+
15+
<Search />
16+
<HelloWorld />
17+
</main>
18+
)
19+
}
20+
21+
IndexPage.getInitialProps = async ctx => {
22+
return { asyncProp: true }
23+
}
24+
25+
export default IndexPage

lib/mount.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,16 @@ export const unmount = () => {
174174
/**
175175
* Creates new instance of `mount` function with default options
176176
* @function createMount
177-
* @param {React.ReactElement} element - component to mount
178177
* @param {MountOptions} [defaultOptions] - defaultOptions for returned `mount` function
178+
* @returns new instance of `mount` with assigned options
179179
* @example
180180
* ```
181181
* import Hello from './hello.jsx'
182182
* import { createMount } from 'cypress-react-unit-test'
183+
*
184+
* const mount = createMount({ strict: true, cssFile: 'path/to/any/css/file.css' })
185+
*
183186
* it('works', () => {
184-
* const mount = createMount({ strict: true, cssFile: 'path/to/any/css/file.css' })
185187
* mount(<Hello />)
186188
* // use Cypress commands
187189
* cy.get('button').should('have.css', 'color', 'rgb(124, 12, 109)')
@@ -190,7 +192,7 @@ export const unmount = () => {
190192
**/
191193
export const createMount = (defaultOptions: MountOptions) => (
192194
element: React.ReactElement,
193-
options: MountOptions,
195+
options?: MountOptions,
194196
) => mount(element, { ...defaultOptions, ...options })
195197

196198
/** @deprecated Should be removed in the next major version */

0 commit comments

Comments
 (0)