├── .babelrc ├── .github └── workflows │ └── valid.yml ├── .gitignore ├── LICENSE ├── README.md ├── cypress └── integration │ └── smoke-test.spec.js ├── db.json ├── json-server.json ├── package-lock.json ├── package.json ├── routes.json ├── src ├── components │ ├── Footer.js │ ├── TodoApp.js │ ├── TodoForm.js │ └── TodoList.js ├── index.html ├── index.js ├── lib │ ├── service.js │ └── utils.js └── styles.css └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets":[ 3 | "env", "react" 4 | ], 5 | "plugins": [ 6 | "transform-object-rest-spread" 7 | ] 8 | } -------------------------------------------------------------------------------- /.github/workflows/valid.yml: -------------------------------------------------------------------------------- 1 | name: Validate initial app state 2 | 3 | on: push 4 | 5 | jobs: 6 | single-run: 7 | runs-on: ubuntu-16.04 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: Cypress run 12 | uses: cypress-io/github-action@v1 13 | with: 14 | build: npm run build 15 | start: npm run serve 16 | config-file: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | .env 10 | 11 | build 12 | 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 avanslaars 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build and test an application using Cypress 2 | 3 | This repository is the starting point for an official Cypress tutorial. We encourage you to clone this repo and follow along. 4 | -------------------------------------------------------------------------------- /cypress/integration/smoke-test.spec.js: -------------------------------------------------------------------------------- 1 | const hostUrl = "http://localhost:3030"; 2 | describe("The application loads", () => { 3 | it("navigates to the / route", () => { 4 | cy.visit(hostUrl); 5 | }); 6 | 7 | it("has the basic Todo list container", () => { 8 | cy.visit(hostUrl); 9 | cy.get(".todo-list").should("exist"); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /db.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /json-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 3030, 3 | "watch": false, 4 | "static": "./build", 5 | "routes": "./routes.json" 6 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypress-tutorial-build-todo-starter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "watch": "webpack --watch", 9 | "serve": "json-server db.json", 10 | "dev": "concurrently \"npm run watch\" \"npm run serve\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "axios": "0.19.2", 17 | "concurrently": "3.6.1", 18 | "json-server": "0.16.1", 19 | "react": "16.13.1", 20 | "react-dom": "16.13.1", 21 | "react-router-dom": "4.2.2" 22 | }, 23 | "devDependencies": { 24 | "babel-core": "6.26.3", 25 | "babel-loader": "7.1.5", 26 | "babel-plugin-transform-object-rest-spread": "6.26.0", 27 | "babel-preset-env": "1.7.0", 28 | "babel-preset-react": "6.24.1", 29 | "css-loader": "0.28.11", 30 | "cypress": "3.5.0", 31 | "html-webpack-plugin": "2.30.1", 32 | "style-loader": "0.19.1", 33 | "webpack": "3.12.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1" 3 | } -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router-dom' 3 | 4 | export default props => 5 | 17 | -------------------------------------------------------------------------------- /src/components/TodoApp.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {BrowserRouter as Router, Route} from 'react-router-dom' 3 | import TodoForm from './TodoForm' 4 | import TodoList from './TodoList' 5 | import Footer from './Footer' 6 | 7 | 8 | export default class TodoApp extends Component { 9 | constructor(props) { 10 | super(props) 11 | 12 | this.state = { 13 | todos: [] 14 | } 15 | } 16 | 17 | 18 | 19 | render () { 20 | return ( 21 | 22 |
23 |
24 |

todos

