From 86b3223c5eeb8631a0c96a7e7663136e9014439c Mon Sep 17 00:00:00 2001 From: Bryan Stearns Date: Tue, 11 Aug 2020 13:25:46 -0700 Subject: [PATCH] feat(generators): Finish simplifying component definitions; component & screen generator tweaks (#363) [skip-ci] * fix(test): update test for screen generator * fix(screen): sync screen generator with template * fix(warn): Fix a new-to-storybook-5.3.0 warning * feat(types): Remove remaining components' & screens' FunctionComponent typing * feat(generators): Update the component generator to generate observer-wrapped and plain components Also, simplified the generated component content to require less initial work on new components, and moved the style definitions into the component file (since our predefined components all have integrated styles) * feat(cli): Allow CamelCase or kebab-case when generating screens or components; tolerate either suffix Consistenly use kebab-case for filenames * fix(lint): `yarn format` fixes Co-authored-by: Lasha Kava Co-authored-by: Jamon Holmgren --- boilerplate/app/app.tsx.ejs | 6 +- .../components/bullet-item/bullet-item.tsx | 4 +- boilerplate/app/components/header/header.tsx | 4 +- boilerplate/app/components/switch/switch.tsx | 4 +- .../app/components/text-field/text-field.tsx | 4 +- .../screens/demo-screen/demo-screen.tsx.ejs | 4 +- .../welcome-screen/welcome-screen.tsx.ejs | 6 +- boilerplate/app/theme/typography.ts | 2 +- boilerplate/assets/fonts/custom-fonts.md | 4 +- boilerplate/storybook/storybook.tsx.ejs | 8 +-- readme.md | 7 ++- src/boilerplate.ts | 4 +- src/commands/generate/component.ts | 63 +++++++------------ src/commands/generate/model.ts | 4 +- src/commands/generate/screen.ts | 29 ++++----- templates/component.story.tsx.ejs | 7 +-- templates/component.tsx.ejs | 45 +++++++------ templates/function-component.tsx.ejs | 30 --------- templates/screen.ejs | 15 ++--- templates/styles.ts.ejs | 13 ---- test/generators-integration.test.ts | 35 +++-------- 21 files changed, 114 insertions(+), 184 deletions(-) delete mode 100644 templates/function-component.tsx.ejs delete mode 100644 templates/styles.ts.ejs diff --git a/boilerplate/app/app.tsx.ejs b/boilerplate/app/app.tsx.ejs index 26f9ede3..70b1b575 100644 --- a/boilerplate/app/app.tsx.ejs +++ b/boilerplate/app/app.tsx.ejs @@ -11,7 +11,7 @@ */ import "./i18n" import "./utils/ignore-warnings" -import React, { useState, useEffect, useRef, FunctionComponent as Component } from "react" +import React, { useState, useEffect, useRef } from "react" import { NavigationContainerRef } from "@react-navigation/native" import { SafeAreaProvider, initialWindowSafeAreaInsets } from "react-native-safe-area-context" <% if (props.useExpo) { -%> @@ -38,7 +38,7 @@ export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE" /** * This is the root component of our app. */ -const App: Component<{}> = () => { +function App() { const navigationRef = useRef() const [rootStore, setRootStore] = useState(undefined) @@ -79,4 +79,4 @@ const App: Component<{}> = () => { ) } -export default App \ No newline at end of file +export default App diff --git a/boilerplate/app/components/bullet-item/bullet-item.tsx b/boilerplate/app/components/bullet-item/bullet-item.tsx index e0d40d7f..d999e102 100644 --- a/boilerplate/app/components/bullet-item/bullet-item.tsx +++ b/boilerplate/app/components/bullet-item/bullet-item.tsx @@ -1,7 +1,7 @@ import * as React from "react" import { View, ViewStyle, ImageStyle, TextStyle } from "react-native" -import {Text} from "../text/text" -import {Icon} from "../icon/icon" +import { Text } from "../text/text" +import { Icon } from "../icon/icon" import { spacing, typography } from "../../theme" const BULLET_ITEM: ViewStyle = { diff --git a/boilerplate/app/components/header/header.tsx b/boilerplate/app/components/header/header.tsx index 1936d84b..2dbbaf3c 100644 --- a/boilerplate/app/components/header/header.tsx +++ b/boilerplate/app/components/header/header.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent as Component } from "react" +import React from "react" import { View, ViewStyle, TextStyle } from "react-native" import { HeaderProps } from "./header.props" import { Button } from "../button/button" @@ -24,7 +24,7 @@ const RIGHT: ViewStyle = { width: 32 } /** * Header that appears on many screens. Will hold navigation buttons and screen title. */ -export const Header: Component = props => { +export function Header(props: HeaderProps) { const { onLeftPress, onRightPress, diff --git a/boilerplate/app/components/switch/switch.tsx b/boilerplate/app/components/switch/switch.tsx index e3786e22..9d17d987 100644 --- a/boilerplate/app/components/switch/switch.tsx +++ b/boilerplate/app/components/switch/switch.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent as Component } from "react" +import React from "react" import { ViewStyle, Animated, Easing, TouchableWithoutFeedback } from "react-native" import { color } from "../../theme" import { SwitchProps } from "./switch.props" @@ -52,7 +52,7 @@ const enhance = (style, newStyles): any => { const makeAnimatedValue = switchOn => new Animated.Value(switchOn ? 1 : 0) -export const Switch: Component = props => { +export function Switch(props: SwitchProps) { const [timer] = React.useState(makeAnimatedValue(props.value)) const startAnimation = React.useMemo( () => (newValue: boolean) => { diff --git a/boilerplate/app/components/text-field/text-field.tsx b/boilerplate/app/components/text-field/text-field.tsx index 69414d27..567d81cc 100644 --- a/boilerplate/app/components/text-field/text-field.tsx +++ b/boilerplate/app/components/text-field/text-field.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent as Component } from "react" +import React from "react" import { View, TextInput, TextStyle, ViewStyle } from "react-native" import { color, spacing, typography } from "../../theme" import { translate } from "../../i18n" @@ -32,7 +32,7 @@ const enhance = (style, styleOverride) => { /** * A component which has a label and an input together. */ -export const TextField: Component = props => { +export function TextField(props: TextFieldProps) { const { placeholderTx, placeholder, diff --git a/boilerplate/app/screens/demo-screen/demo-screen.tsx.ejs b/boilerplate/app/screens/demo-screen/demo-screen.tsx.ejs index cc2ee4cc..1ae84e73 100644 --- a/boilerplate/app/screens/demo-screen/demo-screen.tsx.ejs +++ b/boilerplate/app/screens/demo-screen/demo-screen.tsx.ejs @@ -1,4 +1,4 @@ -import React, { FunctionComponent as Component } from "react" +import React from "react" import { Image, ImageStyle, Platform, TextStyle, View, ViewStyle } from "react-native" import { useNavigation } from "@react-navigation/native" import { observer } from "mobx-react-lite" @@ -77,7 +77,7 @@ const HINT: TextStyle = { marginVertical: spacing[2], } -export const DemoScreen: Component = observer(function DemoScreen() { +export const DemoScreen = observer(function DemoScreen() { const navigation = useNavigation() const goBack = () => navigation.goBack() diff --git a/boilerplate/app/screens/welcome-screen/welcome-screen.tsx.ejs b/boilerplate/app/screens/welcome-screen/welcome-screen.tsx.ejs index f167aceb..33dedd55 100644 --- a/boilerplate/app/screens/welcome-screen/welcome-screen.tsx.ejs +++ b/boilerplate/app/screens/welcome-screen/welcome-screen.tsx.ejs @@ -1,4 +1,4 @@ -import React, { FunctionComponent as Component } from "react" +import React from "react" import { View, Image, ViewStyle, TextStyle, ImageStyle, SafeAreaView } from "react-native" import { useNavigation } from "@react-navigation/native" import { observer } from "mobx-react-lite" @@ -75,7 +75,7 @@ const FOOTER_CONTENT: ViewStyle = { paddingHorizontal: spacing[4], } -export const WelcomeScreen: Component = observer(function WelcomeScreen() { +export const WelcomeScreen = observer(function WelcomeScreen() { const navigation = useNavigation() const nextScreen = () => navigation.navigate("demo") @@ -119,4 +119,4 @@ export const WelcomeScreen: Component = observer(function WelcomeScreen() { ) -}) \ No newline at end of file +}) diff --git a/boilerplate/app/theme/typography.ts b/boilerplate/app/theme/typography.ts index a9cf5c5e..5f7de85f 100644 --- a/boilerplate/app/theme/typography.ts +++ b/boilerplate/app/theme/typography.ts @@ -24,7 +24,7 @@ export const typography = { */ secondary: Platform.select({ ios: "Arial", android: "sans-serif" }), - /** + /** * Lets get fancy with a monospace font! */ code: Platform.select({ ios: "Courier", android: "monospace" }), diff --git a/boilerplate/assets/fonts/custom-fonts.md b/boilerplate/assets/fonts/custom-fonts.md index 926f779d..f653f38a 100644 --- a/boilerplate/assets/fonts/custom-fonts.md +++ b/boilerplate/assets/fonts/custom-fonts.md @@ -9,9 +9,9 @@ ignote-bowser integrated `expo-fonts` according to [this guide](https://docs.exp ## For Non-Expo Projects 1. Find a font in `TTF` or `OTF` format that you like and put it in `./assets/fonts/`. I picked [this one](https://fonts.google.com/specimen/Shadows+Into+Light). -2. Now run `npx react-native link`. Even though we're past react native 0.60 we can still use this command to link the fonts. This is a one-way operation. You'll have to remove fonts yourself later if you want to remove them from your app. This will add items to your xcode project and `Info.plist` file as well as putting some files in place for android to bundle (specifically, it will move the font files to `./android/app/src/main/assets/). +2. Now run `npx react-native link`. Even though we're past react native 0.60 we can still use this command to link the fonts. This is a one-way operation. You'll have to remove fonts yourself later if you want to remove them from your app. This will add items to your xcode project and `Info.plist` file as well as putting some files in place for android to bundle (specifically, it will move the font files to `./android/app/src/main/assets/). 3. Both iOS and Android changes must be committed to source control, but sometimes other common libraries like `react-native-vector-icons` may mess with this process and add some fonts you don't intend to ship. Make sure to review these changes carefully before pushing any changes to source control. -4. The tricky part is next: knowing what font family to use. iOS uses the family name like `{fontFamily: 'ShadowsIntoLight'}` whereas on android, you must include all the different variations of the font you will use and reference them by their *filename*. So I downloaded `ShadowsIntoLight-Regular.ttf` that means android must use `{fontFamily: 'ShadowsIntoLight-Regular'}` (yes, it's case sensitive 🙄). [There are components to help you deal with this](https://github.com/lendup/react-native-cross-platform-text) or you can roll your own based on your needs and something like: +4. The tricky part is next: knowing what font family to use. iOS uses the family name like `{fontFamily: 'ShadowsIntoLight'}` whereas on android, you must include all the different variations of the font you will use and reference them by their _filename_. So I downloaded `ShadowsIntoLight-Regular.ttf` that means android must use `{fontFamily: 'ShadowsIntoLight-Regular'}` (yes, it's case sensitive 🙄). [There are components to help you deal with this](https://github.com/lendup/react-native-cross-platform-text) or you can roll your own based on your needs and something like: ``` const CUSTOM_FONT_TEXT: TextStyle = { diff --git a/boilerplate/storybook/storybook.tsx.ejs b/boilerplate/storybook/storybook.tsx.ejs index 4847be62..c6b35be9 100644 --- a/boilerplate/storybook/storybook.tsx.ejs +++ b/boilerplate/storybook/storybook.tsx.ejs @@ -1,4 +1,4 @@ -import React, { useEffect, FunctionComponent } from "react" +import React, { useEffect } from "react" import { getStorybookUI, configure } from "@storybook/react-native" <% if (props.useExpo) { -%> import { initFonts } from "../app/theme/fonts" @@ -16,12 +16,12 @@ const StorybookUI = getStorybookUI({ onDeviceUI: true, <% if (props.useExpo) { -%> asyncStorage: require("react-native").AsyncStorage, -<% } else { -%> - asyncStorage: require("@react-native-community/async-storage").default +<% } else { -%> + asyncStorage: require("@react-native-community/async-storage").default || null, <% } -%> }) -export const StorybookUIRoot: FunctionComponent = () => { +export function StorybookUIRoot() { useEffect(() => { (async () => { <% if (props.useExpo) { -%> diff --git a/readme.md b/readme.md index 07a39feb..c8d1e791 100644 --- a/readme.md +++ b/readme.md @@ -68,8 +68,11 @@ Will give you information of what generators are present. This is the generator you will be using most often. There are 2 flavors: -- React.FunctionComponent (i.e. "hooks enabled component") -- Stateless function (i.e. the "classic ignite-bowser component") +- Wrapped with mobx-react-lite's `observer` function - you need this if you + pass any mobx-state-tree objects as props to the component, and the component + will dereference properties of those objects. +- Plain, not wrapped with `observer`. If you're only passing plain values or + non-MST objects, this is fine. ``` ignite generate component awesome-component diff --git a/src/boilerplate.ts b/src/boilerplate.ts index b6b0b9a1..68bf0cb9 100644 --- a/src/boilerplate.ts +++ b/src/boilerplate.ts @@ -59,9 +59,7 @@ export const install = async (toolbox: IgniteToolbox) => { ) const name = parameters.first - const spinner = print - .spin(`using the ${red("Infinite Red")} Bowser boilerplate`) - .succeed() + const spinner = print.spin(`using the ${red("Infinite Red")} Bowser boilerplate`).succeed() let useExpo = parameters.options.expo const askAboutExpo = useExpo === undefined diff --git a/src/commands/generate/component.ts b/src/commands/generate/component.ts index 0a986739..2c1be3d5 100644 --- a/src/commands/generate/component.ts +++ b/src/commands/generate/component.ts @@ -1,10 +1,10 @@ import { GluegunToolbox } from "gluegun" -export const description = "Generates a component, supporting files, and a storybook test." +export const description = "Generates a component and a storybook test." export const run = async function(toolbox: GluegunToolbox) { // grab some features const { parameters, strings, print, ignite, patching, filesystem, prompt } = toolbox - const { pascalCase, camelCase, isBlank } = strings + const { camelCase, isBlank, kebabCase, pascalCase } = strings // validation if (isBlank(parameters.first)) { @@ -13,63 +13,44 @@ export const run = async function(toolbox: GluegunToolbox) { return } - let componentType - if (!parameters.options["function-component"] && !parameters.options["stateless-function"]) { - const componentTypes = [ - { - name: "functionComponent", - message: 'React.FunctionComponent, aka "hooks component" (recommended)', - }, - { - name: "statelessFunction", - message: 'Stateless function, aka the "classic" ignite-bowser component', - }, - ] + let observer = parameters.options.observer + if (parameters.options.observer === undefined) { + observer = await prompt.confirm( + `Should this component be _observed_ by Mobx?\n${print.colors.gray(` - const { component } = await prompt.ask([ - { - name: "component", - message: "Which type of component do you want to generate?", - type: "select", - choices: componentTypes, - }, - ]) - componentType = component + If you'll be passing any mobx-state-tree objects in this component's props + and dereferencing them within this component, you'll want the component wrapped + in \`observer\` so that the component rerenders when properties of the object change. + + If all props will be simple values or objects that don't come from a mobx store, + you don't need the component to be wrapped in \`observer\`. + + `)}`, + ) } const name = parameters.first const pascalName = pascalCase(name) const camelCaseName = camelCase(name) - const props = { name, pascalName, camelCaseName } + const kebabCaseName = kebabCase(name) + const props = { camelCaseName, kebabCaseName, name, observer, pascalName } const jobs = [ { template: "component.story.tsx.ejs", - target: `app/components/${name}/${name}.story.tsx`, + target: `app/components/${kebabCaseName}/${kebabCaseName}.story.tsx`, }, { - template: "styles.ts.ejs", - target: `app/components/${name}/${name}.styles.ts`, + template: "component.tsx.ejs", + target: `app/components/${kebabCaseName}/${kebabCaseName}.tsx`, }, ] - if (componentType === "functionComponent" || parameters.options["function-component"]) { - jobs.push({ - template: "function-component.tsx.ejs", - target: `app/components/${name}/${name}.tsx`, - }) - } else if (componentType === "statelessFunction" || parameters.options["stateless-function"]) { - jobs.push({ - template: "component.tsx.ejs", - target: `app/components/${name}/${name}.tsx`, - }) - } - await ignite.copyBatch(toolbox, jobs, props) // patch the barrel export file const barrelExportPath = `${process.cwd()}/app/components/index.ts` - const exportToAdd = `export * from "./${name}/${name}"\n` + const exportToAdd = `export * from "./${kebabCaseName}/${kebabCaseName}"\n` if (!filesystem.exists(barrelExportPath)) { const msg = @@ -83,6 +64,6 @@ export const run = async function(toolbox: GluegunToolbox) { // wire up example await patching.prepend( "./storybook/storybook-registry.ts", - `require("../app/components/${name}/${name}.story")\n`, + `require("../app/components/${kebabCaseName}/${kebabCaseName}.story")\n`, ) } diff --git a/src/commands/generate/model.ts b/src/commands/generate/model.ts index a5281cd8..645ad0a2 100644 --- a/src/commands/generate/model.ts +++ b/src/commands/generate/model.ts @@ -37,9 +37,7 @@ export const run = async function(toolbox: GluegunToolbox) { const exportToAdd = `export * from "./${name}/${name}"\n` if (!filesystem.exists(barrelExportPath)) { - const msg = - `No '${barrelExportPath}' file found. Can't export model.` + - `Export your new model manually.` + const msg = `No '${barrelExportPath}' file found. Can't export model. Export your new model manually.` print.warning(msg) process.exit(1) } diff --git a/src/commands/generate/screen.ts b/src/commands/generate/screen.ts index aabf79d9..72e9e22d 100644 --- a/src/commands/generate/screen.ts +++ b/src/commands/generate/screen.ts @@ -4,7 +4,7 @@ export const description = "Generates a React Native screen." export const run = async function(toolbox: GluegunToolbox) { // grab some features const { parameters, print, strings, ignite, filesystem, patching } = toolbox - const { pascalCase, isBlank, camelCase } = strings + const { camelCase, isBlank, kebabCase, pascalCase } = strings // validation if (isBlank(parameters.first)) { @@ -13,24 +13,25 @@ export const run = async function(toolbox: GluegunToolbox) { return } - const name = parameters.first - const screenName = name.endsWith("-screen") ? name : `${name}-screen` - - // prettier-ignore - if (name.endsWith('-screen')) { - print.info(`Note: For future reference, the \`-screen\` suffix is automatically added for you.`) + let name = parameters.first + const matches = name.match(/(.*)((-s|S)creen)$/) + if (matches) { + name = matches[1] // grab the name without the suffix + // prettier-ignore + print.info(`Note: For future reference, the \`${matches[2]}\` suffix is automatically added for you.`) print.info(`You're welcome to add it manually, but we wanted you to know you don't have to. :)`) } - // get permutations of the given model name - const pascalName = pascalCase(screenName) - const camelName = camelCase(screenName) + // get permutations of the given name, suffixed + const pascalName = pascalCase(name) + "Screen" + const camelName = camelCase(name) + "Screen" + const kebabName = kebabCase(name) + "-screen" - const props = { name: screenName, pascalName, camelName } + const props = { pascalName, camelName } const jobs = [ { template: `screen.ejs`, - target: `app/screens/${screenName}.tsx`, + target: `app/screens/${kebabName}/${kebabName}.tsx`, }, ] @@ -39,7 +40,7 @@ export const run = async function(toolbox: GluegunToolbox) { // patch the barrel export file const barrelExportPath = `${process.cwd()}/app/screens/index.ts` - const exportToAdd = `export * from "./${screenName}"\n` + const exportToAdd = `export * from "./${kebabName}/${kebabName}"\n` if (!filesystem.exists(barrelExportPath)) { const msg = @@ -50,5 +51,5 @@ export const run = async function(toolbox: GluegunToolbox) { } await patching.append(barrelExportPath, exportToAdd) - print.info(`Screen ${screenName} created`) + print.info(`Screen ${pascalName} created`) } diff --git a/templates/component.story.tsx.ejs b/templates/component.story.tsx.ejs index 97736ffe..1182a430 100644 --- a/templates/component.story.tsx.ejs +++ b/templates/component.story.tsx.ejs @@ -1,16 +1,15 @@ import * as React from "react" import { storiesOf } from "@storybook/react-native" import { StoryScreen, Story, UseCase } from "../../../storybook/views" -import { <%= props.pascalName %> } from "./<%= props.name %>" - -declare var module +import { color } from "../../theme" +import { <%= props.pascalName %> } from "./<%= props.kebabCaseName %>" storiesOf("<%= props.pascalName %>", module) .addDecorator(fn => {fn()}) .add("Style Presets", () => ( - <<%= props.pascalName %> text="<%= props.pascalName %>" /> + <<%= props.pascalName %> style={{ backgroundColor: color.error }} /> )) diff --git a/templates/component.tsx.ejs b/templates/component.tsx.ejs index 1e8c5ca6..bc26d42f 100644 --- a/templates/component.tsx.ejs +++ b/templates/component.tsx.ejs @@ -1,19 +1,22 @@ import * as React from "react" -import { View, ViewStyle } from "react-native" +import { TextStyle, View, ViewStyle } from "react-native" +<% if (props.observer) { -%> +import { observer } from "mobx-react-lite" +<% } -%> +import { color, typography } from "../../theme" import { Text } from "../" -import { <%= props.camelCaseName %>Styles as styles } from "./<%= props.name %>.styles" -export interface <%= props.pascalName %>Props { - /** - * Text which is looked up via i18n. - */ - tx?: string +const CONTAINER: ViewStyle = { + justifyContent: "center", +} - /** - * The text to display if not using `tx` or nested components. - */ - text?: string +const TEXT: TextStyle = { + fontFamily: typography.primary, + fontSize: 14, + color: color.primary, +} +export interface <%= props.pascalName %>Props { /** * An optional style override useful for padding & margin. */ @@ -21,18 +24,22 @@ export interface <%= props.pascalName %>Props { } /** - * Stateless functional component for your needs - * - * Component description here for TypeScript tips. + * Describe your component here */ +<% if (props.observer) { -%> +export const <%= props.pascalName %> = observer(function <%= props.pascalName %>(props: <%= props.pascalName %>Props) { +<% } else { -%> export function <%= props.pascalName %>(props: <%= props.pascalName %>Props) { - // grab the props - const { tx, text, style, ...rest } = props - const textStyle = { } +<% } -%> + const { style } = props return ( - - + + Hello ) +<% if (props.observer) { -%> +}) +<% } else { -%> } +<% } -%> diff --git a/templates/function-component.tsx.ejs b/templates/function-component.tsx.ejs deleted file mode 100644 index 827b3571..00000000 --- a/templates/function-component.tsx.ejs +++ /dev/null @@ -1,30 +0,0 @@ -import React, { FunctionComponent as Component } from "react" -import { View } from "react-native" -import { Text } from "../" -// import { observer } from "mobx-react-lite" -// import { useStores } from "../../models" -import { <%= props.camelCaseName %>Styles as styles } from "./<%= props.name %>.styles" - -export interface <%= props.pascalName %>Props {} - -/** - * This is a React functional component, ready to - * - * Component description here for TypeScript tips. - */ -export const <%= props.pascalName %>: Component<<%= props.pascalName %>Props> = props => { - // Note: if you want your componeobservernt to refresh when data is updated in the store, - // wrap this component in `` like so: - // `export const <%= props.pascalName %> = observer(function <%= props.pascalName %> { ... })` - - // Enable this line to retrieve data from the rootStore (or other store) - // const rootStore = useStores() - // or - // const { otherStore, userStore } = useStores() - - return useObserver(() => ( - - Hi Func - - )) -} diff --git a/templates/screen.ejs b/templates/screen.ejs index 5dcb67e2..9a469633 100644 --- a/templates/screen.ejs +++ b/templates/screen.ejs @@ -1,26 +1,27 @@ -import React, { FunctionComponent as Component } from "react" +import React from "react" import { observer } from "mobx-react-lite" import { ViewStyle } from "react-native" -import { Screen, Text } from "../components" +import { Screen, Text } from "../../components" // import { useNavigation } from "@react-navigation/native" -// import { useStores } from "../models" -import { color } from "../theme" +// import { useStores } from "../../models" +import { color } from "../../theme" const ROOT: ViewStyle = { backgroundColor: color.palette.black, + flex: 1, } -export const <%= props.pascalName %>: Component = observer(function <%= props.pascalName %>() { +export const <%= props.pascalName %> = observer(function <%= props.pascalName %>() { // Pull in one of our MST stores // const { someStore, anotherStore } = useStores() // OR // const rootStore = useStores() - + // Pull in navigation via hook // const navigation = useNavigation() return ( - + ) }) diff --git a/templates/styles.ts.ejs b/templates/styles.ts.ejs deleted file mode 100644 index ad082db8..00000000 --- a/templates/styles.ts.ejs +++ /dev/null @@ -1,13 +0,0 @@ -import { ViewStyle, TextStyle } from "react-native" -import { color, typography } from "../../theme" - -export const <%= props.camelCaseName %>Styles = { - WRAPPER: { - justifyContent: 'center', - } as ViewStyle, - TEXT: { - fontFamily: typography.primary, - fontSize: 14, - color: color.primary - } as TextStyle -} diff --git a/test/generators-integration.test.ts b/test/generators-integration.test.ts index 68e47415..da3aa5d4 100644 --- a/test/generators-integration.test.ts +++ b/test/generators-integration.test.ts @@ -57,46 +57,31 @@ describe("a generated app", () => { ) }) - test("generates a stateless function", async () => { - const statelessFunction = "Stateless" - await execaShell(`${IGNITE_COMMAND} g component ${statelessFunction} --stateless-function`, { + test("generates an observed component", async () => { + await execaShell(`${IGNITE_COMMAND} g component observed-thing --observer`, { preferLocal: false, }) - expect(jetpack.exists(`app/components/${statelessFunction}/${statelessFunction}.tsx`)).toBe( - "file", - ) - expect( - jetpack.exists(`app/components/${statelessFunction}/${statelessFunction}.story.tsx`), - ).toBe("file") - expect( - jetpack.exists(`app/components/${statelessFunction}/${statelessFunction}.styles.ts`), - ).toBe("file") + expect(jetpack.exists(`app/components/observed-thing/observed-thing.tsx`)).toBe("file") + expect(jetpack.exists(`app/components/observed-thing/observed-thing.story.tsx`)).toBe("file") const lint = await execa("npm", ["-s", "run", "lint"]) expect(lint.stderr).toBe("") }) - test("generates a function component", async () => { - const functionComponent = "FunctionComponent" - await execaShell(`${IGNITE_COMMAND} g component ${functionComponent} --function-component`, { + test("generates a plain component", async () => { + await execaShell(`${IGNITE_COMMAND} g component UnobservedThing --no-observer`, { preferLocal: false, }) - expect(jetpack.exists(`app/components/${functionComponent}/${functionComponent}.tsx`)).toBe( + expect(jetpack.exists(`app/components/unobserved-thing/unobserved-thing.tsx`)).toBe("file") + expect(jetpack.exists(`app/components/unobserved-thing/unobserved-thing.story.tsx`)).toBe( "file", ) - expect( - jetpack.exists(`app/components/${functionComponent}/${functionComponent}.story.tsx`), - ).toBe("file") - expect( - jetpack.exists(`app/components/${functionComponent}/${functionComponent}.styles.ts`), - ).toBe("file") const lint = await execa("npm", ["-s", "run", "lint"]) expect(lint.stderr).toBe("") }) test("generates a screen", async () => { - const simpleScreen = "test" - await execaShell(`${IGNITE_COMMAND} g screen ${simpleScreen}`, { preferLocal: false }) - expect(jetpack.exists(`app/screens/${simpleScreen}-screen.tsx`)).toBe("file") + await execaShell(`${IGNITE_COMMAND} g screen TestScreen`, { preferLocal: false }) + expect(jetpack.exists(`app/screens/test-screen/test-screen.tsx`)).toBe("file") const lint = await execa("npm", ["-s", "run", "lint"]) expect(lint.stderr).toBe("") })