Skip to content

Commit

Permalink
feat(commands): use waitForElement; add jest & cypress tests (#2)
Browse files Browse the repository at this point in the history
* feat(commands): use waitForElement; add jest & cypress tests

@kentcdodds I apologize for this one huge commit but I wanted to make
it all real, fast (in a few hours).

- change commands to use `waitForElement` from `dom-testing-library`
(updated to latest)
- update `kcd-scripts` dependency to latest
- add jest tests for basic smoke check on the exports
- add cypress tests for functionality check of the commands
- add npm scripts that run the tests in both development and CI
environments (somewhat convoluted because cypress hasn't got enough
hooks to spin up the fixture server from JavaScript; I didn't want to
write a Node app so I used `serve` which needs to be killed after
cypress exits but regardless of its exit code, so I used `npm-run-all`
and `fkill-cli` and a combination of npm scripts with
parallel/sequential execution)

* fix(ci): attempt to use `[` instead of `[[` for bash test tool

* fix(package): add missing `fkill-cli` needed by npm scripts

* Update package.json

* Update cypress.json

* Update commands.spec.js

* Update package.json
  • Loading branch information
sompylasar authored and Kent C. Dodds committed Apr 10, 2018
1 parent 5106558 commit ef22f87
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 39 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ dist
# when working with contributors
package-lock.json
yarn.lock

# cypress recordings during a test run are temporary files
/cypress/videos/
/cypress/screenshots/
5 changes: 5 additions & 0 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"baseUrl": "http://localhost:13370",
"videoRecording": false,
"screenshotOnHeadlessFailure": false
}
61 changes: 61 additions & 0 deletions cypress/fixtures/test-app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>cypress-testing-library</title>
<style>
blockquote {
margin: 0;
border-left: 4px solid grey;
padding-left: 10px;
color: grey;
}
section {
padding: 10px;
}
</style>
</head>
<body>
<blockquote>
No auto-reload after changing this static HTML markup:
click <span title="Run All Tests"></span> Run All Tests.
</blockquote>
<section>
<h2>getByPlaceholderText</h2>
<input type="text" placeholder="Placeholder Text" />
</section>
<section>
<h2>getByText</h2>
<button onclick="this.innerText = 'Button Clicked'">Button Text</button>
</section>
<section>
<h2>getByLabelText</h2>
<label for="input-labelled-by-id">Label For Input Labelled By Id</label>
<input type="text" placeholder="Input Labelled By Id" id="input-labelled-by-id" />
</section>
<section>
<h2>getByAltText</h2>
<img
src="data:image/png;base64,"
alt="Image Alt Text"
onclick="this.style.border = '5px solid red'"
/>
</section>
<section>
<h2>getByTestId</h2>
<img
data-testid="image-with-random-alt-tag"
src="data:image/png;base64,"
onclick="this.style.border = '5px solid red'"
/>
</section>
<!-- Prettier unindents the script tag below -->
<script>
document
.querySelector('[data-testid="image-with-random-alt-tag"]')
.setAttribute('alt', 'Image Random Alt Text ' + Math.random())
</script>
</body>
</html>
38 changes: 38 additions & 0 deletions cypress/integration/commands.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
describe('dom-testing-library commands', () => {
beforeEach(() => {
cy.visit('/')
})
it('getByPlaceholderText', () => {
cy
.getByPlaceholderText('Placeholder Text')
.click()
.type('Hello Placeholder')
})

it('getByText', () => {
cy
.getByText('Button Text')
.click()
})

it('getByLabelText', () => {
cy
.getByLabelText('Label For Input Labelled By Id')
.click()
.type('Hello Input Labelled By Id')
})

it('getByAltText', () => {
cy
.getByAltText('Image Alt Text')
.click()
})

it('getByTestId', () => {
cy
.getByTestId('image-with-random-alt-tag')
.click()
})
})

/* global cy */
2 changes: 2 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Keeping this file here, otherwise it gets recreated by Cypress on each run.
module.exports = () => {}
1 change: 1 addition & 0 deletions cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '../../src/add-commands'
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const jestConfig = require('kcd-scripts/jest')