25 | 26 |
27 |
28 | 29 |
30 |
32 |
33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/TodoForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 |
5 | 9 |
10 | -------------------------------------------------------------------------------- /src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const TodoItem = props => 4 |
  • 5 |
    6 | 7 | 10 |
    12 |
  • 13 | 14 | export default props => 15 | 18 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React todoMVC with Cypress Tests 5 | 6 | 7 | 8 | 9 |
    10 | 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import TodoApp from './components/TodoApp' 4 | import './styles.css' 5 | 6 | ReactDOM.render(, document.getElementById('app')) -------------------------------------------------------------------------------- /src/lib/service.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-tutorial-build-todo-starter/39de647e6bf961eec3fa9d2b8d69fb2ba3f391d8/src/lib/service.js -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/cypress-tutorial-build-todo-starter/39de647e6bf961eec3fa9d2b8d69fb2ba3f391d8/src/lib/utils.js -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | .todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 53 | } 54 | 55 | .todoapp input::-webkit-input-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | .todoapp input::-moz-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | .todoapp input::input-placeholder { 68 | font-style: italic; 69 | font-weight: 300; 70 | color: #e6e6e6; 71 | } 72 | 73 | .todoapp h1 { 74 | position: absolute; 75 | top: -155px; 76 | width: 100%; 77 | font-size: 100px; 78 | font-weight: 100; 79 | text-align: center; 80 | color: rgba(175, 47, 47, 0.15); 81 | -webkit-text-rendering: optimizeLegibility; 82 | -moz-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | .new-todo, 87 | .edit { 88 | position: relative; 89 | margin: 0; 90 | width: 100%; 91 | font-size: 24px; 92 | font-family: inherit; 93 | font-weight: inherit; 94 | line-height: 1.4em; 95 | border: 0; 96 | outline: none; 97 | color: inherit; 98 | padding: 6px; 99 | border: 1px solid #999; 100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 101 | box-sizing: border-box; 102 | -webkit-font-smoothing: antialiased; 103 | -moz-font-smoothing: antialiased; 104 | font-smoothing: antialiased; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | .toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | .toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | .toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | .todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | .todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | .todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | .todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | .todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | .todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | .todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | .todo-list li .toggle:after { 192 | content: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='-10 -18 100 135'%3E%3Ccircle cx='50' cy='50' r='50' fill='none' stroke='%23ededed' stroke-width='3'/%3E%3C/svg%3E"); 193 | } 194 | 195 | .todo-list li .toggle:checked:after { 196 | content: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='-10 -18 100 135'%3E%3Ccircle cx='50' cy='50' r='50' fill='none' stroke='%23bddad5' stroke-width='3'/%3E%3Cpath fill='%235dc2af' d='M72 25L42 71 27 56l-4 4 20 20 34-52z'/%3E%3C/svg%3E"); 197 | } 198 | 199 | .todo-list li label { 200 | white-space: pre-line; 201 | word-break: break-all; 202 | padding: 15px 60px 15px 15px; 203 | margin-left: 45px; 204 | display: block; 205 | line-height: 1.2; 206 | transition: color 0.4s; 207 | } 208 | 209 | .todo-list li.completed label { 210 | color: #d9d9d9; 211 | text-decoration: line-through; 212 | } 213 | 214 | .todo-list li .destroy { 215 | display: none; 216 | position: absolute; 217 | top: 0; 218 | right: 10px; 219 | bottom: 0; 220 | width: 40px; 221 | height: 40px; 222 | margin: auto 0; 223 | font-size: 30px; 224 | color: #cc9a9a; 225 | margin-bottom: 11px; 226 | transition: color 0.2s ease-out; 227 | } 228 | 229 | .todo-list li .destroy:hover { 230 | color: #af5b5e; 231 | } 232 | 233 | .todo-list li .destroy:after { 234 | content: '×'; 235 | } 236 | 237 | .todo-list li:hover .destroy { 238 | display: block; 239 | } 240 | 241 | span.error { 242 | width: 200px; 243 | position: absolute; 244 | top: 25px; 245 | left: 50%; 246 | margin-left: -100px; 247 | font-size: 2em; 248 | font-weight: bolder; 249 | border: solid 1px #E44234; 250 | border-radius: 5px; 251 | padding-top: 5px; 252 | padding-bottom: 5px; 253 | text-align: center; 254 | background: #E44234; 255 | color: white; 256 | } 257 | 258 | .footer { 259 | color: #777; 260 | padding: 10px 15px; 261 | height: 20px; 262 | text-align: center; 263 | border-top: 1px solid #e6e6e6; 264 | } 265 | 266 | .footer:before { 267 | content: ''; 268 | position: absolute; 269 | right: 0; 270 | bottom: 0; 271 | left: 0; 272 | height: 50px; 273 | overflow: hidden; 274 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 275 | 0 8px 0 -3px #f6f6f6, 276 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 277 | 0 16px 0 -6px #f6f6f6, 278 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 279 | } 280 | 281 | .todo-count { 282 | float: left; 283 | text-align: left; 284 | } 285 | 286 | .todo-count strong { 287 | font-weight: 300; 288 | } 289 | 290 | .filters { 291 | margin: 0; 292 | padding: 0; 293 | list-style: none; 294 | position: absolute; 295 | right: 0; 296 | left: 0; 297 | } 298 | 299 | .filters li { 300 | display: inline; 301 | } 302 | 303 | .filters li a { 304 | color: inherit; 305 | margin: 3px; 306 | padding: 3px 7px; 307 | text-decoration: none; 308 | border: 1px solid transparent; 309 | border-radius: 3px; 310 | } 311 | 312 | .filters li a.selected, 313 | .filters li a:hover { 314 | border-color: rgba(175, 47, 47, 0.1); 315 | } 316 | 317 | .filters li a.selected { 318 | border-color: rgba(175, 47, 47, 0.2); 319 | } 320 | 321 | .info { 322 | margin: 65px auto 0; 323 | color: #bfbfbf; 324 | font-size: 10px; 325 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 326 | text-align: center; 327 | } 328 | 329 | .info p { 330 | line-height: 1; 331 | } 332 | 333 | .info a { 334 | color: inherit; 335 | text-decoration: none; 336 | font-weight: 400; 337 | } 338 | 339 | .info a:hover { 340 | text-decoration: underline; 341 | } 342 | 343 | /* 344 | Hack to remove background from Mobile Safari. 345 | Can't use it globally since it destroys checkboxes in Firefox 346 | */ 347 | @media screen and (-webkit-min-device-pixel-ratio:0) { 348 | .toggle-all, 349 | .todo-list li .toggle { 350 | background: none; 351 | } 352 | 353 | .todo-list li .toggle { 354 | height: 40px; 355 | } 356 | 357 | .toggle-all { 358 | -webkit-transform: rotate(90deg); 359 | transform: rotate(90deg); 360 | -webkit-appearance: none; 361 | appearance: none; 362 | } 363 | } 364 | 365 | @media (max-width: 430px) { 366 | .footer { 367 | height: 50px; 368 | } 369 | 370 | .filters { 371 | bottom: 10px; 372 | } 373 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | 4 | const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({ 5 | template: './src/index.html', 6 | filename: 'index.html', 7 | inject: 'body' 8 | }) 9 | 10 | module.exports = { 11 | entry: './src/index.js', 12 | output: { 13 | path: path.join(__dirname, 'build'), 14 | filename: 'app.bundle.js' 15 | }, 16 | module: { 17 | loaders: [ 18 | { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, 19 | { test: /\.css$/, use: ['style-loader', 'css-loader'], exclude: /node_modules/ } 20 | ] 21 | }, 22 | devtool: 'source-map', 23 | plugins: [HtmlWebpackPluginConfig] 24 | } 25 | --------------------------------------------------------------------------------