added cypress

This commit is contained in:
Rick van Lieshout 2022-06-27 18:04:55 +02:00
parent 3cb1759648
commit 30f27d26e6
16 changed files with 2347 additions and 12 deletions

View File

@ -5,7 +5,13 @@
"es6": true "es6": true
}, },
"plugins": ["import"], "plugins": ["import"],
"extends": ["eslint:recommended", "prettier", "react-app", "react-app/jest"], "extends": [
"eslint:recommended",
"prettier",
"react-app",
"react-app/jest",
"plugin:cypress/recommended"
],
"parserOptions": { "parserOptions": {
"ecmaVersion": 2020, "ecmaVersion": 2020,
"sourceType": "module", "sourceType": "module",

2
.gitignore vendored
View File

@ -66,3 +66,5 @@ bundle.zip
#SonarCloud #SonarCloud
.scannerwork .scannerwork
.angular/cache/ .angular/cache/
cypress/videos

View File

@ -8,6 +8,7 @@
"reduxjs", "reduxjs",
"SVGR", "SVGR",
"tailwindcss", "tailwindcss",
"testid",
"typeahead", "typeahead",
"uncompiled" "uncompiled"
], ],

View File

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.2.0] - 2022-06-27
- Added [cypress.io](https://www.cypress.io/)
## [0.1.0] - 2022-06-27 ## [0.1.0] - 2022-06-27
- Updated to React 18 - Updated to React 18

View File

@ -31,6 +31,7 @@ Only the important files are shown
. .
├── .vscode # vscode setup (debug, snippets, etc) ├── .vscode # vscode setup (debug, snippets, etc)
├── config # tool configuration ├── config # tool configuration
├── cypress # e2e tests
├── dist # production version ├── dist # production version
├── public # directory with public files (config, icons, etc) ├── public # directory with public files (config, icons, etc)
├── scripts # Modified default create-react-app scripts ├── scripts # Modified default create-react-app scripts

10
cypress.config.ts Normal file
View File

@ -0,0 +1,10 @@
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
video: false,
});

View File

@ -0,0 +1,14 @@
describe("Application navigation", () => {
beforeEach(() => {
cy.visit("http://localhost:3000");
});
it("Should render the navigation links", () => {
cy.get("[data-testid='nav']");
});
it("Should navigate to about when clicking on About", () => {
cy.get('[data-testid="nav.about"]').click();
cy.contains("about");
});
});

View File

@ -0,0 +1,143 @@
/// <reference types="cypress" />
// Welcome to Cypress!
//
// This spec file contains a variety of sample tests
// for a todo list app that are designed to demonstrate
// the power of writing tests in Cypress.
//
// To learn more about how Cypress works and
// what makes it such an awesome testing tool,
// please read our getting started guide:
// https://on.cypress.io/introduction-to-cypress
describe('example to-do app', () => {
beforeEach(() => {
// Cypress starts out with a blank slate for each test
// so we must tell it to visit our website with the `cy.visit()` command.
// Since we want to visit the same URL at the start of all our tests,
// we include it in our beforeEach function so that it runs before each test
cy.visit('https://example.cypress.io/todo')
})
it('displays two todo items by default', () => {
// We use the `cy.get()` command to get all elements that match the selector.
// Then, we use `should` to assert that there are two matched items,
// which are the two default items.
cy.get('.todo-list li').should('have.length', 2)
// We can go even further and check that the default todos each contain
// the correct text. We use the `first` and `last` functions
// to get just the first and last matched elements individually,
// and then perform an assertion with `should`.
cy.get('.todo-list li').first().should('have.text', 'Pay electric bill')
cy.get('.todo-list li').last().should('have.text', 'Walk the dog')
})
it('can add new todo items', () => {
// We'll store our item text in a variable so we can reuse it
const newItem = 'Feed the cat'
// Let's get the input element and use the `type` command to
// input our new list item. After typing the content of our item,
// we need to type the enter key as well in order to submit the input.
// This input has a data-test attribute so we'll use that to select the
// element in accordance with best practices:
// https://on.cypress.io/selecting-elements
cy.get('[data-test=new-todo]').type(`${newItem}{enter}`)
// Now that we've typed our new item, let's check that it actually was added to the list.
// Since it's the newest item, it should exist as the last element in the list.
// In addition, with the two default items, we should have a total of 3 elements in the list.
// Since assertions yield the element that was asserted on,
// we can chain both of these assertions together into a single statement.
cy.get('.todo-list li')
.should('have.length', 3)
.last()
.should('have.text', newItem)
})
it('can check off an item as completed', () => {
// In addition to using the `get` command to get an element by selector,
// we can also use the `contains` command to get an element by its contents.
// However, this will yield the <label>, which is lowest-level element that contains the text.
// In order to check the item, we'll find the <input> element for this <label>
// by traversing up the dom to the parent element. From there, we can `find`
// the child checkbox <input> element and use the `check` command to check it.
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
// Now that we've checked the button, we can go ahead and make sure
// that the list element is now marked as completed.
// Again we'll use `contains` to find the <label> element and then use the `parents` command
// to traverse multiple levels up the dom until we find the corresponding <li> element.
// Once we get that element, we can assert that it has the completed class.
cy.contains('Pay electric bill')
.parents('li')
.should('have.class', 'completed')
})
context('with a checked task', () => {
beforeEach(() => {
// We'll take the command we used above to check off an element
// Since we want to perform multiple tests that start with checking
// one element, we put it in the beforeEach hook
// so that it runs at the start of every test.
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
})
it('can filter for uncompleted tasks', () => {
// We'll click on the "active" button in order to
// display only incomplete items
cy.contains('Active').click()
// After filtering, we can assert that there is only the one
// incomplete item in the list.
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Walk the dog')
// For good measure, let's also assert that the task we checked off
// does not exist on the page.
cy.contains('Pay electric bill').should('not.exist')
})
it('can filter for completed tasks', () => {
// We can perform similar steps as the test above to ensure
// that only completed tasks are shown
cy.contains('Completed').click()
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Pay electric bill')
cy.contains('Walk the dog').should('not.exist')
})
it('can delete all completed tasks', () => {
// First, let's click the "Clear completed" button
// `contains` is actually serving two purposes here.
// First, it's ensuring that the button exists within the dom.
// This button only appears when at least one task is checked
// so this command is implicitly verifying that it does exist.
// Second, it selects the button so we can click it.
cy.contains('Clear completed').click()
// Then we can make sure that there is only one element
// in the list and our element does not exist
cy.get('.todo-list li')
.should('have.length', 1)
.should('not.have.text', 'Pay electric bill')
// Finally, make sure that the clear button no longer exists.
cy.contains('Clear completed').should('not.exist')
})
})
})