module.exports = Object.assign(jestConfig, {
testEnvironment: 'jest-environment-jsdom',
})
24 changes: 18 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@
"add-contributor": "kcd-scripts contributors add",
"build": "kcd-scripts build",
"lint": "kcd-scripts lint",
"test": "echo TODO",
"test:ci": "echo TODO",
"test:update": "npm test -- --updateSnapshot --coverage",
"test": "npm-run-all --parallel test:unit test:cypress",
"test:unit": "kcd-scripts test --no-watch",
"test:unit:watch": "kcd-scripts test",
"test:cypress:serve": "serve --clipless --local --port 13370 ./cypress/fixtures/test-app",
"test:cypress:run": "cypress run",
"test:cypress:open": "cypress open",
"test:cypress": "npm-run-all --silent --parallel --race test:cypress:serve test:cypress:run",
"test:cypress:dev": "npm-run-all --silent --parallel --race test:cypress:serve test:cypress:open",
"validate": "kcd-scripts validate build,lint,test",
"setup": "npm install && npm run validate -s",
"precommit": "kcd-scripts precommit"
Expand All @@ -34,16 +39,23 @@
"author": "Kent C. Dodds <kent@doddsfamily.us> (http://kentcdodds.com/)",
"license": "MIT",
"dependencies": {
"dom-testing-library": "^1.0.0"
"dom-testing-library": "^1.3.0"
},
"devDependencies": {
"kcd-scripts": "^0.36.1"
"cypress": "^2.1.0",
"kcd-scripts": "^0.37.0",
"npm-run-all": "^4.1.2",
"serve": "^6.5.4"
},
"peerDependencies": {
"cypress": "^2.1.0"
},
"eslintConfig": {
"extends": "./node_modules/kcd-scripts/eslint.js"
"extends": "./node_modules/kcd-scripts/eslint.js",
"rules": {
"import/prefer-default-export": "off",
"import/no-unassigned-import": "off"
}
},
"eslintIgnore": [
"node_modules",
Expand Down
11 changes: 11 additions & 0 deletions src/__tests__/__snapshots__/commands.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`exports expected commands 1`] = `
Array [
"getByPlaceholderText",
"getByText",
"getByLabelText",
"getByAltText",
"getByTestId",
]
`;
19 changes: 19 additions & 0 deletions src/__tests__/add-commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {commands} from '../'

test('adds commands to Cypress', () => {
const addMock = jest.fn().mockName('Cypress.Commands.add')
global.Cypress = {Commands: {add: addMock}}
global.cy = {}

require('../add-commands')

expect(addMock).toHaveBeenCalledTimes(commands.length)
commands.forEach(({name}, index) => {
expect(addMock.mock.calls[index]).toMatchObject([
name,
// We get a new function that is `command.bind(null, cy)` i.e. global `cy` passed into the first argument.
// The commands themselves will be tested separately in the Cypress end-to-end tests.
expect.any(Function),
])
})
})
14 changes: 14 additions & 0 deletions src/__tests__/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {commands} from '../'

test('exports expected commands', () => {
expect(commands).toMatchObject(expect.any(Array))
expect(commands.map(({name}) => name)).toMatchSnapshot()
commands.forEach(command =>
expect(command).toMatchObject({
name: expect.any(String),
// We get a new function that wraps the respective query from `dom-testing-library`.
// The commands themselves will be tested separately in the Cypress end-to-end tests.
command: expect.any(Function),
}),
)
})
4 changes: 2 additions & 2 deletions src/add-commands.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {commands} from './'

commands.forEach(({name, command}) => {
Cypress.Commands.add(name, command)
Cypress.Commands.add(name, command.bind(null, cy))
})

/* global Cypress */
/* global Cypress, cy */
45 changes: 14 additions & 31 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,27 @@
import {queries} from 'dom-testing-library'
import {queries, waitForElement} from 'dom-testing-library'

const commands = Object.keys(queries)
.filter(queryName => queryName.startsWith('getBy'))
.map(queryName => {
return {
name: queryName,
command: (...args) => {
const fn = new Function(
'args',
'query',
'getCommandWaiter',
command: (cy, ...args) => {
const queryImpl = queries[queryName]
const commandImpl = doc =>
waitForElement(() => queryImpl(doc, ...args), {container: doc})
const thenHandler = new Function(
'commandImpl',
`
return function Command__${queryName}({document}) {
return getCommandWaiter(document, () => query(document, ...args))();
};
return function Command__${queryName}(thenArgs) {
return commandImpl(thenArgs.document)
}
`,
)(args, queries[queryName], getCommandWaiter)
return cy.window({log: false}).then(fn)
)(commandImpl)
return cy.window({log: false}).then(thenHandler)
},
}
})

function getCommandWaiter(container, fn) {
return function waiter() {
const val = fn()
if (val) {
return val
} else {
return new Promise(resolve => {
const observer = new MutationObserver(() => {
observer.disconnect()
resolve(waiter())
})
observer.observe(container, {subtree: true, childList: true})
})
}
}
}

export {commands, getCommandWaiter}
export {commands}

/* eslint no-new-func:0, import/default:0 */
/* global cy */
/* eslint no-new-func:0 */

0 comments on commit ef22f87

Please sign in to comment.