📹 Video at https://www.youtube.com/watch?v=PIxaFbMBez0
Why we decided to rewrite our iOS & Android apps from scratch — in React Native. Mercari US
import React from 'react';
import {Text, View} from 'react-native';
import {Header} from './Header';
import {heading} from './Typography';
const WelcomeScreen = () => (
<View>
<Header title="Welcome to React Native"/>
<Text style={heading}>Step One</Text>
<Text>
Edit App.js to change this screen and turn it
into your app.
...
import React from 'react';
import {Text, View} from 'react-native';
import {Header} from './Header';
import {heading} from './Typography';
const WelcomeScreen = () => (
<View>
<Header title="Welcome to React Native"/>
<Text style={heading}>Step One</Text>
<Text>
Edit App.js to change this screen and turn it
into your app.
...
import React, { useEffect, useState } from 'react';
import { ActivityIndicator, FlatList, Text, View } from 'react-native';
export default function App() {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
fetch('https://jsonplaceholder.cypress.io/users')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
return (
<View style={{ flex: 1, padding: 24 }}>
{isLoading ? <ActivityIndicator testID="loading"/> : (
<FlatList
accessibilityLabel="users"
data={data}
keyExtractor={({ id }, index) => String(id)}
renderItem={({ item }) => (
<Text testID="user" accessibilityLabel="user">
{item.name}, {item.email}</Text>
)}
/>
)}
</View>
);
};
App.js
Assume the Expo translated the RN code into the same functionality for the Web, iOS, and Android
{
"baseUrl": "http://localhost:19006",
"viewportWidth": 500,
"viewportHeight": 700
}
cypress.json
it('loads a list of users', () => {
cy.visit('/')
cy.get('[data-testid=user]')
.should('have.length.gt', 3)
})
spec.js
it('spy on the network load', () => {
cy.intercept('/users').as('users')
cy.visit('/')
cy.wait('@users').its('response.body')
.should('be.an', 'Array')
.and('have.length.gt', 5)
.then(users => {
cy.get('[data-testid=user]')
.should('have.length', users.length)
})
})
spec.js
it('spy on the network load', () => {
cy.intercept('/users').as('users')
cy.visit('/')
cy.wait('@users').its('response.body')
.should('be.an', 'Array')
.and('have.length.gt', 5)
.then(users => {
cy.get('[data-testid=user]')
.should('have.length', users.length)
})
})
spec.js
it('shows the loading indicator', () => {
// slow down the response by 1 second
// https://on.cypress.io/intercept
cy.intercept('/users', (req) => {
return req.continue(res => res.setDelay(1000))
}).as('users')
cy.visit('/')
// the loading indicator should be visible at first
cy.get('[data-testid=loading]').should('be.visible')
// the disappear
cy.get('[data-testid=loading]').should('not.exist')
cy.wait('@users')
})
spec.js
import { ActivityIndicator } from 'react-native';
{isLoading ? <ActivityIndicator testID="loading"/> ...
App.js
it('shows the loading indicator', () => {
// slow down the response by 1 second
// https://on.cypress.io/intercept
cy.intercept('/users', (req) => {
return req.continue(res => res.setDelay(1000))
}).as('users')
cy.visit('/')
// the loading indicator should be visible at first
cy.get('[data-testid=loading]').should('be.visible')
// the disappear
cy.get('[data-testid=loading]').should('not.exist')
cy.wait('@users')
})
spec.js
import { ActivityIndicator } from 'react-native';
{isLoading ? <ActivityIndicator testID="loading"/> ...
App.js
it('shows mock data', () => {
cy.intercept('/users', {
fixture: 'users.json'
})
cy.visit('/')
cy.get('[data-testid=user]')
.should('have.length', 3)
})
spec.js
cy.intercept('/users', (req) => {
return Cypress.Promise.delay(1000)
.then(() => req.reply({ forceNetworkError: true }))
})
// observe the application's behavior
// in our case, the app simply logs the error
cy.visit('/', {
onBeforeLoad(win) {
cy.spy(win.console, 'error').as('logError')
},
})
cy.get('[data-testid=loading]').should('be.visible')
// confirm the loading indicator goes away
cy.get('[data-testid=loading]').should('not.exist')
cy.get('@logError').should('have.been.called')
spec.js
useEffect(() => {
fetch('https://jsonplaceholder.cypress.io/users')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
App.js
cy.intercept('/users', (req) => {
return Cypress.Promise.delay(1000)
.then(() => req.reply({ forceNetworkError: true }))
})
// observe the application's behavior
// in our case, the app simply logs the error
cy.visit('/', {
onBeforeLoad(win) {
cy.spy(win.console, 'error').as('logError')
},
})
cy.get('[data-testid=loading]').should('be.visible')
// confirm the loading indicator goes away
cy.get('[data-testid=loading]').should('not.exist')
cy.get('@logError').should('have.been.called')
spec.js
useEffect(() => {
fetch('https://jsonplaceholder.cypress.io/users')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
App.js
RN supports Aria attributes and test IDs
<View style={{ flex: 1, padding: 24 }}>
{isLoading ? (
<ActivityIndicator
testID="loading"
accessibilityLabel="App is loading users"
/>
) : (
<FlatList
accessibilityLabel="users"
data={data}
keyExtractor={({ id }, index) => String(id)}
renderItem={({ item }) => (
<Text testID="user" accessibilityLabel="user">
{item.name}, {item.email}
</Text>
)}
/>
)}
</View>
App.js
it('is accessible', () => {
cy.intercept('/users', {
fixture: 'users.json',
delay: 2000,
})
cy.visit('/')
cy.get('[aria-label="App is loading users"]').should('be.visible')
cy.get('[aria-label="users"]')
.should('be.visible')
.get('[aria-label=user]')
.should('have.length', 3)
})
spec.js
it('is accessible', () => {
cy.intercept('/users', {
fixture: 'users.json',
delay: 2000,
})
cy.visit('/')
cy.get('[aria-label="App is loading users"]').should('be.visible')
cy.get('[aria-label="users"]')
.should('be.visible')
.get('[aria-label=user]')
.should('have.length', 3)
})
spec.js
$ npm install --save-dev cypress-axe axe-core
+ cypress-axe@0.12.2
+ axe-core@4.2.2
spec.js
cy.visit('/')
...
// a11y check
cy.injectAxe();
cy.checkA11y();
$ npm install --save-dev cypress-axe axe-core
+ cypress-axe@0.12.2
+ axe-core@4.2.2
spec.js
cy.visit('/')
...
// a11y check
cy.injectAxe();
cy.checkA11y();
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['istanbul']
};
};
babel.config.js
+ @cypress/code-coverage
Use any Cypress visual plugin
spec.js
cy.visit('/')
...
// visual snapshot
cy.percySnapshot('two items')
Use any Cypress visual plugin
spec.js
cy.visit('/')
...
// visual snapshot
cy.percySnapshot('two items')