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

Commit

Permalink
feat: support cssFile style
Browse files Browse the repository at this point in the history
  • Loading branch information
bahmutov committed Apr 3, 2020
1 parent 796d248 commit fbd07b1
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 48 deletions.
55 changes: 28 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
## TLDR

* What is this? This package allows you to use [Cypress](https://www.cypress.io/) test runner to unit test your React components with zero effort.
- What is this? This package allows you to use [Cypress](https://www.cypress.io/) test runner to unit test your React components with zero effort.

* How is this different from [Enzyme](https://github.com/airbnb/enzyme)? It is similar in functionality BUT runs the component in the real browser with full power of Cypress E2E test runner: [live GUI, full API, screen recording, CI support, cross-platform](https://www.cypress.io/features/).
- How is this different from [Enzyme](https://github.com/airbnb/enzyme)? It is similar in functionality BUT runs the component in the real browser with full power of Cypress E2E test runner: [live GUI, full API, screen recording, CI support, cross-platform](https://www.cypress.io/features/).

## Known problems

Expand Down Expand Up @@ -50,8 +50,7 @@ describe('HelloState component', () => {
cy.contains('Hello Spider-man!')
// mounted component can be selected via its name, function, or JSX
// e.g. '@HelloState', HelloState, or <HelloState />
cy.get(HelloState)
.invoke('setState', { name: 'React' })
cy.get(HelloState).invoke('setState', { name: 'React' })
cy.get(HelloState)
.its('state')
.should('deep.equal', { name: 'React' })
Expand Down Expand Up @@ -81,9 +80,12 @@ it('can be passed as an option', () => {
color: white;
}
`
cy.mount(<Button name='Orange' orange />, null, { style })
cy.get('.orange button')
.should('have.css', 'background-color', 'rgb(245, 146, 62)')
cy.mount(<Button name="Orange" orange />, { style })
cy.get('.orange button').should(
'have.css',
'background-color',
'rgb(245, 146, 62)',
)
})
```

Expand All @@ -98,17 +100,16 @@ import './styles.css'
You can read the CSS file and pass it as `style` option yourself

```js
cy.readFile('cypress/integration/Button.css')
.then(style => {
cy.mount(<Button name='Orange' orange />, null, { style })
})
cy.readFile('cypress/integration/Button.css').then(style => {
cy.mount(<Button name="Orange" orange />, { style })
})
```

You can even let Cypress read the file and inject the style

```js
const cssFile = 'cypress/integration/Button.css'
cy.mount(<Button name='Orange' orange />, null, { cssFile })
cy.mount(<Button name="Orange" orange />, { cssFile })
```

See [cypress/integration/inject-style-spec.js](cypress/integration/inject-style-spec.js) for more examples.
Expand Down Expand Up @@ -136,18 +137,18 @@ How can we use features that require transpilation? By using [@cypress/webpack-p

All components are in [src](src) folder. All tests are in [cypress/integration](cypress/integration) folder.

* [hello-world-spec.js](cypress/integration/hello-world-spec.js) - testing the simplest React component from [hello-world.jsx](src/hello-world.jsx)
* [hello-x-spec.js](cypress/integration/hello-x-spec.js) - testing React component with props and state [hello-x.jsx](src/hello-x.jsx)
* [counter-spec.js](cypress/integration/counter-spec.js) clicks on the component and confirms the result
* [stateless-spec.js](cypress/integration/stateless-spec.js) shows testing a stateless component from [stateless.jsx](src/stateless.jsx)
* [transpiled-spec.js](cypress/integration/stateless-spec.js) shows testing a component with class properties syntax from [transpiled.jsx](src/stateless.jsx)
* [error-boundary-spec.js](cypress/integration/error-boundary-spec.js) shows testing a component acting as an error boundary from [error-boundary.jsx](src/error-boundary.jsx)
* [users-spec.js](cypress/integration/users-spec.js) shows how to observe XHR requests, mock server responses for component [users.jsx](src/users.jsx)
* [alert-spec.js](cypress/integration/alert-spec.js) shows how to spy on `window.alert` calls from your component [stateless-alert.jsx](src/stateless-alert.jsx)
- [hello-world-spec.js](cypress/integration/hello-world-spec.js) - testing the simplest React component from [hello-world.jsx](src/hello-world.jsx)
- [hello-x-spec.js](cypress/integration/hello-x-spec.js) - testing React component with props and state [hello-x.jsx](src/hello-x.jsx)
- [counter-spec.js](cypress/integration/counter-spec.js) clicks on the component and confirms the result
- [stateless-spec.js](cypress/integration/stateless-spec.js) shows testing a stateless component from [stateless.jsx](src/stateless.jsx)
- [transpiled-spec.js](cypress/integration/stateless-spec.js) shows testing a component with class properties syntax from [transpiled.jsx](src/stateless.jsx)
- [error-boundary-spec.js](cypress/integration/error-boundary-spec.js) shows testing a component acting as an error boundary from [error-boundary.jsx](src/error-boundary.jsx)
- [users-spec.js](cypress/integration/users-spec.js) shows how to observe XHR requests, mock server responses for component [users.jsx](src/users.jsx)
- [alert-spec.js](cypress/integration/alert-spec.js) shows how to spy on `window.alert` calls from your component [stateless-alert.jsx](src/stateless-alert.jsx)

## Large examples

* [bahmutov/calculator](https://github.com/bahmutov/calculator) tests multiple components: calculator App, Button, Display.
- [bahmutov/calculator](https://github.com/bahmutov/calculator) tests multiple components: calculator App, Button, Display.

## Development

Expand All @@ -171,12 +172,12 @@ Uses [Percy.io](https://percy.io) visual diffing service as a GitHub pull reques

Same feature for unit testing components from other frameworks using Cypress

* [cypress-vue-unit-test](https://github.com/bahmutov/cypress-vue-unit-test)
* [cypress-cycle-unit-test](https://github.com/bahmutov/cypress-cycle-unit-test)
* [cypress-svelte-unit-test](https://github.com/bahmutov/cypress-svelte-unit-test)
* [cypress-angular-unit-test](https://github.com/bahmutov/cypress-angular-unit-test)
* [cypress-hyperapp-unit-test](https://github.com/bahmutov/cypress-hyperapp-unit-test)
* [cypress-angularjs-unit-test](https://github.com/bahmutov/cypress-angularjs-unit-test)
- [cypress-vue-unit-test](https://github.com/bahmutov/cypress-vue-unit-test)
- [cypress-cycle-unit-test](https://github.com/bahmutov/cypress-cycle-unit-test)
- [cypress-svelte-unit-test](https://github.com/bahmutov/cypress-svelte-unit-test)
- [cypress-angular-unit-test](https://github.com/bahmutov/cypress-angular-unit-test)
- [cypress-hyperapp-unit-test](https://github.com/bahmutov/cypress-hyperapp-unit-test)
- [cypress-angularjs-unit-test](https://github.com/bahmutov/cypress-angularjs-unit-test)

[renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg
[renovate-app]: https://renovateapp.com/
16 changes: 16 additions & 0 deletions cypress/component/component tests/basic/css-file/css-file-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// <reference types="cypress" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'

describe('cssFile', () => {
it('is loaded', () => {
const Component = () => <button className="green">Green button</button>
mount(<Component />, {
cssFile: 'cypress/component/component tests/basic/css-file/index.css',
})

cy.get('button')
.should('have.class', 'green')
.and('have.css', 'background-color', 'rgb(0, 255, 0)')
})
})
3 changes: 3 additions & 0 deletions cypress/component/component tests/basic/css-file/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
button.green {
background-color: #00ff00;
}
61 changes: 40 additions & 21 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,45 @@ interface MountOptions {
ReactDom?: typeof ReactDOM
stylesheets?: string | string[]
style?: string
cssFile?: string
}

/**
* Inject custom style text or CSS file or 3rd party style resources
*/
const injectStyles = (options: MountOptions) => () => {
const document = cy.state('document')

const el = document.getElementById('cypress-jsdom')

// insert any custom global styles before the component
if (typeof options.stylesheets === 'string') {
options.stylesheets = [options.stylesheets]
}
if (Array.isArray(options.stylesheets)) {
// console.log('adding stylesheets')
options.stylesheets.forEach(href => {
const link = document.createElement('link')
link.type = 'text/css'
link.rel = 'stylesheet'
link.href = href
document.body.insertBefore(link, el)
})
}

if (options.style) {
const style = document.createElement('style')
style.appendChild(document.createTextNode(options.style))
document.body.insertBefore(style, el)
}

if (options.cssFile) {
return cy.readFile(options.cssFile).then(css => {
const style = document.createElement('style')
style.appendChild(document.createTextNode(css))
document.body.appendChild(style)
})
}
}

/**
Expand Down Expand Up @@ -55,33 +94,13 @@ export const mount = (jsx: React.ReactElement, options: MountOptions = {}) => {
},
})
})
.then(injectStyles(options))
.then(() => {
const document = cy.state('document')
const reactDomToUse = options.ReactDom || ReactDOM

const el = document.getElementById('cypress-jsdom')

// insert any custom global styles before the component
if (typeof options.stylesheets === 'string') {
options.stylesheets = [options.stylesheets]
}
if (Array.isArray(options.stylesheets)) {
console.log('adding stylesheets')
options.stylesheets.forEach(href => {
const link = document.createElement('link')
link.type = 'text/css'
link.rel = 'stylesheet'
link.href = href
document.body.insertBefore(link, el)
})
}

if (options.style) {
const style = document.createElement('style')
style.appendChild(document.createTextNode(options.style))
document.body.insertBefore(style, el)
}

const props = {
// @ts-ignore provide unique key to the the wrapped component to make sure we are rerendering between tests
key: Cypress?.mocha?.getRunner()?.test?.title || Math.random(),
Expand Down

0 comments on commit fbd07b1

Please sign in to comment.