Skip to content
This repository was archived by the owner on Mar 5, 2022. It is now read-only.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ Spec | Description
[tutorial](cypress/component/advanced/tutorial) | A few tests adopted from [ReactJS Tutorial](https://reactjs.org/tutorial/tutorial.html), including Tic-Tac-Toe game
[use-local-storage](cypress/component/advanced/use-local-storage) | Use hooks to load and save items into `localStorage`
[portal](cypress/component/advanced/portal) | Component test for `ReactDOM.createPortal` feature
[radioactive-state](cypress/component/advanced/radioactive-state) | Testing components that use [radioactive-state](https://github.com/MananTank/radioactive-state) library
[react-bootstrap](cypress/component/advanced/react-bootstrap) | Confirms [react-bootstrap](https://react-bootstrap.github.io/) components are working
[select React component](cypress/component/advanced/react-book-example/src/components/ProductsList.spec.js) | Uses [cypress-react-selector](https://github.com/abhinaba-ghosh/cypress-react-selector) to find DOM elements using React component name and state values
<!-- prettier-ignore-end -->
Expand Down
9 changes: 9 additions & 0 deletions cypress/component/advanced/radioactive-state/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# radioactive-state example

Testing components that use [radioactive-state](https://github.com/MananTank/radioactive-state) library.

![Counters test](images/counters.gif)

![Todos test](images/todos.gif)

Tests [counter](./counter), [counters](./counters), [todos](./todos)
20 changes: 20 additions & 0 deletions cypress/component/advanced/radioactive-state/counter/Counter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import useRS from 'radioactive-state'

// click on the counter to increment

export const Counter = () => {
// create a radioactive state
const state = useRS({
count: 0,
})

// mutating the state triggers a re-render
const increment = () => state.count++

return (
<div className="count" onClick={increment}>
{state.count}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import './counter.css'
import React from 'react'
import { mount } from 'cypress-react-unit-test'
import { Counter } from './Counter.jsx'

describe('reactive-state Counter', () => {
it('increments count on click', () => {
mount(
<div className="App">
<Counter />
</div>,
)
cy.contains('.count', '0')
.click()
.click()
.click()
cy.contains('.count', '3')
})
})
43 changes: 43 additions & 0 deletions cypress/component/advanced/radioactive-state/counter/counter.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.App {
font-family: sans-serif;
text-align: center;
display: grid;
place-items: center;
min-height: 100vh;
}

.count {
font-size: 5rem;
color: white;
width: 150px;
height: 150px;
border-radius: 50%;
display: grid;
place-items: center;
cursor: pointer;
user-select: none;
transition: all 100ms ease;
background: #3069f9;
border: 10px solid rgb(198, 214, 253);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
}

.count:active {
transform: scale(0.95);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
33 changes: 33 additions & 0 deletions cypress/component/advanced/radioactive-state/counters/Counters.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import useRS from 'radioactive-state'

// deep mutation also triggers re-render !
const Counters = () => {
const state = useRS({
counts: [0],
sum: 0,
})

const increment = i => {
state.counts[i]++
state.sum++
}

const addCounter = () => state.counts.push(0)

return (
<>
<button onClick={addCounter}> Add Counter </button>
<div className="counts">
{state.counts.map((count, i) => (
<div className="count" onClick={() => increment(i)} key={i}>
{count}
</div>
))}
</div>
<div className="count sum">{state.sum}</div>
</>
)
}

export default Counters
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import './counters.css'
import React from 'react'
import { mount } from 'cypress-react-unit-test'
import Counters from './Counters.jsx'

describe('reactive-state Counters', () => {
it('increments single count on click', () => {
mount(
<div className="App">
<Counters />
</div>,
)
cy.contains('.count', '0')
.click()
.click()
.click()
// increments the counter itself
cy.contains('.count', '3')
// increments the sum
cy.contains('.sum', '3')

// add two more counters
cy.contains('Add Counter')
.click()
.click()
cy.get('.counts .count').should('have.length', 3)
// clicking the new counters increments the sum
cy.get('.count')
.eq(1)
.click()
cy.contains('.sum', '4')
cy.get('.count')
.eq(2)
.click()
cy.contains('.sum', '5')
})
})
77 changes: 77 additions & 0 deletions cypress/component/advanced/radioactive-state/counters/counters.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
.App {
font-family: sans-serif;
text-align: center;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}

.count {
background: #3069f9;
border: 6px solid rgb(198, 214, 253);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
margin: 10px;
font-size: 2rem;
color: white;
width: 80px;
height: 80px;
border-radius: 50%;
display: grid;
place-items: center;
cursor: pointer;
user-select: none;
transition: all 100ms ease;
}

.count.sum {
background: #ff3944;
border-color: #ffb0b5;
width: 100px;
height: 100px;
font-size: 3rem;
}

.counts {
display: flex;
flex-wrap: wrap;
justify-content: center;
}

.count:not(.sum):active {
transform: scale(0.95);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

button {
background: #ffd400;
border: 5px solid #fdee81;
outline: none;
font-size: 1.5rem;
padding: 15px;
border-radius: 10px;
margin: 20px;
transition: all 150ms ease;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}

button:active {
transform: scale(0.9);
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions cypress/component/advanced/radioactive-state/todos/AddTodo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react'
import useRS from 'radioactive-state'

const AddTodo = ({ onAdd }) => {
const state = useRS({
input: '',
})

// handle events
const handleEnter = e => e.key === 'Enter' && handleAdd()

const handleAdd = () => {
if (!state.input) return // ignore empty input
const todo = { title: state.input, completed: false } // make todo
onAdd(todo) // add it
state.input = '' // clear input
}

return (
<div className="add-todo">
<input {...state.$input} onKeyDown={handleEnter} />
<button onClick={handleAdd}> Add </button>
</div>
)
}

export default AddTodo
24 changes: 24 additions & 0 deletions cypress/component/advanced/radioactive-state/todos/Todo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'

// todo is a reactive prop,
// mutating it triggers re-render in parent component: Todos
const Todo = ({ todo, onRemove }) => {
const className = todo.completed ? 'todo completed' : 'todo'

const toggleTodo = () => {
todo.completed = !todo.completed // 🤩
}

return (
<div className={className}>
<div className="todo_title" onClick={toggleTodo}>
{todo.title}
</div>
<div className="todo_remove" onClick={onRemove}>
x
</div>
</div>
)
}

export default Todo
29 changes: 29 additions & 0 deletions cypress/component/advanced/radioactive-state/todos/Todos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'
import useRS from 'radioactive-state'
import Todo from './Todo'
import AddTodo from './AddTodo'

const Todos = () => {
const state = useRS({
todos: [],
})

const removeTodo = i => state.todos.splice(i, 1)
const addTodo = todo => state.todos.push(todo)

return (
<div className="container">
<AddTodo onAdd={addTodo} />

{!state.todos.length && <div className="no-todos"> No Todos added </div>}

<div className="todos">
{state.todos.map((todo, i) => (
<Todo todo={todo} key={i} onRemove={() => removeTodo(i)} />
))}
</div>
</div>
)
}

export default Todos
32 changes: 32 additions & 0 deletions cypress/component/advanced/radioactive-state/todos/todos-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import './todos.css'
import React from 'react'
import { mount } from 'cypress-react-unit-test'
import Todos from './Todos'

describe('reactive-state Todos', () => {
it('adds and removes todos', () => {
mount(
<div className="App">
<Todos />
</div>,
)
cy.get('.add-todo input').type('code{enter}')
cy.get('.add-todo input').type('test')
cy.get('.add-todo')
.contains('Add')
.click()
// now check things
cy.get('.todos .todo').should('have.length', 2)
// remove the first one
cy.get('.todos .todo')
.first()
.should('contain', 'code')
.find('.todo_remove')
.click()
// single todo left
cy.get('.todos .todo')
.should('have.length', 1)
.first()
.should('contain', 'test')
})
})
Loading