Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show GitHub projects on new About tab, add Home screen content #53

Merged
merged 2 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ bun bin/belt.js MyApp

This generates a new Belt app in builds/MyApp, `cd`s to the directory, runs tests, and then `cd`s back. You can then run that app by `cd`ing to the directory and running `yarn ios` or the desired command.

## Common development techniques

One way to build new features is to generate a new Belt app locally using the command outlined above. You can then build the new feature in the generated app and then copy the changed files back over to Belt. Example:

```
> bun bin/belt.js MyApp
> cd builds/MyApp
# now make some changes

# now copy changes back into Belt. Go back to Belt project:
> cd ../..

# run sync script
> node bin/sync-from-app.js MyApp --dry-run

# now run without the dry-run flag:
> node bin/sync-from-app.js MyApp
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of automating this process as part of a command? Are there parts of the rsync process that would be reliant on the specifics of each new feature?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! I added a script bin/sync-from-app.js which syncs files back from the app into Belt, and I updated the CONTRIBUTING.md doc.


## Creating a pull request

Make sure the tests pass:
Expand Down
40 changes: 40 additions & 0 deletions bin/sync-from-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { execSync } from 'child_process';

// copies files over from a sample app generated with `bin/belt.js`
// See CONTRIBUTING.md for more info
function run() {
const appDir = process.argv[2];

if (!appDir || appDir.includes('builds')) {
console.error('Please provide an app directory, relative to `builds`');
console.error('Usage: node bin/sync-from-app.js MyApp --dry-run');
process.exit(1);
}

const excludes = [
'node_modules',
'.cache',
'.expo',
'.vscode',
'assets',
'.git',
'.gitignore',
];

const excludesStr = excludes.map((e) => `--exclude ${e}`).join(' ');

// provide additional flags, eg. --dry-run
const flags = `-avp ${process.argv[3] || ''}`;

const command = `rsync ${flags} ${excludesStr} builds/${appDir}/ templates/boilerplate/`;
console.log(command);
execSync(command, {
stdio: 'inherit',
});

console.log(
"\n\n🎉 Success! Ensure that all files have copied over correctly, remove any unwanted modifications (eg. app.json, package.json, etc), and manually remove any files that need to be deleted (these don't sync)",
);
}

run();
2 changes: 1 addition & 1 deletion templates/boilerplate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"msw": "^2.2.14",
"react": "18.2.0",
"react-native": "0.74.5",
"react-native-keyboard-aware-scrollview": "^2.1.0",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are adding react-native-keyboard-aware-scroll-view do we still need the existing react-native-keyboard-aware-scrollview? (scroll-view vs scrollview)

Copy link
Author

@stevehanson stevehanson Aug 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! scroll-view is actually what we want. It must have been a mistake to originally add scrollview. I removed scrollview.

"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1"
},
Expand Down
58 changes: 58 additions & 0 deletions templates/boilerplate/src/__tests__/App.integration.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { screen, userEvent } from '@testing-library/react-native';

import mock from 'src/test/mock';
import { renderApplication } from 'src/test/render';
import { GithubProjectsResponse } from 'src/util/api/api';

// Testing philosophy:
// - Tests that render the entire application with `renderApplication` go in the
// top level `src/__tests__` directory and are named with `mytest.integration.test.tsx`.
// These are ideal for when you need to test flows that include navigation between screens
// - Tests that render a single screen or component are colocated in
// `__tests__/MyComponent.test.tsx`. These call `render` and are not able to
// navigate between screens, since the Navigator is not mounted
test('renders app, can navigate between screens', async () => {
jest.useFakeTimers();

const mocks = [mockGitHubProjects()];

// load the app on the Home screen
renderApplication({ mocks });
expect(
await screen.findByRole('header', { name: /Welcome to your new app/ }),
).toBeDefined();

// go to About tab
await userEvent.press(screen.getByRole('button', { name: /About/ }));
expect(
await screen.findByRole('header', { name: 'Open Source' }),
).toBeDefined();

// expect GitHub project loaded via API
expect(await screen.findByText(/Belt is a CLI/)).toBeDefined();
});

// TODO: sample data, remove
// creates a mock for a GET request to the GitHub projects API.
// Pass this mock to `render` or `renderApplication` to register it with MSW.
// Recommended to place these mocks in a central location like `src/test/mocks`
function mockGitHubProjects() {
return mock.get<GithubProjectsResponse, null>(
'https://github-projects-api.vercel.app/api/projects',
{
response: {
projects: [
{
id: 635980144,
name: 'belt',
description:
'Belt is a CLI for starting a new React Native Expo app and will even keep your pants secure as you continue development.',
url: 'https://github.com/thoughtbot/belt',
stars: 8,
forks: 0,
},
],
},
},
);
}
31 changes: 0 additions & 31 deletions templates/boilerplate/src/__tests__/App.test.tsx

This file was deleted.

49 changes: 0 additions & 49 deletions templates/boilerplate/src/components/ExampleCoffees.tsx

This file was deleted.

107 changes: 107 additions & 0 deletions templates/boilerplate/src/components/Screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useNavigation } from '@react-navigation/native';
import { ReactNode } from 'react';
import { StyleSheet, View } from 'react-native';
import {
KeyboardAwareScrollView,
KeyboardAwareScrollViewProps,
} from 'react-native-keyboard-aware-scroll-view';
import {
SafeAreaView,
useSafeAreaInsets,
} from 'react-native-safe-area-context';

type Props = KeyboardAwareScrollViewProps & {
/**
* If true (default), horizontal padding is added to the screen content
*/
padHorizontal?: boolean;
/**
* If true, the screen will be scrollable. If false, the screen will not scroll.
* Set to false if screen includes a scrollable component like a FlatList
*/
scroll?: boolean;
/**
* If true, a safe area view is not added for the top of the screen, since it is
* handled instead by React Navigation
*/
hasHeader?: boolean;
/** A React component to render fixed to the bottom of the screen. It is not
* positioned absolutely and would show above a tab bar. If your screen does
* not have a tab bar, set fixedBottomAddSafeArea to ensure a safe area view
* is used on the bottom */
FixedBottomComponent?: ReactNode;
fixedBottomAddSafeArea?: boolean;
};

export default function Screen({
style,
padHorizontal = true,
scroll = true,
testID,
hasHeader = false,
children,
FixedBottomComponent,
fixedBottomAddSafeArea = false,
...props
}: Props) {
const navigation = useNavigation();
const insets = useSafeAreaInsets();

return (
<SafeAreaView
style={styles.wrapper}
edges={{
top: hasHeader ? 'off' : 'additive',
bottom: 'off',
}}
>
<View
testID={testID}
style={[
styles.contentContainer,
padHorizontal && styles.horizontalPadding,
style,
]}
>
{scroll ? (
<KeyboardAwareScrollView
keyboardShouldPersistTaps="handled"
contentInsetAdjustmentBehavior="automatic"
bounces
onAccessibilityEscape={() => navigation.goBack()}
testID={`${testID || 'Screen'}ScrollView`}
showsVerticalScrollIndicator={false}
{...props}
>
{children}
</KeyboardAwareScrollView>
) : (
children
)}
</View>
{!!FixedBottomComponent && (
<View
style={[
fixedBottomAddSafeArea && {
paddingBottom: insets.bottom,
},
]}
>
{FixedBottomComponent}
</View>
)}
</SafeAreaView>
);
}

const styles = StyleSheet.create({
wrapper: {
flex: 1,
},
contentContainer: {
flex: 1,
},
horizontalPadding: {
paddingHorizontal: 20,
},
});
14 changes: 14 additions & 0 deletions templates/boilerplate/src/navigators/AboutStack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import AboutScreen from 'src/screens/AboutScreen/AboutScreen';
import { AboutTabParamList } from './navigatorTypes';

const About = createNativeStackNavigator<AboutTabParamList>();

export default function AboutStack() {
return (
<About.Navigator screenOptions={{ headerShown: false }}>
<About.Screen name="About" component={AboutScreen} />
</About.Navigator>
);
}
8 changes: 6 additions & 2 deletions templates/boilerplate/src/navigators/DashboardStack.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import HomeScreen from 'src/screens/HomeScreen/HomeScreen';
import HomeScreen from '../screens/HomeScreen/HomeScreen';
import InformationScreen from '../screens/InformationScreen/InformationScreen';
import { DashboardTabParamList } from './navigatorTypes';

Expand All @@ -9,7 +9,11 @@ const Dashboard = createNativeStackNavigator<DashboardTabParamList>();
export default function DashboardStack() {
return (
<Dashboard.Navigator>
<Dashboard.Screen name="Home" component={HomeScreen} />
<Dashboard.Screen
name="Home"
component={HomeScreen}
options={{ headerShown: false }}
/>
<Dashboard.Screen name="Information" component={InformationScreen} />
</Dashboard.Navigator>
);
Expand Down
Loading
Loading