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

Commit

Permalink
feat: Nextjs support plugin (#422)
Browse files Browse the repository at this point in the history
- closes #155 

Co-authored-by: Gleb Bahmutov <[email protected]>
  • Loading branch information
dmtrKovalenko and bahmutov authored Sep 16, 2020
1 parent 54857cd commit bf94dea
Show file tree
Hide file tree
Showing 23 changed files with 5,835 additions and 2,161 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ package-lock.json
.nyc_output
coverage
*.generated.css
.next
20 changes: 20 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ workflows:
npm run only-covered
working_directory: examples/react-scripts

- cypress/run:
name: Example Next.js
requires:
- Install
executor: cypress/base-12
# each example installs "cypress-react-unit-test" as a local dependency (symlink)
install-command: npm install
verify-command: echo 'Already verified'
no-workspace: true
working_directory: examples/nextjs
command: npm test
store_artifacts: true
post-steps:
- run:
name: Check coverage 📈
command: |
npm run check-coverage
npm run only-covered
working_directory: examples/nextjs

- cypress/run:
# react-scripts example with component tests not in "src" folder
# but in "cypress/component" folder
Expand Down
3 changes: 2 additions & 1 deletion cypress/component/advanced/framer-motion/Motion.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ describe('framer-motion', () => {
cy.get("[data-testid='motion']").should('have.css', 'border-radius', '20%')
})

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

Expand Down
1 change: 1 addition & 0 deletions examples/nextjs/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
54 changes: 54 additions & 0 deletions examples/nextjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# example: Next.js

> A typical project using [next.js](https://nextjs.org/)
## Configuration

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)

```js
const preprocessor = require('cypress-react-unit-test/plugins/next')

module.exports = (on, config) => {
preprocessor(on, config)

return config
}
```

## Usage

1. Run `npm install` in this folder to install dependencies.

```bash
# in this folder
npm install
```

3. Start Cypress

```bash
npm run cy:open
# or just run headless tests
npm test
```

## Server side props

⚠️⚠️ **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.

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**.

But still you can call them manually:

```js
IndexPage.getInitialProps().then(props => {
mount(<IndexPage {...props} />)
})

cy.contains(
'`.getInitialProps()` was called and passed props to this component',
)
```

Find more examples in [Page.spec.jsx](./cypress/components/Page.spec.jsx).
8 changes: 8 additions & 0 deletions examples/nextjs/components/HelloWorld.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Hello, *world*!

Below is an example of JSX embedded in Markdown. <br /> **Try and change
the background color!**

<div style={{ padding: '20px', backgroundColor: 'tomato' }}>
<h3>This is JSX</h3>
</div>
26 changes: 26 additions & 0 deletions examples/nextjs/components/Search.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react'

export function Search() {
const [value, setValue] = React.useState('')
return (
<div>
<input
aria-label="search"
value={value}
onChange={e => setValue(e.currentTarget.value)}
/>

<p className="search-text">You are searching for: {value}</p>

<style jsx>{`
input {
border-radius: 20px;
}
div {
padding: 16px;
background: tomato;
}
`}</style>
</div>
)
}
8 changes: 8 additions & 0 deletions examples/nextjs/cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"testFiles": "**/*.spec.{js,jsx}",
"viewportWidth": 500,
"viewportHeight": 800,
"experimentalComponentTesting": true,
"experimentalFetchPolyfill": true,
"componentFolder": "cypress/components"
}
39 changes: 39 additions & 0 deletions examples/nextjs/cypress/components/Page.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/// <reference types="cypress" />
import * as React from 'react'
import IndexPage from '../../pages/index'
import { mount } from 'cypress-react-unit-test'

