docs: basic cookbook docs and app (#1622)

* docs: basic cookbook docs and app

* chore: add tests

* chore: enable example apps

* chore: fix typecheck

* chore: add lint & fix issues

* docs: tweaks

* docs: tweaks

* docs: tweaks

* chore: tweaks
This commit is contained in:
Maciej Jastrzebski
2024-05-28 11:24:05 +01:00
committed by GitHub
parent e418b6b5bf
commit 723c8e2c04
28 changed files with 11012 additions and 1 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
test-example:
strategy:
matrix:
example: [basic]
example: [basic, cookbook]
name: Test Example
runs-on: ubuntu-latest
+1
View File
@@ -0,0 +1 @@
test-utils.*
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "@callstack"
}
@@ -0,0 +1,4 @@
{
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
}
+25
View File
@@ -0,0 +1,25 @@
# General Node.js
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# Yarn 4.x
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# macOS
.DS_Store
+8
View File
@@ -0,0 +1,8 @@
import * as React from 'react';
import { View } from 'react-native';
const App = () => {
return <View />;
};
export default App;
+8
View File
@@ -0,0 +1,8 @@
# RNTL Cookbook
This example app gathers recipes from the [RNTL Cookbook](https://callstack.github.io/react-native-testing-library/cookbook).
Each recipe described in the Cookbook should have a corresponding code example in this repo.
Note:
Since examples will showcase usage of different dependencies, the dependencies in `package.json` fill will grow much larger that in an normal React Native. This is fine 🐶☕️🔥.
+31
View File
@@ -0,0 +1,31 @@
{
"expo": {
"name": "RNTL Cookbook App",
"slug": "rntl-cookbook",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

+6
View File
@@ -0,0 +1,6 @@
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
@@ -0,0 +1,24 @@
import * as React from 'react';
import { screen } from '@testing-library/react-native';
import { renderWithProviders } from './test-utils';
import { WelcomeScreen } from './WelcomeScreen';
test('renders WelcomeScreen in light theme', () => {
renderWithProviders(<WelcomeScreen />, { theme: 'light' });
expect(screen.getByText('Theme: light')).toBeOnTheScreen();
});
test('renders WelcomeScreen in dark theme', () => {
renderWithProviders(<WelcomeScreen />, { theme: 'dark' });
expect(screen.getByText('Theme: dark')).toBeOnTheScreen();
});
test('renders WelcomeScreen with user', () => {
renderWithProviders(<WelcomeScreen />, { user: { name: 'Jar-Jar' } });
expect(screen.getByText(/hello Jar-Jar/i)).toBeOnTheScreen();
});
test('renders WelcomeScreen without user', () => {
renderWithProviders(<WelcomeScreen />, { user: null });
expect(screen.getByText(/hello stranger/i)).toBeOnTheScreen();
});
@@ -0,0 +1,16 @@
import * as React from 'react';
import { View, Text } from 'react-native';
import { useUser } from './providers/user-provider';
import { useTheme } from './providers/theme-provider';
export function WelcomeScreen() {
const theme = useTheme();
const user = useUser();
return (
<View>
<Text>Hello {user ? user.name : 'Stranger'}</Text>
<Text>Theme: {theme}</Text>
</View>
);
}
@@ -0,0 +1,13 @@
import * as React from 'react';
export type Theme = 'light' | 'dark';
export const ThemeProvider = React.createContext<Theme | undefined>(undefined);
export function useTheme() {
const theme = React.useContext(ThemeProvider);
if (theme === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return theme;
}
@@ -0,0 +1,8 @@
import * as React from 'react';
export type User = { name: string };
export const UserProvider = React.createContext<User | null>(null);
export function useUser() {
return React.useContext(UserProvider);
}
@@ -0,0 +1,20 @@
import * as React from 'react';
import { render } from '@testing-library/react-native';
import { User, UserProvider } from './providers/user-provider';
import { Theme, ThemeProvider } from './providers/theme-provider';
interface RenderWithProvidersProps {
user?: User | null;
theme?: Theme;
}
export function renderWithProviders<T>(
ui: React.ReactElement<T>,
options?: RenderWithProvidersProps,
) {
return render(
<UserProvider.Provider value={options?.user ?? null}>
<ThemeProvider.Provider value={options?.theme ?? 'light'}>{ui}</ThemeProvider.Provider>
</UserProvider.Provider>,
);
}
+7
View File
@@ -0,0 +1,7 @@
/* eslint-disable no-undef, import/no-extraneous-dependencies */
// Import built-in Jest matchers
import '@testing-library/react-native/extend-expect';
// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
preset: '@testing-library/react-native',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
setupFilesAfterEnv: ['./jest-setup.ts'],
};
+34
View File
@@ -0,0 +1,34 @@
{
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject",
"test": "jest",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"expo": "^50.0.4",
"expo-status-bar": "~1.11.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.73.2",
"react-native-web": "~0.19.6"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@testing-library/react-native": "^12.4.0",
"@types/eslint": "^8.56.10",
"@types/jest": "^29.5.12",
"@types/react": "~18.2.45",
"eslint": "^8.57.0",
"jest": "^29.7.0",
"react-test-renderer": "18.2.0",
"typescript": "^5.3.0"
},
"private": true,
"packageManager": "yarn@4.0.1"
}
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"allowJs": false
}
}
File diff suppressed because it is too large Load Diff
+10
View File
@@ -1,4 +1,14 @@
[
{
"text": "Docs",
"link": "/docs/start/intro",
"activeMatch": "^/docs/"
},
{
"text": "Cookbook",
"link": "/cookbook/",
"activeMatch": "^/cookbook/"
},
{
"text": "Examples",
"link": "https://github.com/callstack/react-native-testing-library/tree/main/examples"
+1
View File
@@ -0,0 +1 @@
["index", { "type": "dir", "name": "basics", "label": "Basic Recipes" }]
@@ -0,0 +1 @@
["custom-render"]
@@ -0,0 +1,78 @@
# Custom `render` function
### Summary
RNTL exposes the `render` function as the primary entry point for tests. If you make complex, repeating setups for your tests, consider creating a custom render function. The idea is to encapsulate common setup steps and test wiring inside a render function suitable for your tests.
### Example
```tsx title=test-utils.ts
// ...
interface RenderWithProvidersProps {
user?: User | null;
theme?: Theme;
}
export function renderWithProviders<T>(
ui: React.ReactElement<T>,
options?: RenderWithProvidersProps
) {
return render(
<UserProvider.Provider value={options?.user ?? null}>
<ThemeProvider.Provider value={options?.theme ?? 'light'}>{ui}</ThemeProvider.Provider>
</UserProvider.Provider>
);
}
```
```tsx title=WelcomeScreen.test.tsx
import { screen } from '@testing-library/react-native';
import { renderWithProviders } from '../test-utils';
// ...
test('renders WelcomeScreen with user', () => {
renderWithProviders(<WelcomeScreen />, { user: { name: 'Jar-Jar' } });
expect(screen.getByText(/hello Jar-Jar/i)).toBeOnTheScreen();
});
test('renders WelcomeScreen without user', () => {
renderWithProviders(<WelcomeScreen />, { user: null });
expect(screen.getByText(/hello stranger/i)).toBeOnTheScreen();
});
```
Example [full source code](https://github.com/callstack/react-native-testing-library/tree/main/examples/cookbook/custom-render).
### More info
#### Additional params
A custom render function might accept additional parameters to allow for setting up different start conditions for a test, e.g., the initial state for global state management.
```tsx title=SomeScreen.test.tsx
test('renders SomeScreen for logged in user', () => {
renderScreen(<SomeScreen />, { state: loggedInState });
// ...
});
```
#### Multiple functions
Depending on the situation, you may declare more than one custom render function. For example, you have one function for testing application flows and a second for testing individual screens.
```tsx title=test-utils.tsx
function renderNavigator(ui, options);
function renderScreen(ui, options);
```
#### Async function
Make it async if you want to put some async setup in your custom render function.
```tsx title=SomeScreen.test.tsx
test('renders SomeScreen', async () => {
await renderWithAsync(<SomeScreen />);
// ...
});
```
+5
View File
@@ -0,0 +1,5 @@
# Introduction
This cookbook is intended to showcase best practices, tips & tricks, and ready-to-use recipes for using React Native Testing Library.
We invite you to contribute your favorite recipes to the Cookbook. More info [here](https://github.com/callstack/react-native-testing-library/issues/1624).