mirror of
https://github.com/callstack/react-native-testing-library.git
synced 2026-09-18 23:09:04 +08:00
Merge branch 'main' into poc/custom-renderer
# Conflicts: # package.json # src/__tests__/__snapshots__/render-debug.test.tsx.snap # yarn.lock
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
flow-typed/
|
||||
build/
|
||||
experiments-rtl/
|
||||
website/
|
||||
|
||||
jest-setup.ts
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": "@callstack",
|
||||
"rules": {
|
||||
"flowtype/no-weak-types": 0,
|
||||
"react-native/no-raw-text": 0,
|
||||
"no-console": 1,
|
||||
"react/no-multi-comp": 0,
|
||||
|
||||
+3
-3
@@ -32,9 +32,9 @@ Our pre-commit hooks verify that your commit message matches this format when co
|
||||
|
||||
### Linting and tests
|
||||
|
||||
We use `flow` for type checking, `eslint` with `prettier` for linting and formatting the code, and `jest` for testing. Our pre-commit hooks verify that the linter and tests pass when committing. You can also run the following commands manually:
|
||||
We use TypeScript for type checking, `eslint` with `prettier` for linting and formatting the code, and `jest` for testing. Our pre-commit hooks verify that the linter and tests pass when committing. You can also run the following commands manually:
|
||||
|
||||
- `yarn flow`: run flow on all files.
|
||||
- `yarn typecheck`: run TypeScript compiler on all files.
|
||||
- `yarn lint`: run eslint and prettier.
|
||||
- `yarn test`: run tests.
|
||||
|
||||
@@ -43,7 +43,7 @@ We use `flow` for type checking, `eslint` with `prettier` for linting and format
|
||||
When you're sending a pull request:
|
||||
|
||||
- Prefer small pull requests focused on one change.
|
||||
- Verify that `flow`, `eslint` and tests are passing.
|
||||
- Verify that `typecheck`, `eslint` and tests are passing.
|
||||
- Preview the documentation to make sure it looks good.
|
||||
- Follow the pull request template when opening a pull request.
|
||||
|
||||
|
||||
+1
-2
@@ -14,8 +14,7 @@ module.exports = {
|
||||
],
|
||||
env: {
|
||||
test: {
|
||||
// https://github.com/react-native-community/upgrade-support/issues/152
|
||||
plugins: ['@babel/plugin-transform-flow-strip-types'],
|
||||
presets: ['@react-native/babel-preset'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,30 @@
|
||||
# RNTL Cookbook
|
||||
<p align="center">
|
||||
<img alt="banner" src="assets/readme/banner.png" />
|
||||
</p>
|
||||
|
||||
This example app gathers recipes from
|
||||
the [RNTL Cookbook](https://callstack.github.io/react-native-testing-library/cookbook).
|
||||
# React Native Testing Library Cookbook App
|
||||
Welcome to the React Native Testing Library (RNTL) Cookbook! This app is designed to provide developers with a collection of best practices, ready-made recipes, and tips & tricks to help you effectively test your React Native applications. Whether you’re just starting out with testing or looking to deepen your skills, this cookbook offers something for everyone.
|
||||
|
||||
Each recipe described in the Cookbook should have a corresponding code example screen in this repo.
|
||||
|
||||
Note:
|
||||
Since examples will showcase usage of different dependencies, the dependencies in `package.json`
|
||||
file will grow much larger that in a normal React Native. This is fine 🐶☕️🔥.
|
||||
|
||||
## Running the App
|
||||
1. Clone the repo `git clone git@github.com:callstack/react-native-testing-library.git`
|
||||
2. Go to the `examples/cookbook` directory `cd examples/cookbook`
|
||||
3. Install dependencies `yarn`
|
||||
4. Run the app `yarn start`
|
||||
5. Run the app either on iOS or Android by clicking on `i` or `a` in the terminal.
|
||||
|
||||
## How to Contribute
|
||||
We invite all developers, from beginners to experts, to contribute your own recipes! If you have a clever solution, best practice, or useful tip, we encourage you to:
|
||||
|
||||
1. Submit a Pull Request with your recipe.
|
||||
2. Join the conversation on GitHub [here](https://github.com/callstack/react-native-testing-library/issues/1624) to discuss ideas, ask questions, or provide feedback.
|
||||
|
||||
## Screenshots From the App
|
||||
| Home Screen | Phonebook with Net. Req. Example |
|
||||
|-------------------------------------------------------|-----------------------------------------------------------------|
|
||||
|  |  |
|
||||
|
||||
@@ -82,6 +82,7 @@ type Recipe = {
|
||||
};
|
||||
|
||||
const recipes: Recipe[] = [
|
||||
{ id: 2, title: 'Welcome Screen with Custom Render', path: 'custom-render/' },
|
||||
{ id: 1, title: 'Task List with Jotai', path: 'jotai/' },
|
||||
{ id: 1, title: 'Welcome Screen with Custom Render', path: 'custom-render/' },
|
||||
{ id: 2, title: 'Task List with Jotai', path: 'state-management/jotai/' },
|
||||
{ id: 3, title: 'Phone book with\na Variety of Net. Req. Methods', path: 'network-requests/' },
|
||||
];
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { User } from './types';
|
||||
import ContactsList from './components/ContactsList';
|
||||
import FavoritesList from './components/FavoritesList';
|
||||
import getAllContacts from './api/getAllContacts';
|
||||
import getAllFavorites from './api/getAllFavorites';
|
||||
|
||||
export default () => {
|
||||
const [usersData, setUsersData] = useState<User[]>([]);
|
||||
const [favoritesData, setFavoritesData] = useState<User[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const _getAllContacts = async () => {
|
||||
const _data = await getAllContacts();
|
||||
setUsersData(_data);
|
||||
};
|
||||
const _getAllFavorites = async () => {
|
||||
const _data = await getAllFavorites();
|
||||
setFavoritesData(_data);
|
||||
};
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
await Promise.all([_getAllContacts(), _getAllFavorites()]);
|
||||
} catch (e) {
|
||||
const message = isErrorWithMessage(e) ? e.message : 'Something went wrong';
|
||||
setError(message);
|
||||
}
|
||||
};
|
||||
|
||||
void run();
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return <Text>An error occurred: {error}</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FavoritesList users={favoritesData} />
|
||||
<ContactsList users={usersData} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const isErrorWithMessage = (
|
||||
e: unknown,
|
||||
): e is {
|
||||
message: string;
|
||||
} => typeof e === 'object' && e !== null && 'message' in e;
|
||||
@@ -0,0 +1,40 @@
|
||||
import { render, screen, waitForElementToBeRemoved } from '@testing-library/react-native';
|
||||
import React from 'react';
|
||||
import PhoneBook from '../PhoneBook';
|
||||
import {
|
||||
mockServerFailureForGetAllContacts,
|
||||
mockServerFailureForGetAllFavorites,
|
||||
} from './test-utils';
|
||||
|
||||
jest.setTimeout(10000);
|
||||
|
||||
describe('PhoneBook', () => {
|
||||
it('fetches all contacts and favorites successfully and renders lists in sections correctly', async () => {
|
||||
render(<PhoneBook />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByText(/users data not quite there yet/i));
|
||||
expect(await screen.findByText('Name: Mrs Ida Kristensen')).toBeOnTheScreen();
|
||||
expect(await screen.findByText('Email: ida.kristensen@example.com')).toBeOnTheScreen();
|
||||
expect(await screen.findAllByText(/name/i)).toHaveLength(3);
|
||||
expect(await screen.findByText(/my favorites/i)).toBeOnTheScreen();
|
||||
expect(await screen.findAllByLabelText('favorite-contact-avatar')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('fails to fetch all contacts and renders error message', async () => {
|
||||
mockServerFailureForGetAllContacts();
|
||||
render(<PhoneBook />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByText(/users data not quite there yet/i));
|
||||
expect(
|
||||
await screen.findByText(/an error occurred: error fetching contacts/i),
|
||||
).toBeOnTheScreen();
|
||||
});
|
||||
|
||||
it('fails to fetch favorites and renders error message', async () => {
|
||||
mockServerFailureForGetAllFavorites();
|
||||
render(<PhoneBook />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByText(/figuring out your favorites/i));
|
||||
expect(await screen.findByText(/error fetching favorites/i)).toBeOnTheScreen();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
import { User } from '../types';
|
||||
import {http, HttpResponse} from "msw";
|
||||
import {setupServer} from "msw/node";
|
||||
|
||||
// Define request handlers and response resolvers for random user API.
|
||||
// By default, we always return the happy path response.
|
||||
const handlers = [
|
||||
http.get('https://randomuser.me/api/*', () => {
|
||||
return HttpResponse.json(DATA);
|
||||
}),
|
||||
];
|
||||
|
||||
export const server = setupServer(...handlers);
|
||||
|
||||
export const mockServerFailureForGetAllContacts = () => {
|
||||
server.use(
|
||||
http.get('https://randomuser.me/api/', ({ request }) => {
|
||||
// Construct a URL instance out of the intercepted request.
|
||||
const url = new URL(request.url);
|
||||
// Read the "results" URL query parameter using the "URLSearchParams" API.
|
||||
const resultsLength = url.searchParams.get('results');
|
||||
// Simulate a server error for the get all contacts request.
|
||||
// We check if the "results" query parameter is set to "25"
|
||||
// to know it's the correct request to mock, in our case get all contacts.
|
||||
if (resultsLength === '25') {
|
||||
return new HttpResponse(null, { status: 500 });
|
||||
}
|
||||
|
||||
return HttpResponse.json(DATA);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const mockServerFailureForGetAllFavorites = () => {
|
||||
server.use(
|
||||
http.get('https://randomuser.me/api/', ({ request }) => {
|
||||
// Construct a URL instance out of the intercepted request.
|
||||
const url = new URL(request.url);
|
||||
// Read the "results" URL query parameter using the "URLSearchParams" API.
|
||||
const resultsLength = url.searchParams.get('results');
|
||||
// Simulate a server error for the get all favorites request.
|
||||
// We check if the "results" query parameter is set to "10"
|
||||
// to know it's the correct request to mock, in our case get all favorites.
|
||||
if (resultsLength === '10') {
|
||||
return new HttpResponse(null, { status: 500 });
|
||||
}
|
||||
|
||||
return HttpResponse.json(DATA);
|
||||
}),
|
||||
);
|
||||
};
|
||||
export const DATA: { results: User[] } = {
|
||||
results: [
|
||||
{
|
||||
name: {
|
||||
title: 'Mrs',
|
||||
first: 'Ida',
|
||||
last: 'Kristensen',
|
||||
},
|
||||
email: 'ida.kristensen@example.com',
|
||||
id: {
|
||||
name: 'CPR',
|
||||
value: '250562-5730',
|
||||
},
|
||||
picture: {
|
||||
large: 'https://randomuser.me/api/portraits/women/26.jpg',
|
||||
medium: 'https://randomuser.me/api/portraits/med/women/26.jpg',
|
||||
thumbnail: 'https://randomuser.me/api/portraits/thumb/women/26.jpg',
|
||||
},
|
||||
cell: '123-4567-890',
|
||||
},
|
||||
{
|
||||
name: {
|
||||
title: 'Mr',
|
||||
first: 'Elijah',
|
||||
last: 'Ellis',
|
||||
},
|
||||
email: 'elijah.ellis@example.com',
|
||||
id: {
|
||||
name: 'TFN',
|
||||
value: '138117486',
|
||||
},
|
||||
picture: {
|
||||
large: 'https://randomuser.me/api/portraits/men/53.jpg',
|
||||
medium: 'https://randomuser.me/api/portraits/med/men/53.jpg',
|
||||
thumbnail: 'https://randomuser.me/api/portraits/thumb/men/53.jpg',
|
||||
},
|
||||
cell: '123-4567-890',
|
||||
},
|
||||
{
|
||||
name: {
|
||||
title: 'Mr',
|
||||
first: 'Miro',
|
||||
last: 'Halko',
|
||||
},
|
||||
email: 'miro.halko@example.com',
|
||||
id: {
|
||||
name: 'HETU',
|
||||
value: 'NaNNA945undefined',
|
||||
},
|
||||
picture: {
|
||||
large: 'https://randomuser.me/api/portraits/men/17.jpg',
|
||||
medium: 'https://randomuser.me/api/portraits/med/men/17.jpg',
|
||||
thumbnail: 'https://randomuser.me/api/portraits/thumb/men/17.jpg',
|
||||
},
|
||||
cell: '123-4567-890',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { User } from '../types';
|
||||
|
||||
export default async (): Promise<User[]> => {
|
||||
const res = await fetch('https://randomuser.me/api/?results=25');
|
||||
if (!res.ok) {
|
||||
throw new Error(`Error fetching contacts`);
|
||||
}
|
||||
const json = await res.json();
|
||||
return json.results;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { User } from '../types';
|
||||
|
||||
export default async (): Promise<User[]> => {
|
||||
const res = await fetch('https://randomuser.me/api/?results=10');
|
||||
if (!res.ok) {
|
||||
throw new Error(`Error fetching favorites`);
|
||||
}
|
||||
const json = await res.json();
|
||||
return json.results;
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { FlatList, Image, StyleSheet, Text, View } from 'react-native';
|
||||
import React, { useCallback } from 'react';
|
||||
import type { ListRenderItem } from '@react-native/virtualized-lists';
|
||||
import { User } from '../types';
|
||||
|
||||
export default ({ users }: { users: User[] }) => {
|
||||
const renderItem: ListRenderItem<User> = useCallback(
|
||||
({ item: { name, email, picture, cell }, index }) => {
|
||||
const { title, first, last } = name;
|
||||
const backgroundColor = index % 2 === 0 ? '#f9f9f9' : '#fff';
|
||||
return (
|
||||
<View style={[{ backgroundColor }, styles.userContainer]}>
|
||||
<Image source={{ uri: picture.thumbnail }} style={styles.userImage} />
|
||||
<View>
|
||||
<Text>
|
||||
Name: {title} {first} {last}
|
||||
</Text>
|
||||
<Text>Email: {email}</Text>
|
||||
<Text>Mobile: {cell}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
if (users.length === 0) return <FullScreenLoader />;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<FlatList<User>
|
||||
data={users}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item, index) => `${index}-${item.id.value}`}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const FullScreenLoader = () => {
|
||||
return (
|
||||
<View style={styles.loaderContainer}>
|
||||
<Text>Users data not quite there yet...</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
userContainer: {
|
||||
padding: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
userImage: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 24,
|
||||
marginRight: 16,
|
||||
},
|
||||
loaderContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { FlatList, Image, StyleSheet, Text, View } from 'react-native';
|
||||
import React, { useCallback } from 'react';
|
||||
import type { ListRenderItem } from '@react-native/virtualized-lists';
|
||||
import { User } from '../types';
|
||||
|
||||
export default ({ users }: { users: User[] }) => {
|
||||
const renderItem: ListRenderItem<User> = useCallback(({ item: { picture } }) => {
|
||||
return (
|
||||
<View style={styles.userContainer}>
|
||||
<Image
|
||||
source={{ uri: picture.thumbnail }}
|
||||
style={styles.userImage}
|
||||
accessibilityLabel={'favorite-contact-avatar'}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (users.length === 0) return <FullScreenLoader />;
|
||||
|
||||
return (
|
||||
<View style={styles.outerContainer}>
|
||||
<Text>⭐My Favorites</Text>
|
||||
<FlatList<User>
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
data={users}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item, index) => `${index}-${item.id.value}`}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const FullScreenLoader = () => {
|
||||
return (
|
||||
<View style={styles.loaderContainer}>
|
||||
<Text>Figuring out your favorites...</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
outerContainer: {
|
||||
padding: 8,
|
||||
},
|
||||
userContainer: {
|
||||
padding: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
userImage: {
|
||||
width: 52,
|
||||
height: 52,
|
||||
borderRadius: 36,
|
||||
borderColor: '#9b6dff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
loaderContainer: { height: 52, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import PhoneBook from './PhoneBook';
|
||||
|
||||
export default function Example() {
|
||||
return <PhoneBook />;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export type User = {
|
||||
name: {
|
||||
title: string;
|
||||
first: string;
|
||||
last: string;
|
||||
};
|
||||
email: string;
|
||||
id: {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
picture: {
|
||||
large: string;
|
||||
medium: string;
|
||||
thumbnail: string;
|
||||
};
|
||||
cell: string;
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 774 KiB |
@@ -2,6 +2,14 @@
|
||||
|
||||
// Import built-in Jest matchers
|
||||
import '@testing-library/react-native/extend-expect';
|
||||
import { server } from './app/network-requests/__tests__/test-utils';
|
||||
|
||||
// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
|
||||
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
|
||||
|
||||
// Enable API mocking via Mock Service Worker (MSW)
|
||||
beforeAll(() => server.listen());
|
||||
// Reset any runtime request handlers we may add during the tests
|
||||
afterEach(() => server.resetHandlers());
|
||||
// Disable API mocking after the tests are done
|
||||
afterAll(() => server.close());
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "^51.0.26",
|
||||
"expo": "~51.0.31",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-router": "~3.5.21",
|
||||
"expo-router": "~3.5.23",
|
||||
"expo-splash-screen": "~0.27.5",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"jotai": "^2.8.4",
|
||||
@@ -29,14 +29,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@expo/metro-runtime": "~3.2.1",
|
||||
"@testing-library/react-native": "^12.7.1",
|
||||
"@expo/metro-runtime": "~3.2.3",
|
||||
"@testing-library/react-native": "^12.7.2",
|
||||
"@types/eslint": "^8.56.10",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "~18.2.45",
|
||||
"@types/react-native-get-random-values": "^1",
|
||||
"eslint": "^8.57.0",
|
||||
"jest": "^29.7.0",
|
||||
"msw": "^2.4.4",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
|
||||
+371
-50
@@ -1137,6 +1137,34 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@bundled-es-modules/cookie@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "@bundled-es-modules/cookie@npm:2.0.0"
|
||||
dependencies:
|
||||
cookie: "npm:^0.5.0"
|
||||
checksum: 10c0/0655dd331b35d7b5b6dd2301c3bcfb7233018c0e3235a40ced1d53f00463ab92dc01f0091f153812867bc0ef0f8e0a157a30acb16e8d7ef149702bf8db9fe7a6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@bundled-es-modules/statuses@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@bundled-es-modules/statuses@npm:1.0.1"
|
||||
dependencies:
|
||||
statuses: "npm:^2.0.1"
|
||||
checksum: 10c0/c1a8ede3efa8da61ccda4b98e773582a9733edfbeeee569d4630785f8e018766202edb190a754a3ec7a7f6bd738e857829affc2fdb676b6dab4db1bb44e62785
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@bundled-es-modules/tough-cookie@npm:^0.1.6":
|
||||
version: 0.1.6
|
||||
resolution: "@bundled-es-modules/tough-cookie@npm:0.1.6"
|
||||
dependencies:
|
||||
"@types/tough-cookie": "npm:^4.0.5"
|
||||
tough-cookie: "npm:^4.1.4"
|
||||
checksum: 10c0/28bcac878bff6b34719ba3aa8341e9924772ee55de5487680ebe784981ec9fccb70ed5d46f563e2404855a04de606f9e56aa4202842d4f5835bc04a4fe820571
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.2.0":
|
||||
version: 4.4.0
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.4.0"
|
||||
@@ -1188,9 +1216,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@expo/cli@npm:0.18.28":
|
||||
version: 0.18.28
|
||||
resolution: "@expo/cli@npm:0.18.28"
|
||||
"@expo/cli@npm:0.18.29":
|
||||
version: 0.18.29
|
||||
resolution: "@expo/cli@npm:0.18.29"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.20.0"
|
||||
"@expo/code-signing-certificates": "npm:0.0.5"
|
||||
@@ -1271,7 +1299,7 @@ __metadata:
|
||||
ws: "npm:^8.12.1"
|
||||
bin:
|
||||
expo-internal: build/bin/cli
|
||||
checksum: 10c0/fa956b1ddd2a61b62972058cc28a025a1d032effdb1b177fd27651437c368fa1ff8d93da29ea33f78735fbbc064da82d4111a9e09b4fcc3c496ca305f659229e
|
||||
checksum: 10c0/6e9e86f37b84da600db01cdd554cd76ea6a94a50103fa54731d441dfb1b3a81ef25760c14da99cf6590588e001f5abbacec1b43b3414b61f862b491c12cf2568
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1422,12 +1450,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@expo/metro-runtime@npm:3.2.1, @expo/metro-runtime@npm:~3.2.1":
|
||||
version: 3.2.1
|
||||
resolution: "@expo/metro-runtime@npm:3.2.1"
|
||||
"@expo/metro-runtime@npm:3.2.3, @expo/metro-runtime@npm:~3.2.3":
|
||||
version: 3.2.3
|
||||
resolution: "@expo/metro-runtime@npm:3.2.3"
|
||||
peerDependencies:
|
||||
react-native: "*"
|
||||
checksum: 10c0/8ae119ade669444f6ae89d941b5afde8003750e1666e95fd0d670c52bb839db4ecc0676324db5399c231b3a79f9c11ea667074371d2d1ae702c69b11ded1d52f
|
||||
checksum: 10c0/a5357c32663e516833feed8f6fd899e1a6ab6acf79b198e860bb82076512e07f95730420eefc87a354d4004d9482b29fbecadcbdcf59b6f8737bba4da03e405e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1630,6 +1658,53 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@inquirer/confirm@npm:^3.0.0":
|
||||
version: 3.2.0
|
||||
resolution: "@inquirer/confirm@npm:3.2.0"
|
||||
dependencies:
|
||||
"@inquirer/core": "npm:^9.1.0"
|
||||
"@inquirer/type": "npm:^1.5.3"
|
||||
checksum: 10c0/a2cbfc8ae9c880bba4cce1993f5c399fb0d12741fdd574917c87fceb40ece62ffa60e35aaadf4e62d7c114f54008e45aee5d6d90497bb62d493996c02725d243
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@inquirer/core@npm:^9.1.0":
|
||||
version: 9.1.0
|
||||
resolution: "@inquirer/core@npm:9.1.0"
|
||||
dependencies:
|
||||
"@inquirer/figures": "npm:^1.0.5"
|
||||
"@inquirer/type": "npm:^1.5.3"
|
||||
"@types/mute-stream": "npm:^0.0.4"
|
||||
"@types/node": "npm:^22.5.2"
|
||||
"@types/wrap-ansi": "npm:^3.0.0"
|
||||
ansi-escapes: "npm:^4.3.2"
|
||||
cli-spinners: "npm:^2.9.2"
|
||||
cli-width: "npm:^4.1.0"
|
||||
mute-stream: "npm:^1.0.0"
|
||||
signal-exit: "npm:^4.1.0"
|
||||
strip-ansi: "npm:^6.0.1"
|
||||
wrap-ansi: "npm:^6.2.0"
|
||||
yoctocolors-cjs: "npm:^2.1.2"
|
||||
checksum: 10c0/c86cbd1980788dee4151002ed717b5664a79eec1d925e1b38896bbad079647af5c423eaaa39a2291ba4fdf78a33c541ea3f69cbbf030f03815eb523fa05230f8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@inquirer/figures@npm:^1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "@inquirer/figures@npm:1.0.5"
|
||||
checksum: 10c0/ec9ba23db42cb33fa18eb919abf2a18e750e739e64c1883ce4a98345cd5711c60cac12d1faf56a859f52d387deb221c8d3dfe60344ee07955a9a262f8b821fe3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@inquirer/type@npm:^1.5.3":
|
||||
version: 1.5.3
|
||||
resolution: "@inquirer/type@npm:1.5.3"
|
||||
dependencies:
|
||||
mute-stream: "npm:^1.0.0"
|
||||
checksum: 10c0/da92a7410efcb20cf12422558fb8e00136e2ff1746ae1d17ea05511e77139bf2044527d37a70e77f188f158099f7751ed808ca3f82769cbe99c1052509481e95
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@isaacs/cliui@npm:^8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "@isaacs/cliui@npm:8.0.2"
|
||||
@@ -1975,6 +2050,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mswjs/interceptors@npm:^0.35.0":
|
||||
version: 0.35.0
|
||||
resolution: "@mswjs/interceptors@npm:0.35.0"
|
||||
dependencies:
|
||||
"@open-draft/deferred-promise": "npm:^2.2.0"
|
||||
"@open-draft/logger": "npm:^0.3.0"
|
||||
"@open-draft/until": "npm:^2.0.0"
|
||||
is-node-process: "npm:^1.2.0"
|
||||
outvariant: "npm:^1.4.3"
|
||||
strict-event-emitter: "npm:^0.5.1"
|
||||
checksum: 10c0/7e1a03a32afb9dafd6bdd8a77b838d4c6cff61a9d0aecf76a898ca2715c9068a8dfd3a40869dfaf42c834bc833fc057ccc096c2819c8f9a3212469fddc543b66
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nodelib/fs.scandir@npm:2.1.5":
|
||||
version: 2.1.5
|
||||
resolution: "@nodelib/fs.scandir@npm:2.1.5"
|
||||
@@ -2024,6 +2113,30 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@open-draft/deferred-promise@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "@open-draft/deferred-promise@npm:2.2.0"
|
||||
checksum: 10c0/eafc1b1d0fc8edb5e1c753c5e0f3293410b40dde2f92688211a54806d4136887051f39b98c1950370be258483deac9dfd17cf8b96557553765198ef2547e4549
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@open-draft/logger@npm:^0.3.0":
|
||||
version: 0.3.0
|
||||
resolution: "@open-draft/logger@npm:0.3.0"
|
||||
dependencies:
|
||||
is-node-process: "npm:^1.2.0"
|
||||
outvariant: "npm:^1.4.0"
|
||||
checksum: 10c0/90010647b22e9693c16258f4f9adb034824d1771d3baa313057b9a37797f571181005bc50415a934eaf7c891d90ff71dcd7a9d5048b0b6bb438f31bef2c7c5c1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@open-draft/until@npm:^2.0.0, @open-draft/until@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "@open-draft/until@npm:2.1.0"
|
||||
checksum: 10c0/61d3f99718dd86bb393fee2d7a785f961dcaf12f2055f0c693b27f4d0cd5f7a03d498a6d9289773b117590d794a43cd129366fd8e99222e4832f67b1653d54cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@pkgjs/parseargs@npm:^0.11.0":
|
||||
version: 0.11.0
|
||||
resolution: "@pkgjs/parseargs@npm:0.11.0"
|
||||
@@ -2720,9 +2833,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/react-native@npm:^12.7.1":
|
||||
version: 12.7.1
|
||||
resolution: "@testing-library/react-native@npm:12.7.1"
|
||||
"@testing-library/react-native@npm:^12.7.2":
|
||||
version: 12.7.2
|
||||
resolution: "@testing-library/react-native@npm:12.7.2"
|
||||
dependencies:
|
||||
jest-matcher-utils: "npm:^29.7.0"
|
||||
pretty-format: "npm:^29.7.0"
|
||||
@@ -2735,7 +2848,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
jest:
|
||||
optional: true
|
||||
checksum: 10c0/caaa4bdf97834b307b72af05c447ce40a2ba2ff40b464050bc29535caadf81981ea2873668445e633fdb3d13efccb136ef0932d6d9f4736bc6f7f98be98088d4
|
||||
checksum: 10c0/0e4e26bd211056646f8b5c80e9177efc90affe0ddc7e1a2c22742a4e6da7129ec1f9125c7d233adddeb27f429fb3eb91e3f3bfa9e77e176f042475574546b001
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2855,6 +2968,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/mute-stream@npm:^0.0.4":
|
||||
version: 0.0.4
|
||||
resolution: "@types/mute-stream@npm:0.0.4"
|
||||
dependencies:
|
||||
"@types/node": "npm:*"
|
||||
checksum: 10c0/944730fd7b398c5078de3c3d4d0afeec8584283bc694da1803fdfca14149ea385e18b1b774326f1601baf53898ce6d121a952c51eb62d188ef6fcc41f725c0dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node-forge@npm:^1.3.0":
|
||||
version: 1.3.11
|
||||
resolution: "@types/node-forge@npm:1.3.11"
|
||||
@@ -2882,6 +3004,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^22.5.2":
|
||||
version: 22.5.4
|
||||
resolution: "@types/node@npm:22.5.4"
|
||||
dependencies:
|
||||
undici-types: "npm:~6.19.2"
|
||||
checksum: 10c0/b445daa7eecd761ad4d778b882d6ff7bcc3b4baad2086ea9804db7c5d4a4ab0298b00d7f5315fc640a73b5a1d52bbf9628e09c9fec0cf44dbf9b4df674a8717d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/prop-types@npm:*":
|
||||
version: 15.7.12
|
||||
resolution: "@types/prop-types@npm:15.7.12"
|
||||
@@ -2913,6 +3044,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/statuses@npm:^2.0.4":
|
||||
version: 2.0.5
|
||||
resolution: "@types/statuses@npm:2.0.5"
|
||||
checksum: 10c0/4dacec0b29483a44be902a022a11a22b339de7a6e7b2059daa4f7add10cb6dbcc28d02d2a416fe9687e48d335906bf983065391836d4e7c847e55ddef4de8fad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/tough-cookie@npm:^4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@types/tough-cookie@npm:4.0.5"
|
||||
checksum: 10c0/68c6921721a3dcb40451543db2174a145ef915bc8bcbe7ad4e59194a0238e776e782b896c7a59f4b93ac6acefca9161fccb31d1ce3b3445cb6faa467297fb473
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/wrap-ansi@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@types/wrap-ansi@npm:3.0.0"
|
||||
checksum: 10c0/8d8f53363f360f38135301a06b596c295433ad01debd082078c33c6ed98b05a5c8fe8853a88265432126096084f4a135ec1564e3daad631b83296905509f90b3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/yargs-parser@npm:*":
|
||||
version: 21.0.3
|
||||
resolution: "@types/yargs-parser@npm:21.0.3"
|
||||
@@ -3480,9 +3632,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"babel-preset-expo@npm:~11.0.13":
|
||||
version: 11.0.13
|
||||
resolution: "babel-preset-expo@npm:11.0.13"
|
||||
"babel-preset-expo@npm:~11.0.14":
|
||||
version: 11.0.14
|
||||
resolution: "babel-preset-expo@npm:11.0.14"
|
||||
dependencies:
|
||||
"@babel/plugin-proposal-decorators": "npm:^7.12.9"
|
||||
"@babel/plugin-transform-export-namespace-from": "npm:^7.22.11"
|
||||
@@ -3494,7 +3646,7 @@ __metadata:
|
||||
babel-plugin-react-compiler: "npm:^0.0.0-experimental-592953e-20240517"
|
||||
babel-plugin-react-native-web: "npm:~0.19.10"
|
||||
react-refresh: "npm:^0.14.2"
|
||||
checksum: 10c0/3a377380dd1cfabeb936082b03514638325563b8d7ee02063ccf254adbd53737e889d0439bafbd42a29b569b5f386768a354a8598e7f61390d14100883615308
|
||||
checksum: 10c0/9d5bb94c21555610c67b7dbe0e592f1ab7f53571dfe72a3ed314768f983a847a9b1dd0efd70e9f172bd68e7dee53d3d012601b8dae27f0593fcdf99c41bcc66f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3872,13 +4024,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-spinners@npm:^2.0.0, cli-spinners@npm:^2.5.0":
|
||||
"cli-spinners@npm:^2.0.0, cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2":
|
||||
version: 2.9.2
|
||||
resolution: "cli-spinners@npm:2.9.2"
|
||||
checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-width@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "cli-width@npm:4.1.0"
|
||||
checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cliui@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "cliui@npm:6.0.0"
|
||||
@@ -4114,6 +4273,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cookie@npm:^0.5.0":
|
||||
version: 0.5.0
|
||||
resolution: "cookie@npm:0.5.0"
|
||||
checksum: 10c0/c01ca3ef8d7b8187bae434434582288681273b5a9ed27521d4d7f9f7928fe0c920df0decd9f9d3bbd2d14ac432b8c8cf42b98b3bdd5bfe0e6edddeebebe8b61d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cookie@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "cookie@npm:0.6.0"
|
||||
@@ -5009,14 +5175,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo-font@npm:~12.0.9":
|
||||
version: 12.0.9
|
||||
resolution: "expo-font@npm:12.0.9"
|
||||
"expo-font@npm:~12.0.10":
|
||||
version: 12.0.10
|
||||
resolution: "expo-font@npm:12.0.10"
|
||||
dependencies:
|
||||
fontfaceobserver: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
expo: "*"
|
||||
checksum: 10c0/9c7b63b3a3ee89bfcdbc1704451019b956b451208f0eca3bb1e57b53dd5dcdfb4d080d423583b92f864889a2a5d7624985c0e5103c54b36b8daf813471696b41
|
||||
checksum: 10c0/49b7da4c5099f74a3641841e8a684a15b743e0d63bfc60355c7b2cf0a5b33b4321b0657c282126795da5ef53778b4d29a765dc9d08fe395e4bc801662305dee8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5039,35 +5205,37 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo-modules-autolinking@npm:1.11.1":
|
||||
version: 1.11.1
|
||||
resolution: "expo-modules-autolinking@npm:1.11.1"
|
||||
"expo-modules-autolinking@npm:1.11.2":
|
||||
version: 1.11.2
|
||||
resolution: "expo-modules-autolinking@npm:1.11.2"
|
||||
dependencies:
|
||||
chalk: "npm:^4.1.0"
|
||||
commander: "npm:^7.2.0"
|
||||
fast-glob: "npm:^3.2.5"
|
||||
find-up: "npm:^5.0.0"
|
||||
fs-extra: "npm:^9.1.0"
|
||||
require-from-string: "npm:^2.0.2"
|
||||
resolve-from: "npm:^5.0.0"
|
||||
bin:
|
||||
expo-modules-autolinking: bin/expo-modules-autolinking.js
|
||||
checksum: 10c0/8d70dda4d63f8ab2323fae13f46191cb8f54d76d6c37f9dc449fbe5a393b5f36f975a9a77ec3257ff880b361999e723be7bc793cbd85eadf9899861cb574469c
|
||||
checksum: 10c0/3a0da953bcd6f0db6374056216117c8764c8e8cddd51d9fd30990c09080704ecf1abc05425a352a21ae09255d9c9eae99ce34ca6091d27dcd1eb8f0c510e3578
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo-modules-core@npm:1.12.20":
|
||||
version: 1.12.20
|
||||
resolution: "expo-modules-core@npm:1.12.20"
|
||||
"expo-modules-core@npm:1.12.24":
|
||||
version: 1.12.24
|
||||
resolution: "expo-modules-core@npm:1.12.24"
|
||||
dependencies:
|
||||
invariant: "npm:^2.2.4"
|
||||
checksum: 10c0/f4b5a5c6b54b1fd8bd8ce896ffbbbc50cd0e25d7d55e16441c1c63da770f1f51aef16f7b9e4be745c921af48b99878d3ba14f9372cba6155119388060cce5351
|
||||
checksum: 10c0/812180ff32f288843592ca38dbebd67878beeb41796a19dd639a2379f0dc728955cff7eabd3e1aedea5ed0c786561334afc41e8fcf206b800c863b8658706d5d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo-router@npm:~3.5.21":
|
||||
version: 3.5.21
|
||||
resolution: "expo-router@npm:3.5.21"
|
||||
"expo-router@npm:~3.5.23":
|
||||
version: 3.5.23
|
||||
resolution: "expo-router@npm:3.5.23"
|
||||
dependencies:
|
||||
"@expo/metro-runtime": "npm:3.2.1"
|
||||
"@expo/metro-runtime": "npm:3.2.3"
|
||||
"@expo/server": "npm:^0.4.0"
|
||||
"@radix-ui/react-slot": "npm:1.0.1"
|
||||
"@react-navigation/bottom-tabs": "npm:~6.5.7"
|
||||
@@ -5092,7 +5260,7 @@ __metadata:
|
||||
optional: true
|
||||
react-native-reanimated:
|
||||
optional: true
|
||||
checksum: 10c0/84c279f2d6b36557e74b74195e72af9161ff05f65574fc09176a6988a8d0e3911711c7cc8fa3b92b2a08f97662f4d091f1cd5beb2dca14439e450351aa7b7d15
|
||||
checksum: 10c0/b14b3598ed8bcfcb46d356203f5031a6ee7c4d905d951d22ffd45800329a4bbd1e833e50628f65c2c99d85b9aa94aac4625b4cdd272dc10530cc8d6ebc3ebcda
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5114,28 +5282,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo@npm:^51.0.26":
|
||||
version: 51.0.26
|
||||
resolution: "expo@npm:51.0.26"
|
||||
"expo@npm:~51.0.31":
|
||||
version: 51.0.32
|
||||
resolution: "expo@npm:51.0.32"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.20.0"
|
||||
"@expo/cli": "npm:0.18.28"
|
||||
"@expo/cli": "npm:0.18.29"
|
||||
"@expo/config": "npm:9.0.3"
|
||||
"@expo/config-plugins": "npm:8.0.8"
|
||||
"@expo/metro-config": "npm:0.18.11"
|
||||
"@expo/vector-icons": "npm:^14.0.0"
|
||||
babel-preset-expo: "npm:~11.0.13"
|
||||
babel-preset-expo: "npm:~11.0.14"
|
||||
expo-asset: "npm:~10.0.10"
|
||||
expo-file-system: "npm:~17.0.1"
|
||||
expo-font: "npm:~12.0.9"
|
||||
expo-font: "npm:~12.0.10"
|
||||
expo-keep-awake: "npm:~13.0.2"
|
||||
expo-modules-autolinking: "npm:1.11.1"
|
||||
expo-modules-core: "npm:1.12.20"
|
||||
expo-modules-autolinking: "npm:1.11.2"
|
||||
expo-modules-core: "npm:1.12.24"
|
||||
fbemitter: "npm:^3.0.0"
|
||||
whatwg-url-without-unicode: "npm:8.0.0-3"
|
||||
bin:
|
||||
expo: bin/cli
|
||||
checksum: 10c0/092cd106e48b8f31eb7e21c782c38816569ba5a26d50c08b74f5bf795cb835e4f4f803e636392a87b2f3e703d60ab885f9b2d576796be2aa961b99201e455312
|
||||
checksum: 10c0/8a5ba07402451e027b58a8e64a7051b91d45776f1b3397bfd71a1f2a9d7752a0e50d5343baf53b985f497de3e735f5494322b9f4a68c832c7a034b2d81748409
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5763,6 +5931,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphql@npm:^16.8.1":
|
||||
version: 16.9.0
|
||||
resolution: "graphql@npm:16.9.0"
|
||||
checksum: 10c0/a8850f077ff767377237d1f8b1da2ec70aeb7623cdf1dfc9e1c7ae93accc0c8149c85abe68923be9871a2934b1bce5a2496f846d4d56e1cfb03eaaa7ddba9b6a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "has-bigints@npm:1.0.2"
|
||||
@@ -5825,6 +6000,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"headers-polyfill@npm:^4.0.2":
|
||||
version: 4.0.3
|
||||
resolution: "headers-polyfill@npm:4.0.3"
|
||||
checksum: 10c0/53e85b2c6385f8d411945fb890c5369f1469ce8aa32a6e8d28196df38568148de640c81cf88cbc7c67767103dd9acba48f4f891982da63178fc6e34560022afe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hermes-estree@npm:0.19.1":
|
||||
version: 0.19.1
|
||||
resolution: "hermes-estree@npm:0.19.1"
|
||||
@@ -6316,6 +6498,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-node-process@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "is-node-process@npm:1.2.0"
|
||||
checksum: 10c0/5b24fda6776d00e42431d7bcd86bce81cb0b6cabeb944142fe7b077a54ada2e155066ad06dbe790abdb397884bdc3151e04a9707b8cd185099efbc79780573ed
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-number-object@npm:^1.0.4":
|
||||
version: 1.0.7
|
||||
resolution: "is-number-object@npm:1.0.7"
|
||||
@@ -8105,6 +8294,45 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"msw@npm:^2.4.4":
|
||||
version: 2.4.4
|
||||
resolution: "msw@npm:2.4.4"
|
||||
dependencies:
|
||||
"@bundled-es-modules/cookie": "npm:^2.0.0"
|
||||
"@bundled-es-modules/statuses": "npm:^1.0.1"
|
||||
"@bundled-es-modules/tough-cookie": "npm:^0.1.6"
|
||||
"@inquirer/confirm": "npm:^3.0.0"
|
||||
"@mswjs/interceptors": "npm:^0.35.0"
|
||||
"@open-draft/until": "npm:^2.1.0"
|
||||
"@types/cookie": "npm:^0.6.0"
|
||||
"@types/statuses": "npm:^2.0.4"
|
||||
chalk: "npm:^4.1.2"
|
||||
graphql: "npm:^16.8.1"
|
||||
headers-polyfill: "npm:^4.0.2"
|
||||
is-node-process: "npm:^1.2.0"
|
||||
outvariant: "npm:^1.4.2"
|
||||
path-to-regexp: "npm:^6.2.0"
|
||||
strict-event-emitter: "npm:^0.5.1"
|
||||
type-fest: "npm:^4.9.0"
|
||||
yargs: "npm:^17.7.2"
|
||||
peerDependencies:
|
||||
typescript: ">= 4.8.x"
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
bin:
|
||||
msw: cli/index.js
|
||||
checksum: 10c0/0be51fbd6ef1b4fd8906353b6e40473f9466ea7ef9cc805e49c5937886721db20801b2322535b456d523040c012afb34e0e76a5beaa4891cbdde2408d2652094
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mute-stream@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "mute-stream@npm:1.0.0"
|
||||
checksum: 10c0/dce2a9ccda171ec979a3b4f869a102b1343dee35e920146776780de182f16eae459644d187e38d59a3d37adf85685e1c17c38cf7bfda7e39a9880f7a1d10a74c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mz@npm:^2.7.0":
|
||||
version: 2.7.0
|
||||
resolution: "mz@npm:2.7.0"
|
||||
@@ -8493,6 +8721,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"outvariant@npm:^1.4.0, outvariant@npm:^1.4.2, outvariant@npm:^1.4.3":
|
||||
version: 1.4.3
|
||||
resolution: "outvariant@npm:1.4.3"
|
||||
checksum: 10c0/5976ca7740349cb8c71bd3382e2a762b1aeca6f33dc984d9d896acdf3c61f78c3afcf1bfe9cc633a7b3c4b295ec94d292048f83ea2b2594fae4496656eba992c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-finally@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "p-finally@npm:1.0.0"
|
||||
@@ -8677,6 +8912,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-to-regexp@npm:^6.2.0":
|
||||
version: 6.2.2
|
||||
resolution: "path-to-regexp@npm:6.2.2"
|
||||
checksum: 10c0/4b60852d3501fd05ca9dd08c70033d73844e5eca14e41f499f069afa8364f780f15c5098002f93bd42af8b3514de62ac6e82a53b5662de881d2b08c9ef21ea6b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-type@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "path-type@npm:4.0.0"
|
||||
@@ -8887,6 +9129,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"psl@npm:^1.1.33":
|
||||
version: 1.9.0
|
||||
resolution: "psl@npm:1.9.0"
|
||||
checksum: 10c0/6a3f805fdab9442f44de4ba23880c4eba26b20c8e8e0830eff1cb31007f6825dace61d17203c58bfe36946842140c97a1ba7f67bc63ca2d88a7ee052b65d97ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pump@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "pump@npm:3.0.0"
|
||||
@@ -8939,6 +9188,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"querystringify@npm:^2.1.1":
|
||||
version: 2.2.0
|
||||
resolution: "querystringify@npm:2.2.0"
|
||||
checksum: 10c0/3258bc3dbdf322ff2663619afe5947c7926a6ef5fb78ad7d384602974c467fadfc8272af44f5eb8cddd0d011aae8fabf3a929a8eee4b86edcc0a21e6bd10f9aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"queue-microtask@npm:^1.2.2":
|
||||
version: 1.2.3
|
||||
resolution: "queue-microtask@npm:1.2.3"
|
||||
@@ -9356,6 +9612,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"requires-port@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "requires-port@npm:1.0.0"
|
||||
checksum: 10c0/b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve-cwd@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "resolve-cwd@npm:3.0.0"
|
||||
@@ -9498,21 +9761,22 @@ __metadata:
|
||||
resolution: "root-workspace-0b6124@workspace:."
|
||||
dependencies:
|
||||
"@babel/core": "npm:^7.20.0"
|
||||
"@expo/metro-runtime": "npm:~3.2.1"
|
||||
"@testing-library/react-native": "npm:^12.7.1"
|
||||
"@expo/metro-runtime": "npm:~3.2.3"
|
||||
"@testing-library/react-native": "npm:^12.7.2"
|
||||
"@types/eslint": "npm:^8.56.10"
|
||||
"@types/jest": "npm:^29.5.12"
|
||||
"@types/react": "npm:~18.2.45"
|
||||
"@types/react-native-get-random-values": "npm:^1"
|
||||
eslint: "npm:^8.57.0"
|
||||
expo: "npm:^51.0.26"
|
||||
expo: "npm:~51.0.31"
|
||||
expo-constants: "npm:~16.0.2"
|
||||
expo-linking: "npm:~6.3.1"
|
||||
expo-router: "npm:~3.5.21"
|
||||
expo-router: "npm:~3.5.23"
|
||||
expo-splash-screen: "npm:~0.27.5"
|
||||
expo-status-bar: "npm:~1.12.1"
|
||||
jest: "npm:^29.7.0"
|
||||
jotai: "npm:^2.8.4"
|
||||
msw: "npm:^2.4.4"
|
||||
nanoid: "npm:^3.3.7"
|
||||
react: "npm:18.2.0"
|
||||
react-dom: "npm:18.2.0"
|
||||
@@ -9821,7 +10085,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"signal-exit@npm:^4.0.1":
|
||||
"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "signal-exit@npm:4.1.0"
|
||||
checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83
|
||||
@@ -10020,7 +10284,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"statuses@npm:2.0.1":
|
||||
"statuses@npm:2.0.1, statuses@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "statuses@npm:2.0.1"
|
||||
checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0
|
||||
@@ -10048,6 +10312,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strict-event-emitter@npm:^0.5.1":
|
||||
version: 0.5.1
|
||||
resolution: "strict-event-emitter@npm:0.5.1"
|
||||
checksum: 10c0/f5228a6e6b6393c57f52f62e673cfe3be3294b35d6f7842fc24b172ae0a6e6c209fa83241d0e433fc267c503bc2f4ffdbe41a9990ff8ffd5ac425ec0489417f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strict-uri-encode@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "strict-uri-encode@npm:2.0.0"
|
||||
@@ -10498,6 +10769,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tough-cookie@npm:^4.1.4":
|
||||
version: 4.1.4
|
||||
resolution: "tough-cookie@npm:4.1.4"
|
||||
dependencies:
|
||||
psl: "npm:^1.1.33"
|
||||
punycode: "npm:^2.1.1"
|
||||
universalify: "npm:^0.2.0"
|
||||
url-parse: "npm:^1.5.3"
|
||||
checksum: 10c0/aca7ff96054f367d53d1e813e62ceb7dd2eda25d7752058a74d64b7266fd07be75908f3753a32ccf866a2f997604b414cfb1916d6e7f69bc64d9d9939b0d6c45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tr46@npm:~0.0.3":
|
||||
version: 0.0.3
|
||||
resolution: "tr46@npm:0.0.3"
|
||||
@@ -10588,6 +10871,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-fest@npm:^4.9.0":
|
||||
version: 4.26.1
|
||||
resolution: "type-fest@npm:4.26.1"
|
||||
checksum: 10c0/d2719ff8d380befe8a3c61068f37f28d6fa2849fd140c5d2f0f143099e371da6856aad7c97e56b83329d45bfe504afe9fd936a7cff600cc0d46aa9ffb008d6c6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typed-array-buffer@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "typed-array-buffer@npm:1.0.2"
|
||||
@@ -10707,6 +10997,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici-types@npm:~6.19.2":
|
||||
version: 6.19.8
|
||||
resolution: "undici-types@npm:6.19.8"
|
||||
checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici@npm:^6.11.1":
|
||||
version: 6.19.7
|
||||
resolution: "undici@npm:6.19.7"
|
||||
@@ -10788,6 +11085,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"universalify@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "universalify@npm:0.2.0"
|
||||
checksum: 10c0/cedbe4d4ca3967edf24c0800cfc161c5a15e240dac28e3ce575c689abc11f2c81ccc6532c8752af3b40f9120fb5e454abecd359e164f4f6aa44c29cd37e194fe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"universalify@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "universalify@npm:1.0.0"
|
||||
@@ -10839,6 +11143,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"url-parse@npm:^1.5.3":
|
||||
version: 1.5.10
|
||||
resolution: "url-parse@npm:1.5.10"
|
||||
dependencies:
|
||||
querystringify: "npm:^2.1.1"
|
||||
requires-port: "npm:^1.0.0"
|
||||
checksum: 10c0/bd5aa9389f896974beb851c112f63b466505a04b4807cea2e5a3b7092f6fbb75316f0491ea84e44f66fed55f1b440df5195d7e3a8203f64fcefa19d182f5be87
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-latest-callback@npm:^0.2.1":
|
||||
version: 0.2.1
|
||||
resolution: "use-latest-callback@npm:0.2.1"
|
||||
@@ -11329,7 +11643,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs@npm:^17.3.1, yargs@npm:^17.6.2":
|
||||
"yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2":
|
||||
version: 17.7.2
|
||||
resolution: "yargs@npm:17.7.2"
|
||||
dependencies:
|
||||
@@ -11350,3 +11664,10 @@ __metadata:
|
||||
checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yoctocolors-cjs@npm:^2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "yoctocolors-cjs@npm:2.1.2"
|
||||
checksum: 10c0/a0e36eb88fea2c7981eab22d1ba45e15d8d268626e6c4143305e2c1628fa17ebfaa40cd306161a8ce04c0a60ee0262058eab12567493d5eb1409780853454c6f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { resetToDefaults } from './src/pure';
|
||||
|
||||
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
|
||||
|
||||
beforeEach(() => {
|
||||
resetToDefaults();
|
||||
});
|
||||
|
||||
+3
-3
@@ -70,13 +70,13 @@
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@callstack/eslint-config": "^15.0.0",
|
||||
"@release-it/conventional-changelog": "^8.0.1",
|
||||
"@release-it/conventional-changelog": "^9.0.0",
|
||||
"@relmify/jest-serializer-strip-ansi": "^1.0.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-reconciler": "^0",
|
||||
"babel-jest": "^29.7.0",
|
||||
"del-cli": "^5.1.0",
|
||||
"del-cli": "^6.0.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-flowtype": "^8.0.3",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
@@ -84,7 +84,7 @@
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^2.8.8",
|
||||
"react": "18.3.1",
|
||||
"react-native": "0.75.1",
|
||||
"react-native": "0.76.0-rc.6",
|
||||
"release-it": "^17.6.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"typescript": "^5.5.4"
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
"name": "basics",
|
||||
"label": "Basic Recipes"
|
||||
},
|
||||
{
|
||||
"type": "dir",
|
||||
"name": "advanced",
|
||||
"label": "Advanced Recipes"
|
||||
},
|
||||
{
|
||||
"type": "dir",
|
||||
"name": "state-management",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
["network-requests"]
|
||||
@@ -0,0 +1,380 @@
|
||||
# Network Requests
|
||||
|
||||
## Introduction
|
||||
|
||||
Mocking network requests is an essential part of testing React Native applications. By mocking
|
||||
network
|
||||
requests, you can control the data that is returned from the server and test how your application
|
||||
behaves in different scenarios, such as when the request is successful or when it fails.
|
||||
|
||||
In this guide, we will show you how to mock network requests and guard your test suits from unwanted
|
||||
and unmocked/unhandled network requests
|
||||
|
||||
:::info
|
||||
To simulate a real-world scenario, we will use the [Random User Generator API](https://randomuser.me/) that provides random user data.
|
||||
:::
|
||||
|
||||
## Phonebook Example
|
||||
|
||||
Let's assume we have a simple phonebook application that
|
||||
uses [`fetch`](https://reactnative.dev/docs/network#using-fetch) for fetching Data from a server.
|
||||
In our case, we have a list of contacts and favorites that we want to display in our application.
|
||||
|
||||
This is how the root of the application looks like:
|
||||
|
||||
```tsx title=network-requests/Phonebook.tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { User } from './types';
|
||||
import ContactsList from './components/ContactsList';
|
||||
import FavoritesList from './components/FavoritesList';
|
||||
import getAllContacts from './api/getAllContacts';
|
||||
import getAllFavorites from './api/getAllFavorites';
|
||||
|
||||
export default () => {
|
||||
const [usersData, setUsersData] = useState<User[]>([]);
|
||||
const [favoritesData, setFavoritesData] = useState<User[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const _getAllContacts = async () => {
|
||||
const _data = await getAllContacts();
|
||||
setUsersData(_data);
|
||||
};
|
||||
const _getAllFavorites = async () => {
|
||||
const _data = await getAllFavorites();
|
||||
setFavoritesData(_data);
|
||||
};
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
await Promise.all([_getAllContacts(), _getAllFavorites()]);
|
||||
} catch (e) {
|
||||
const message = isErrorWithMessage(e) ? e.message : 'Something went wrong';
|
||||
setError(message);
|
||||
}
|
||||
};
|
||||
|
||||
void run();
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return <Text>An error occurred: {error}</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FavoritesList users={favoritesData} />
|
||||
<ContactsList users={usersData} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
We fetch the contacts from the server using the `getAllFavorites` function that utilizes `fetch`.
|
||||
|
||||
```tsx title=network-requests/api/getAllContacts.ts
|
||||
import { User } from '../types';
|
||||
|
||||
export default async (): Promise<User[]> => {
|
||||
const res = await fetch('https://randomuser.me/api/?results=25');
|
||||
if (!res.ok) {
|
||||
throw new Error(`Error fetching contacts`);
|
||||
}
|
||||
const json = await res.json();
|
||||
return json.results;
|
||||
};
|
||||
```
|
||||
|
||||
We have similar function for fetching the favorites, but this time limiting the results to 10.
|
||||
|
||||
```tsx title=network-requests/api/getAllFavorites.ts
|
||||
import { User } from '../types';
|
||||
|
||||
export default async (): Promise<User[]> => {
|
||||
const res = await fetch('https://randomuser.me/api/?results=10');
|
||||
if (!res.ok) {
|
||||
throw new Error(`Error fetching favorites`);
|
||||
}
|
||||
const json = await res.json();
|
||||
return json.results;
|
||||
};
|
||||
```
|
||||
|
||||
Our `FavoritesList` component is a simple component that displays the list of favorite contacts and
|
||||
their avatars horizontally.
|
||||
|
||||
```tsx title=network-requests/components/FavoritesList.tsx
|
||||
import {FlatList, Image, StyleSheet, Text, View} from 'react-native';
|
||||
import React, {useCallback} from 'react';
|
||||
import type {ListRenderItem} from '@react-native/virtualized-lists';
|
||||
import {User} from '../types';
|
||||
|
||||
export default ({users}: { users: User[] }) => {
|
||||
const renderItem: ListRenderItem<User> = useCallback(({item: {picture}}) => {
|
||||
return (
|
||||
<View style={styles.userContainer}>
|
||||
<Image
|
||||
source={{uri: picture.thumbnail}}
|
||||
style={styles.userImage}
|
||||
accessibilityLabel={'favorite-contact-avatar'}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (users.length === 0) return (
|
||||
<View style={styles.loaderContainer}>
|
||||
<Text>Figuring out your favorites...</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.outerContainer}>
|
||||
<Text>⭐My Favorites</Text>
|
||||
<FlatList<User>
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
data={users}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item, index) => `${index}-${item.id.value}`}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// Looking for styles?
|
||||
// Check examples/cookbook/app/advanced/components/FavoritesList.tsx
|
||||
const styles =
|
||||
...
|
||||
```
|
||||
|
||||
Our `ContactsList` component is similar to the `FavoritesList` component, but it displays the list
|
||||
of
|
||||
all contacts vertically.
|
||||
|
||||
```tsx title=network-requests/components/ContactsList.tsx
|
||||
import { FlatList, Image, StyleSheet, Text, View } from 'react-native';
|
||||
import React, { useCallback } from 'react';
|
||||
import type { ListRenderItem } from '@react-native/virtualized-lists';
|
||||
import { User } from '../types';
|
||||
|
||||
export default ({ users }: { users: User[] }) => {
|
||||
const renderItem: ListRenderItem<User> = useCallback(
|
||||
({ item: { name, email, picture, cell }, index }) => {
|
||||
const { title, first, last } = name;
|
||||
const backgroundColor = index % 2 === 0 ? '#f9f9f9' : '#fff';
|
||||
return (
|
||||
<View style={[{ backgroundColor }, styles.userContainer]}>
|
||||
<Image source={{ uri: picture.thumbnail }} style={styles.userImage} />
|
||||
<View>
|
||||
<Text>
|
||||
Name: {title} {first} {last}
|
||||
</Text>
|
||||
<Text>Email: {email}</Text>
|
||||
<Text>Mobile: {cell}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
if (users.length === 0) return <FullScreenLoader />;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<FlatList<User>
|
||||
data={users}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item, index) => `${index}-${item.id.value}`}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// Looking for styles or FullScreenLoader component?
|
||||
// Check examples/cookbook/app/advanced/components/ContactsList.tsx
|
||||
const FullScreenLoader = () => ...
|
||||
const styles = ...
|
||||
```
|
||||
|
||||
## Start testing with a simple test
|
||||
|
||||
In our initial test we would like to test if the `PhoneBook` component renders the `FavoritesList`
|
||||
and `ContactsList` components correctly.
|
||||
We will need to mock the network requests and their corresponding responses to ensure that the component behaves as
|
||||
expected. To mock the network requests we will use [MSW (Mock Service Worker)](https://mswjs.io/docs/getting-started).
|
||||
|
||||
:::note
|
||||
We recommend using the Mock Service Worker (MSW) library to declaratively mock API communication in your tests instead of stubbing `fetch`, or relying on third-party adapters.
|
||||
:::
|
||||
|
||||
:::info
|
||||
You can install MSW by running `npm install msw --save-dev` or `yarn add msw --dev`.
|
||||
More info regarding installation can be found in [MSW's getting started guide](https://mswjs.io/docs/getting-started#step-1-install).
|
||||
|
||||
Please make sure you're also aware of [MSW's setup guide](https://mswjs.io/docs/integrations/react-native).
|
||||
Please be minded that the MSW's setup guide is potentially incomplete and might contain discrepancies/missing pieces.
|
||||
:::
|
||||
|
||||
```tsx title=network-requests/Phonebook.test.tsx
|
||||
import { render, screen, waitForElementToBeRemoved } from '@testing-library/react-native';
|
||||
import React from 'react';
|
||||
import PhoneBook from '../PhoneBook';
|
||||
import { User } from '../types';
|
||||
import {http, HttpResponse} from "msw";
|
||||
import {setupServer} from "msw/node";
|
||||
|
||||
// Define request handlers and response resolvers for random user API.
|
||||
// By default, we always return the happy path response.
|
||||
const handlers = [
|
||||
http.get('https://randomuser.me/api/*', () => {
|
||||
return HttpResponse.json(DATA);
|
||||
}),
|
||||
];
|
||||
|
||||
// Setup a request interception server with the given request handlers.
|
||||
const server = setupServer(...handlers);
|
||||
|
||||
// Enable API mocking via Mock Service Worker (MSW)
|
||||
beforeAll(() => server.listen());
|
||||
// Reset any runtime request handlers we may add during the tests
|
||||
afterEach(() => server.resetHandlers());
|
||||
// Disable API mocking after the tests are done
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe('PhoneBook', () => {
|
||||
it('fetches all contacts and favorites successfully and renders lists in sections correctly', async () => {
|
||||
render(<PhoneBook />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByText(/users data not quite there yet/i));
|
||||
expect(await screen.findByText('Name: Mrs Ida Kristensen')).toBeOnTheScreen();
|
||||
expect(await screen.findByText('Email: ida.kristensen@example.com')).toBeOnTheScreen();
|
||||
expect(await screen.findAllByText(/name/i)).toHaveLength(3);
|
||||
expect(await screen.findByText(/my favorites/i)).toBeOnTheScreen();
|
||||
expect(await screen.findAllByLabelText('favorite-contact-avatar')).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
const DATA: { results: User[] } = {
|
||||
results: [
|
||||
{
|
||||
name: {
|
||||
title: 'Mrs',
|
||||
first: 'Ida',
|
||||
last: 'Kristensen',
|
||||
},
|
||||
email: 'ida.kristensen@example.com',
|
||||
id: {
|
||||
name: 'CPR',
|
||||
value: '250562-5730',
|
||||
},
|
||||
picture: {
|
||||
large: 'https://randomuser.me/api/portraits/women/26.jpg',
|
||||
medium: 'https://randomuser.me/api/portraits/med/women/26.jpg',
|
||||
thumbnail: 'https://randomuser.me/api/portraits/thumb/women/26.jpg',
|
||||
},
|
||||
cell: '123-4567-890',
|
||||
},
|
||||
// For brevity, we have omitted the rest of the users, you can still find them in
|
||||
// examples/cookbook/app/network-requests/__tests__/test-utils.ts
|
||||
...
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
:::info
|
||||
More info regarding how to describe the network using request handlers, intercepting a request and handling its response can be found in the [MSW's documentation](https://mswjs.io/docs/getting-started#step-2-describe).
|
||||
:::
|
||||
|
||||
## Testing error handling
|
||||
|
||||
As we are dealing with network requests, and things can go wrong, we should also cover the case when
|
||||
the API request fails. In this case, we would like to test how our application behaves when the API request fails.
|
||||
|
||||
:::info
|
||||
The nature of the network can be highly dynamic, which makes it challenging to describe it completely in a fixed list of request handlers.
|
||||
MSW provides us the means to override any particular network behavior using the designated `.use()` API.
|
||||
More info can be found in [MSW's Network behavior overrides documentation](https://mswjs.io/docs/best-practices/network-behavior-overrides)
|
||||
:::
|
||||
|
||||
```tsx title=network-requests/Phonebook.test.tsx
|
||||
...
|
||||
|
||||
const mockServerFailureForGetAllContacts = () => {
|
||||
server.use(
|
||||
http.get('https://randomuser.me/api/', ({ request }) => {
|
||||
// Construct a URL instance out of the intercepted request.
|
||||
const url = new URL(request.url);
|
||||
// Read the "results" URL query parameter using the "URLSearchParams" API.
|
||||
const resultsLength = url.searchParams.get('results');
|
||||
// Simulate a server error for the get all contacts request.
|
||||
// We check if the "results" query parameter is set to "25"
|
||||
// to know it's the correct request to mock, in our case get all contacts.
|
||||
if (resultsLength === '25') {
|
||||
return new HttpResponse(null, { status: 500 });
|
||||
}
|
||||
// Return the default response for all other requests that match URL and verb. (in our case get favorites)
|
||||
return HttpResponse.json(DATA);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
describe('PhoneBook', () => {
|
||||
...
|
||||
it('fails to fetch all contacts and renders error message', async () => {
|
||||
mockServerFailureForGetAllContacts();
|
||||
render(<PhoneBook />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByText(/users data not quite there yet/i));
|
||||
expect(
|
||||
await screen.findByText(/an error occurred: error fetching contacts/i),
|
||||
).toBeOnTheScreen();
|
||||
});
|
||||
});
|
||||
|
||||
````
|
||||
|
||||
## Global guarding against unwanted API requests
|
||||
|
||||
As mistakes may happen, we might forget to mock a network request in one of our tests in the future.
|
||||
To prevent us from happening, and alert when a certain network request is left unhandled, you may choose to
|
||||
move MSW's server management from `PhoneBook.test.tsx` to Jest's setup file via [`setupFilesAfterEnv`](https://jestjs.io/docs/configuration#setupfilesafterenv-array).
|
||||
|
||||
```tsx title=examples/cookbook/jest-setup.ts
|
||||
// Enable API mocking via Mock Service Worker (MSW)
|
||||
beforeAll(() => server.listen());
|
||||
// Reset any runtime request handlers we may add during the tests
|
||||
afterEach(() => server.resetHandlers());
|
||||
// Disable API mocking after the tests are done
|
||||
afterAll(() => server.close());
|
||||
|
||||
// ... rest of your setup file
|
||||
```
|
||||
|
||||
This setup will ensure you have the MSW server running before any test suite starts and stops it after all tests are done.
|
||||
Which will result in a warning in the console if you forget to mock an API request in your test suite.
|
||||
|
||||
```bash
|
||||
[MSW] Warning: intercepted a request without a matching request handler:
|
||||
• GET https://randomuser.me/api/?results=25?results=25
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Testing a component that makes network requests in combination with MSW takes some initial preparation to configure and describe the overridden networks.
|
||||
We can achieve that by using MSW's request handlers and intercepting APIs.
|
||||
|
||||
Once up and running we gain full grip over the network requests, their responses, statuses.
|
||||
Doing so is crucial to be able to test how our application behaves in different
|
||||
scenarios, such as when the request is successful or when it fails.
|
||||
|
||||
When global configuration is in place, MSW's will also warn us when an unhandled network requests has occurred throughout a test suite.
|
||||
|
||||
## Further Reading and Alternatives
|
||||
|
||||
Explore more advanced scenarios for mocking network requests with MSW:
|
||||
|
||||
- MSW's Basics - [Intercepting requests](https://mswjs.io/docs/basics/intercepting-requests) and/or [Mocking responses](https://mswjs.io/docs/basics/mocking-responses)
|
||||
- MSW's Network behavior - how to describe [REST](https://mswjs.io/docs/network-behavior/rest) and/or [GraphQL](https://mswjs.io/docs/network-behavior/graphql) APIs
|
||||
@@ -1,5 +1,29 @@
|
||||
# Introduction
|
||||
|
||||
This cookbook is intended to showcase best practices, tips & tricks, and ready-to-use recipes for using React Native Testing Library.
|
||||
Welcome to the **React Native Testing Library (RNTL) Cookbook**!
|
||||
This app is your go-to resource for learning how to effectively test React Native applications.
|
||||
It provides a collection of **best practices**, **ready-made recipes**, and **tips & tricks** to
|
||||
simplify and improve your testing workflow. Whether you’re a beginner just getting started or a
|
||||
seasoned developer looking to sharpen your
|
||||
skills, the Cookbook has something for everyone.
|
||||
|
||||
We invite you to contribute your favorite recipes to the Cookbook. More info [here](https://github.com/callstack/react-native-testing-library/issues/1624).
|
||||
## What's Inside the Cookbook?
|
||||
|
||||
The Cookbook is currently organized into **three main chapters**:
|
||||
|
||||
- **Basic Recipes**: A great starting point, covering essential testing scenarios such as async
|
||||
operations and custom render functions.
|
||||
- **Advanced Recipes**: More complex scenarios like network requests and in the future, navigation
|
||||
testing and more.
|
||||
- **State Management Recipes**: Best practices for testing state management libraries
|
||||
|
||||
Each recipe includes a clear explanation along with a corresponding code example to help you get
|
||||
hands-on with testing. Checkout
|
||||
the [Cookbook App](https://github.com/callstack/react-native-testing-library/tree/main/examples/cookbook#rntl-cookbook) to see the
|
||||
recipes in action.
|
||||
|
||||
## What's Next?
|
||||
|
||||
Join the conversation
|
||||
on [GitHub](https://github.com/callstack/react-native-testing-library/issues/1624) here to discuss
|
||||
ideas, ask questions, or provide feedback.
|
||||
|
||||
@@ -12,7 +12,7 @@ the developer experience.
|
||||
Let's assume we have a simple task list component that uses Jotai for state management. The
|
||||
component has a list of tasks, a text input for typing new task name and a button to add a new task to the list.
|
||||
|
||||
```tsx title=jotai/index.test.tsx
|
||||
```tsx title=state-management/jotai/TaskList.tsx
|
||||
import * as React from 'react';
|
||||
import { Pressable, Text, TextInput, View } from 'react-native';
|
||||
import { useAtom } from 'jotai';
|
||||
@@ -65,7 +65,7 @@ We can test our `TaskList` component using React Native Testing Library's (RNTL)
|
||||
function. Although it is sufficient to test the empty state of the `TaskList` component, it is not
|
||||
enough to test the component with initial tasks present in the list.
|
||||
|
||||
```tsx title=jotai/index.test.tsx
|
||||
```tsx title=status-management/jotai/__tests__/TaskList.test.tsx
|
||||
import * as React from 'react';
|
||||
import { render, screen, userEvent } from '@testing-library/react-native';
|
||||
import { renderWithAtoms } from './test-utils';
|
||||
@@ -88,7 +88,7 @@ initial values. We can create a custom render function that uses Jotai's `useHyd
|
||||
hydrate the atoms with initial values. This function will accept the initial atoms and their
|
||||
corresponding values as an argument.
|
||||
|
||||
```tsx title=test-utils.tsx
|
||||
```tsx title=status-management/jotai/test-utils.tsx
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react-native';
|
||||
import { useHydrateAtoms } from 'jotai/utils';
|
||||
@@ -144,7 +144,7 @@ We can now use the `renderWithAtoms` function to render the `TaskList` component
|
||||
In our test, we populated only one atom and its initial value, but you can add other Jotai atoms and their corresponding values to the initialValues array as needed.
|
||||
:::
|
||||
|
||||
```tsx title=jotai/index.test.tsx
|
||||
```tsx title=status-management/jotai/__tests__/TaskList.test.tsx
|
||||
=======
|
||||
const INITIAL_TASKS: Task[] = [{ id: '1', title: 'Buy bread' }];
|
||||
|
||||
@@ -173,7 +173,7 @@ test('renders a to do list with 1 items initially, and adds a new item', async (
|
||||
In several cases, you might need to change an atom's state outside a React component. In our case,
|
||||
we have a set of functions to get tasks and set tasks, which change the state of the task list atom.
|
||||
|
||||
```tsx title=state.ts
|
||||
```tsx title=state-management/jotai/state.ts
|
||||
import { atom, createStore } from 'jotai';
|
||||
import { Task } from './types';
|
||||
|
||||
@@ -201,7 +201,7 @@ the initial to-do items in the store and then checking if the functions work as
|
||||
No special setup is required to test these functions, as `store.set` is available by default by
|
||||
Jotai.
|
||||
|
||||
```tsx title=jotai/index.test.tsx
|
||||
```tsx title=state-management/jotai/__tests__/TaskList.test.tsx
|
||||
import { addTask, getAllTasks, store, tasksAtom } from './state';
|
||||
|
||||
//...
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Queries
|
||||
|
||||
Queries are one of the main building blocks for the React Native Testing Library. They enable you to find relevant elements in the element tree, which represents the your application's user interface when running under tests.
|
||||
Queries are one of the main building blocks for the React Native Testing Library. They enable you to find relevant elements in the element tree, which represents your application's user interface when running under tests.
|
||||
|
||||
## Accessing queries
|
||||
|
||||
|
||||
Reference in New Issue
Block a user