describe('NextJS page', () => {
it('Renders page component', () => {
mount(<IndexPage />)

cy.contains('Welcome to Next.js')
})

it("It doesn't run the `.getInitialProps()`", () => {
mount(<IndexPage />)

cy.get('[data-testid="server-result"').should(
'not.contain',
'`.getInitialProps()` was called and passed props to this component',
)
})

it('Allows to manually mock the server side props', () => {
mount(<IndexPage asyncProp />)

cy.contains(
'`.getInitialProps()` was called and passed props to this component',
)
})

it('can be tested with real .getInitialProps call', () => {
IndexPage.getInitialProps().then(props => {
mount(<IndexPage {...props} />)
})

cy.contains(
'`.getInitialProps()` was called and passed props to this component',
)
})
})
23 changes: 23 additions & 0 deletions examples/nextjs/cypress/components/Search.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference types="cypress" />
import * as React from 'react'
import { mount } from 'cypress-react-unit-test'
import { Search } from '../../components/Search'
import HelloWorld from '../../components/HelloWorld.mdx'

describe('<Search /> NextJS component', () => {
it('Renders component', () => {
mount(<Search />)

cy.get('input').type('124152')
cy.contains('.search-text', '124152').should('be.visible')
})

it('Renders mdx component using custom next.config.js', () => {
mount(<HelloWorld />)

cy.contains('Hello').should('have.css', 'fontWeight', '700')
cy.contains('This is JSX')
.parent()
.should('have.css', 'background-color', 'rgb(255, 99, 71)')
})
})
5 changes: 5 additions & 0 deletions examples/nextjs/cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
6 changes: 6 additions & 0 deletions examples/nextjs/cypress/integration/cy.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="cypress" />
describe('integration spec', () => {
it('works', () => {
expect(1).to.equal(1)
})
})
10 changes: 10 additions & 0 deletions examples/nextjs/cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const preprocessor = require('cypress-react-unit-test/plugins/next')

/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
preprocessor(on, config)

return config
}
2 changes: 2 additions & 0 deletions examples/nextjs/cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import 'cypress-react-unit-test/support'
import '@cypress/code-coverage/support'
4 changes: 4 additions & 0 deletions examples/nextjs/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const withMDX = require('@next/mdx')()
const withSass = require('@zeit/next-sass')

module.exports = withSass(withMDX())
19 changes: 19 additions & 0 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "example-nextjs",
"version": "0.1.0",
"license": "MIT",
"description": "Fancy Next.js app",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"test": "node ../../scripts/cypress-expect run --passing 7",
"cy:open": "../../node_modules/.bin/cypress open",
"build:static": "next build && next out",
"check-coverage": "echo no code coverage yet",
"only-covered": "echo no code coverage yet"
},
"devDependencies": {
"cypress-react-unit-test": "file:../.."
}
}
25 changes: 25 additions & 0 deletions examples/nextjs/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Search } from '../components/Search'
import HelloWorld from '../components/HelloWorld.mdx'

function IndexPage({ asyncProp }) {
return (
<main>
<h1>Welcome to Next.js</h1>

{asyncProp && (
<p data-testid="server-result">
`.getInitialProps()` was called and passed props to this component
</p>
)}

<Search />
<HelloWorld />
</main>
)
}

IndexPage.getInitialProps = async ctx => {
return { asyncProp: true }
}

export default IndexPage
8 changes: 5 additions & 3 deletions lib/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,16 @@ export const unmount = () => {
/**
* Creates new instance of `mount` function with default options
* @function createMount
* @param {React.ReactElement} element - component to mount
* @param {MountOptions} [defaultOptions] - defaultOptions for returned `mount` function
* @returns new instance of `mount` with assigned options
* @example
* ```
* import Hello from './hello.jsx'
* import { createMount } from 'cypress-react-unit-test'
*
* const mount = createMount({ strict: true, cssFile: 'path/to/any/css/file.css' })
*
* it('works', () => {
* const mount = createMount({ strict: true, cssFile: 'path/to/any/css/file.css' })
* mount(<Hello />)
* // use Cypress commands
* cy.get('button').should('have.css', 'color', 'rgb(124, 12, 109)')
Expand All @@ -190,7 +192,7 @@ export const unmount = () => {
**/
export const createMount = (defaultOptions: MountOptions) => (
element: React.ReactElement,
options: MountOptions,
options?: MountOptions,
) => mount(element, { ...defaultOptions, ...options })

/** @deprecated Should be removed in the next major version */
Expand Down
Loading

0 comments on commit bf94dea

Please sign in to comment.