View File

@ -0,0 +1,5 @@
{
"name": "Some name",
"email": "info@someName.com",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }

20
cypress/support/e2e.ts Normal file
View File

@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

2076
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "react-starter-kit", "name": "react-starter-kit",
"version": "0.1.0", "version": "0.2.0",
"description": "A modern, create-react-app-based, starter kit for React projects", "description": "A modern, create-react-app-based, starter kit for React projects",
"keywords": [ "keywords": [
"react", "react",
@ -27,14 +27,17 @@
"build": "node scripts/build.js", "build": "node scripts/build.js",
"lint": "eslint src/**", "lint": "eslint src/**",
"organize-package-json": "npx format-package -w && npx sort-package-json", "organize-package-json": "npx format-package -w && npx sort-package-json",
"e2e": "cypress open -d --e2e",
"e2e-ci": "cypress run",
"start": "node scripts/start.js", "start": "node scripts/start.js",
"test": "node scripts/test.js", "test": "node scripts/test.js",
"test-ci": "node scripts/test.js --ci",
"test-live-coverage": " concurrently --kill-others \"npm run test-with-coverage\" \"npx http-server -c-1 coverage/lcov-report\"", "test-live-coverage": " concurrently --kill-others \"npm run test-with-coverage\" \"npx http-server -c-1 coverage/lcov-report\"",
"test-with-coverage": "node scripts/test.js --coverage" "test-with-coverage": "node scripts/test.js --coverage"
}, },
"husky": { "husky": {
"hooks": { "hooks": {
"pre-commit": "npm run lint && lint-staged" "pre-commit": "npm run lint && lint-staged && npm run test-ci && npm run e2e-ci"
} }
}, },
"lint-staged": { "lint-staged": {
@ -73,11 +76,13 @@
"concurrently": "^7.2.2", "concurrently": "^7.2.2",
"css-loader": "^6.5.1", "css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^3.2.0", "css-minimizer-webpack-plugin": "^3.2.0",
"cypress": "^10.2.0",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"dotenv-expand": "^5.1.0", "dotenv-expand": "^5.1.0",
"eslint": "^8.3.0", "eslint": "^8.3.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-react": "^7.30.1", "eslint-plugin-react": "^7.30.1",
"eslint-watch": "^8.0.0", "eslint-watch": "^8.0.0",

View File

@ -2,8 +2,11 @@ const config = {
version: "0.1.0", version: "0.1.0",
}; };
try {
window.config = config; window.config = config;
if (module) { if (module) {
module.exports = config; module.exports = config;
} }
} catch {
// ignore
}

View File

@ -38,7 +38,12 @@ function isInMercurialRepository() {
} }
// Watch unless on CI or explicitly running all tests // Watch unless on CI or explicitly running all tests
if (!process.env.CI && argv.indexOf("--watchAll") === -1 && argv.indexOf("--watchAll=false") === -1) { if (
!process.env.CI &&
!argv.indexOf("--ci" !== -1) &&
argv.indexOf("--watchAll") === -1 &&
argv.indexOf("--watchAll=false") === -1
) {
// https://github.com/facebook/create-react-app/issues/5210 // https://github.com/facebook/create-react-app/issues/5210
const hasSourceControl = isInGitRepository() || isInMercurialRepository(); const hasSourceControl = isInGitRepository() || isInMercurialRepository();
argv.push(hasSourceControl ? "--watch" : "--watchAll"); argv.push(hasSourceControl ? "--watch" : "--watchAll");

View File

@ -10,8 +10,15 @@ export const Navbar: FunctionComponent<Props> = () => {
<h1>Our fancy header with navigation.</h1> <h1>Our fancy header with navigation.</h1>
<p>App version: {JSON.stringify(Config.version)}</p> <p>App version: {JSON.stringify(Config.version)}</p>
<nav data-testid="nav"> <nav data-testid="nav">
<Link to="/">Home</Link> <Link to="/about">About</Link> <Link to="/" data-testid="nav.home">
<Link to="/counter">Counter</Link> Home
</Link>{" "}
<Link to="/about" data-testid="nav.about">
About
</Link>
<Link to="/counter" data-testid="nav.counter">
Counter
</Link>
<hr /> <hr />
</nav> </nav>
</> </>