Skip to content

Commit

Permalink
Move React Query setup into main app boilerplate
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen Hanson committed May 3, 2024
1 parent fc7a332 commit 271cb7d
Show file tree
Hide file tree
Showing 15 changed files with 81 additions and 31 deletions.
6 changes: 1 addition & 5 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,7 @@ export default function runCli() {
'--no-interactive',
'Pass true to skip all prompts and use default values',
)
.action(buildAction(import('./commands/notifications')))

.command('react-query')
.description('Add React Query')
.action(buildAction(import('./commands/reactQuery')));
.action(buildAction(import('./commands/notifications')));

printWelcome();
program.parse();
Expand Down
1 change: 1 addition & 0 deletions src/commands/createApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ async function printIntro(appName: string) {
- ESLint
- Jest, React Native Testing Library
- React Navigation
- TanStack React Query
- Intuitive directory structure
`);

Expand Down
22 changes: 0 additions & 22 deletions src/commands/reactQuery.ts

This file was deleted.

14 changes: 14 additions & 0 deletions templates/boilerplate/jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import '@testing-library/jest-native/extend-expect';
import { configure } from '@testing-library/react-native';
import mockSafeAreaContext from 'react-native-safe-area-context/jest/mock';
import mockBackHandler from 'react-native/Libraries/Utilities/__mocks__/BackHandler.js';
import server from 'src/test/server';
import queryClient from 'src/util/api/queryClient';

beforeEach(() => {
jest.clearAllMocks();
Expand Down Expand Up @@ -35,6 +37,18 @@ jest.mock('@react-native-async-storage/async-storage', () =>

jest.mock('react-native-keyboard-aware-scroll-view');

// listen with MSW server. Individual tests can pass mocks to 'render' function
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterAll(() => server.close());

beforeEach(() => {
server.resetHandlers();
});

afterEach(() => {
queryClient.clear();
});

// configure debug output for RN Testing Library
// is way too verbose by default. Only include common
// props that might affect test failure.
Expand Down
4 changes: 4 additions & 0 deletions templates/boilerplate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
"@react-navigation/bottom-tabs": "^6.5.20",
"@react-navigation/native": "^6.1.10",
"@react-navigation/native-stack": "^6.9.18",
"@tanstack/react-query": "^5.32.1",
"axios": "^1.6.8",
"expo": "^50.0.17",
"expo-status-bar": "~1.11.1",
"jest": "^29.3.1",
"jest-expo": "~50.0.2",
"msw": "^2.2.14",
"react": "18.2.0",
"react-native": "0.73.6",
"react-native-keyboard-aware-scrollview": "^2.1.0",
Expand All @@ -40,6 +43,7 @@
"@thoughtbot/eslint-config": "^1.0.2",
"@types/jest": "^29.5.12",
"@types/react": "~18.2.73",
"@types/react-test-renderer": "^18.0.7",
"babel-jest": "^29.7.0",
"create-belt-app": "^0.4.0",
"eslint": "^8.56.0",
Expand Down
7 changes: 7 additions & 0 deletions templates/boilerplate/src/__tests__/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import mock from 'src/test/mock';
import { renderApplication } from 'src/test/render';

test('renders', async () => {
// We would not normally recommend fake timers, but the tests are currently
// throwing a "not wrapped in act" warning after this test finishes. One
// option is to put a `await waitForUpdates()` at the end of the test, but
// fake timers also work here until we find a better solution. The stack trace
// seems to point to React Navigation bottom tabs.
jest.useFakeTimers();

const mocks = [mockCoffees()];

renderApplication({ mocks });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import { FlatList, Image, Text, View } from 'react-native';
import api, { Coffee as CoffeeType } from 'src/util/api/api';

// TODO: sample component, remove
// TODO: sample data, remove
export default function ExampleCoffees() {
const { data } = useQuery({ queryKey: ['coffee'], queryFn: api.coffee });

Expand Down
File renamed without changes.
33 changes: 31 additions & 2 deletions templates/boilerplate/src/test/render.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
import { NavigationContainer } from '@react-navigation/native';
import { QueryClientProvider } from '@tanstack/react-query';
import {
RenderAPI,
// eslint-disable-next-line no-restricted-imports
render as TestingLibraryRender,
} from '@testing-library/react-native';
import { RequestHandler } from 'msw';
import { ReactElement } from 'react';
import Providers, { Provider } from 'src/components/Providers';
import RootNavigator from 'src/navigators/RootNavigator';
import queryClient from 'src/util/api/queryClient';
import server from './server';

export type RenderOptions = {
mocks?: Array<RequestHandler>;
};

// TODO: this will become customized as the codebase progresses, so our
// tests can be wrapped with appropriate providers, mocks can be supplied, etc
export default function render(element: ReactElement): RenderAPI {
export default function render(
element: ReactElement,
{ mocks }: RenderOptions = {},
): RenderAPI {
if (mocks) {
server.use(...mocks);
}

const providers: Provider[] = [
(children) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
),
(children) => <NavigationContainer>{children}</NavigationContainer>,
// CODEGEN:BELT:PROVIDERS - do not remove
];

return TestingLibraryRender(
<NavigationContainer>{element}</NavigationContainer>,
<Providers providers={providers}>{element}</Providers>,
);
}

export function renderApplication(options: RenderOptions = {}) {
return render(<RootNavigator />, options);
}
File renamed without changes.
5 changes: 5 additions & 0 deletions templates/boilerplate/src/test/sleep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function sleep(ms: number) {
return new Promise<void>((resolve) => {
setTimeout(resolve, ms);
});
}
12 changes: 12 additions & 0 deletions templates/boilerplate/src/test/waitForUpdates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { act } from '@testing-library/react-native';
import sleep from './sleep';

/**
* Wait a specified time, wrapped in act
* Usually, it is better to use waitFor or a findBy* matcher,
* but this is sometimes required
* @param time
*/
export default async function waitForUpdates(time = 2) {
return act(() => sleep(time));
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const api = {
export type Coffee = {
title: string;
description: string;
/** url */
/** the url to the image */
image: string;
id: number;
};
Expand Down
4 changes: 4 additions & 0 deletions templates/createApp/src/test/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export default function render(
);
}

/**
* Render the full RootNavigator. Use this function for integration tests
* that need to be able to navigate between screens.
*/
export function renderApplication(options: RenderOptions = {}) {
return render(<RootNavigator />, options);
}

0 comments on commit 271cb7d

Please sign in to comment.