Gleb Bahmutov
Sr Director of Engineering
C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional programming / testing
Let's test
the game
modes
npm start
npx cypress open
npm i -D cypress
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
},
component: {
...
},
})
cypress.config.js
it('changes the number of filled cells', () => {
cy.visit('/')
cy.get('select[name=status__difficulty-select]').should(
'have.value',
'Easy',
)
cy.get('.game__cell--filled').should('have.length', 45)
cy.get('select[name=status__difficulty-select]').select(
'Medium',
)
cy.get('.game__cell--filled').should('have.length', 40)
cy.get('select[name=status__difficulty-select]').select(
'Hard',
)
cy.get('.game__cell--filled').should('have.length', 30)
})
cypress/e2e/modes.cy.js
The game modes test
Can we
test
the game
play?
It is hard
to control
the random
game board
😢
We can
use the
Hint
button
it('fills each empty cell using Hint', () => {
cy.visit('/')
cy.get('.game__cell.game__cell--filled').should(
'have.length',
45,
)
cy.get('.game__cell')
.not('.game__cell--filled')
.each(($cell) => {
cy.wrap($cell, { log: false }).click()
cy.get('.status__action-hint').click()
})
cy.contains('.overlay__text', 'You solved it').should(
'be.visible',
)
})
cypress/e2e/hint.cy.js
it('fills each empty cell using Hint', () => {
cy.visit('/')
cy.get('.game__cell.game__cell--filled').should(
'have.length',
45,
)
cy.get('.game__cell')
.not('.game__cell--filled')
.each(($cell) => {
cy.wrap($cell, { log: false }).click()
cy.get('.status__action-hint').click()
})
cy.contains('.overlay__text', 'You solved it').should(
'be.visible',
)
})
cypress/e2e/hint.cy.js
Click on the cell
it('fills each empty cell using Hint', () => {
cy.visit('/')
cy.get('.game__cell.game__cell--filled').should(
'have.length',
45,
)
cy.get('.game__cell')
.not('.game__cell--filled')
.each(($cell) => {
cy.wrap($cell, { log: false }).click()
cy.get('.status__action-hint').click()
})
cy.contains('.overlay__text', 'You solved it').should(
'be.visible',
)
})
cypress/e2e/hint.cy.js
Click on the hint
it('fills each empty cell using Hint', () => {
cy.visit('/')
cy.get('.game__cell.game__cell--filled').should(
'have.length',
45,
)
cy.get('.game__cell')
.not('.game__cell--filled')
.each(($cell) => {
cy.wrap($cell, { log: false }).click()
cy.get('.status__action-hint').click()
})
cy.contains('.overlay__text', 'You solved it').should(
'be.visible',
)
})
cypress/e2e/hint.cy.js
The hint test
Random data
Server data
Timers and clocks
Conditional testing
Unclear test requirements
Let's
test the
Timer
it('Timer shows 10 seconds', () => {
cy.visit('/')
for (let k = 0; k < 10; k++) {
cy.contains('.status__time', `00:0${k}`)
}
})
cypress/e2e/timer-clock.cy.js
it('Timer shows 15 minutes', () => {
cy.visit('/')
cy.contains('.status__time', '15:00', {
timeout: 900_000,
})
})
Wait 15 minutes...
export const formatTime = ({ hours, minutes, seconds }) => {
if (typeof seconds === 'undefined') {
return '00:00'
}
let stringTimer = ''
stringTimer += hours ? '' + hours + ':' : ''
stringTimer += minutes ? (minutes < 10 ? '0' : '') + minutes + ':' : '00:'
stringTimer += seconds < 10 ? '0' + seconds : seconds
return stringTimer
}
src/components/Timer.js
import { formatTime } from '../../src/components/Timer'
it('formats the time', () => {
expect(formatTime({})).to.equal('00:00')
expect(formatTime({ seconds: 3 })).to.equal('00:03')
expect(formatTime({ minutes: 55, seconds: 3 })).to.equal(
'55:03',
)
expect(
formatTime({ hours: 110, minutes: 55, seconds: 3 }),
).to.equal('110:55:03')
})
cypress/e2e/format-time.cy.js
import { formatTime } from '../../src/components/Timer'
it('formats the time', () => {
expect(formatTime({})).to.equal('00:00')
expect(formatTime({ seconds: 3 })).to.equal('00:03')
expect(formatTime({ minutes: 55, seconds: 3 })).to.equal(
'55:03',
)
expect(
formatTime({ hours: 110, minutes: 55, seconds: 3 }),
).to.equal('110:55:03')
})
cypress/e2e/format-time.cy.js
<Timer .../>
import { Timer } from '../../src/components/Timer'
import React from 'react';
import { mount } from 'enzyme';
test('shows the time', () => {
const wrapper = mount(<Timer minutes={15} />)
const p = wrapper.find('.status__time');
expect(p.text()).toBe('15:00');
});
import { Timer } from '../../src/components/Timer'
it('shows the time', () => {
<Timer minutes={15} />
cy.contains('.status__time', '15:00')
})
I want this!
// imports framework-specific component
// needs to bundle all its dependencies
import { Timer } from '../../src/components/Timer'
// needs to "start" the framework
mount(<Timer ...>)
We are not in cy.visit('Kansas') anymore
npm start
npx cypress open
Configure Cypress component testing
Supported frameworks
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
},
component: {
devServer: {
framework: 'create-react-app',
bundler: 'webpack',
},
},
})
cypress.config.js
import React from 'react'
import { Timer } from './Timer'
import '../App.css'
import { SudokuContext } from '../context/SudokuContext'
import moment from 'moment'
describe('Timer', () => {
it('sets the clock to the given value', () => {
const timeGameStarted = moment().subtract(
900,
'seconds',
)
cy.mount(
<SudokuContext.Provider value={{ timeGameStarted }}>
<section className="status">
<Timer />
</section>
</SudokuContext.Provider>,
)
cy.contains('15:00')
})
})
src/components/Timer.cy.js
import React from 'react'
import { Timer } from './Timer'
import '../App.css'
import { SudokuContext } from '../context/SudokuContext'
import moment from 'moment'
describe('Timer', () => {
it('sets the clock to the given value', () => {
const timeGameStarted = moment().subtract(
900,
'seconds',
)
cy.mount(
<SudokuContext.Provider value={{ timeGameStarted }}>
<section className="status">
<Timer />
</section>
</SudokuContext.Provider>,
)
cy.contains('15:00')
})
})
src/components/Timer.cy.js
Cypress "understands" how to bundle and mount your framework component
import React from 'react'
import { Timer } from './Timer'
import '../App.css'
import { SudokuContext } from '../context/SudokuContext'
import moment from 'moment'
describe('Timer', () => {
it('sets the clock to the given value', () => {
const timeGameStarted = moment().subtract(
900,
'seconds',
)
cy.mount(
<SudokuContext.Provider value={{ timeGameStarted }}>
<section className="status">
<Timer />
</section>
</SudokuContext.Provider>,
)
cy.contains('15:00')
})
})
src/components/Timer.cy.js
Import app or component CSS and use the markup close to the app
without App.css or markup
import React from 'react'
import { Timer } from './Timer'
import '../App.css'
import { SudokuContext } from '../context/SudokuContext'
import moment from 'moment'
describe('Timer', () => {
it('sets the clock to the given value', () => {
const timeGameStarted = moment().subtract(
900,
'seconds',
)
cy.mount(
<SudokuContext.Provider value={{ timeGameStarted }}>
<section className="status">
<Timer />
</section>
</SudokuContext.Provider>,
)
cy.contains('15:00')
})
})
src/components/Timer.cy.js
Import app or component CSS and use the markup close to the app
with App.css and markup
import React from 'react'
import { Timer } from './Timer'
import '../App.css'
import { SudokuContext } from '../context/SudokuContext'
import moment from 'moment'
describe('Timer', () => {
it('sets the clock to the given value', () => {
const timeGameStarted = moment().subtract(
900,
'seconds',
)
cy.mount(
<SudokuContext.Provider value={{ timeGameStarted }}>
<section className="status">
<Timer />
</section>
</SudokuContext.Provider>,
)
cy.contains('15:00')
})
})
src/components/Timer.cy.js
Pass the component data using props or other mechanisms
Timer
Numbers
Difficulty
StatusSection
GameSection
Game
App
import React from 'react'
import { Numbers } from './Numbers'
import '../App.css'
import { SudokuContext } from '../context/SudokuContext'
it('shows the selected number', () => {
cy.mount(
<SudokuContext.Provider
value={{ numberSelected: '8' }}
>
<div className="innercontainer">
<section className="status">
<Numbers
onClickNumber={cy.stub().as('click')}
/>
</section>
</div>
</SudokuContext.Provider>,
)
cy.contains('.status__number', '8').should(
'have.class',
'status__number--selected',
)
cy.contains('.status__number', '9').click()
cy.get('@click').should(
'have.been.calledOnceWithExactly',
'9',
)
})
Props + Provider are framework specific
src/components/Numbers.cy.js
import React from 'react'
import { Numbers } from './Numbers'
import '../App.css'
import { SudokuContext } from '../context/SudokuContext'
it('shows the selected number', () => {
cy.mount(
<SudokuContext.Provider
value={{ numberSelected: '8' }}
>
<div className="innercontainer">
<section className="status">
<Numbers
onClickNumber={cy.stub().as('click')}
/>
</section>
</div>
</SudokuContext.Provider>,
)
cy.contains('.status__number', '8').should(
'have.class',
'status__number--selected',
)
cy.contains('.status__number', '9').click()
cy.get('@click').should(
'have.been.calledOnceWithExactly',
'9',
)
})
Props + Provider are framework specific
The rest of the test is Cypress API only
src/components/Numbers.cy.js
import React from 'react'
import { Numbers } from './Numbers'
import '../App.css'
import { SudokuContext } from '../context/SudokuContext'
it('shows the selected number', () => {
cy.mount(
<SudokuContext.Provider
value={{ numberSelected: '8' }}
>
<div className="innercontainer">
<section className="status">
<Numbers
onClickNumber={cy.stub().as('click')}
/>
</section>
</div>
</SudokuContext.Provider>,
)
cy.contains('.status__number', '8').should(
'have.class',
'status__number--selected',
)
cy.contains('.status__number', '9').click()
cy.get('@click').should(
'have.been.calledOnceWithExactly',
'9',
)
})
Props + Provider are framework specific
The rest of the test is Cypress API only
src/components/Numbers.cy.js
Each React component has
A huge collection of various React testing examples with matching Cypress component specs
No more framework-specific syntax
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import Toggle from "./toggle";
let container = null;
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("changes value when clicked", () => {
const onChange = jest.fn();
act(() => {
render(<Toggle onChange={onChange} />, container);
});
// get a hold of the button element, and trigger some clicks on it
const button = document.querySelector("[data-testid=toggle]");
expect(button.innerHTML).toBe("Turn on");
act(() => {
button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(onChange).toHaveBeenCalledTimes(1);
expect(button.innerHTML).toBe("Turn off");
act(() => {
for (let i = 0; i < 5; i++) {
button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
}
});
expect(onChange).toHaveBeenCalledTimes(6);
expect(button.innerHTML).toBe("Turn on");
});
import React from "react";
import Toggle from "./toggle";
it("changes value when clicked", () => {
cy.mount(<Toggle onChange={cy.stub().as('change')} />);
// get a hold of the button element, and trigger some clicks on it
cy.contains("[data-testid=toggle]", "Turn on").click()
cy.get('@change').should('have.been.calledOnce')
cy.contains("[data-testid=toggle]", "Turn off")
.click()
.click()
.click()
.click()
.click()
cy.get('@change').its('callCount').should('eq', 6)
cy.contains("[data-testid=toggle]", "Turn on")
});
equivalent Cypress component test
No more framework-specific syntax
import React from "react";
import Toggle from "./toggle";
it("changes value when clicked", () => {
cy.mount(<Toggle onChange={cy.stub().as('change')} />);
// get a hold of the button element, and trigger some clicks on it
cy.contains("[data-testid=toggle]", "Turn on").click()
cy.get('@change').should('have.been.calledOnce')
cy.contains("[data-testid=toggle]", "Turn off")
.click()
.click()
.click()
.click()
.click()
cy.get('@change').its('callCount').should('eq', 6)
cy.contains("[data-testid=toggle]", "Turn on")
});
equivalent Cypress component test https://on.cypress.io/api
import { NavComponent } from './nav.component'
import { SigninService } from '../signin.service'
describe('NavComponent', () => {
it('should create and show the links', () => {
const signinService = new SigninService()
cy.spy(signinService, 'login').as('login')
cy.spy(signinService, 'logout').as('logout')
cy.mount(NavComponent, {
componentProperties: {
signinService,
},
})
cy.contains('a', 'HOME')
cy.contains('button', 'LOG IN').should('be.visible').wait(1000).click()
cy.get('@login').should('have.been.called')
cy.contains('a', 'HOME')
cy.contains('a', 'PROFILE').should('be.visible')
cy.contains('button', 'LOG OUT').should('be.visible').wait(1000).click()
cy.get('@logout').should('have.been.called')
cy.contains('a', 'HOME')
cy.contains('button', 'LOG IN').should('be.visible')
})
})
src/app/nav/nav.component.cy.ts
Mount is framework specific
Cypress API
NavComponent
import { NavComponent } from './nav.component'
import { SigninService } from '../signin.service'
describe('NavComponent', () => {
it('should create and show the links', () => {
const signinService = new SigninService()
cy.spy(signinService, 'login').as('login')
cy.spy(signinService, 'logout').as('logout')
cy.mount(NavComponent, {
componentProperties: {
signinService,
},
})
cy.contains('a', 'HOME')
cy.contains('button', 'LOG IN').should('be.visible').wait(1000).click()
cy.get('@login').should('have.been.called')
cy.contains('a', 'HOME')
cy.contains('a', 'PROFILE').should('be.visible')
cy.contains('button', 'LOG OUT').should('be.visible').wait(1000).click()
cy.get('@logout').should('have.been.called')
cy.contains('a', 'HOME')
cy.contains('button', 'LOG IN').should('be.visible')
})
})
src/app/nav/nav.component.cy.ts
🎁
import { NavComponent } from './nav.component'
import { SigninService } from '../signin.service'
describe('NavComponent', () => {
it('should create and show the links', () => {
const signinService = new SigninService()
cy.spy(signinService, 'login').as('login')
cy.spy(signinService, 'logout').as('logout')
cy.mount(NavComponent, {
componentProperties: {
signinService,
},
})
cy.contains('a', 'HOME')
cy.contains('button', 'LOG IN').should('be.visible').wait(1000).click()
cy.get('@login').should('have.been.called')
cy.contains('a', 'HOME')
cy.contains('a', 'PROFILE').should('be.visible')
cy.contains('button', 'LOG OUT').should('be.visible').wait(1000).click()
cy.get('@logout').should('have.been.called')
cy.contains('a', 'HOME')
cy.contains('button', 'LOG IN').should('be.visible')
})
})
src/app/nav/nav.component.cy.ts
Angular
Examples for several frameworks
Timer
Numbers
Difficulty
StatusSection
GameSection
Game
App
Overlay
import { Game } from './Game'
import { SudokuProvider } from './context/SudokuContext'
import { WinProvider } from './context/WinContext'
import { starting, solved } from '../cypress/fixtures/sudoku.json'
it('plays the game', () => {
cy.mount(
<SudokuProvider>
<WinProvider>
<Game initArray={starting} solvedArray={solved} />
</WinProvider>
</SudokuProvider>,
)
cy.get('.game__cell:not(.game__cell--filled)').should(
'have.length',
3,
)
starting.forEach((cell, index) => {
if (cell === '0') {
cy.get('.game__cell').eq(index).click()
cy.contains('.status__number', solved[index])
.click()
.wait(500, { log: false })
}
})
cy.contains('.overlay__text', 'You solved it').should('be.visible')
})
src/Game.cy.js
import { Game } from './Game'
import { SudokuProvider } from './context/SudokuContext'
import { WinProvider } from './context/WinContext'
import { starting, solved } from '../cypress/fixtures/sudoku.json'
it('plays the game', () => {
cy.mount(
<SudokuProvider>
<WinProvider>
<Game initArray={starting} solvedArray={solved} />
</WinProvider>
</SudokuProvider>,
)
cy.get('.game__cell:not(.game__cell--filled)').should(
'have.length',
3,
)
starting.forEach((cell, index) => {
if (cell === '0') {
cy.get('.game__cell').eq(index).click()
cy.contains('.status__number', solved[index])
.click()
.wait(500, { log: false })
}
})
cy.contains('.overlay__text', 'You solved it').should('be.visible')
})
src/Game.cy.js
import { Game } from './Game'
import { SudokuProvider } from './context/SudokuContext'
import { WinProvider } from './context/WinContext'
import { starting, solved } from '../cypress/fixtures/sudoku.json'
it('plays the game', () => {
cy.mount(
<SudokuProvider>
<WinProvider>
<Game initArray={starting} solvedArray={solved} />
</WinProvider>
</SudokuProvider>,
)
cy.get('.game__cell:not(.game__cell--filled)').should(
'have.length',
3,
)
starting.forEach((cell, index) => {
if (cell === '0') {
cy.get('.game__cell').eq(index).click()
cy.contains('.status__number', solved[index])
.click()
.wait(500, { log: false })
}
})
cy.contains('.overlay__text', 'You solved it').should('be.visible')
})
src/Game.cy.js
src/Game.cy.js
import React, { useState, useEffect } from 'react'
import { formatTime } from './Timer'
const useFetch = (url) => {
const [data, setData] = useState([])
const [loading, setLoading] = useState(url ? true : false)
async function fetchData() {
if (url) {
const response = await fetch(url)
const json = await response.json()
setData(json)
setLoading(false)
}
}
useEffect(() => {
if (!url) {
return
}
fetchData()
}, [url])
return { loading, data }
}
export const Overlay = (props) => {
const { loading, data } = useFetch(
props.overlay && props.time ? '/times/' + props.time : null,
)
const className = props.overlay
? 'overlay overlay--visible'
: 'overlay'
return (
<div className={className} onClick={props.onClickOverlay}>
<h2 className="overlay__text">
<div className="overlay__greeting">
You <span className="overlay__textspan1">solved</span>{' '}
<span className="overlay__textspan2">it!</span>
</div>
{loading && (
<div className="overlay__loading">Loading...</div>
)}
{data.length > 0 && (
<ul className="overlay__times">
{data.map((item, index) => {
return (
<li
key={index}
className={item.current ? 'overlay__current' : ''}
>
{formatTime(item)}
</li>
)
})}
</ul>
)}
</h2>
</div>
)
}
src/components/Overlay.js
import React, { useState, useEffect } from 'react'
import { formatTime } from './Timer'
const useFetch = (url) => {
const [data, setData] = useState([])
const [loading, setLoading] = useState(url ? true : false)
async function fetchData() {
if (url) {
const response = await fetch(url)
const json = await response.json()
setData(json)
setLoading(false)
}
}
useEffect(() => {
if (!url) {
return
}
fetchData()
}, [url])
return { loading, data }
}
export const Overlay = (props) => {
const { loading, data } = useFetch(
props.overlay && props.time ? '/times/' + props.time : null,
)
const className = props.overlay
? 'overlay overlay--visible'
: 'overlay'
return (
<div className={className} onClick={props.onClickOverlay}>
<h2 className="overlay__text">
<div className="overlay__greeting">
You <span className="overlay__textspan1">solved</span>{' '}
<span className="overlay__textspan2">it!</span>
</div>
{loading && (
<div className="overlay__loading">Loading...</div>
)}
{data.length > 0 && (
<ul className="overlay__times">
{data.map((item, index) => {
return (
<li
key={index}
className={item.current ? 'overlay__current' : ''}
>
{formatTime(item)}
</li>
)
})}
</ul>
)}
</h2>
</div>
)
}
src/components/Overlay.js
it('shows the loading element', () => {
cy.intercept('GET', '/times/90', {
delay: 1000,
statusCode: 404,
body: [],
}).as('times')
cy.mount(<Overlay overlay={true} time={90} />)
cy.contains('.overlay__loading', 'Loading').should('be.visible')
cy.wait('@times')
cy.get('.overlay__loading').should('not.exist')
})
it('shows the loading element', () => {
cy.intercept('GET', '/times/90', {
delay: 1000,
statusCode: 404,
body: [],
}).as('times')
cy.mount(<Overlay overlay={true} time={90} />)
cy.contains('.overlay__loading', 'Loading').should('be.visible')
cy.wait('@times')
cy.get('.overlay__loading').should('not.exist')
})
it('shows the top times', () => {
cy.intercept('GET', '/times/90', {
fixture: 'times.json',
}).as('scores')
cy.mount(<Overlay overlay={true} time={90} />)
cy.wait('@scores')
cy.get('.overlay__times li').should('have.length', 4)
cy.contains('.overlay__times li', '01:30').should(
'have.class',
'overlay__current',
)
})
My advice: Mock / spy on the public APIs (network, storage, cookies, DOM) from your component tests rather than the inner code.
It is ok to pass spies and stubs as props.
import { starting, solved } from '../fixtures/sudoku.json'
describe('Sudoku', () => {
it('plays the same game', () => {
// to play the same game, we will pass
// the starting and the solved arrays
// to the application via the "window" object
cy.visit('/', {
onBeforeLoad(window) {
window.starting = starting
window.solved = solved
},
})
cy.intercept('GET', '/times/*', {
fixture: 'times.json',
}).as('scores')
// our initial array only has 3 cells to fill
cy.get('.game__cell:contains(0)').should('have.length', 3)
starting.forEach((cell, index) => {
if (cell === '0') {
cy.get('.game__cell').eq(index).click()
cy.contains('.status__number', solved[index])
.click()
}
})
cy.contains('.overlay__text', 'You solved it').should('be.visible')
cy.wait('@scores')
cy.fixture('times.json')
.its('length')
.then((n) => {
cy.get('.overlay__times li').should('have.length', n)
})
cy.get('.overlay__times li.overlay__current').should('have.length', 1)
})
})
import { starting, solved } from '../fixtures/sudoku.json'
describe('Sudoku', () => {
it('plays the same game', () => {
// to play the same game, we will pass
// the starting and the solved arrays
// to the application via the "window" object
cy.visit('/', {
onBeforeLoad(window) {
window.starting = starting
window.solved = solved
},
})
cy.intercept('GET', '/times/*', {
fixture: 'times.json',
}).as('scores')
// our initial array only has 3 cells to fill
cy.get('.game__cell:contains(0)').should('have.length', 3)
starting.forEach((cell, index) => {
if (cell === '0') {
cy.get('.game__cell').eq(index).click()
cy.contains('.status__number', solved[index])
.click()
}
})
cy.contains('.overlay__text', 'You solved it').should('be.visible')
cy.wait('@scores')
cy.fixture('times.json')
.its('length')
.then((n) => {
cy.get('.overlay__times li').should('have.length', n)
})
cy.get('.overlay__times li.overlay__current').should('have.length', 1)
})
})
import { starting, solved } from '../fixtures/sudoku.json'
describe('Sudoku', () => {
it('plays the same game', () => {
// to play the same game, we will pass
// the starting and the solved arrays
// to the application via the "window" object
cy.visit('/', {
onBeforeLoad(window) {
window.starting = starting
window.solved = solved
},
})
cy.intercept('GET', '/times/*', {
fixture: 'times.json',
}).as('scores')
// our initial array only has 3 cells to fill
cy.get('.game__cell:contains(0)').should('have.length', 3)
starting.forEach((cell, index) => {
if (cell === '0') {
cy.get('.game__cell').eq(index).click()
cy.contains('.status__number', solved[index])
.click()
}
})
cy.contains('.overlay__text', 'You solved it').should('be.visible')
cy.wait('@scores')
cy.fixture('times.json')
.its('length')
.then((n) => {
cy.get('.overlay__times li').should('have.length', n)
})
cy.get('.overlay__times li.overlay__current').should('have.length', 1)
})
})
import { Game } from './Game'
import { SudokuProvider } from './context/SudokuContext'
import { WinProvider } from './context/WinContext'
import { starting, solved } from '../cypress/fixtures/sudoku.json'
it('plays the game', () => {
cy.mount(
<SudokuProvider>
<WinProvider>
<Game initArray={starting} solvedArray={solved} />
</WinProvider>
</SudokuProvider>,
)
cy.intercept('GET', '/times/*', {
fixture: 'times.json',
}).as('scores')
// our initial array only has 3 cells to fill
cy.get('.game__cell:contains(0)').should('have.length', 3)
starting.forEach((cell, index) => {
if (cell === '0') {
cy.get('.game__cell').eq(index).click()
cy.contains('.status__number', solved[index])
.click()
}
})
cy.contains('.overlay__text', 'You solved it').should('be.visible')
cy.wait('@scores')
cy.fixture('times.json')
.its('length')
.then((n) => {
cy.get('.overlay__times li').should('have.length', n)
})
cy.get('.overlay__times li.overlay__current').should('have.length', 1)
})
End-to-End test
Component test
import { starting, solved } from '../fixtures/sudoku.json'
describe('Sudoku', () => {
it('plays the same game', () => {
// to play the same game, we will pass
// the starting and the solved arrays
// to the application via the "window" object
cy.visit('/', {
onBeforeLoad(window) {
window.starting = starting
window.solved = solved
},
})
cy.intercept('GET', '/times/*', {
fixture: 'times.json',
}).as('scores')
// our initial array only has 3 cells to fill
cy.get('.game__cell:contains(0)').should('have.length', 3)
starting.forEach((cell, index) => {
if (cell === '0') {
cy.get('.game__cell').eq(index).click()
cy.contains('.status__number', solved[index])
.click()
}
})
cy.contains('.overlay__text', 'You solved it').should('be.visible')
cy.wait('@scores')
cy.fixture('times.json')
.its('length')
.then((n) => {
cy.get('.overlay__times li').should('have.length', n)
})
cy.get('.overlay__times li.overlay__current').should('have.length', 1)
})
})
import { Game } from './Game'
import { SudokuProvider } from './context/SudokuContext'
import { WinProvider } from './context/WinContext'
import { starting, solved } from '../cypress/fixtures/sudoku.json'
it('plays the game', () => {
cy.mount(
<SudokuProvider>
<WinProvider>
<Game initArray={starting} solvedArray={solved} />
</WinProvider>
</SudokuProvider>,
)
cy.intercept('GET', '/times/*', {
fixture: 'times.json',
}).as('scores')
// our initial array only has 3 cells to fill
cy.get('.game__cell:contains(0)').should('have.length', 3)
starting.forEach((cell, index) => {
if (cell === '0') {
cy.get('.game__cell').eq(index).click()
cy.contains('.status__number', solved[index])
.click()
}
})
cy.contains('.overlay__text', 'You solved it').should('be.visible')
cy.wait('@scores')
cy.fixture('times.json')
.its('length')
.then((n) => {
cy.get('.overlay__times li').should('have.length', n)
})
cy.get('.overlay__times li.overlay__current').should('have.length', 1)
})
End-to-End test
Component test
expect(formatTime({ seconds: 3 }))
.to.equal('00:03')
import { Component } from './Component'
cy.mount(<Component props=... />)
cy.get(...).click()
cy.visit('/')
cy.get(...).click()
👏 Thank you 👏