packages = new PackageList(this).getPackages();
27 | // Packages that cannot be autolinked yet can be added manually here, for example:
28 | // packages.add(new MyReactNativePackage());
29 | return packages;
30 | }
31 |
32 | @Override
33 | protected String getJSMainModuleName() {
34 | return "index";
35 | }
36 | };
37 |
38 | @Override
39 | public ReactNativeHost getReactNativeHost() {
40 | return mReactNativeHost;
41 | }
42 |
43 | @Override
44 | public void onCreate() {
45 | super.onCreate();
46 | SoLoader.init(this, /* native exopackage */ false);
47 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
48 | }
49 |
50 | /**
51 | * Loads Flipper in React Native templates. Call this in the onCreate method with something like
52 | * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
53 | *
54 | * @param context
55 | * @param reactInstanceManager
56 | */
57 | private static void initializeFlipper(
58 | Context context, ReactInstanceManager reactInstanceManager) {
59 | if (BuildConfig.DEBUG) {
60 | try {
61 | /*
62 | We use reflection here to pick up the class that initializes Flipper,
63 | since Flipper library is not available in release mode
64 | */
65 | Class> aClass = Class.forName("com.reactnativetodo.ReactNativeFlipper");
66 | aClass
67 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
68 | .invoke(null, context, reactInstanceManager);
69 | } catch (ClassNotFoundException e) {
70 | e.printStackTrace();
71 | } catch (NoSuchMethodException e) {
72 | e.printStackTrace();
73 | } catch (IllegalAccessException e) {
74 | e.printStackTrace();
75 | } catch (InvocationTargetException e) {
76 | e.printStackTrace();
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/reducers/index.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../actions';
2 |
3 | export const initialState = {
4 | todos: [],
5 | creating: false,
6 | error: null,
7 | fetching: false,
8 | filter: 'All',
9 | };
10 |
11 | export const rootReducer = (state = initialState, action) => {
12 | switch (action.type) {
13 | case Actions.CREATE_TODO:
14 | return {
15 | ...state,
16 | creating: true,
17 | };
18 | case Actions.CREATE_TODO_ERROR:
19 | return {
20 | ...state,
21 | creating: false,
22 | error: action.error
23 | };
24 | case Actions.CREATE_TODO_SUCCESS:
25 | return {
26 | ...state,
27 | creating: false,
28 | todos: [ ...state.todos, action.todo ],
29 | error: null,
30 | };
31 | case Actions.DELETE_TODO:
32 | return {
33 | ...state,
34 | todos: state.todos.map(todo => todo.id === action.id
35 | ? { ...todo, progress: true }
36 | : todo
37 | )
38 | };
39 | case Actions.DELETE_TODO_ERROR:
40 | return {
41 | ...state,
42 | error: action.error,
43 | todos: state.todos.map(todo => todo.id === action.id
44 | ? { ...todo, progress: false }
45 | : todo
46 | )
47 | };
48 | case Actions.DELETE_TODO_SUCCESS:
49 | return {
50 | ...state,
51 | error: null,
52 | todos: state.todos.filter(todo => todo.id !== action.id)
53 | };
54 | case Actions.FETCH_TODOS:
55 | return {
56 | ...state,
57 | fetching: true,
58 | };
59 | case Actions.FETCH_TODOS_ERROR:
60 | return {
61 | ...state,
62 | fetching: false,
63 | error: action.error
64 | };
65 | case Actions.FETCH_TODOS_SUCCESS:
66 | return {
67 | ...state,
68 | fetching: false,
69 | todos: action.todos,
70 | error: null,
71 | };
72 | case Actions.FILTER_TODOS:
73 | return {
74 | ...state,
75 | filter: action.filter
76 | };
77 | case Actions.TOGGLE_TODO:
78 | return {
79 | ...state,
80 | todos: state.todos.map(todo => todo.id === action.id
81 | ? { ...todo, progress: true }
82 | : todo
83 | )
84 | };
85 | case Actions.TOGGLE_TODO_ERROR:
86 | return {
87 | ...state,
88 | error: action.error,
89 | todos: state.todos.map(todo => todo.id === action.id
90 | ? { ...todo, progress: false }
91 | : todo
92 | )
93 | };
94 | case Actions.TOGGLE_TODO_SUCCESS:
95 | return {
96 | ...state,
97 | error: null,
98 | todos: state.todos.map(todo => todo.id === action.id
99 | ? { ...todo, progress: false, done: action.done }
100 | : todo
101 | )
102 | };
103 | default:
104 | return state;
105 | }
106 | };
107 |
--------------------------------------------------------------------------------
/__tests__/components/CreateTodo.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestRenderer from 'react-test-renderer';
3 | import { TextInput, TouchableOpacity } from 'react-native';
4 | import * as reactRedux from 'react-redux'
5 |
6 | import * as Actions from '../../actions';
7 | import { CreateTodo } from '../../components/CreateTodo';
8 |
9 | // useDispatch() will be called by every test
10 | const useDispatchMock = jest.fn();
11 | jest.spyOn(reactRedux, 'useDispatch').mockReturnValue(useDispatchMock);
12 |
13 | describe('CreateTodo', () => {
14 | it('Handles clean state', () => {
15 | const useRefMock = {
16 | current: {
17 | clear: jest.fn(),
18 | focus: jest.fn()
19 | }
20 | };
21 | jest.spyOn(React, 'useRef').mockReturnValue(useRefMock); // TextInput ref
22 | // useSelector() mocks must be defined in the same order as the calls
23 | jest.spyOn(reactRedux, 'useSelector')
24 | .mockReturnValueOnce(null) // state.error
25 | .mockReturnValueOnce(false); // state.creating
26 |
27 | let renderer;
28 | // Wrap create() in act() for hooks to take effect
29 | TestRenderer.act(() => {
30 | renderer = TestRenderer.create(
31 |
32 | );
33 | });
34 | const root = renderer.root;
35 | const textInput = root.findByType(TextInput);
36 |
37 | expect(textInput.props.value).toBe(null);
38 | expect(useRefMock.current.clear).toHaveBeenCalled();
39 | expect(useRefMock.current.focus).toHaveBeenCalled();
40 | });
41 |
42 | it('Handles busy state', () => {
43 | // useSelector() mocks must be defined in the same order as the calls
44 | jest.spyOn(reactRedux, 'useSelector')
45 | .mockReturnValueOnce(null) // state.error
46 | .mockReturnValueOnce(true); // state.creating
47 |
48 | let renderer;
49 | // Wrap create() in act() for hooks to take effect
50 | TestRenderer.act(() => {
51 | renderer = TestRenderer.create(
52 |
53 | );
54 | });
55 | const root = renderer.root;
56 | const touchable = root.findByType(TouchableOpacity);
57 |
58 | expect(touchable.props.disabled).toBe(true);
59 | });
60 |
61 | it('Handles success', () => {
62 | const text = 'Todo';
63 | const expectedAction = Actions.createTodo(text);
64 | // useSelector() will be called by every act()
65 | jest.spyOn(reactRedux, 'useSelector').mockReturnValue(null);
66 |
67 | let renderer;
68 | // Wrap create() in act() for hooks to take effect
69 | TestRenderer.act(() => {
70 | renderer = TestRenderer.create(
71 |
72 | );
73 | });
74 | const root = renderer.root;
75 | const textInput = root.findByType(TextInput);
76 |
77 | // Wrap each call in act() for it to take effect before the next one
78 | TestRenderer.act(() => {
79 | textInput.props.onChangeText(text);
80 | });
81 |
82 | TestRenderer.act(() => {
83 | textInput.props.onSubmitEditing();
84 | });
85 |
86 | expect(useDispatchMock).toHaveBeenCalledWith(expectedAction);
87 | });
88 | });
89 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/__tests__/components/TodoFilters.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestRenderer from 'react-test-renderer';
3 | import { Text, TouchableOpacity } from 'react-native';
4 | import * as reactRedux from 'react-redux';
5 |
6 | import { Colors } from '../../components/Styles';
7 | import { TodoFilters, TodoFilter } from '../../components/TodoFilters';
8 | import * as Actions from '../../actions';
9 |
10 | // useDispatch() will be called by every TodoFilters test
11 | const useDispatchMock = jest.fn();
12 | jest.spyOn(reactRedux, 'useDispatch').mockReturnValue(useDispatchMock);
13 |
14 | describe('TodoFilters', () => {
15 | it('Renders filters', () => {
16 | jest.spyOn(reactRedux, 'useSelector').mockReturnValue(null); // state.filter
17 |
18 | const renderer = TestRenderer.create(
19 |
20 | );
21 | const root = renderer.root;
22 | const filters = root.findAllByType(TodoFilter);
23 |
24 | expect(filters).toHaveLength(3);
25 | expect(filters[0].props.filter).toBe('All');
26 | expect(filters[1].props.filter).toBe('Active');
27 | expect(filters[2].props.filter).toBe('Done');
28 | });
29 |
30 | it('Handles selected state', () => {
31 | jest.spyOn(reactRedux, 'useSelector').mockReturnValue('Done'); // state.filter
32 |
33 | const renderer = TestRenderer.create(
34 |
35 | );
36 | const root = renderer.root;
37 | const filters = root.findAllByType(TodoFilter);
38 |
39 | expect(filters[2].props.selected).toBe(true);
40 | });
41 |
42 | it('Handles user actions', () => {
43 | jest.spyOn(reactRedux, 'useSelector').mockReturnValue(null); // state.filter
44 |
45 | const renderer = TestRenderer.create(
46 |
47 | );
48 | const root = renderer.root;
49 | const filters = root.findAllByType(TodoFilter);
50 | const touchable = filters[2].findByType(TouchableOpacity);
51 | const expectedAction = Actions.filterTodos('Done');
52 |
53 | touchable.props.onPress();
54 |
55 | expect(useDispatchMock).toHaveBeenCalledWith(expectedAction);
56 | });
57 | });
58 |
59 | describe('TodoFilter', () => {
60 | it('Handles clean state', () => {
61 | const renderer = TestRenderer.create(
62 |
63 | );
64 | const root = renderer.root;
65 | const text = root.findByType(Text);
66 |
67 | expect(text.props.children).toBe('All');
68 | });
69 |
70 | it('Handles selected state', () => {
71 | const renderer = TestRenderer.create(
72 |
73 | );
74 | const root = renderer.root;
75 | const touchable = root.findByType(TouchableOpacity);
76 | const selectedStyle = {
77 | backgroundColor: Colors.buttonActive,
78 | };
79 |
80 | expect(touchable.props.style).toContainEqual(selectedStyle);
81 | });
82 |
83 | it('Handles user actions', () => {
84 | const selectMock = jest.fn();
85 | const renderer = TestRenderer.create(
86 |
87 | );
88 | const root = renderer.root;
89 | const touchable = root.findByType(TouchableOpacity);
90 |
91 | touchable.props.onPress();
92 |
93 | expect(selectMock).toHaveBeenCalledWith('All');
94 | });
95 | });
96 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 | const fs = require('fs');
4 | const cors = require('cors');
5 | const bodyParser = require('body-parser');
6 |
7 | const app = express();
8 | app.use(bodyParser.urlencoded({extended: false}));
9 | app.use(bodyParser.json());
10 |
11 | app.use(cors({
12 | 'origin': '*',
13 | 'allowedHeaders': ['Content-Type', 'Accept'],
14 | 'methods': ['GET, POST']
15 | }));
16 |
17 | const PORT = 3000;
18 | const FILE = path.join(__dirname, './todos.json');
19 | const TIMEOUT = 200;
20 |
21 | const readTodos = () => {
22 | return new Promise((resolve, reject) => {
23 | fs.readFile(FILE, 'utf8', (err, todos) => {
24 | if (err) {
25 | reject(err);
26 | }
27 | else {
28 | resolve(JSON.parse(todos));
29 | }
30 | });
31 | });
32 | };
33 |
34 | const writeTodos = (todos) => {
35 | return new Promise((resolve, reject) => {
36 | fs.writeFile(FILE, JSON.stringify(todos), err => {
37 | if (err) {
38 | reject(err);
39 | }
40 | else {
41 | resolve(todos);
42 | }
43 | });
44 | });
45 | };
46 |
47 | const fetchTodos = async (req, res, next) => {
48 | try {
49 | const todos = await readTodos();
50 | setTimeout(() => res.json(todos), TIMEOUT);
51 | }
52 | catch (err) {
53 | next(err);
54 | }
55 | };
56 |
57 | const createTodo = async (req, res, next) => {
58 | const { text } = req.body;
59 | try {
60 | if (!text) {
61 | throw Error('No text provided!');
62 | }
63 | let todos = await readTodos();
64 | let todo = todos.find(todo => todo.text === text);
65 | if (todo) {
66 | throw Error('Todo already exists!');
67 | }
68 | else {
69 | todo = { text, id: Date.now(), done: false };
70 | todos.push(todo);
71 | await writeTodos(todos);
72 | setTimeout(() => res.json(todo), TIMEOUT);
73 | }
74 | }
75 | catch (err) {
76 | next(err);
77 | }
78 | };
79 |
80 | const deleteTodo = async (req, res, next) => {
81 | const { id } = req.body;
82 | try {
83 | let todos = await readTodos();
84 | let todo = todos.find(todo => todo.id === id);
85 | if (todo) {
86 | todos = todos.filter(todo => todo.id !== id);
87 | await writeTodos(todos);
88 | setTimeout(() => res.json(todo), TIMEOUT);
89 | }
90 | else {
91 | throw Error('Todo not found!');
92 | }
93 | }
94 | catch (err) {
95 | next(err);
96 | }
97 | };
98 |
99 | const toggleTodo = async (req, res, next) => {
100 | const { id, done } = req.body;
101 | try {
102 | let todos = await readTodos();
103 | let todo = todos.find(todo => todo.id === id);
104 | if (todo) {
105 | todo.done = done;
106 | await writeTodos(todos);
107 | setTimeout(() => res.json(todo), TIMEOUT);
108 | }
109 | else {
110 | throw Error('Todo not found!');
111 | }
112 | }
113 | catch (err) {
114 | next(err);
115 | }
116 | };
117 |
118 | const handleError = (err, req, res, next) => {
119 | // console.error(err);
120 | res.status(500).json({ error: err.message });
121 | };
122 |
123 | app.get('/', fetchTodos);
124 | app.post('/create', createTodo);
125 | app.post('/delete', deleteTodo);
126 | app.post('/toggle', toggleTodo);
127 | app.use(handleError);
128 |
129 | app.listen(PORT, () => console.log(`App listening on port ${PORT}!`));
130 |
--------------------------------------------------------------------------------
/android/app/src/debug/java/com/reactnativetodo/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.reactnativetodo;
8 |
9 | import android.content.Context;
10 | import com.facebook.flipper.android.AndroidFlipperClient;
11 | import com.facebook.flipper.android.utils.FlipperUtils;
12 | import com.facebook.flipper.core.FlipperClient;
13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping;
17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
22 | import com.facebook.react.ReactInstanceManager;
23 | import com.facebook.react.bridge.ReactContext;
24 | import com.facebook.react.modules.network.NetworkingModule;
25 | import okhttp3.OkHttpClient;
26 |
27 | public class ReactNativeFlipper {
28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
29 | if (FlipperUtils.shouldEnableFlipper(context)) {
30 | final FlipperClient client = AndroidFlipperClient.getInstance(context);
31 |
32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
33 | client.addPlugin(new ReactFlipperPlugin());
34 | client.addPlugin(new DatabasesFlipperPlugin(context));
35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context));
36 | client.addPlugin(CrashReporterPlugin.getInstance());
37 |
38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
39 | NetworkingModule.setCustomClientBuilder(
40 | new NetworkingModule.CustomClientBuilder() {
41 | @Override
42 | public void apply(OkHttpClient.Builder builder) {
43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
44 | }
45 | });
46 | client.addPlugin(networkFlipperPlugin);
47 | client.start();
48 |
49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
50 | // Hence we run if after all native modules have been initialized
51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
52 | if (reactContext == null) {
53 | reactInstanceManager.addReactInstanceEventListener(
54 | new ReactInstanceManager.ReactInstanceEventListener() {
55 | @Override
56 | public void onReactContextInitialized(ReactContext reactContext) {
57 | reactInstanceManager.removeReactInstanceEventListener(this);
58 | reactContext.runOnNativeModulesQueueThread(
59 | new Runnable() {
60 | @Override
61 | public void run() {
62 | client.addPlugin(new FrescoFlipperPlugin());
63 | }
64 | });
65 | }
66 | });
67 | } else {
68 | client.addPlugin(new FrescoFlipperPlugin());
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/ios/ReactNativeTodo.xcodeproj/xcshareddata/xcschemes/ReactNativeTodo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/__tests__/components/TodoList.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestRenderer from 'react-test-renderer';
3 | import { FlatList, ActivityIndicator } from 'react-native';
4 | import * as reactRedux from 'react-redux';
5 |
6 | import { TodoList } from '../../components/TodoList';
7 | import { Todo } from '../../components/Todo';
8 | import * as Actions from '../../actions';
9 |
10 | // useDispatch() will be called by every test
11 | const useDispatchMock = jest.fn();
12 | jest.spyOn(reactRedux, 'useDispatch').mockReturnValue(useDispatchMock);
13 | // fetchTodosAsync() will be called by every test
14 | jest.spyOn(Actions, 'fetchTodosAsync').mockImplementation(jest.fn());
15 |
16 | describe('TodoList', () => {
17 | it('Renders busy state', () => {
18 | // useSelector() mocks must be defined in the same order as the calls
19 | jest.spyOn(reactRedux, 'useSelector')
20 | .mockReturnValueOnce([]) // state.todos
21 | .mockReturnValueOnce(null) // state.filter
22 | .mockReturnValueOnce(true) // state.fetching
23 | .mockReturnValue(null); // any other selector
24 |
25 | const renderer = TestRenderer.create(
26 |
27 | );
28 | const root = renderer.root;
29 |
30 | expect(root.findAllByType(ActivityIndicator)).toHaveLength(1);
31 | });
32 |
33 | it('Renders ready state', () => {
34 | const todosMock = [
35 | { id: 1, text: 'Todo one', done: false },
36 | { id: 2, text: 'Todo two', done: true },
37 | ];
38 | // useSelector() mocks must be defined in the same order as the calls
39 | jest.spyOn(reactRedux, 'useSelector')
40 | .mockReturnValueOnce(todosMock) // state.todos
41 | .mockReturnValueOnce(null) // state.filter
42 | .mockReturnValueOnce(false) // state.fetching
43 | .mockReturnValue(null); // any other selector
44 |
45 | const renderer = TestRenderer.create(
46 |
47 | );
48 | const root = renderer.root;
49 | const todos = root.findAllByType(Todo);
50 |
51 | expect(todos).toHaveLength(2);
52 | expect(todos[0].props.text).toBe('Todo one');
53 | expect(todos[1].props.text).toBe('Todo two');
54 | });
55 |
56 | it('Handles filters', () => {
57 | const todosMock = [
58 | { id: 1, text: 'Todo one', done: false },
59 | { id: 2, text: 'Todo two', done: true },
60 | ];
61 | // useSelector() mocks must be defined in the same order as the calls
62 | jest.spyOn(reactRedux, 'useSelector')
63 | .mockReturnValueOnce(todosMock) // state.todos
64 | .mockReturnValueOnce('Done') // state.filter
65 | .mockReturnValueOnce(false) // state.fetching
66 | .mockReturnValue(null); // any other selector
67 |
68 | const renderer = TestRenderer.create(
69 |
70 | );
71 | const root = renderer.root;
72 | const todos = root.findAllByType(Todo);
73 |
74 | expect(todos).toHaveLength(1);
75 | expect(todos[0].props.done).toBe(true);
76 | });
77 |
78 | it('Handles mounting and unmounting', () => {
79 | // useSelector() mocks must be defined in the same order as the calls
80 | jest.spyOn(reactRedux, 'useSelector')
81 | .mockReturnValueOnce([]) // state.todos
82 | .mockReturnValueOnce(null) // state.filter
83 | .mockReturnValueOnce(false) // state.fetching
84 | .mockReturnValue(null); // any other selector
85 |
86 | let renderer;
87 | // Wrap create() in act() for hooks to take effect
88 | TestRenderer.act(() => {
89 | renderer = TestRenderer.create(
90 |
91 | );
92 | });
93 | // fetchTodosAsync() called on mount
94 | expect(useDispatchMock).toHaveBeenCalledWith(Actions.fetchTodosAsync());
95 |
96 | TestRenderer.act(() => {
97 | renderer.unmount();
98 | });
99 | // toggleTodoCancel() called on unmount
100 | expect(useDispatchMock).toHaveBeenCalledWith(Actions.toggleTodoCancel());
101 | });
102 | });
103 |
--------------------------------------------------------------------------------
/ios/ReactNativeTodo/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/__tests__/integration/integration.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestRenderer from 'react-test-renderer';
3 | import { Text, TextInput, TouchableOpacity } from 'react-native';
4 | import { Provider } from 'react-redux';
5 | import { createStore, applyMiddleware } from 'redux';
6 | import createSagaMiddleware from 'redux-saga';
7 | import thunk from 'redux-thunk';
8 |
9 | import * as Api from '../../api';
10 |
11 | import { App } from '../../components/App';
12 | import { Checkbox } from '../../components/Checkbox';
13 | import { Error } from '../../components/Error';
14 | import { IconButton } from '../../components/IconButton';
15 | import { Todo } from '../../components/Todo';
16 | import { TodoFilter } from '../../components/TodoFilters';
17 |
18 | import { rootReducer } from '../../reducers';
19 | import { rootSaga } from '../../sagas';
20 |
21 | const sagaMiddleware = createSagaMiddleware();
22 | const store = createStore(rootReducer, applyMiddleware(thunk, sagaMiddleware));
23 | sagaMiddleware.run(rootSaga);
24 |
25 | jest.useFakeTimers(); // Prevent issues with LayoutAnimation
26 |
27 | jest.spyOn(Api, 'fetchTodos').mockResolvedValue([
28 | { id: 1, text: 'Todo one', done: false },
29 | { id: 2, text: 'Todo two', done: false },
30 | ]);
31 |
32 | describe('App', () => {
33 | // Use async/await to allow for async calls to resolve
34 | // https://reactjs.org/docs/testing-recipes.html#data-fetching
35 | let root;
36 | beforeAll(() => TestRenderer.act(async () => {
37 | const renderer = await TestRenderer.create(
38 |
39 |
40 |
41 | );
42 | root = renderer.root;
43 | }));
44 |
45 | it('Fetches todos', () => {
46 | const todos = root.findAllByType(Todo);
47 |
48 | expect(todos).toHaveLength(2);
49 | expect(todos[0].props.text).toBe('Todo one');
50 | expect(todos[1].props.text).toBe('Todo two');
51 | });
52 |
53 | it('Creates todos', async () => {
54 | const textInput = root.findByType(TextInput);
55 | const createTodoMock = jest.spyOn(Api, 'createTodo')
56 | .mockResolvedValue({ id: 3, text: 'Todo three', done: false });
57 |
58 | // Wrap each call in act() for it to take effect before the next one
59 | await TestRenderer.act(async () => {
60 | textInput.props.onChangeText('Todo three');
61 | });
62 |
63 | await TestRenderer.act(async () => {
64 | textInput.props.onSubmitEditing();
65 | });
66 |
67 | const todos = root.findAllByType(Todo);
68 |
69 | expect(todos).toHaveLength(3);
70 | expect(todos[2].props.text).toBe('Todo three');
71 | });
72 |
73 | it('Deletes todos', async () => {
74 | const deleteTodoMock = jest.spyOn(Api, 'deleteTodo')
75 | .mockResolvedValue({ id: 3 });
76 |
77 | let todos = root.findAllByType(Todo);
78 | const deleteButton = todos[2].findByType(IconButton);
79 |
80 | await TestRenderer.act(async () => {
81 | deleteButton.props.onPress();
82 | });
83 |
84 | expect(root.findAllByType(Todo)).toHaveLength(2);
85 | });
86 |
87 | it('Toggles todos', async () => {
88 | jest.spyOn(Api, 'toggleTodo')
89 | .mockResolvedValue({ id: 2, done: true });
90 |
91 | const todos = root.findAllByType(Todo);
92 | const checkbox = todos[1].findByType(Checkbox);
93 |
94 | await TestRenderer.act(async () => {
95 | checkbox.props.onChange();
96 | });
97 |
98 | expect(todos[1].props.done).toBe(true);
99 | });
100 |
101 | it('Handles errors', async () => {
102 | jest.spyOn(Api, 'toggleTodo')
103 | .mockRejectedValue('Error');
104 |
105 | const todos = root.findAllByType(Todo);
106 | const checkbox = todos[1].findByType(Checkbox);
107 |
108 | await TestRenderer.act(async () => {
109 | checkbox.props.onChange();
110 | });
111 |
112 | const error = root.findByType(Error);
113 | const errorText = error.findByType(Text);
114 |
115 | expect(errorText.props.children).toBe('Error');
116 | });
117 |
118 | it('Filters todos', async () => {
119 | const filters = root.findAllByType(TodoFilter);
120 | const touchable = filters[2].findByType(TouchableOpacity);
121 |
122 | await TestRenderer.act(async () => {
123 | touchable.props.onPress();
124 | });
125 |
126 | const todos = root.findAllByType(Todo);
127 |
128 | expect(todos).toHaveLength(1);
129 | expect(todos[0].props.done).toBe(true);
130 | });
131 | });
132 |
--------------------------------------------------------------------------------
/__tests__/sagas/sagas.test.js:
--------------------------------------------------------------------------------
1 | import { channel } from 'redux-saga';
2 | import { actionChannel, call, flush, fork, put, race, take, takeEvery } from 'redux-saga/effects';
3 |
4 | import * as Actions from '../../actions';
5 | import * as Api from '../../api';
6 | import { rootSaga, createTodo, deleteTodo, toggleTodo, toggleTodoQueue } from '../../sagas';
7 |
8 | describe('rootSaga()', () => {
9 | it('Yields effects', () => {
10 | const saga = rootSaga();
11 |
12 | const yield1 = takeEvery(Actions.CREATE_TODO, createTodo);
13 | const yield2 = takeEvery(Actions.DELETE_TODO, deleteTodo);
14 | const yield3 = fork(toggleTodoQueue);
15 |
16 | expect(saga.next().value).toStrictEqual(yield1);
17 | expect(saga.next().value).toStrictEqual(yield2);
18 | expect(saga.next().value).toStrictEqual(yield3);
19 | expect(saga.next().done).toBe(true);
20 | });
21 | });
22 |
23 | describe('createTodo()', () => {
24 | it('Handles success', () => {
25 | const saga = createTodo(Actions.createTodo('Todo'));
26 | const todo = { id: 123, text: 'Todo', done: true };
27 |
28 | const yield1 = call(Api.createTodo, 'Todo');
29 | const yield2 = put(Actions.createTodoSuccess(todo));
30 |
31 | expect(saga.next().value).toStrictEqual(yield1);
32 | expect(saga.next(todo).value).toStrictEqual(yield2);
33 | expect(saga.next().done).toBe(true);
34 | });
35 |
36 | it('Handles error', () => {
37 | const saga = createTodo(Actions.createTodo('Todo'));
38 | const error = new Error();
39 |
40 | const yield1 = call(Api.createTodo, 'Todo');
41 | const yield2 = put(Actions.createTodoError(error));
42 |
43 | expect(saga.next().value).toStrictEqual(yield1);
44 | expect(saga.throw(error).value).toEqual(yield2);
45 | expect(saga.next().done).toBe(true);
46 | });
47 | });
48 |
49 | describe('deleteTodo()', () => {
50 | it('Handles success', () => {
51 | const saga = deleteTodo(Actions.deleteTodo(123));
52 | const todo = { id: 123, text: 'Todo', done: true };
53 |
54 | const yield1 = call(Api.deleteTodo, 123);
55 | const yield2 = put(Actions.deleteTodoSuccess(123));
56 |
57 | expect(saga.next().value).toStrictEqual(yield1);
58 | expect(saga.next(todo).value).toStrictEqual(yield2);
59 | expect(saga.next().done).toBe(true);
60 | });
61 |
62 | it('Handles error', () => {
63 | const saga = deleteTodo(Actions.deleteTodo(123));
64 | const error = new Error();
65 |
66 | const yield1 = call(Api.deleteTodo, 123);
67 | const yield2 = put(Actions.deleteTodoError(123, error));
68 |
69 | expect(saga.next().value).toStrictEqual(yield1);
70 | expect(saga.throw(error).value).toEqual(yield2);
71 | expect(saga.next().done).toBe(true);
72 | });
73 | });
74 |
75 | describe('toggleTodo()', () => {
76 | it('Handles success', () => {
77 | const saga = toggleTodo(Actions.toggleTodo(123, true));
78 | const todo = { id: 123, text: 'Todo', done: true };
79 |
80 | const yield1 = call(Api.toggleTodo, 123, true);
81 | const yield2 = put(Actions.toggleTodoSuccess(123, true));
82 |
83 | expect(saga.next().value).toStrictEqual(yield1);
84 | expect(saga.next(todo).value).toStrictEqual(yield2);
85 | expect(saga.next().done).toBe(true);
86 | });
87 |
88 | it('Handles error', () => {
89 | const saga = toggleTodo(Actions.toggleTodo(123, true));
90 | const error = new Error();
91 |
92 | const yield1 = call(Api.toggleTodo, 123, true);
93 | const yield2 = put(Actions.toggleTodoError(123, error));
94 |
95 | expect(saga.next().value).toStrictEqual(yield1);
96 | expect(saga.throw(error).value).toEqual(yield2);
97 | expect(saga.next().done).toBe(true);
98 | });
99 | });
100 |
101 | describe('toggleTodoQueue()', () => {
102 | it('Handles action channel', () => {
103 | const saga = toggleTodoQueue();
104 | const mockChannel = channel();
105 |
106 | const toggleTodoAction = Actions.toggleTodo();
107 | const toggleTodoCancelAction = Actions.toggleTodoCancel();
108 |
109 | const yield1 = actionChannel(Actions.TOGGLE_TODO);
110 | const yield2 = take(mockChannel);
111 | const yield3 = race({
112 | todo: call(toggleTodo, toggleTodoAction),
113 | cancel: take(Actions.TOGGLE_TODO_CANCEL)
114 | });
115 | const yield4 = flush(mockChannel);
116 |
117 | // Create a channel
118 | expect(saga.next().value).toStrictEqual(yield1);
119 | // Start receiving actions from the channel
120 | expect(saga.next(mockChannel).value).toStrictEqual(yield2);
121 | // Receive a toggleTodo action and pass it to the toggleTodo saga
122 | expect(saga.next(toggleTodoAction).value).toStrictEqual(yield3);
123 | // Receive a toggleTodoCancel action and flush the channel
124 | expect(saga.next({ cancel: toggleTodoCancelAction }).value).toStrictEqual(yield4);
125 | // Return to waiting for new actions from the channel
126 | expect(saga.next().value).toStrictEqual(yield2);
127 | });
128 | });
129 |
--------------------------------------------------------------------------------
/__tests__/actions/actions.test.js:
--------------------------------------------------------------------------------
1 | import thunk from 'redux-thunk';
2 | import configureMockStore from 'redux-mock-store';
3 |
4 | import * as Api from '../../api';
5 | import * as Actions from '../../actions';
6 |
7 | const mockStore = configureMockStore([ thunk ]);
8 |
9 | describe('Async actions', () => {
10 | it('Handles successful fetch', () => {
11 | const store = mockStore();
12 | const todos = [{ text: 'Text', id: 123, done: false }];
13 | // Mock async API method success
14 | const fetchTodosMock = jest.spyOn(Api, 'fetchTodos').mockResolvedValue(todos);
15 |
16 | const expectedActions = [
17 | Actions.fetchTodos(),
18 | Actions.fetchTodosSuccess(todos),
19 | ];
20 |
21 | // If a test returns nothing it will pass by default
22 | return store
23 | // Dispatch async action
24 | .dispatch(Actions.fetchTodosAsync())
25 | // Wait for async action to complete
26 | .then(() => {
27 | // Mocked method is called
28 | expect(fetchTodosMock).toHaveBeenCalled();
29 | // Expected actions are dispatched
30 | expect(store.getActions()).toStrictEqual(expectedActions);
31 | });
32 | });
33 |
34 | it('Handles unsuccessful fetch', () => {
35 | const store = mockStore();
36 | // Mock async API method error
37 | const fetchTodosMock = jest.spyOn(Api, 'fetchTodos').mockRejectedValue('Error');
38 |
39 | const expectedActions = [
40 | Actions.fetchTodos(),
41 | Actions.fetchTodosError('Error')
42 | ];
43 |
44 | // If a test returns nothing it will pass by default
45 | return store
46 | // Dispatch async action
47 | .dispatch(Actions.fetchTodosAsync())
48 | // Wait for async action to complete
49 | .then(() => {
50 | // Mocked method is called
51 | expect(fetchTodosMock).toHaveBeenCalled();
52 | // Expected actions are dispatched
53 | expect(store.getActions()).toStrictEqual(expectedActions);
54 | });
55 | });
56 | });
57 |
58 | describe('Action creators', () => {
59 | it('Creates CREATE_TODO action', () => {
60 | const action = { type: Actions.CREATE_TODO, text: 'Todo' };
61 | expect(Actions.createTodo('Todo')).toStrictEqual(action);
62 | });
63 |
64 | it('Creates CREATE_TODO_ERROR action', () => {
65 | const action = { type: Actions.CREATE_TODO_ERROR, error: 'Error' };
66 | expect(Actions.createTodoError('Error')).toStrictEqual(action);
67 | });
68 |
69 | it('Creates CREATE_TODO_SUCCESS action', () => {
70 | const todo = { text: 'Text', id: 123, done: false };
71 | const action = { type: Actions.CREATE_TODO_SUCCESS, todo };
72 | expect(Actions.createTodoSuccess(todo)).toStrictEqual(action);
73 | });
74 |
75 | it('Creates DELETE_TODO action', () => {
76 | const action = { type: Actions.DELETE_TODO, id: 123 };
77 | expect(Actions.deleteTodo(123)).toStrictEqual(action);
78 | });
79 |
80 | it('Creates DELETE_TODO_ERROR action', () => {
81 | const action = { type: Actions.DELETE_TODO_ERROR, id: 123, error: 'Error' };
82 | expect(Actions.deleteTodoError(123, 'Error')).toStrictEqual(action);
83 | });
84 |
85 | it('Creates DELETE_TODO_SUCCESS action', () => {
86 | const action = { type: Actions.DELETE_TODO_SUCCESS, id: 123 };
87 | expect(Actions.deleteTodoSuccess(123)).toStrictEqual(action);
88 | });
89 |
90 | it('Creates FETCH_TODOS action', () => {
91 | const action = { type: Actions.FETCH_TODOS };
92 | expect(Actions.fetchTodos()).toStrictEqual(action);
93 | });
94 |
95 | it('Creates FETCH_TODOS_ERROR action', () => {
96 | const action = { type: Actions.FETCH_TODOS_ERROR, error: 'Error' };
97 | expect(Actions.fetchTodosError('Error')).toStrictEqual(action);
98 | });
99 |
100 | it('Creates FETCH_TODOS_SUCCESS action', () => {
101 | const todos = [{ text: 'Text', id: 123, done: false }];
102 | const action = { type: Actions.FETCH_TODOS_SUCCESS, todos };
103 | expect(Actions.fetchTodosSuccess(todos)).toStrictEqual(action);
104 | });
105 |
106 | it('Creates FILTER_TODOS action', () => {
107 | const action = { type: Actions.FILTER_TODOS, filter: 'All' };
108 | expect(Actions.filterTodos('All')).toStrictEqual(action);
109 | });
110 |
111 | it('Creates TOGGLE_TODO action', () => {
112 | const action = { type: Actions.TOGGLE_TODO, id: 123, done: true };
113 | expect(Actions.toggleTodo(123, true)).toStrictEqual(action);
114 | });
115 |
116 | it('Creates TOGGLE_TODO_CANCEL action', () => {
117 | const action = { type: Actions.TOGGLE_TODO_CANCEL };
118 | expect(Actions.toggleTodoCancel()).toStrictEqual(action);
119 | });
120 |
121 | it('Creates TOGGLE_TODO_ERROR action', () => {
122 | const action = { type: Actions.TOGGLE_TODO_ERROR, id: 123, error: 'Error' };
123 | expect(Actions.toggleTodoError(123, 'Error')).toStrictEqual(action);
124 | });
125 |
126 | it('Creates TOGGLE_TODO_SUCCESS action', () => {
127 | const action = { type: Actions.TOGGLE_TODO_SUCCESS, id: 123, done: true };
128 | expect(Actions.toggleTodoSuccess(123, true)).toStrictEqual(action);
129 | });
130 | });
131 |
--------------------------------------------------------------------------------
/__tests__/reducers/reducers.test.js:
--------------------------------------------------------------------------------
1 | import { rootReducer, initialState } from '../../reducers';
2 | import * as Actions from '../../actions';
3 |
4 | describe('rootReducer()', () => {
5 | it('Returns initial state', () => {
6 | const state = rootReducer(undefined, {});
7 | expect(state).toStrictEqual(initialState);
8 | });
9 |
10 | it('Handles FETCH_TODOS', () => {
11 | const action = Actions.fetchTodos();
12 | const state = rootReducer(initialState, action);
13 | const expectedState = {
14 | ...initialState,
15 | fetching: true,
16 | };
17 | expect(state).toStrictEqual(expectedState);
18 | });
19 |
20 | it('Handles FETCH_TODOS_ERROR', () => {
21 | const action = Actions.fetchTodosError('Error');
22 | const state = rootReducer(initialState, action);
23 | const expectedState = {
24 | ...initialState,
25 | fetching: false,
26 | error: 'Error',
27 | };
28 | expect(state).toStrictEqual(expectedState);
29 | });
30 |
31 | it('Handles FETCH_TODOS_SUCCESS', () => {
32 | const todos = [{ text: 'Text', id: 123, done: false }];
33 | const action = Actions.fetchTodosSuccess(todos);
34 | const state = rootReducer(initialState, action);
35 | const expectedState = {
36 | ...initialState,
37 | todos: todos,
38 | fetching: false,
39 | error: null,
40 | };
41 | expect(state).toStrictEqual(expectedState);
42 | });
43 |
44 | it('Handles CREATE_TODO', () => {
45 | const action = Actions.createTodo();
46 | const state = rootReducer(initialState, action);
47 | const expectedState = {
48 | ...initialState,
49 | creating: true,
50 | };
51 | expect(state).toStrictEqual(expectedState);
52 | });
53 |
54 | it('Handles CREATE_TODO_ERROR', () => {
55 | const action = Actions.createTodoError('Error');
56 | const state = rootReducer(initialState, action);
57 | const expectedState = {
58 | ...initialState,
59 | creating: false,
60 | error: 'Error',
61 | };
62 | expect(state).toStrictEqual(expectedState);
63 | });
64 |
65 | it('Handles CREATE_TODO_SUCCESS', () => {
66 | const todo = { text: 'Text', id: 123, done: false };
67 | const action = Actions.createTodoSuccess(todo);
68 | const state = rootReducer(initialState, action);
69 | const expectedState = {
70 | ...initialState,
71 | todos: [ todo ],
72 | creating: false,
73 | error: null,
74 | };
75 | expect(state).toStrictEqual(expectedState);
76 | });
77 |
78 | it('Handles TOGGLE_TODO', () => {
79 | const todo = { text: 'Text', id: 123, done: false };
80 | const action = Actions.toggleTodo(123);
81 | const initialState = { ...initialState, todos: [ todo ] };
82 | const state = rootReducer(initialState, action);
83 | const expectedState = {
84 | ...initialState,
85 | todos: [{ ...todo, progress: true }],
86 | };
87 | expect(state).toStrictEqual(expectedState);
88 | });
89 |
90 | it('Handles TOGGLE_TODO_ERROR', () => {
91 | const todo = { text: 'Text', id: 123, done: false };
92 | const action = Actions.toggleTodoError(123, 'Error');
93 | const initialState = { ...initialState, todos: [ todo ] };
94 | const state = rootReducer(initialState, action);
95 | const expectedState = {
96 | ...initialState,
97 | todos: [{ ...todo, progress: false }],
98 | error: 'Error',
99 | };
100 | expect(state).toStrictEqual(expectedState);
101 | });
102 |
103 | it('Handles TOGGLE_TODO_SUCCESS', () => {
104 | const todo = { text: 'Text', id: 123, done: false };
105 | const action = Actions.toggleTodoSuccess(123, true);
106 | const initialState = { ...initialState, todos: [ todo ] };
107 | const state = rootReducer(initialState, action);
108 | const expectedState = {
109 | ...initialState,
110 | todos: [{ ...todo, progress: false, done: true }],
111 | error: null,
112 | };
113 | expect(state).toStrictEqual(expectedState);
114 | });
115 |
116 | it('Handles DELETE_TODO', () => {
117 | const todo = { text: 'Text', id: 123, done: false };
118 | const action = Actions.deleteTodo(123);
119 | const initialState = { ...initialState, todos: [ todo ] };
120 | const state = rootReducer(initialState, action);
121 | const expectedState = {
122 | ...initialState,
123 | todos: [{ ...todo, progress: true }],
124 | };
125 | expect(state).toStrictEqual(expectedState);
126 | });
127 |
128 | it('Handles DELETE_TODO_ERROR', () => {
129 | const todo = { text: 'Text', id: 123, done: false };
130 | const action = Actions.deleteTodoError(123, 'Error');
131 | const initialState = { ...initialState, todos: [ todo ] };
132 | const state = rootReducer(initialState, action);
133 | const expectedState = {
134 | ...initialState,
135 | todos: [{ ...todo, progress: false }],
136 | error: 'Error',
137 | };
138 | expect(state).toStrictEqual(expectedState);
139 | });
140 |
141 | it('Handles DELETE_TODO_SUCCESS', () => {
142 | const todo = { text: 'Text', id: 123, done: false };
143 | const action = Actions.deleteTodoSuccess(123);
144 | const initialState = { ...initialState, todos: [ todo ] };
145 | const state = rootReducer(initialState, action);
146 | const expectedState = {
147 | ...initialState,
148 | todos: [],
149 | error: null,
150 | };
151 | expect(state).toStrictEqual(expectedState);
152 | });
153 |
154 | it('Handles FILTER_TODOS', () => {
155 | const action = Actions.filterTodos('Done');
156 | const state = rootReducer(initialState, action);
157 | const expectedState = {
158 | ...initialState,
159 | filter: 'Done'
160 | };
161 | expect(state).toStrictEqual(expectedState);
162 | });
163 | });
164 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 |
3 | import com.android.build.OutputFile
4 |
5 | /**
6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
7 | * and bundleReleaseJsAndAssets).
8 | * These basically call `react-native bundle` with the correct arguments during the Android build
9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
10 | * bundle directly from the development server. Below you can see all the possible configurations
11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the
12 | * `apply from: "../../node_modules/react-native/react.gradle"` line.
13 | *
14 | * project.ext.react = [
15 | * // the name of the generated asset file containing your JS bundle
16 | * bundleAssetName: "index.android.bundle",
17 | *
18 | * // the entry file for bundle generation. If none specified and
19 | * // "index.android.js" exists, it will be used. Otherwise "index.js" is
20 | * // default. Can be overridden with ENTRY_FILE environment variable.
21 | * entryFile: "index.android.js",
22 | *
23 | * // https://reactnative.dev/docs/performance#enable-the-ram-format
24 | * bundleCommand: "ram-bundle",
25 | *
26 | * // whether to bundle JS and assets in debug mode
27 | * bundleInDebug: false,
28 | *
29 | * // whether to bundle JS and assets in release mode
30 | * bundleInRelease: true,
31 | *
32 | * // whether to bundle JS and assets in another build variant (if configured).
33 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
34 | * // The configuration property can be in the following formats
35 | * // 'bundleIn${productFlavor}${buildType}'
36 | * // 'bundleIn${buildType}'
37 | * // bundleInFreeDebug: true,
38 | * // bundleInPaidRelease: true,
39 | * // bundleInBeta: true,
40 | *
41 | * // whether to disable dev mode in custom build variants (by default only disabled in release)
42 | * // for example: to disable dev mode in the staging build type (if configured)
43 | * devDisabledInStaging: true,
44 | * // The configuration property can be in the following formats
45 | * // 'devDisabledIn${productFlavor}${buildType}'
46 | * // 'devDisabledIn${buildType}'
47 | *
48 | * // the root of your project, i.e. where "package.json" lives
49 | * root: "../../",
50 | *
51 | * // where to put the JS bundle asset in debug mode
52 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
53 | *
54 | * // where to put the JS bundle asset in release mode
55 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
56 | *
57 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
58 | * // require('./image.png')), in debug mode
59 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
60 | *
61 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
62 | * // require('./image.png')), in release mode
63 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
64 | *
65 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
66 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
67 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle
68 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
69 | * // for example, you might want to remove it from here.
70 | * inputExcludes: ["android/**", "ios/**"],
71 | *
72 | * // override which node gets called and with what additional arguments
73 | * nodeExecutableAndArgs: ["node"],
74 | *
75 | * // supply additional arguments to the packager
76 | * extraPackagerArgs: []
77 | * ]
78 | */
79 |
80 | project.ext.react = [
81 | enableHermes: false, // clean and rebuild if changing
82 | ]
83 |
84 | apply from: "../../node_modules/react-native/react.gradle"
85 |
86 | /**
87 | * Set this to true to create two separate APKs instead of one:
88 | * - An APK that only works on ARM devices
89 | * - An APK that only works on x86 devices
90 | * The advantage is the size of the APK is reduced by about 4MB.
91 | * Upload all the APKs to the Play Store and people will download
92 | * the correct one based on the CPU architecture of their device.
93 | */
94 | def enableSeparateBuildPerCPUArchitecture = false
95 |
96 | /**
97 | * Run Proguard to shrink the Java bytecode in release builds.
98 | */
99 | def enableProguardInReleaseBuilds = false
100 |
101 | /**
102 | * The preferred build flavor of JavaScriptCore.
103 | *
104 | * For example, to use the international variant, you can use:
105 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
106 | *
107 | * The international variant includes ICU i18n library and necessary data
108 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
109 | * give correct results when using with locales other than en-US. Note that
110 | * this variant is about 6MiB larger per architecture than default.
111 | */
112 | def jscFlavor = 'org.webkit:android-jsc:+'
113 |
114 | /**
115 | * Whether to enable the Hermes VM.
116 | *
117 | * This should be set on project.ext.react and mirrored here. If it is not set
118 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
119 | * and the benefits of using Hermes will therefore be sharply reduced.
120 | */
121 | def enableHermes = project.ext.react.get("enableHermes", false);
122 |
123 | android {
124 | ndkVersion rootProject.ext.ndkVersion
125 |
126 | compileSdkVersion rootProject.ext.compileSdkVersion
127 |
128 | compileOptions {
129 | sourceCompatibility JavaVersion.VERSION_1_8
130 | targetCompatibility JavaVersion.VERSION_1_8
131 | }
132 |
133 | defaultConfig {
134 | applicationId "com.reactnativetodo"
135 | minSdkVersion rootProject.ext.minSdkVersion
136 | targetSdkVersion rootProject.ext.targetSdkVersion
137 | versionCode 1
138 | versionName "1.0"
139 | }
140 | splits {
141 | abi {
142 | reset()
143 | enable enableSeparateBuildPerCPUArchitecture
144 | universalApk false // If true, also generate a universal APK
145 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
146 | }
147 | }
148 | signingConfigs {
149 | debug {
150 | storeFile file('debug.keystore')
151 | storePassword 'android'
152 | keyAlias 'androiddebugkey'
153 | keyPassword 'android'
154 | }
155 | }
156 | buildTypes {
157 | debug {
158 | signingConfig signingConfigs.debug
159 | }
160 | release {
161 | // Caution! In production, you need to generate your own keystore file.
162 | // see https://reactnative.dev/docs/signed-apk-android.
163 | signingConfig signingConfigs.debug
164 | minifyEnabled enableProguardInReleaseBuilds
165 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
166 | }
167 | }
168 |
169 | // applicationVariants are e.g. debug, release
170 | applicationVariants.all { variant ->
171 | variant.outputs.each { output ->
172 | // For each separate APK per architecture, set a unique version code as described here:
173 | // https://developer.android.com/studio/build/configure-apk-splits.html
174 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
175 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
176 | def abi = output.getFilter(OutputFile.ABI)
177 | if (abi != null) { // null for the universal-debug, universal-release variants
178 | output.versionCodeOverride =
179 | defaultConfig.versionCode * 1000 + versionCodes.get(abi)
180 | }
181 |
182 | }
183 | }
184 | }
185 |
186 | dependencies {
187 | implementation fileTree(dir: "libs", include: ["*.jar"])
188 | //noinspection GradleDynamicVersion
189 | implementation "com.facebook.react:react-native:+" // From node_modules
190 |
191 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
192 |
193 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
194 | exclude group:'com.facebook.fbjni'
195 | }
196 |
197 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
198 | exclude group:'com.facebook.flipper'
199 | exclude group:'com.squareup.okhttp3', module:'okhttp'
200 | }
201 |
202 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
203 | exclude group:'com.facebook.flipper'
204 | }
205 |
206 | if (enableHermes) {
207 | def hermesPath = "../../node_modules/hermes-engine/android/";
208 | debugImplementation files(hermesPath + "hermes-debug.aar")
209 | releaseImplementation files(hermesPath + "hermes-release.aar")
210 | } else {
211 | implementation jscFlavor
212 | }
213 | }
214 |
215 | // Run this once to be able to run the application with BUCK
216 | // puts all compile dependencies into folder libs for BUCK to use
217 | task copyDownloadableDepsToLibs(type: Copy) {
218 | from configurations.compile
219 | into 'libs'
220 | }
221 |
222 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
223 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Quick Guide to React Native Testing
2 |
3 | ## Why Write Tests?
4 |
5 | Writing tests can help you make your app more robust, your team happier, and save you a lot of time in the long term. Another, less obvious reason is that good tests reflect good architecture. When your code has a coherent structure, proper separation of concerns, and follows conventions, writing tests should be a breeze.
6 |
7 | The ease of testing is one of the best parts of React/Redux architecture. Everything in React/Redux is a plain JavaScript object at some point. That makes testing it as simple as checking its properties. So when you're not writing tests for your React Native app you're missing out on one of its best features.
8 |
9 | ## Unit, Integration or End-to-end?
10 |
11 | You've probably heard "Write tests. Not too many. Mostly integration." There are many different paradigms when it comes to testing, with varying emphasis on unit, integration, and end-to-end. But generally speaking, when it comes to impact/effort and working with a continuously changing codebase, unit tests are your best bet.
12 |
13 | Unit tests make it possible to test different parts of your app independently, are fast, easy to maintain, and incentivise developers to take responsibility for their work. But more importantly, unit tests cover the basic building blocks of your code, creating a solid foundation for higher-level tests (e.g. integration) making them simpler and more focused.
14 |
15 | ## What Are Good Tests?
16 |
17 | So what makes good tests? Speed, isolation, and repeatability are some of the things often mentioned. Also, good tests simulate real case scenarios without creating complexity. Simply checking if a component gets rendered probably won't cut it, but testing every detail of a component's implementation is likely to make your tests slow and fragile.
18 |
19 | Simply put, the payoff should always be greater than the cost of writing and maintaining your tests. After all, you'd rather be writing your app than tests. The tests are meant to help you get there safer, faster, and with a minimal amount of setback. So while writings tests can make your code more robust, don't waste your time trying to cover every detail.
20 |
21 | ## Example App
22 |
23 | Let’s consider these ideas in the context of a simple React Native [todo app](https://github.com/stassop/ReactNativeTodo).
24 |
25 | 
26 |
27 | Run the app
28 | ```
29 | npx react-native run-ios
30 | ```
31 |
32 | Run the backend
33 | ```
34 | node server/app.js
35 | ```
36 |
37 | Run the tests
38 | ```
39 | npm test
40 | ```
41 |
42 | ## Testing Components
43 |
44 | Testing React Native components is a breeze with [Jest](https://jestjs.io/) and [Test Renderer](https://reactjs.org/docs/test-renderer.html). Because React's [Virtual DOM](https://reactjs.org/docs/faq-internals.html) is basically an object, you can test components by parsing them and checking their properties. Test Renderer converts components to plain JavaScript objects, without any native dependencies.
45 |
46 | When testing components, don't get carried away with details. Only test things the component's core functionality depends on, e.g. rendering other components, dispatching actions etc. Avoid testing things that aren't vital for the component's behaviour and/or can change often.
47 |
48 | Consider this simple [Todo component](https://github.com/stassop/ReactNativeTodo/blob/master/components/Todo.js). It does two things: toggling the todo's done state and deleting the todo. Because this is a unit test it should be agnostic about its context, so it spies on the component's internal functions and mocks everything outside of it (keep in mind this is a snippet from a [bigger file](https://github.com/stassop/ReactNativeTodo/blob/master/__tests__/components/Todo.test.js)):
49 |
50 | ```
51 | // Todo.test.js
52 |
53 | const useDispatchMock = jest.fn();
54 | jest.spyOn(reactRedux, 'useDispatch').mockReturnValue(useDispatchMock);
55 |
56 | describe('Todo', () => {
57 | it('Handles user actions', () => {
58 | const renderer = TestRenderer.create(
59 |
60 | );
61 | const root = renderer.root;
62 | const checkbox = root.findByType(Checkbox);
63 |
64 | checkbox.props.onChange(true);
65 | expect(useDispatchMock).toHaveBeenCalledWith(Actions.toggleTodo(123, true));
66 | });
67 | });
68 | ```
69 |
70 | An important part of the Todo component behaviour is that it looks a certain way when done, so that must be tested. Notice that the style is just a plain object. Because Test Renderer renders an object, the component's props are objects too. Also, because Checkbox has its own test, it's enough to check if it has the right props here:
71 |
72 | ```
73 | it('Handles done state', () => {
74 | const renderer = TestRenderer.create(
75 |
76 | );
77 | const root = renderer.root;
78 | const checkbox = root.findByType(Checkbox);
79 | const text = root.findByProps({ children: 'Todo' });
80 | const doneStyle = {
81 | textDecorationLine: 'line-through',
82 | color: Colors.todoDone,
83 | };
84 |
85 | expect(checkbox.props.checked).toBe(true);
86 | expect(text.props.style).toContainEqual(doneStyle);
87 | });
88 | ```
89 |
90 | You can find the rest of the component tests [here](https://github.com/stassop/ReactNativeTodo/tree/master/__tests__/components).
91 |
92 | ## Testing Actions
93 |
94 | Action tests are generally very basic but they allow you to rely on action creators in other tests without having to bother with their internals. If an action creator changes, you won't have to change every test that uses the action. To test an action creator simply call the function and compare the result to a plain object you expect to get back:
95 |
96 | ```
97 | // actions.test.js
98 |
99 | it('Creates CREATE_TODO action', () => {
100 | const action = { type: Actions.CREATE_TODO, text: 'Todo' };
101 | expect(Actions.createTodo('Todo')).toStrictEqual(action);
102 | });
103 | ```
104 |
105 | Testing async actions is a bit more tricky but quite simple once you get the idea. Mock the API methods the action relies on, dispatch the action, and check if the [Mock Store](https://github.com/reduxjs/redux-mock-store) has received the right actions:
106 |
107 | ```
108 | import thunk from 'redux-thunk';
109 | import configureMockStore from 'redux-mock-store';
110 |
111 | const mockStore = configureMockStore([ thunk ]);
112 |
113 | describe('Async actions', () => {
114 | it('Handles successful fetch', () => {
115 | const store = mockStore();
116 | const todos = [{ text: 'Text', id: 123, done: false }];
117 | // Mock async API method success
118 | const fetchTodosMock = jest.spyOn(Api, 'fetchTodos').mockResolvedValue(todos);
119 |
120 | const expectedActions = [
121 | Actions.fetchTodos(),
122 | Actions.fetchTodosSuccess(todos),
123 | ];
124 |
125 | // If a test returns nothing it will pass by default
126 | return store
127 | // Dispatch async action
128 | .dispatch(Actions.fetchTodosAsync())
129 | // Wait for async action to complete
130 | .then(() => {
131 | // Mocked method is called
132 | expect(fetchTodosMock).toHaveBeenCalled();
133 | // Expected actions are dispatched
134 | expect(store.getActions()).toStrictEqual(expectedActions);
135 | });
136 | });
137 | });
138 | ```
139 |
140 | You can find the rest of the action tests [here](https://github.com/stassop/ReactNativeTodo/blob/master/__tests__/actions/actions.test.js).
141 |
142 | ## Testing Reducers
143 |
144 | Because Redux reducers are just [pure functions](https://en.wikipedia.org/wiki/Pure_function), to test them simply call them with an initial state and an action, and check the resulting state.
145 |
146 | ```
147 | // reducers.test.js
148 |
149 | import { rootReducer, initialState } from '../../reducers';
150 | import * as Actions from '../../actions';
151 |
152 | describe('rootReducer()', () => {
153 | it('Handles FETCH_TODOS', () => {
154 | const action = Actions.fetchTodos();
155 | const state = rootReducer(initialState, action);
156 | const expectedState = {
157 | ...initialState,
158 | fetching: true,
159 | };
160 | expect(state).toStrictEqual(expectedState);
161 | });
162 | });
163 | ```
164 |
165 | You can find the rest of the reducer tests [here](https://github.com/stassop/ReactNativeTodo/blob/master/__tests__/reducers/reducers.test.js).
166 |
167 | ## Testing Redux Saga
168 |
169 | Although understanding [Redux Saga](https://redux-saga.js.org/) can sometimes be challenging, testing it is surprisingly easy. Testability is actually one of its best features. Because sagas are essentially [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators), and every saga effect (e.g. put(), take(), etc.) returns a plain object, testing sagas is as simple as calling next(), and checking the yielded result. If an effect is expected to yield a value, pass it as an argument to the following next().
170 |
171 | ```
172 | // sagas.test.js
173 |
174 | describe('createTodo()', () => {
175 | it('Handles success', () => {
176 | const saga = createTodo(Actions.createTodo('Todo'));
177 | const todo = { id: 123, text: 'Todo', done: true };
178 |
179 | const yield1 = call(Api.createTodo, 'Todo');
180 | const yield2 = put(Actions.createTodoSuccess(todo));
181 |
182 | expect(saga.next().value).toStrictEqual(yield1);
183 | expect(saga.next(todo).value).toStrictEqual(yield2);
184 | expect(saga.next().done).toBe(true);
185 | });
186 | });
187 | ```
188 |
189 | You can find the rest of the saga tests [here](https://github.com/stassop/ReactNativeTodo/blob/master/__tests__/sagas/sagas.test.js).
190 |
191 | ## Integration Tests
192 |
193 | Integration tests are meant to check if all the parts of our app work together as one. They provide a high-level overview without going into details. Here a single instance of the App component is created, together with all of its constituents (reducers, sagas, etc.), to preserve its state throughout the tests. The only thing mocked here are the API endpoints because they are outside of the React/Redux scope.
194 |
195 | ```
196 | import React from 'react';
197 | import TestRenderer from 'react-test-renderer';
198 | import { Provider } from 'react-redux';
199 | import { createStore, applyMiddleware } from 'redux';
200 | import createSagaMiddleware from 'redux-saga';
201 | import thunk from 'redux-thunk';
202 |
203 | import * as Api from '../../api';
204 |
205 | import { rootReducer } from '../../reducers';
206 | import { rootSaga } from '../../sagas';
207 |
208 | const sagaMiddleware = createSagaMiddleware();
209 | const store = createStore(rootReducer, applyMiddleware(thunk, sagaMiddleware));
210 | sagaMiddleware.run(rootSaga);
211 |
212 | jest.spyOn(Api, 'fetchTodos').mockResolvedValue([
213 | { id: 1, text: 'Todo one', done: false },
214 | { id: 2, text: 'Todo two', done: false },
215 | ]);
216 |
217 | describe('App', () => {
218 | // Use async/await to allow for async calls to resolve
219 | // https://reactjs.org/docs/testing-recipes.html#data-fetching
220 | let root;
221 | beforeAll(() => TestRenderer.act(async () => {
222 | const renderer = await TestRenderer.create(
223 |
224 |
225 |
226 | );
227 | root = renderer.root;
228 | }));
229 |
230 | it('Fetches todos', () => {
231 | const todos = root.findAllByType(Todo);
232 |
233 | expect(todos).toHaveLength(2);
234 | expect(todos[0].props.text).toBe('Todo one');
235 | expect(todos[1].props.text).toBe('Todo two');
236 | });
237 | });
238 | ```
239 |
240 | Now call the component's methods to simulate user actions, and expect them to take effect after they get processed by sagas, reducers etc.
241 |
242 | ```
243 | it('Creates todos', async () => {
244 | const textInput = root.findByType(TextInput);
245 | const createTodoMock = jest.spyOn(Api, 'createTodo')
246 | .mockResolvedValue({ id: 3, text: 'Todo three', done: false });
247 |
248 | // Wrap each call in act() for it to take effect before the next one
249 | await TestRenderer.act(async () => {
250 | textInput.props.onChangeText('Todo three');
251 | });
252 |
253 | await TestRenderer.act(async () => {
254 | textInput.props.onSubmitEditing();
255 | });
256 |
257 | const todos = root.findAllByType(Todo);
258 |
259 | expect(todos).toHaveLength(3);
260 | expect(todos[2].props.text).toBe('Todo three');
261 | });
262 | ```
263 |
264 | You can find the rest of the integration tests [here](https://github.com/stassop/ReactNativeTodo/blob/master/__tests__/integration/integration.test.js).
265 |
266 | ## Conclusion
267 |
268 | Don't overthink tests, they are supposed to be a complement to your app, not a maintenance burden. Start writing tests from the ground up, gradually moving from the basic building blocks of your app to more complex ones. And remember, good code is testable code. If something is tricky to test, it could be a clue that it has to be split up into more atomic parts.
269 |
270 | This article is intended to glance over React Native testing, and therefore is by no means complete. I strongly recommend you to explore other testing techniques and tools such as [Snapshot Testing](https://jestjs.io/docs/snapshot-testing), [Detox](https://github.com/wix/Detox), [React Native Testing Library](https://github.com/callstack/react-native-testing-library), and choose the ones that best suit your project.
271 |
272 | ## Resources
273 |
274 | * https://reactjs.org/docs/test-renderer.html
275 | * https://reactnative.dev/docs/testing-overview
276 | * https://github.com/reduxjs/redux-mock-store
277 | * https://redux-saga.js.org/docs/advanced/Testing/
278 | * https://reactjs.org/docs/testing-recipes.html#data-fetching
279 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - boost-for-react-native (1.63.0)
3 | - DoubleConversion (1.1.6)
4 | - FBLazyVector (0.64.0)
5 | - FBReactNativeSpec (0.64.0):
6 | - RCT-Folly (= 2020.01.13.00)
7 | - RCTRequired (= 0.64.0)
8 | - RCTTypeSafety (= 0.64.0)
9 | - React-Core (= 0.64.0)
10 | - React-jsi (= 0.64.0)
11 | - ReactCommon/turbomodule/core (= 0.64.0)
12 | - glog (0.3.5)
13 | - RCT-Folly (2020.01.13.00):
14 | - boost-for-react-native
15 | - DoubleConversion
16 | - glog
17 | - RCT-Folly/Default (= 2020.01.13.00)
18 | - RCT-Folly/Default (2020.01.13.00):
19 | - boost-for-react-native
20 | - DoubleConversion
21 | - glog
22 | - RCTRequired (0.64.0)
23 | - RCTTypeSafety (0.64.0):
24 | - FBLazyVector (= 0.64.0)
25 | - RCT-Folly (= 2020.01.13.00)
26 | - RCTRequired (= 0.64.0)
27 | - React-Core (= 0.64.0)
28 | - React (0.64.0):
29 | - React-Core (= 0.64.0)
30 | - React-Core/DevSupport (= 0.64.0)
31 | - React-Core/RCTWebSocket (= 0.64.0)
32 | - React-RCTActionSheet (= 0.64.0)
33 | - React-RCTAnimation (= 0.64.0)
34 | - React-RCTBlob (= 0.64.0)
35 | - React-RCTImage (= 0.64.0)
36 | - React-RCTLinking (= 0.64.0)
37 | - React-RCTNetwork (= 0.64.0)
38 | - React-RCTSettings (= 0.64.0)
39 | - React-RCTText (= 0.64.0)
40 | - React-RCTVibration (= 0.64.0)
41 | - React-callinvoker (0.64.0)
42 | - React-Core (0.64.0):
43 | - glog
44 | - RCT-Folly (= 2020.01.13.00)
45 | - React-Core/Default (= 0.64.0)
46 | - React-cxxreact (= 0.64.0)
47 | - React-jsi (= 0.64.0)
48 | - React-jsiexecutor (= 0.64.0)
49 | - React-perflogger (= 0.64.0)
50 | - Yoga
51 | - React-Core/CoreModulesHeaders (0.64.0):
52 | - glog
53 | - RCT-Folly (= 2020.01.13.00)
54 | - React-Core/Default
55 | - React-cxxreact (= 0.64.0)
56 | - React-jsi (= 0.64.0)
57 | - React-jsiexecutor (= 0.64.0)
58 | - React-perflogger (= 0.64.0)
59 | - Yoga
60 | - React-Core/Default (0.64.0):
61 | - glog
62 | - RCT-Folly (= 2020.01.13.00)
63 | - React-cxxreact (= 0.64.0)
64 | - React-jsi (= 0.64.0)
65 | - React-jsiexecutor (= 0.64.0)
66 | - React-perflogger (= 0.64.0)
67 | - Yoga
68 | - React-Core/DevSupport (0.64.0):
69 | - glog
70 | - RCT-Folly (= 2020.01.13.00)
71 | - React-Core/Default (= 0.64.0)
72 | - React-Core/RCTWebSocket (= 0.64.0)
73 | - React-cxxreact (= 0.64.0)
74 | - React-jsi (= 0.64.0)
75 | - React-jsiexecutor (= 0.64.0)
76 | - React-jsinspector (= 0.64.0)
77 | - React-perflogger (= 0.64.0)
78 | - Yoga
79 | - React-Core/RCTActionSheetHeaders (0.64.0):
80 | - glog
81 | - RCT-Folly (= 2020.01.13.00)
82 | - React-Core/Default
83 | - React-cxxreact (= 0.64.0)
84 | - React-jsi (= 0.64.0)
85 | - React-jsiexecutor (= 0.64.0)
86 | - React-perflogger (= 0.64.0)
87 | - Yoga
88 | - React-Core/RCTAnimationHeaders (0.64.0):
89 | - glog
90 | - RCT-Folly (= 2020.01.13.00)
91 | - React-Core/Default
92 | - React-cxxreact (= 0.64.0)
93 | - React-jsi (= 0.64.0)
94 | - React-jsiexecutor (= 0.64.0)
95 | - React-perflogger (= 0.64.0)
96 | - Yoga
97 | - React-Core/RCTBlobHeaders (0.64.0):
98 | - glog
99 | - RCT-Folly (= 2020.01.13.00)
100 | - React-Core/Default
101 | - React-cxxreact (= 0.64.0)
102 | - React-jsi (= 0.64.0)
103 | - React-jsiexecutor (= 0.64.0)
104 | - React-perflogger (= 0.64.0)
105 | - Yoga
106 | - React-Core/RCTImageHeaders (0.64.0):
107 | - glog
108 | - RCT-Folly (= 2020.01.13.00)
109 | - React-Core/Default
110 | - React-cxxreact (= 0.64.0)
111 | - React-jsi (= 0.64.0)
112 | - React-jsiexecutor (= 0.64.0)
113 | - React-perflogger (= 0.64.0)
114 | - Yoga
115 | - React-Core/RCTLinkingHeaders (0.64.0):
116 | - glog
117 | - RCT-Folly (= 2020.01.13.00)
118 | - React-Core/Default
119 | - React-cxxreact (= 0.64.0)
120 | - React-jsi (= 0.64.0)
121 | - React-jsiexecutor (= 0.64.0)
122 | - React-perflogger (= 0.64.0)
123 | - Yoga
124 | - React-Core/RCTNetworkHeaders (0.64.0):
125 | - glog
126 | - RCT-Folly (= 2020.01.13.00)
127 | - React-Core/Default
128 | - React-cxxreact (= 0.64.0)
129 | - React-jsi (= 0.64.0)
130 | - React-jsiexecutor (= 0.64.0)
131 | - React-perflogger (= 0.64.0)
132 | - Yoga
133 | - React-Core/RCTSettingsHeaders (0.64.0):
134 | - glog
135 | - RCT-Folly (= 2020.01.13.00)
136 | - React-Core/Default
137 | - React-cxxreact (= 0.64.0)
138 | - React-jsi (= 0.64.0)
139 | - React-jsiexecutor (= 0.64.0)
140 | - React-perflogger (= 0.64.0)
141 | - Yoga
142 | - React-Core/RCTTextHeaders (0.64.0):
143 | - glog
144 | - RCT-Folly (= 2020.01.13.00)
145 | - React-Core/Default
146 | - React-cxxreact (= 0.64.0)
147 | - React-jsi (= 0.64.0)
148 | - React-jsiexecutor (= 0.64.0)
149 | - React-perflogger (= 0.64.0)
150 | - Yoga
151 | - React-Core/RCTVibrationHeaders (0.64.0):
152 | - glog
153 | - RCT-Folly (= 2020.01.13.00)
154 | - React-Core/Default
155 | - React-cxxreact (= 0.64.0)
156 | - React-jsi (= 0.64.0)
157 | - React-jsiexecutor (= 0.64.0)
158 | - React-perflogger (= 0.64.0)
159 | - Yoga
160 | - React-Core/RCTWebSocket (0.64.0):
161 | - glog
162 | - RCT-Folly (= 2020.01.13.00)
163 | - React-Core/Default (= 0.64.0)
164 | - React-cxxreact (= 0.64.0)
165 | - React-jsi (= 0.64.0)
166 | - React-jsiexecutor (= 0.64.0)
167 | - React-perflogger (= 0.64.0)
168 | - Yoga
169 | - React-CoreModules (0.64.0):
170 | - FBReactNativeSpec (= 0.64.0)
171 | - RCT-Folly (= 2020.01.13.00)
172 | - RCTTypeSafety (= 0.64.0)
173 | - React-Core/CoreModulesHeaders (= 0.64.0)
174 | - React-jsi (= 0.64.0)
175 | - React-RCTImage (= 0.64.0)
176 | - ReactCommon/turbomodule/core (= 0.64.0)
177 | - React-cxxreact (0.64.0):
178 | - boost-for-react-native (= 1.63.0)
179 | - DoubleConversion
180 | - glog
181 | - RCT-Folly (= 2020.01.13.00)
182 | - React-callinvoker (= 0.64.0)
183 | - React-jsi (= 0.64.0)
184 | - React-jsinspector (= 0.64.0)
185 | - React-perflogger (= 0.64.0)
186 | - React-runtimeexecutor (= 0.64.0)
187 | - React-jsi (0.64.0):
188 | - boost-for-react-native (= 1.63.0)
189 | - DoubleConversion
190 | - glog
191 | - RCT-Folly (= 2020.01.13.00)
192 | - React-jsi/Default (= 0.64.0)
193 | - React-jsi/Default (0.64.0):
194 | - boost-for-react-native (= 1.63.0)
195 | - DoubleConversion
196 | - glog
197 | - RCT-Folly (= 2020.01.13.00)
198 | - React-jsiexecutor (0.64.0):
199 | - DoubleConversion
200 | - glog
201 | - RCT-Folly (= 2020.01.13.00)
202 | - React-cxxreact (= 0.64.0)
203 | - React-jsi (= 0.64.0)
204 | - React-perflogger (= 0.64.0)
205 | - React-jsinspector (0.64.0)
206 | - React-perflogger (0.64.0)
207 | - React-RCTActionSheet (0.64.0):
208 | - React-Core/RCTActionSheetHeaders (= 0.64.0)
209 | - React-RCTAnimation (0.64.0):
210 | - FBReactNativeSpec (= 0.64.0)
211 | - RCT-Folly (= 2020.01.13.00)
212 | - RCTTypeSafety (= 0.64.0)
213 | - React-Core/RCTAnimationHeaders (= 0.64.0)
214 | - React-jsi (= 0.64.0)
215 | - ReactCommon/turbomodule/core (= 0.64.0)
216 | - React-RCTBlob (0.64.0):
217 | - FBReactNativeSpec (= 0.64.0)
218 | - RCT-Folly (= 2020.01.13.00)
219 | - React-Core/RCTBlobHeaders (= 0.64.0)
220 | - React-Core/RCTWebSocket (= 0.64.0)
221 | - React-jsi (= 0.64.0)
222 | - React-RCTNetwork (= 0.64.0)
223 | - ReactCommon/turbomodule/core (= 0.64.0)
224 | - React-RCTImage (0.64.0):
225 | - FBReactNativeSpec (= 0.64.0)
226 | - RCT-Folly (= 2020.01.13.00)
227 | - RCTTypeSafety (= 0.64.0)
228 | - React-Core/RCTImageHeaders (= 0.64.0)
229 | - React-jsi (= 0.64.0)
230 | - React-RCTNetwork (= 0.64.0)
231 | - ReactCommon/turbomodule/core (= 0.64.0)
232 | - React-RCTLinking (0.64.0):
233 | - FBReactNativeSpec (= 0.64.0)
234 | - React-Core/RCTLinkingHeaders (= 0.64.0)
235 | - React-jsi (= 0.64.0)
236 | - ReactCommon/turbomodule/core (= 0.64.0)
237 | - React-RCTNetwork (0.64.0):
238 | - FBReactNativeSpec (= 0.64.0)
239 | - RCT-Folly (= 2020.01.13.00)
240 | - RCTTypeSafety (= 0.64.0)
241 | - React-Core/RCTNetworkHeaders (= 0.64.0)
242 | - React-jsi (= 0.64.0)
243 | - ReactCommon/turbomodule/core (= 0.64.0)
244 | - React-RCTSettings (0.64.0):
245 | - FBReactNativeSpec (= 0.64.0)
246 | - RCT-Folly (= 2020.01.13.00)
247 | - RCTTypeSafety (= 0.64.0)
248 | - React-Core/RCTSettingsHeaders (= 0.64.0)
249 | - React-jsi (= 0.64.0)
250 | - ReactCommon/turbomodule/core (= 0.64.0)
251 | - React-RCTText (0.64.0):
252 | - React-Core/RCTTextHeaders (= 0.64.0)
253 | - React-RCTVibration (0.64.0):
254 | - FBReactNativeSpec (= 0.64.0)
255 | - RCT-Folly (= 2020.01.13.00)
256 | - React-Core/RCTVibrationHeaders (= 0.64.0)
257 | - React-jsi (= 0.64.0)
258 | - ReactCommon/turbomodule/core (= 0.64.0)
259 | - React-runtimeexecutor (0.64.0):
260 | - React-jsi (= 0.64.0)
261 | - ReactCommon/turbomodule/core (0.64.0):
262 | - DoubleConversion
263 | - glog
264 | - RCT-Folly (= 2020.01.13.00)
265 | - React-callinvoker (= 0.64.0)
266 | - React-Core (= 0.64.0)
267 | - React-cxxreact (= 0.64.0)
268 | - React-jsi (= 0.64.0)
269 | - React-perflogger (= 0.64.0)
270 | - Yoga (1.14.0)
271 |
272 | DEPENDENCIES:
273 | - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
274 | - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
275 | - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
276 | - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
277 | - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
278 | - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
279 | - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
280 | - React (from `../node_modules/react-native/`)
281 | - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
282 | - React-Core (from `../node_modules/react-native/`)
283 | - React-Core/DevSupport (from `../node_modules/react-native/`)
284 | - React-Core/RCTWebSocket (from `../node_modules/react-native/`)
285 | - React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
286 | - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
287 | - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
288 | - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
289 | - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
290 | - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
291 | - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
292 | - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
293 | - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`)
294 | - React-RCTImage (from `../node_modules/react-native/Libraries/Image`)
295 | - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`)
296 | - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`)
297 | - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
298 | - React-RCTText (from `../node_modules/react-native/Libraries/Text`)
299 | - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
300 | - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
301 | - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
302 | - Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
303 |
304 | SPEC REPOS:
305 | trunk:
306 | - boost-for-react-native
307 |
308 | EXTERNAL SOURCES:
309 | DoubleConversion:
310 | :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
311 | FBLazyVector:
312 | :path: "../node_modules/react-native/Libraries/FBLazyVector"
313 | FBReactNativeSpec:
314 | :path: "../node_modules/react-native/React/FBReactNativeSpec"
315 | glog:
316 | :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
317 | RCT-Folly:
318 | :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
319 | RCTRequired:
320 | :path: "../node_modules/react-native/Libraries/RCTRequired"
321 | RCTTypeSafety:
322 | :path: "../node_modules/react-native/Libraries/TypeSafety"
323 | React:
324 | :path: "../node_modules/react-native/"
325 | React-callinvoker:
326 | :path: "../node_modules/react-native/ReactCommon/callinvoker"
327 | React-Core:
328 | :path: "../node_modules/react-native/"
329 | React-CoreModules:
330 | :path: "../node_modules/react-native/React/CoreModules"
331 | React-cxxreact:
332 | :path: "../node_modules/react-native/ReactCommon/cxxreact"
333 | React-jsi:
334 | :path: "../node_modules/react-native/ReactCommon/jsi"
335 | React-jsiexecutor:
336 | :path: "../node_modules/react-native/ReactCommon/jsiexecutor"
337 | React-jsinspector:
338 | :path: "../node_modules/react-native/ReactCommon/jsinspector"
339 | React-perflogger:
340 | :path: "../node_modules/react-native/ReactCommon/reactperflogger"
341 | React-RCTActionSheet:
342 | :path: "../node_modules/react-native/Libraries/ActionSheetIOS"
343 | React-RCTAnimation:
344 | :path: "../node_modules/react-native/Libraries/NativeAnimation"
345 | React-RCTBlob:
346 | :path: "../node_modules/react-native/Libraries/Blob"
347 | React-RCTImage:
348 | :path: "../node_modules/react-native/Libraries/Image"
349 | React-RCTLinking:
350 | :path: "../node_modules/react-native/Libraries/LinkingIOS"
351 | React-RCTNetwork:
352 | :path: "../node_modules/react-native/Libraries/Network"
353 | React-RCTSettings:
354 | :path: "../node_modules/react-native/Libraries/Settings"
355 | React-RCTText:
356 | :path: "../node_modules/react-native/Libraries/Text"
357 | React-RCTVibration:
358 | :path: "../node_modules/react-native/Libraries/Vibration"
359 | React-runtimeexecutor:
360 | :path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
361 | ReactCommon:
362 | :path: "../node_modules/react-native/ReactCommon"
363 | Yoga:
364 | :path: "../node_modules/react-native/ReactCommon/yoga"
365 |
366 | SPEC CHECKSUMS:
367 | boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
368 | DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
369 | FBLazyVector: 49cbe4b43e445b06bf29199b6ad2057649e4c8f5
370 | FBReactNativeSpec: dfa02a2cc4fb571c3e0fde2426825e9b513cbc07
371 | glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
372 | RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c
373 | RCTRequired: 2f8cb5b7533219bf4218a045f92768129cf7050a
374 | RCTTypeSafety: 512728b73549e72ad7330b92f3d42936f2a4de5b
375 | React: 98eac01574128a790f0bbbafe2d1a8607291ac24
376 | React-callinvoker: def3f7fae16192df68d9b69fd4bbb59092ee36bc
377 | React-Core: 70a52aa5dbe9b83befae82038451a7df9fd54c5a
378 | React-CoreModules: 052edef46117862e2570eb3a0f06d81c61d2c4b8
379 | React-cxxreact: c1dc71b30653cfb4770efdafcbdc0ad6d388baab
380 | React-jsi: 74341196d9547cbcbcfa4b3bbbf03af56431d5a1
381 | React-jsiexecutor: 06a9c77b56902ae7ffcdd7a4905f664adc5d237b
382 | React-jsinspector: 0ae35a37b20d5e031eb020a69cc5afdbd6406301
383 | React-perflogger: 9c547d8f06b9bf00cb447f2b75e8d7f19b7e02af
384 | React-RCTActionSheet: 3080b6e12e0e1a5b313c8c0050699b5c794a1b11
385 | React-RCTAnimation: 3f96f21a497ae7dabf4d2f150ee43f906aaf516f
386 | React-RCTBlob: 283b8e5025e7f954176bc48164f846909002f3ed
387 | React-RCTImage: 5088a484faac78f2d877e1b79125d3bb1ea94a16
388 | React-RCTLinking: 5e8fbb3e9a8bc2e4e3eb15b1eb8bda5fcac27b8c
389 | React-RCTNetwork: 38ec277217b1e841d5e6a1fa78da65b9212ccb28
390 | React-RCTSettings: 242d6e692108c3de4f3bb74b7586a8799e9ab070
391 | React-RCTText: 8746736ac8eb5a4a74719aa695b7a236a93a83d2
392 | React-RCTVibration: 0fd6b21751a33cb72fce1a4a33ab9678416d307a
393 | React-runtimeexecutor: cad74a1eaa53ee6e7a3620231939d8fe2c6afcf0
394 | ReactCommon: cfe2b7fd20e0dbd2d1185cd7d8f99633fbc5ff05
395 | Yoga: 8c8436d4171c87504c648ae23b1d81242bdf3bbf
396 |
397 | PODFILE CHECKSUM: 17ebc9ead1da61cb18fe0063ec4d909c23d679d3
398 |
399 | COCOAPODS: 1.10.1
400 |
--------------------------------------------------------------------------------
/ios/ReactNativeTodo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 00E356F31AD99517003FC87E /* ReactNativeTodoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ReactNativeTodoTests.m */; };
11 | 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
12 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13 | 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
14 | 58D834B6092DA87088FE8A2D /* libPods-ReactNativeTodo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A25FFA35ECD5FAA59FD46C92 /* libPods-ReactNativeTodo.a */; };
15 | 80626352CE354C6308B8201B /* libPods-ReactNativeTodo-ReactNativeTodoTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 65321A2A73EDEE3C7BC333BB /* libPods-ReactNativeTodo-ReactNativeTodoTests.a */; };
16 | 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
17 | 252D34B3AA9D4C18A2870CF1 /* MaterialIcons-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C1090302743E404381B8F171 /* MaterialIcons-Regular.ttf */; };
18 | 7B6E48329B0E4D759FADFB98 /* Montserrat-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 674550559EB447B4AC8C4E06 /* Montserrat-Black.ttf */; };
19 | CACD170FAF8243698A7E1D78 /* Montserrat-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 89AC23767A8349FA9130E06F /* Montserrat-BlackItalic.ttf */; };
20 | 097F1D49B72E42E7BFDEBB6E /* Montserrat-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BC91317C8C984B3E85BA1D55 /* Montserrat-Bold.ttf */; };
21 | 6014CA59A77F4A33A083B749 /* Montserrat-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B9B7F1E393A24470AC3D2184 /* Montserrat-BoldItalic.ttf */; };
22 | 3BFDA5469DCC4E47B1C0BEE8 /* Montserrat-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E770B0A8ADDF4AF88879701D /* Montserrat-ExtraBold.ttf */; };
23 | 044BDD223EB541038B0E3B9E /* Montserrat-ExtraBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DBEB2B5DAF634684A6B89E7B /* Montserrat-ExtraBoldItalic.ttf */; };
24 | 6530443EB2B44AF58E74126A /* Montserrat-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B3DCBC48A81C4E97A7CC0505 /* Montserrat-ExtraLight.ttf */; };
25 | 915EF8B2CFEB4FD499AAC9C0 /* Montserrat-ExtraLightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 40CB230C01414912AF5BD543 /* Montserrat-ExtraLightItalic.ttf */; };
26 | D4BD6C4334114F86AF6132A3 /* Montserrat-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F0621A9B7F78413184254191 /* Montserrat-Italic.ttf */; };
27 | 8D1163FC432B47B39392703B /* Montserrat-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 413586879BE6492DB53B68E2 /* Montserrat-Light.ttf */; };
28 | 4229AB3EEFED449591F436C3 /* Montserrat-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CFD0212016794607A3675115 /* Montserrat-LightItalic.ttf */; };
29 | 6EFFA74D09904DB288698BD3 /* Montserrat-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0E7B347C8CCC4A9191A2A2FE /* Montserrat-Medium.ttf */; };
30 | E7F285D2AF594E8C8607C080 /* Montserrat-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CA221355B6704271A86D984C /* Montserrat-MediumItalic.ttf */; };
31 | DB5E4F0E8DF54D7286632D6E /* Montserrat-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7691423FC7604B06935D61D7 /* Montserrat-Regular.ttf */; };
32 | E837D3FF57934A188B0CF627 /* Montserrat-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F26F89055612402F85ABF396 /* Montserrat-SemiBold.ttf */; };
33 | 1A93C9ACE80849318C2D91AF /* Montserrat-SemiBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 76EA140A06AD42C08346856B /* Montserrat-SemiBoldItalic.ttf */; };
34 | A5EC6D75BDC54D3B9591E6F9 /* Montserrat-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D2016C5DF5EE42C7911EF94E /* Montserrat-Thin.ttf */; };
35 | 496862C30E8B4D82AE4D8589 /* Montserrat-ThinItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 573FF1318E7F441296EBDBDD /* Montserrat-ThinItalic.ttf */; };
36 | /* End PBXBuildFile section */
37 |
38 | /* Begin PBXContainerItemProxy section */
39 | 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
40 | isa = PBXContainerItemProxy;
41 | containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
42 | proxyType = 1;
43 | remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
44 | remoteInfo = ReactNativeTodo;
45 | };
46 | /* End PBXContainerItemProxy section */
47 |
48 | /* Begin PBXFileReference section */
49 | 00E356EE1AD99517003FC87E /* ReactNativeTodoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactNativeTodoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
50 | 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
51 | 00E356F21AD99517003FC87E /* ReactNativeTodoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactNativeTodoTests.m; sourceTree = ""; };
52 | 050F5C44555B2EA1957D9E35 /* Pods-ReactNativeTodo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeTodo.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeTodo/Pods-ReactNativeTodo.release.xcconfig"; sourceTree = ""; };
53 | 069352C661B7ACBAADD3D4AA /* Pods-ReactNativeTodo-ReactNativeTodoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeTodo-ReactNativeTodoTests.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeTodo-ReactNativeTodoTests/Pods-ReactNativeTodo-ReactNativeTodoTests.release.xcconfig"; sourceTree = ""; };
54 | 13B07F961A680F5B00A75B9A /* ReactNativeTodo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactNativeTodo.app; sourceTree = BUILT_PRODUCTS_DIR; };
55 | 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = ReactNativeTodo/AppDelegate.h; sourceTree = ""; };
56 | 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = ReactNativeTodo/AppDelegate.m; sourceTree = ""; };
57 | 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNativeTodo/Images.xcassets; sourceTree = ""; };
58 | 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeTodo/Info.plist; sourceTree = ""; };
59 | 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ReactNativeTodo/main.m; sourceTree = ""; };
60 | 40D876A6A38F47613D845C24 /* Pods-ReactNativeTodo-ReactNativeTodoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeTodo-ReactNativeTodoTests.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeTodo-ReactNativeTodoTests/Pods-ReactNativeTodo-ReactNativeTodoTests.debug.xcconfig"; sourceTree = ""; };
61 | 65321A2A73EDEE3C7BC333BB /* libPods-ReactNativeTodo-ReactNativeTodoTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeTodo-ReactNativeTodoTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
62 | 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeTodo/LaunchScreen.storyboard; sourceTree = ""; };
63 | A25FFA35ECD5FAA59FD46C92 /* libPods-ReactNativeTodo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeTodo.a"; sourceTree = BUILT_PRODUCTS_DIR; };
64 | C0FCFA1F10B4A7C637CA29EC /* Pods-ReactNativeTodo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeTodo.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeTodo/Pods-ReactNativeTodo.debug.xcconfig"; sourceTree = ""; };
65 | ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
66 | C1090302743E404381B8F171 /* MaterialIcons-Regular.ttf */ = {isa = PBXFileReference; name = "MaterialIcons-Regular.ttf"; path = "../assets/fonts/MaterialIcons-Regular.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
67 | 674550559EB447B4AC8C4E06 /* Montserrat-Black.ttf */ = {isa = PBXFileReference; name = "Montserrat-Black.ttf"; path = "../assets/fonts/Montserrat-Black.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
68 | 89AC23767A8349FA9130E06F /* Montserrat-BlackItalic.ttf */ = {isa = PBXFileReference; name = "Montserrat-BlackItalic.ttf"; path = "../assets/fonts/Montserrat-BlackItalic.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
69 | BC91317C8C984B3E85BA1D55 /* Montserrat-Bold.ttf */ = {isa = PBXFileReference; name = "Montserrat-Bold.ttf"; path = "../assets/fonts/Montserrat-Bold.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
70 | B9B7F1E393A24470AC3D2184 /* Montserrat-BoldItalic.ttf */ = {isa = PBXFileReference; name = "Montserrat-BoldItalic.ttf"; path = "../assets/fonts/Montserrat-BoldItalic.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
71 | E770B0A8ADDF4AF88879701D /* Montserrat-ExtraBold.ttf */ = {isa = PBXFileReference; name = "Montserrat-ExtraBold.ttf"; path = "../assets/fonts/Montserrat-ExtraBold.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
72 | DBEB2B5DAF634684A6B89E7B /* Montserrat-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; name = "Montserrat-ExtraBoldItalic.ttf"; path = "../assets/fonts/Montserrat-ExtraBoldItalic.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
73 | B3DCBC48A81C4E97A7CC0505 /* Montserrat-ExtraLight.ttf */ = {isa = PBXFileReference; name = "Montserrat-ExtraLight.ttf"; path = "../assets/fonts/Montserrat-ExtraLight.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
74 | 40CB230C01414912AF5BD543 /* Montserrat-ExtraLightItalic.ttf */ = {isa = PBXFileReference; name = "Montserrat-ExtraLightItalic.ttf"; path = "../assets/fonts/Montserrat-ExtraLightItalic.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
75 | F0621A9B7F78413184254191 /* Montserrat-Italic.ttf */ = {isa = PBXFileReference; name = "Montserrat-Italic.ttf"; path = "../assets/fonts/Montserrat-Italic.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
76 | 413586879BE6492DB53B68E2 /* Montserrat-Light.ttf */ = {isa = PBXFileReference; name = "Montserrat-Light.ttf"; path = "../assets/fonts/Montserrat-Light.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
77 | CFD0212016794607A3675115 /* Montserrat-LightItalic.ttf */ = {isa = PBXFileReference; name = "Montserrat-LightItalic.ttf"; path = "../assets/fonts/Montserrat-LightItalic.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
78 | 0E7B347C8CCC4A9191A2A2FE /* Montserrat-Medium.ttf */ = {isa = PBXFileReference; name = "Montserrat-Medium.ttf"; path = "../assets/fonts/Montserrat-Medium.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
79 | CA221355B6704271A86D984C /* Montserrat-MediumItalic.ttf */ = {isa = PBXFileReference; name = "Montserrat-MediumItalic.ttf"; path = "../assets/fonts/Montserrat-MediumItalic.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
80 | 7691423FC7604B06935D61D7 /* Montserrat-Regular.ttf */ = {isa = PBXFileReference; name = "Montserrat-Regular.ttf"; path = "../assets/fonts/Montserrat-Regular.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
81 | F26F89055612402F85ABF396 /* Montserrat-SemiBold.ttf */ = {isa = PBXFileReference; name = "Montserrat-SemiBold.ttf"; path = "../assets/fonts/Montserrat-SemiBold.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
82 | 76EA140A06AD42C08346856B /* Montserrat-SemiBoldItalic.ttf */ = {isa = PBXFileReference; name = "Montserrat-SemiBoldItalic.ttf"; path = "../assets/fonts/Montserrat-SemiBoldItalic.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
83 | D2016C5DF5EE42C7911EF94E /* Montserrat-Thin.ttf */ = {isa = PBXFileReference; name = "Montserrat-Thin.ttf"; path = "../assets/fonts/Montserrat-Thin.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
84 | 573FF1318E7F441296EBDBDD /* Montserrat-ThinItalic.ttf */ = {isa = PBXFileReference; name = "Montserrat-ThinItalic.ttf"; path = "../assets/fonts/Montserrat-ThinItalic.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
85 | /* End PBXFileReference section */
86 |
87 | /* Begin PBXFrameworksBuildPhase section */
88 | 00E356EB1AD99517003FC87E /* Frameworks */ = {
89 | isa = PBXFrameworksBuildPhase;
90 | buildActionMask = 2147483647;
91 | files = (
92 | 80626352CE354C6308B8201B /* libPods-ReactNativeTodo-ReactNativeTodoTests.a in Frameworks */,
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | 13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
97 | isa = PBXFrameworksBuildPhase;
98 | buildActionMask = 2147483647;
99 | files = (
100 | 58D834B6092DA87088FE8A2D /* libPods-ReactNativeTodo.a in Frameworks */,
101 | );
102 | runOnlyForDeploymentPostprocessing = 0;
103 | };
104 | /* End PBXFrameworksBuildPhase section */
105 |
106 | /* Begin PBXGroup section */
107 | 00E356EF1AD99517003FC87E /* ReactNativeTodoTests */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 00E356F21AD99517003FC87E /* ReactNativeTodoTests.m */,
111 | 00E356F01AD99517003FC87E /* Supporting Files */,
112 | );
113 | path = ReactNativeTodoTests;
114 | sourceTree = "";
115 | };
116 | 00E356F01AD99517003FC87E /* Supporting Files */ = {
117 | isa = PBXGroup;
118 | children = (
119 | 00E356F11AD99517003FC87E /* Info.plist */,
120 | );
121 | name = "Supporting Files";
122 | sourceTree = "";
123 | };
124 | 13B07FAE1A68108700A75B9A /* ReactNativeTodo */ = {
125 | isa = PBXGroup;
126 | children = (
127 | 13B07FAF1A68108700A75B9A /* AppDelegate.h */,
128 | 13B07FB01A68108700A75B9A /* AppDelegate.m */,
129 | 13B07FB51A68108700A75B9A /* Images.xcassets */,
130 | 13B07FB61A68108700A75B9A /* Info.plist */,
131 | 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
132 | 13B07FB71A68108700A75B9A /* main.m */,
133 | );
134 | name = ReactNativeTodo;
135 | sourceTree = "";
136 | };
137 | 2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
138 | isa = PBXGroup;
139 | children = (
140 | ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
141 | A25FFA35ECD5FAA59FD46C92 /* libPods-ReactNativeTodo.a */,
142 | 65321A2A73EDEE3C7BC333BB /* libPods-ReactNativeTodo-ReactNativeTodoTests.a */,
143 | );
144 | name = Frameworks;
145 | sourceTree = "";
146 | };
147 | 373CC6F252F996DB1AEFF807 /* Pods */ = {
148 | isa = PBXGroup;
149 | children = (
150 | C0FCFA1F10B4A7C637CA29EC /* Pods-ReactNativeTodo.debug.xcconfig */,
151 | 050F5C44555B2EA1957D9E35 /* Pods-ReactNativeTodo.release.xcconfig */,
152 | 40D876A6A38F47613D845C24 /* Pods-ReactNativeTodo-ReactNativeTodoTests.debug.xcconfig */,
153 | 069352C661B7ACBAADD3D4AA /* Pods-ReactNativeTodo-ReactNativeTodoTests.release.xcconfig */,
154 | );
155 | name = Pods;
156 | path = Pods;
157 | sourceTree = "";
158 | };
159 | 832341AE1AAA6A7D00B99B32 /* Libraries */ = {
160 | isa = PBXGroup;
161 | children = (
162 | );
163 | name = Libraries;
164 | sourceTree = "";
165 | };
166 | 83CBB9F61A601CBA00E9B192 = {
167 | isa = PBXGroup;
168 | children = (
169 | 13B07FAE1A68108700A75B9A /* ReactNativeTodo */,
170 | 832341AE1AAA6A7D00B99B32 /* Libraries */,
171 | 00E356EF1AD99517003FC87E /* ReactNativeTodoTests */,
172 | 83CBBA001A601CBA00E9B192 /* Products */,
173 | 2D16E6871FA4F8E400B85C8A /* Frameworks */,
174 | 373CC6F252F996DB1AEFF807 /* Pods */,
175 | C1280775897B4A088C58BEAE /* Resources */,
176 | );
177 | indentWidth = 2;
178 | sourceTree = "";
179 | tabWidth = 2;
180 | usesTabs = 0;
181 | };
182 | 83CBBA001A601CBA00E9B192 /* Products */ = {
183 | isa = PBXGroup;
184 | children = (
185 | 13B07F961A680F5B00A75B9A /* ReactNativeTodo.app */,
186 | 00E356EE1AD99517003FC87E /* ReactNativeTodoTests.xctest */,
187 | );
188 | name = Products;
189 | sourceTree = "";
190 | };
191 | C1280775897B4A088C58BEAE /* Resources */ = {
192 | isa = "PBXGroup";
193 | children = (
194 | C1090302743E404381B8F171 /* MaterialIcons-Regular.ttf */,
195 | 674550559EB447B4AC8C4E06 /* Montserrat-Black.ttf */,
196 | 89AC23767A8349FA9130E06F /* Montserrat-BlackItalic.ttf */,
197 | BC91317C8C984B3E85BA1D55 /* Montserrat-Bold.ttf */,
198 | B9B7F1E393A24470AC3D2184 /* Montserrat-BoldItalic.ttf */,
199 | E770B0A8ADDF4AF88879701D /* Montserrat-ExtraBold.ttf */,
200 | DBEB2B5DAF634684A6B89E7B /* Montserrat-ExtraBoldItalic.ttf */,
201 | B3DCBC48A81C4E97A7CC0505 /* Montserrat-ExtraLight.ttf */,
202 | 40CB230C01414912AF5BD543 /* Montserrat-ExtraLightItalic.ttf */,
203 | F0621A9B7F78413184254191 /* Montserrat-Italic.ttf */,
204 | 413586879BE6492DB53B68E2 /* Montserrat-Light.ttf */,
205 | CFD0212016794607A3675115 /* Montserrat-LightItalic.ttf */,
206 | 0E7B347C8CCC4A9191A2A2FE /* Montserrat-Medium.ttf */,
207 | CA221355B6704271A86D984C /* Montserrat-MediumItalic.ttf */,
208 | 7691423FC7604B06935D61D7 /* Montserrat-Regular.ttf */,
209 | F26F89055612402F85ABF396 /* Montserrat-SemiBold.ttf */,
210 | 76EA140A06AD42C08346856B /* Montserrat-SemiBoldItalic.ttf */,
211 | D2016C5DF5EE42C7911EF94E /* Montserrat-Thin.ttf */,
212 | 573FF1318E7F441296EBDBDD /* Montserrat-ThinItalic.ttf */,
213 | );
214 | name = Resources;
215 | sourceTree = "";
216 | path = "";
217 | };
218 | /* End PBXGroup section */
219 |
220 | /* Begin PBXNativeTarget section */
221 | 00E356ED1AD99517003FC87E /* ReactNativeTodoTests */ = {
222 | isa = PBXNativeTarget;
223 | buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ReactNativeTodoTests" */;
224 | buildPhases = (
225 | 279AC4CDD2BE5702CC32CABC /* [CP] Check Pods Manifest.lock */,
226 | 00E356EA1AD99517003FC87E /* Sources */,
227 | 00E356EB1AD99517003FC87E /* Frameworks */,
228 | 00E356EC1AD99517003FC87E /* Resources */,
229 | 49DC283C3FFB05C59D316DFD /* [CP] Copy Pods Resources */,
230 | );
231 | buildRules = (
232 | );
233 | dependencies = (
234 | 00E356F51AD99517003FC87E /* PBXTargetDependency */,
235 | );
236 | name = ReactNativeTodoTests;
237 | productName = ReactNativeTodoTests;
238 | productReference = 00E356EE1AD99517003FC87E /* ReactNativeTodoTests.xctest */;
239 | productType = "com.apple.product-type.bundle.unit-test";
240 | };
241 | 13B07F861A680F5B00A75B9A /* ReactNativeTodo */ = {
242 | isa = PBXNativeTarget;
243 | buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeTodo" */;
244 | buildPhases = (
245 | A48B5484A8B33C970157C125 /* [CP] Check Pods Manifest.lock */,
246 | FD10A7F022414F080027D42C /* Start Packager */,
247 | 13B07F871A680F5B00A75B9A /* Sources */,
248 | 13B07F8C1A680F5B00A75B9A /* Frameworks */,
249 | 13B07F8E1A680F5B00A75B9A /* Resources */,
250 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
251 | 840A7F8B56BE64344E177E79 /* [CP] Copy Pods Resources */,
252 | );
253 | buildRules = (
254 | );
255 | dependencies = (
256 | );
257 | name = ReactNativeTodo;
258 | productName = ReactNativeTodo;
259 | productReference = 13B07F961A680F5B00A75B9A /* ReactNativeTodo.app */;
260 | productType = "com.apple.product-type.application";
261 | };
262 | /* End PBXNativeTarget section */
263 |
264 | /* Begin PBXProject section */
265 | 83CBB9F71A601CBA00E9B192 /* Project object */ = {
266 | isa = PBXProject;
267 | attributes = {
268 | LastUpgradeCheck = 1210;
269 | TargetAttributes = {
270 | 00E356ED1AD99517003FC87E = {
271 | CreatedOnToolsVersion = 6.2;
272 | TestTargetID = 13B07F861A680F5B00A75B9A;
273 | };
274 | 13B07F861A680F5B00A75B9A = {
275 | LastSwiftMigration = 1120;
276 | };
277 | };
278 | };
279 | buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNativeTodo" */;
280 | compatibilityVersion = "Xcode 12.0";
281 | developmentRegion = en;
282 | hasScannedForEncodings = 0;
283 | knownRegions = (
284 | en,
285 | Base,
286 | );
287 | mainGroup = 83CBB9F61A601CBA00E9B192;
288 | productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
289 | projectDirPath = "";
290 | projectRoot = "";
291 | targets = (
292 | 13B07F861A680F5B00A75B9A /* ReactNativeTodo */,
293 | 00E356ED1AD99517003FC87E /* ReactNativeTodoTests */,
294 | );
295 | };
296 | /* End PBXProject section */
297 |
298 | /* Begin PBXResourcesBuildPhase section */
299 | 00E356EC1AD99517003FC87E /* Resources */ = {
300 | isa = PBXResourcesBuildPhase;
301 | buildActionMask = 2147483647;
302 | files = (
303 | );
304 | runOnlyForDeploymentPostprocessing = 0;
305 | };
306 | 13B07F8E1A680F5B00A75B9A /* Resources */ = {
307 | isa = PBXResourcesBuildPhase;
308 | buildActionMask = 2147483647;
309 | files = (
310 | 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
311 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
312 | 252D34B3AA9D4C18A2870CF1 /* MaterialIcons-Regular.ttf in Resources */,
313 | 7B6E48329B0E4D759FADFB98 /* Montserrat-Black.ttf in Resources */,
314 | CACD170FAF8243698A7E1D78 /* Montserrat-BlackItalic.ttf in Resources */,
315 | 097F1D49B72E42E7BFDEBB6E /* Montserrat-Bold.ttf in Resources */,
316 | 6014CA59A77F4A33A083B749 /* Montserrat-BoldItalic.ttf in Resources */,
317 | 3BFDA5469DCC4E47B1C0BEE8 /* Montserrat-ExtraBold.ttf in Resources */,
318 | 044BDD223EB541038B0E3B9E /* Montserrat-ExtraBoldItalic.ttf in Resources */,
319 | 6530443EB2B44AF58E74126A /* Montserrat-ExtraLight.ttf in Resources */,
320 | 915EF8B2CFEB4FD499AAC9C0 /* Montserrat-ExtraLightItalic.ttf in Resources */,
321 | D4BD6C4334114F86AF6132A3 /* Montserrat-Italic.ttf in Resources */,
322 | 8D1163FC432B47B39392703B /* Montserrat-Light.ttf in Resources */,
323 | 4229AB3EEFED449591F436C3 /* Montserrat-LightItalic.ttf in Resources */,
324 | 6EFFA74D09904DB288698BD3 /* Montserrat-Medium.ttf in Resources */,
325 | E7F285D2AF594E8C8607C080 /* Montserrat-MediumItalic.ttf in Resources */,
326 | DB5E4F0E8DF54D7286632D6E /* Montserrat-Regular.ttf in Resources */,
327 | E837D3FF57934A188B0CF627 /* Montserrat-SemiBold.ttf in Resources */,
328 | 1A93C9ACE80849318C2D91AF /* Montserrat-SemiBoldItalic.ttf in Resources */,
329 | A5EC6D75BDC54D3B9591E6F9 /* Montserrat-Thin.ttf in Resources */,
330 | 496862C30E8B4D82AE4D8589 /* Montserrat-ThinItalic.ttf in Resources */,
331 | );
332 | runOnlyForDeploymentPostprocessing = 0;
333 | };
334 | /* End PBXResourcesBuildPhase section */
335 |
336 | /* Begin PBXShellScriptBuildPhase section */
337 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
338 | isa = PBXShellScriptBuildPhase;
339 | buildActionMask = 2147483647;
340 | files = (
341 | );
342 | inputPaths = (
343 | );
344 | name = "Bundle React Native code and images";
345 | outputPaths = (
346 | );
347 | runOnlyForDeploymentPostprocessing = 0;
348 | shellPath = /bin/sh;
349 | shellScript = "set -e\n\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n";
350 | };
351 | 279AC4CDD2BE5702CC32CABC /* [CP] Check Pods Manifest.lock */ = {
352 | isa = PBXShellScriptBuildPhase;
353 | buildActionMask = 2147483647;
354 | files = (
355 | );
356 | inputFileListPaths = (
357 | );
358 | inputPaths = (
359 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
360 | "${PODS_ROOT}/Manifest.lock",
361 | );
362 | name = "[CP] Check Pods Manifest.lock";
363 | outputFileListPaths = (
364 | );
365 | outputPaths = (
366 | "$(DERIVED_FILE_DIR)/Pods-ReactNativeTodo-ReactNativeTodoTests-checkManifestLockResult.txt",
367 | );
368 | runOnlyForDeploymentPostprocessing = 0;
369 | shellPath = /bin/sh;
370 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
371 | showEnvVarsInLog = 0;
372 | };
373 | 49DC283C3FFB05C59D316DFD /* [CP] Copy Pods Resources */ = {
374 | isa = PBXShellScriptBuildPhase;
375 | buildActionMask = 2147483647;
376 | files = (
377 | );
378 | inputFileListPaths = (
379 | "${PODS_ROOT}/Target Support Files/Pods-ReactNativeTodo-ReactNativeTodoTests/Pods-ReactNativeTodo-ReactNativeTodoTests-resources-${CONFIGURATION}-input-files.xcfilelist",
380 | );
381 | name = "[CP] Copy Pods Resources";
382 | outputFileListPaths = (
383 | "${PODS_ROOT}/Target Support Files/Pods-ReactNativeTodo-ReactNativeTodoTests/Pods-ReactNativeTodo-ReactNativeTodoTests-resources-${CONFIGURATION}-output-files.xcfilelist",
384 | );
385 | runOnlyForDeploymentPostprocessing = 0;
386 | shellPath = /bin/sh;
387 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeTodo-ReactNativeTodoTests/Pods-ReactNativeTodo-ReactNativeTodoTests-resources.sh\"\n";
388 | showEnvVarsInLog = 0;
389 | };
390 | 840A7F8B56BE64344E177E79 /* [CP] Copy Pods Resources */ = {
391 | isa = PBXShellScriptBuildPhase;
392 | buildActionMask = 2147483647;
393 | files = (
394 | );
395 | inputFileListPaths = (
396 | "${PODS_ROOT}/Target Support Files/Pods-ReactNativeTodo/Pods-ReactNativeTodo-resources-${CONFIGURATION}-input-files.xcfilelist",
397 | );
398 | name = "[CP] Copy Pods Resources";
399 | outputFileListPaths = (
400 | "${PODS_ROOT}/Target Support Files/Pods-ReactNativeTodo/Pods-ReactNativeTodo-resources-${CONFIGURATION}-output-files.xcfilelist",
401 | );
402 | runOnlyForDeploymentPostprocessing = 0;
403 | shellPath = /bin/sh;
404 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeTodo/Pods-ReactNativeTodo-resources.sh\"\n";
405 | showEnvVarsInLog = 0;
406 | };
407 | A48B5484A8B33C970157C125 /* [CP] Check Pods Manifest.lock */ = {
408 | isa = PBXShellScriptBuildPhase;
409 | buildActionMask = 2147483647;
410 | files = (
411 | );
412 | inputFileListPaths = (
413 | );
414 | inputPaths = (
415 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
416 | "${PODS_ROOT}/Manifest.lock",
417 | );
418 | name = "[CP] Check Pods Manifest.lock";
419 | outputFileListPaths = (
420 | );
421 | outputPaths = (
422 | "$(DERIVED_FILE_DIR)/Pods-ReactNativeTodo-checkManifestLockResult.txt",
423 | );
424 | runOnlyForDeploymentPostprocessing = 0;
425 | shellPath = /bin/sh;
426 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
427 | showEnvVarsInLog = 0;
428 | };
429 | FD10A7F022414F080027D42C /* Start Packager */ = {
430 | isa = PBXShellScriptBuildPhase;
431 | buildActionMask = 2147483647;
432 | files = (
433 | );
434 | inputFileListPaths = (
435 | );
436 | inputPaths = (
437 | );
438 | name = "Start Packager";
439 | outputFileListPaths = (
440 | );
441 | outputPaths = (
442 | );
443 | runOnlyForDeploymentPostprocessing = 0;
444 | shellPath = /bin/sh;
445 | shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
446 | showEnvVarsInLog = 0;
447 | };
448 | /* End PBXShellScriptBuildPhase section */
449 |
450 | /* Begin PBXSourcesBuildPhase section */
451 | 00E356EA1AD99517003FC87E /* Sources */ = {
452 | isa = PBXSourcesBuildPhase;
453 | buildActionMask = 2147483647;
454 | files = (
455 | 00E356F31AD99517003FC87E /* ReactNativeTodoTests.m in Sources */,
456 | );
457 | runOnlyForDeploymentPostprocessing = 0;
458 | };
459 | 13B07F871A680F5B00A75B9A /* Sources */ = {
460 | isa = PBXSourcesBuildPhase;
461 | buildActionMask = 2147483647;
462 | files = (
463 | 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
464 | 13B07FC11A68108700A75B9A /* main.m in Sources */,
465 | );
466 | runOnlyForDeploymentPostprocessing = 0;
467 | };
468 | /* End PBXSourcesBuildPhase section */
469 |
470 | /* Begin PBXTargetDependency section */
471 | 00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
472 | isa = PBXTargetDependency;
473 | target = 13B07F861A680F5B00A75B9A /* ReactNativeTodo */;
474 | targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
475 | };
476 | /* End PBXTargetDependency section */
477 |
478 | /* Begin XCBuildConfiguration section */
479 | 00E356F61AD99517003FC87E /* Debug */ = {
480 | isa = XCBuildConfiguration;
481 | baseConfigurationReference = 40D876A6A38F47613D845C24 /* Pods-ReactNativeTodo-ReactNativeTodoTests.debug.xcconfig */;
482 | buildSettings = {
483 | BUNDLE_LOADER = "$(TEST_HOST)";
484 | GCC_PREPROCESSOR_DEFINITIONS = (
485 | "DEBUG=1",
486 | "$(inherited)",
487 | );
488 | INFOPLIST_FILE = ReactNativeTodoTests/Info.plist;
489 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
490 | LD_RUNPATH_SEARCH_PATHS = (
491 | "$(inherited)",
492 | "@executable_path/Frameworks",
493 | "@loader_path/Frameworks",
494 | );
495 | OTHER_LDFLAGS = (
496 | "-ObjC",
497 | "-lc++",
498 | "$(inherited)",
499 | );
500 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
501 | PRODUCT_NAME = "$(TARGET_NAME)";
502 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReactNativeTodo.app/ReactNativeTodo";
503 | };
504 | name = Debug;
505 | };
506 | 00E356F71AD99517003FC87E /* Release */ = {
507 | isa = XCBuildConfiguration;
508 | baseConfigurationReference = 069352C661B7ACBAADD3D4AA /* Pods-ReactNativeTodo-ReactNativeTodoTests.release.xcconfig */;
509 | buildSettings = {
510 | BUNDLE_LOADER = "$(TEST_HOST)";
511 | COPY_PHASE_STRIP = NO;
512 | INFOPLIST_FILE = ReactNativeTodoTests/Info.plist;
513 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
514 | LD_RUNPATH_SEARCH_PATHS = (
515 | "$(inherited)",
516 | "@executable_path/Frameworks",
517 | "@loader_path/Frameworks",
518 | );
519 | OTHER_LDFLAGS = (
520 | "-ObjC",
521 | "-lc++",
522 | "$(inherited)",
523 | );
524 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
525 | PRODUCT_NAME = "$(TARGET_NAME)";
526 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReactNativeTodo.app/ReactNativeTodo";
527 | };
528 | name = Release;
529 | };
530 | 13B07F941A680F5B00A75B9A /* Debug */ = {
531 | isa = XCBuildConfiguration;
532 | baseConfigurationReference = C0FCFA1F10B4A7C637CA29EC /* Pods-ReactNativeTodo.debug.xcconfig */;
533 | buildSettings = {
534 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
535 | CLANG_ENABLE_MODULES = YES;
536 | CURRENT_PROJECT_VERSION = 1;
537 | ENABLE_BITCODE = NO;
538 | INFOPLIST_FILE = ReactNativeTodo/Info.plist;
539 | LD_RUNPATH_SEARCH_PATHS = (
540 | "$(inherited)",
541 | "@executable_path/Frameworks",
542 | );
543 | OTHER_LDFLAGS = (
544 | "$(inherited)",
545 | "-ObjC",
546 | "-lc++",
547 | );
548 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
549 | PRODUCT_NAME = ReactNativeTodo;
550 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
551 | SWIFT_VERSION = 5.0;
552 | VERSIONING_SYSTEM = "apple-generic";
553 | };
554 | name = Debug;
555 | };
556 | 13B07F951A680F5B00A75B9A /* Release */ = {
557 | isa = XCBuildConfiguration;
558 | baseConfigurationReference = 050F5C44555B2EA1957D9E35 /* Pods-ReactNativeTodo.release.xcconfig */;
559 | buildSettings = {
560 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
561 | CLANG_ENABLE_MODULES = YES;
562 | CURRENT_PROJECT_VERSION = 1;
563 | INFOPLIST_FILE = ReactNativeTodo/Info.plist;
564 | LD_RUNPATH_SEARCH_PATHS = (
565 | "$(inherited)",
566 | "@executable_path/Frameworks",
567 | );
568 | OTHER_LDFLAGS = (
569 | "$(inherited)",
570 | "-ObjC",
571 | "-lc++",
572 | );
573 | PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
574 | PRODUCT_NAME = ReactNativeTodo;
575 | SWIFT_VERSION = 5.0;
576 | VERSIONING_SYSTEM = "apple-generic";
577 | };
578 | name = Release;
579 | };
580 | 83CBBA201A601CBA00E9B192 /* Debug */ = {
581 | isa = XCBuildConfiguration;
582 | buildSettings = {
583 | ALWAYS_SEARCH_USER_PATHS = NO;
584 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
585 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
586 | CLANG_CXX_LIBRARY = "libc++";
587 | CLANG_ENABLE_MODULES = YES;
588 | CLANG_ENABLE_OBJC_ARC = YES;
589 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
590 | CLANG_WARN_BOOL_CONVERSION = YES;
591 | CLANG_WARN_COMMA = YES;
592 | CLANG_WARN_CONSTANT_CONVERSION = YES;
593 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
594 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
595 | CLANG_WARN_EMPTY_BODY = YES;
596 | CLANG_WARN_ENUM_CONVERSION = YES;
597 | CLANG_WARN_INFINITE_RECURSION = YES;
598 | CLANG_WARN_INT_CONVERSION = YES;
599 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
600 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
601 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
602 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
603 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
604 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
605 | CLANG_WARN_STRICT_PROTOTYPES = YES;
606 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
607 | CLANG_WARN_UNREACHABLE_CODE = YES;
608 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
609 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
610 | COPY_PHASE_STRIP = NO;
611 | ENABLE_STRICT_OBJC_MSGSEND = YES;
612 | ENABLE_TESTABILITY = YES;
613 | "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 ";
614 | GCC_C_LANGUAGE_STANDARD = gnu99;
615 | GCC_DYNAMIC_NO_PIC = NO;
616 | GCC_NO_COMMON_BLOCKS = YES;
617 | GCC_OPTIMIZATION_LEVEL = 0;
618 | GCC_PREPROCESSOR_DEFINITIONS = (
619 | "DEBUG=1",
620 | "$(inherited)",
621 | );
622 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
623 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
624 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
625 | GCC_WARN_UNDECLARED_SELECTOR = YES;
626 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
627 | GCC_WARN_UNUSED_FUNCTION = YES;
628 | GCC_WARN_UNUSED_VARIABLE = YES;
629 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
630 | LD_RUNPATH_SEARCH_PATHS = (
631 | /usr/lib/swift,
632 | "$(inherited)",
633 | );
634 | LIBRARY_SEARCH_PATHS = (
635 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
636 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
637 | "\"$(inherited)\"",
638 | );
639 | MTL_ENABLE_DEBUG_INFO = YES;
640 | ONLY_ACTIVE_ARCH = YES;
641 | SDKROOT = iphoneos;
642 | };
643 | name = Debug;
644 | };
645 | 83CBBA211A601CBA00E9B192 /* Release */ = {
646 | isa = XCBuildConfiguration;
647 | buildSettings = {
648 | ALWAYS_SEARCH_USER_PATHS = NO;
649 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
650 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
651 | CLANG_CXX_LIBRARY = "libc++";
652 | CLANG_ENABLE_MODULES = YES;
653 | CLANG_ENABLE_OBJC_ARC = YES;
654 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
655 | CLANG_WARN_BOOL_CONVERSION = YES;
656 | CLANG_WARN_COMMA = YES;
657 | CLANG_WARN_CONSTANT_CONVERSION = YES;
658 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
659 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
660 | CLANG_WARN_EMPTY_BODY = YES;
661 | CLANG_WARN_ENUM_CONVERSION = YES;
662 | CLANG_WARN_INFINITE_RECURSION = YES;
663 | CLANG_WARN_INT_CONVERSION = YES;
664 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
665 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
666 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
667 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
668 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
669 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
670 | CLANG_WARN_STRICT_PROTOTYPES = YES;
671 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
672 | CLANG_WARN_UNREACHABLE_CODE = YES;
673 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
674 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
675 | COPY_PHASE_STRIP = YES;
676 | ENABLE_NS_ASSERTIONS = NO;
677 | ENABLE_STRICT_OBJC_MSGSEND = YES;
678 | "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 ";
679 | GCC_C_LANGUAGE_STANDARD = gnu99;
680 | GCC_NO_COMMON_BLOCKS = YES;
681 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
682 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
683 | GCC_WARN_UNDECLARED_SELECTOR = YES;
684 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
685 | GCC_WARN_UNUSED_FUNCTION = YES;
686 | GCC_WARN_UNUSED_VARIABLE = YES;
687 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
688 | LD_RUNPATH_SEARCH_PATHS = (
689 | /usr/lib/swift,
690 | "$(inherited)",
691 | );
692 | LIBRARY_SEARCH_PATHS = (
693 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
694 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
695 | "\"$(inherited)\"",
696 | );
697 | MTL_ENABLE_DEBUG_INFO = NO;
698 | SDKROOT = iphoneos;
699 | VALIDATE_PRODUCT = YES;
700 | };
701 | name = Release;
702 | };
703 | /* End XCBuildConfiguration section */
704 |
705 | /* Begin XCConfigurationList section */
706 | 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ReactNativeTodoTests" */ = {
707 | isa = XCConfigurationList;
708 | buildConfigurations = (
709 | 00E356F61AD99517003FC87E /* Debug */,
710 | 00E356F71AD99517003FC87E /* Release */,
711 | );
712 | defaultConfigurationIsVisible = 0;
713 | defaultConfigurationName = Release;
714 | };
715 | 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeTodo" */ = {
716 | isa = XCConfigurationList;
717 | buildConfigurations = (
718 | 13B07F941A680F5B00A75B9A /* Debug */,
719 | 13B07F951A680F5B00A75B9A /* Release */,
720 | );
721 | defaultConfigurationIsVisible = 0;
722 | defaultConfigurationName = Release;
723 | };
724 | 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNativeTodo" */ = {
725 | isa = XCConfigurationList;
726 | buildConfigurations = (
727 | 83CBBA201A601CBA00E9B192 /* Debug */,
728 | 83CBBA211A601CBA00E9B192 /* Release */,
729 | );
730 | defaultConfigurationIsVisible = 0;
731 | defaultConfigurationName = Release;
732 | };
733 | /* End XCConfigurationList section */
734 | };
735 | rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
736 | }
737 |
--------------------------------------------------------------------------------