Repository: scalable-react/scalable-react-typescript-boilerplate Branch: master Commit: 9c6eb0bb74a1 Files: 414 Total size: 346.7 KB Directory structure: gitextract_ld6wikvo/ ├── .babelrc ├── .eslintignore ├── .github/ │ ├── CHANGELOG.md │ └── CONTRIBUTING.md ├── .gitignore ├── .nvmrc ├── .travis.yml ├── LICENSE ├── Procfile ├── README.md ├── config/ │ ├── .storybook/ │ │ ├── addons.js │ │ ├── config.js │ │ ├── stories/ │ │ │ └── index.js │ │ └── webpack.config.js │ ├── generators/ │ │ ├── cli.js │ │ ├── component/ │ │ │ ├── es6class.tsx.hbs │ │ │ ├── export.ts.hbs │ │ │ ├── import.ts.hbs │ │ │ ├── index.js │ │ │ ├── stateless.tsx.hbs │ │ │ ├── styles.ts.hbs │ │ │ └── types.ts.hbs │ │ ├── container/ │ │ │ ├── actionCreators.js.hbs │ │ │ ├── actions.js.hbs │ │ │ ├── constants.js.hbs │ │ │ ├── export.js.hbs │ │ │ ├── import.js.hbs │ │ │ ├── index.js │ │ │ ├── index.js.hbs │ │ │ ├── presentation.js.hbs │ │ │ ├── reducer.export-state.js.hbs │ │ │ ├── reducer.export.js.hbs │ │ │ ├── reducer.import.js.hbs │ │ │ ├── reducer.js.hbs │ │ │ ├── selectors.js.hbs │ │ │ ├── state-type.js.hbs │ │ │ ├── state.import.js.hbs │ │ │ ├── state.js.hbs │ │ │ ├── styles.js.hbs │ │ │ ├── types.export.ts.hbs │ │ │ ├── types.import.ts.hbs │ │ │ └── types.ts.hbs │ │ └── utils/ │ │ ├── index.js │ │ └── safeString.js │ ├── ignoreAssets.js │ ├── scripts/ │ │ └── clean.js │ ├── testing/ │ │ ├── __mocks__/ │ │ │ ├── fileMock.js │ │ │ └── styleMock.js │ │ ├── preprocessor.js │ │ └── templates/ │ │ └── _index.prod.html │ └── types/ │ └── require.d.ts ├── devServer.js ├── index.html ├── lerna.json ├── netlify.toml ├── package.json ├── packages/ │ ├── docs/ │ │ ├── .babelrc │ │ ├── config/ │ │ │ ├── generators/ │ │ │ │ ├── cli.js │ │ │ │ ├── component/ │ │ │ │ │ ├── es6class.tsx.hbs │ │ │ │ │ ├── export.ts.hbs │ │ │ │ │ ├── import.ts.hbs │ │ │ │ │ ├── index.js │ │ │ │ │ ├── stateless.tsx.hbs │ │ │ │ │ ├── styles.ts.hbs │ │ │ │ │ └── types.ts.hbs │ │ │ │ ├── container/ │ │ │ │ │ ├── actionCreators.js.hbs │ │ │ │ │ ├── actions.js.hbs │ │ │ │ │ ├── constants.js.hbs │ │ │ │ │ ├── export.js.hbs │ │ │ │ │ ├── import.js.hbs │ │ │ │ │ ├── index.js │ │ │ │ │ ├── index.js.hbs │ │ │ │ │ ├── presentation.js.hbs │ │ │ │ │ ├── reducer.export-state.js.hbs │ │ │ │ │ ├── reducer.export.js.hbs │ │ │ │ │ ├── reducer.import.js.hbs │ │ │ │ │ ├── reducer.js.hbs │ │ │ │ │ ├── selectors.js.hbs │ │ │ │ │ ├── state-type.js.hbs │ │ │ │ │ ├── state.import.js.hbs │ │ │ │ │ ├── state.js.hbs │ │ │ │ │ ├── styles.js.hbs │ │ │ │ │ ├── types.export.ts.hbs │ │ │ │ │ ├── types.import.ts.hbs │ │ │ │ │ └── types.ts.hbs │ │ │ │ └── utils/ │ │ │ │ ├── index.js │ │ │ │ └── safeString.js │ │ │ ├── ignoreAssets.js │ │ │ └── types/ │ │ │ └── require.d.ts │ │ ├── devServer.js │ │ ├── index.html │ │ ├── package.json │ │ ├── server.js │ │ ├── src/ │ │ │ ├── client/ │ │ │ │ ├── apolloClient.ts │ │ │ │ ├── components/ │ │ │ │ │ ├── AddComment/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ ├── __mocks__/ │ │ │ │ │ │ │ │ └── addCommentMocks.mock.ts │ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ │ │ └── index.test.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── Comment/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ ├── __mocks__/ │ │ │ │ │ │ │ │ └── commentMocks.mock.ts │ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ │ │ └── index.test.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── Html/ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── NavBar/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ ├── __mocks__/ │ │ │ │ │ │ │ │ └── navBarMocks.mock.ts │ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ │ │ └── index.test.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── Pic/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── Post/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── PostCard/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── shortenText.ts │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── remStringFromPx.ts │ │ │ │ ├── containers/ │ │ │ │ │ ├── About/ │ │ │ │ │ │ ├── about.ts │ │ │ │ │ │ ├── contributors.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styles.ts │ │ │ │ │ ├── App/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ │ │ ├── actionCreators.test.ts │ │ │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ │ │ ├── reducer.test.ts │ │ │ │ │ │ │ └── selectors.test.ts │ │ │ │ │ │ ├── actionCreators.ts │ │ │ │ │ │ ├── actions.ts │ │ │ │ │ │ ├── constants.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── presentation.tsx │ │ │ │ │ │ ├── reducer.ts │ │ │ │ │ │ ├── selectors.ts │ │ │ │ │ │ ├── state.ts │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── Blog/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── posts.graphql.ts │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── BlogPost/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ ├── __mocks__/ │ │ │ │ │ │ │ │ ├── blogPostPresentation.mock.ts │ │ │ │ │ │ │ │ └── blogPostSelectors.mock.ts │ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ │ │ ├── actionCreators.test.ts │ │ │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ │ │ ├── reducer.test.ts │ │ │ │ │ │ │ └── selectors.test.ts │ │ │ │ │ │ ├── actionCreators.ts │ │ │ │ │ │ ├── actions.ts │ │ │ │ │ │ ├── apollo.ts │ │ │ │ │ │ ├── commentMutation.graphql.ts │ │ │ │ │ │ ├── constants.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── postQuery.graphql.ts │ │ │ │ │ │ ├── presentation.tsx │ │ │ │ │ │ ├── reducer.ts │ │ │ │ │ │ ├── selectors.ts │ │ │ │ │ │ ├── state.ts │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── Docs/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ ├── __mocks__/ │ │ │ │ │ │ │ │ └── docsMocks.mock.ts │ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ │ │ ├── actionCreators.test.ts │ │ │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ │ │ ├── logic.test.ts │ │ │ │ │ │ │ ├── reducer.test.ts │ │ │ │ │ │ │ └── selectors.test.ts │ │ │ │ │ │ ├── actionCreators.ts │ │ │ │ │ │ ├── actions.ts │ │ │ │ │ │ ├── constants.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── logic.ts │ │ │ │ │ │ ├── presentation.tsx │ │ │ │ │ │ ├── reducer.ts │ │ │ │ │ │ ├── selectors.ts │ │ │ │ │ │ ├── state.ts │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── Features/ │ │ │ │ │ │ ├── features.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── Home/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── presentation.tsx │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── TodoApp/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ ├── __mocks__/ │ │ │ │ │ │ │ │ ├── presentation.mock.ts │ │ │ │ │ │ │ │ └── selectors.mock.ts │ │ │ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ │ │ ├── actionCreators.test.ts │ │ │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ │ │ ├── reducer.test.ts │ │ │ │ │ │ │ └── selectors.test.ts │ │ │ │ │ │ ├── actionCreators.ts │ │ │ │ │ │ ├── actions.ts │ │ │ │ │ │ ├── constants.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── presentation.tsx │ │ │ │ │ │ ├── reducer.ts │ │ │ │ │ │ ├── selectors.ts │ │ │ │ │ │ ├── state.ts │ │ │ │ │ │ ├── styles.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── logic.ts │ │ │ │ ├── reducers.ts │ │ │ │ ├── routes.tsx │ │ │ │ ├── shared/ │ │ │ │ │ ├── actionCreators.ts │ │ │ │ │ ├── actions.ts │ │ │ │ │ └── constants.ts │ │ │ │ ├── state.ts │ │ │ │ ├── store.tsx │ │ │ │ ├── test/ │ │ │ │ │ └── mockstore.ts │ │ │ │ ├── theming/ │ │ │ │ │ ├── colorMap.ts │ │ │ │ │ ├── globalCss.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ └── types.ts │ │ │ └── server/ │ │ │ ├── db/ │ │ │ │ ├── index.ts │ │ │ │ ├── models/ │ │ │ │ │ ├── comment.ts │ │ │ │ │ └── post.ts │ │ │ │ └── utils/ │ │ │ │ └── uuid.ts │ │ │ ├── graph/ │ │ │ │ ├── index.ts │ │ │ │ ├── mutations/ │ │ │ │ │ ├── comment/ │ │ │ │ │ │ ├── createComment.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── queries/ │ │ │ │ │ ├── comment/ │ │ │ │ │ │ ├── comment.ts │ │ │ │ │ │ ├── comments.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── post/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── post.ts │ │ │ │ │ └── posts.ts │ │ │ │ ├── schema.json │ │ │ │ └── types/ │ │ │ │ ├── comment/ │ │ │ │ │ ├── comment.ts │ │ │ │ │ └── commentInput.ts │ │ │ │ ├── index.ts │ │ │ │ └── post/ │ │ │ │ ├── post.ts │ │ │ │ └── postInput.ts │ │ │ ├── graphqlEntry.ts │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ ├── webpack.config.js │ │ ├── webpack.config.prod.js │ │ └── webpack.config.server.js │ └── openui/ │ ├── package.json │ ├── src/ │ │ ├── Anchor/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── anchorMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Article/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── articleMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Avatar/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── avatarMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── default.ts │ │ │ ├── index.tsx │ │ │ ├── maps.ts │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Box/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── boxMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── maps.ts │ │ │ ├── styleUtils.ts │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Button/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── buttonMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── maps.ts │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Footer/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── footerMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Header/ │ │ │ ├── header.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── Heading/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── headingProps.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styleUtils.ts │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Headline/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── headlineProps.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styleUtils.ts │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Hero/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── heroMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Image/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── imageMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── LoadingIndicator/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── loadingIndicatorMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Markdown/ │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Notification/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── notificationMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Paragraph/ │ │ │ ├── index.tsx │ │ │ ├── styleUtils.ts │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Section/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── sectionMocks.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── SvgIcon/ │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ ├── Toast/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── toast.mock.ts │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── WithAnimation/ │ │ │ ├── animation.ts │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── theming/ │ │ │ ├── colorMap.ts │ │ │ ├── globalCss.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── utils/ │ │ ├── index.ts │ │ └── remStringFromPx.ts │ └── tsconfig.json ├── server.js ├── src/ │ ├── client/ │ │ ├── apolloClient.ts │ │ ├── components/ │ │ │ ├── Html/ │ │ │ │ └── index.tsx │ │ │ └── index.ts │ │ ├── features/ │ │ │ ├── Landing/ │ │ │ │ ├── index.tsx │ │ │ │ ├── presentation.tsx │ │ │ │ ├── styles.ts │ │ │ │ └── types.ts │ │ │ ├── Layout/ │ │ │ │ ├── index.tsx │ │ │ │ ├── main.ts │ │ │ │ ├── presentation.tsx │ │ │ │ ├── styles.ts │ │ │ │ └── types.ts │ │ │ └── index.ts │ │ ├── index.tsx │ │ ├── logic.ts │ │ ├── reducers.ts │ │ ├── routes.tsx │ │ ├── shared/ │ │ │ ├── actionCreators.ts │ │ │ ├── actions.ts │ │ │ └── constants.ts │ │ ├── state.ts │ │ ├── store.tsx │ │ ├── test/ │ │ │ └── mockstore.ts │ │ ├── theming/ │ │ │ ├── colorMap.ts │ │ │ ├── globalCss.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── types.ts │ └── server/ │ ├── db/ │ │ ├── index.ts │ │ ├── models/ │ │ │ └── post.ts │ │ └── utils/ │ │ └── uuid.ts │ ├── graph/ │ │ ├── index.ts │ │ ├── mutations/ │ │ │ └── index.ts │ │ ├── queries/ │ │ │ └── index.ts │ │ ├── schema.json │ │ └── types/ │ │ └── index.ts │ ├── graphqlEntry.ts │ └── index.tsx ├── tsconfig.json ├── tslint.json ├── webpack.config.js ├── webpack.config.prod.js └── webpack.config.server.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": ["es2015", "react", "stage-0"], "env": { "development": { "plugins": [ "react-hot-loader/babel" ] }, "test": { "plugins": [ [ "babel-plugin-webpack-alias", { "config": "./webpack.config.prod.js" } ] ] }, "server": { "plugins": [ [ "babel-plugin-webpack-alias", { "config": "./webpack.config.server.js" } ] ] } } } ================================================ FILE: .eslintignore ================================================ **/*.ts **/*.tsx ================================================ FILE: .github/CHANGELOG.md ================================================ # Change Log ## 1.0.1 - Add jest snapshot / enzyme testing - Introduce changelog, contributing.md and roadmap.md ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. Please note we have a code of conduct, please follow it in all your interactions with the project. ## Pull Request Process We are following the [feature branch git workflow](https://www.atlassian.com/git/tutorials/comparing-workflows). Please post an issue if you have any questions about this process. 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters. 3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. 5. Write tests if you can! ## Code of Conduct ### Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ### Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ### Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [INSERT EMAIL ADDRESS]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ### Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: .gitignore ================================================ node_modules build dist npm-debug.log .vscode coverage yarn-error.log .DS_STORE lerna-debug.log ================================================ FILE: .nvmrc ================================================ 6.9.5 ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "6.9.5" script: npm run test notifications: slack: scalable-react:HPFuyoipfw9RROPGrZrczz1m email: on_failure: always ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Ryan Collins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Procfile ================================================ web: cross-env NODE_ENV=server node server.js ================================================ FILE: README.md ================================================ ![](https://github.com/RyanCCollins/cdn/raw/master/road_not_maintained.jpg?raw=true) # __NOTICE__: The maintainers of this repo are no longer able to support a thriving open source community. This repo will stay put to serve as an example for the community, but should not be depended on in a production application unless you and are your team intend to build upon it. If you're interested in helping to support and grow this library, please let us know in the issues! ### Open UI We are working on a ui kit, which you'll find located in the packages directory. Much like this project, this code is not yet production ready and we have yet to document it. Please stay tuned. ![TypeScript](https://raygun.com/blog/wp-content/uploads/2016/07/Callums-post-on-Typescript.png) # Scalable React TS Boilerplate [![Build Status](https://travis-ci.org/scalable-react/scalable-react-typescript-boilerplate.svg?branch=master)](https://travis-ci.org/scalable-react/scalable-react-typescript-boilerplate) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Roadmap](https://camo.githubusercontent.com/de5f01c39b77893278b0f44ebf20ecca32adb13c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f2546302539462539332539342d726f61646d61702d4344393532332e7376673f7374796c653d666c61742d737175617265)](https://github.com/scalable-react/scalable-react-typescript-boilerplate#roadmap) [![Star on Github](https://img.shields.io/github/stars/scalable-react/scalable-react-typescript-boilerplate.svg?style=social)](https://github.com/scalable-react/scalable-react-typescript-boilerplate/stargazers) [![Watch on Github](https://img.shields.io/github/watchers/scalable-react/scalable-react-typescript-boilerplate.svg?style=social)](https://github.com/scalable-react/scalable-react-typescript-boilerplate/watchers) [![Tweet](https://img.shields.io/twitter/url/https/github.com/scalable-react/scalable-react-typescript-boilerplate.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20scalable-react-typescript-boilerplate!%20https://github.com/scalable-react/scalable-react-typescript-boilerplate%20%F0%9F%91%8D) Checkout [our website](https://scalable-react-ts-boilerplate.herokuapp.com/) for documentation and examples. ## Background Info We know that there are a ton of react boilerplates and starter projects to choose from. Our hope with this project is to provide an example of one of the best front-end architectural patterns available: Feature First. We started initially with a vanilla JS version of this architecture and have since converted it entirely to strongly-typed TypeScript. Above all else, the hope of this repository is to provide the open source community with a great example of how to build large-scale apps with React and TypeScript using the feature-first modularization pattern. Read on to get started! ## Main Features - Feature First - Statically Typed - TypeScript - Lerna - Starter UI Kit (Open UI) - Redux Logic - Webpack 2 - React - Redux - Hot Module Reloading - Server Side Rendering - Highly optimized webpack configuration - Code Chunking - Lazy route loading - Uglification / minification ## Getting Started 1. Clone the Repo `git clone https://github.com/RyanCCollins/scalable-react-ts-boilerplate` 2. Install Dependencies From the root of the project directory, run `yarn` if you have yarn installed globally. --- or --- `npm install`. 3. Clear out the code you don't want. Run `npm run clean` to get rid of the docs package and to reset the codebase to the bare minimum (note: we are still working on this and this would be a great place to submit an improvement). 4. Start the Development Server Run `npm run start` then browse http://localhost:1337 to see your running app. ## What is Feature First? In many projects and frameworks, files are organized by their file type. For example, you will find tests in a test folder, reducers in a reducers folder and so on and so forth. This framework takes a different approach. We encourage modularization / encapsulation of features by asking that you organize your files by feature, rather than file type. When you begin working on your next container, instead of having to root through multiple files to find all of the files that the container depends on, you can expect to find these files in one place: with the feature that they represent. This helps to decouple the features in your app, lending itself well to code reuse, scalability and modularization. On top of that, we also ask that you think about separation of concern as you are building your features. You will see in the example application in this repository that a feature is built up of a dozen or so small, single purpose modules. By following these simple patterns, you will set yourself up for maximum scalability. Give it a try! We think you will enjoy it. ## Lerna We are hard at work converting the structure of this boilerplate to use Lerna. Stay tuned for more information! ## Styled Components This project embraces [styled-components](https://github.com/styled-components/styled-components) as it's a fantastic way to style your React components. Check the components directory for examples. ## Full Stack This boilerplate contains setup to quickly get started with a full stack application. Within the [`src/`](https://github.com/scalable-react/scalable-react-typescript-boilerplate/tree/master/src) directory, you will find a server and a client folder. ## File Tree Structure ### Client File Structure ``` src/client ├── apolloClient.ts ├── components │ ├── Box │ │ ├── __tests__ │ │ │ ├── __mocks__ │ │ │ │ └── boxMocks.mock.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ └── index.test.tsx │ │ ├── index.tsx │ │ ├── maps.ts │ │ ├── styleUtils.ts │ │ ├── styles.ts │ │ └── types.ts │ ├── Section │ │ ├── __tests__ │ │ │ ├── __mocks__ │ │ │ │ └── sectionMocks.mock.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ └── index.test.tsx │ │ ├── index.tsx │ │ └── styles.ts │ └── index.ts ├── containers │ ├── Blog │ │ ├── index.tsx │ │ ├── posts.graphql.ts │ │ └── styles.ts │ ├── BlogPost │ │ ├── comments.graphql.ts │ │ ├── index.tsx │ │ ├── post.graphql.ts │ │ └── styles.ts │ └── index.ts ├── index.tsx ├── reducers.ts ├── routes.tsx ├── store.tsx └── styles └── index.css ``` ### Server file structure ``` src/server ├── db │ ├── index.ts │ ├── models │ │ ├── comment.ts │ │ └── post.ts │ └── utils │ └── uuid.ts ├── graph │ ├── index.ts │ ├── mutations │ │ ├── comment │ │ │ ├── createComment.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── queries │ │ ├── comment │ │ │ ├── comment.ts │ │ │ ├── comments.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── post │ │ ├── index.ts │ │ ├── post.ts │ │ └── posts.ts │ ├── schema.json │ └── types │ ├── comment │ │ ├── comment.ts │ │ └── commentInput.ts │ ├── index.ts │ └── post │ ├── post.ts │ └── postInput.ts ├── graphqlEntry.ts └── index.tsx ``` ## Apollo GraphQL Recently, we've added support for Apollo and GraphQL both server and client side. The starter code in this repo demonstrates how to setup your GraphQL server. The `/blog` route will show you a very simple example of loading data via Apollo Graphql. ## UI Components #### Build your next UIKit with this library! Included in this project are a few primitive components that you can use to bootstrap your next project, or as a reference for building a UIKit. - [Box](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Box/index.tsx) - Flex Box component! Whoot! - [Section](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Section/index.tsx) - A section component. Extends the Box component giving flex-box properties. - [Anchor](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Anchor/index.tsx) - [Article](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Article/index.tsx) - [Button](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Button/index.tsx) - [Footer](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Footer/index.tsx) - [Heading](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Heading/index.tsx) - [Headline](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Headline/index.tsx) - [Hero](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Hero/index.tsx) - [Image](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Image/index.tsx) - [Markdown](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Markdown/index.tsx) - [NavBar](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/NavBar/index.tsx) - [Paragraph](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/Paragraph/index.tsx) - [SvgIcon](https://github.com/scalable-react/scalable-react-typescript-boilerplate/blob/master/src/client/components/SvgIcon/index.tsx) - And more! ## React Storybook We've included react storybook to make it easy for you to test your ui kit components. Run `npm run storybook` and navigate to `http://localhost:9001` to see your stories. You can add more stories as you are building out your components within the `./config/.storybook/stories` folder. Simply [write a story](https://getstorybook.io/docs/react-storybook/basics/writing-stories) and export it in the `stories/index.js` file. Checkout the [React Storybook](https://getstorybook.io/) docs for more info! Note: the components are currently being served from the `build` directory, so please make sure you have compiled (`npm run compile`) the app before running. ## Generators We've included some generators so that you can easily scaffold out components & containers from the command line. To use the generators, run `npm run generator` and select the options you want to use. The generators will create your component or container and their accompanying imports / exports. ## Server Rendering We have included setup to get you server-rendering out of the box. Included in the setup is an [Express server](https://github.com/RyanCCollins/scalable-react-ts-boilerplate/blob/master/src/server.tsx) that will server render. Note that the server-rendering will not work with the TypeScript source code, so you must compile the project into the Build directory first. Also, you must copy any other assets (images, markdown, etc.) into the build folder. ## Testing Included is a test framework for all of your React testing needs. We are using Jest to run the test suite and generate snapshots, plus Enzyme for component introspection. Tests should be collocated within the component / container they represent. Test files should be named `index.test.tsx` and mocks must be named `myMock.mock.ts`. Please reference the [`Box`](https://github.com/RyanCCollins/scalable-react-typescript-boilerplate/tree/master/src/client/components/Box) and [`Section`](https://github.com/RyanCCollins/scalable-react-typescript-boilerplate/tree/master/src/client/components/Section) components for example tests. More will be added at a later time. Note that the test are not compiled by TypeScript, that way there is no code duplication and you can use static types in your tests. P.S. If you are looking to contribute, this would be a great first contribution! ## Deployment The [documentation website](https://scalable-react-ts-boilerplate.herokuapp.com/) built for this boilerplate is deployed to Heroku. Included is a Procfile that will run the server. The deployment is automated completely. After the install script, the deploy script will run on the server in order to compile the bundle and prepare the build folder for server-rendering. ## Scripts - `npm run setup:yarn` - Install the package dependencies via yarn (recomended) - `npm install` - Install dependencies (the ol' fashioned way) - `npm run start` - Start the dev server - `npm run build` - Build the project - `npm run deploy` - Create a production bundle for deployment - `npm run serve` - Serve the production bundle on port 1337 - `npm run test` - Run the test suite - `npm run test:watch` - Run the test suite in watch mode - `npm run test:update` - Update the failing snapshot tests - `npm run storybook` - Run the storybook server ## Resources - [JavaScript Code Quality with Free Tools](https://dev-blog.apollodata.com/javascript-code-quality-with-free-tools-9a6d80e29f2d#.1unvvh8vw) - [Working with React & TypeScript](http://blog.wolksoftware.com/working-with-react-and-typescript) - [Setting up a New React / TypeScript Project](http://blog.tomduncalf.com/posts/setting-up-typescript-and-react/) - [Feature First Architecture](https://medium.com/front-end-hacking/the-secret-to-organization-in-functional-programming-913484e85fc9#.8nerdsqhd) ## Contributing See here for our [contribution guide](https://github.com/RyanCCollins/scalable-react-typescript-boilerplate/blob/master/CONTRIBUTING.md). We are on slack, please go [here for an invite](https://scalable-react-slack.herokuapp.com/)! We'd love to hear from you! ## License See here for the [license](https://github.com/RyanCCollins/scalable-react-typescript-boilerplate/blob/master/LICENSE). ## Roadmap ### v1.0.2 - [X] Add CI - [x] Introduce an async redux workflow - Redux logic - [x] Integrate storybook - [x] Standardize the tslint configuration - [x] Add more reusable modules, ala box / section - [x] Refactor reusable modules into uikit - [x] Remove requires and use es6 imports for all libs - [x] Add a no any rule and update source to use it - [ ] Migrate to a multi-module pattern using lerna (in progress) ## Troubleshooting Make sure to use the right version of node. You can reference the version in the `.nvmrc` file. For help installing and using NVM, please refer to this [gist](https://gist.github.com/RyanCCollins/1a5686ff9dd51b72eb2d4dc70aa6c1f4). ================================================ FILE: config/.storybook/addons.js ================================================ // To get default addons (actions and links) import '@kadira/storybook/addons'; // To add the knobs addon import '@kadira/storybook-addon-knobs/register' ================================================ FILE: config/.storybook/config.js ================================================ import { configure, setAddon } from '@kadira/storybook'; import infoAddon from '@kadira/react-storybook-addon-info'; import withPropsCombinations, { setDefaults } from 'react-storybook-addon-props-combinations' setAddon(withPropsCombinations) setAddon(infoAddon); function loadStories() { require('./stories/index.js'); } configure(loadStories, module); ================================================ FILE: config/.storybook/stories/index.js ================================================ ================================================ FILE: config/.storybook/webpack.config.js ================================================ const path = require('path'); const config = require('../../webpack.config.server.js'); const ROOT_PATH = path.resolve(__dirname, '../../'); module.exports = { resolve: { extensions: ['', '.js', '.jsx', '.json', '.css'], alias: config.resolve.alias, }, module: { loaders: [ { test: /\.css$/, loaders: ['style-loader', 'css-loader'] }, ] } } ================================================ FILE: config/generators/cli.js ================================================ const componentGenerator = require('./component/index.js'); const SafeString = require('./utils/safeString').SafeString; const containerGenerator = require('./container/index.js'); module.exports = (plop) => { plop.setGenerator('component', componentGenerator); plop.setGenerator('feature', containerGenerator); plop.addHelper('uppercase', (text) => { return text.toUpperCase(); }); plop.addHelper('getPath', (p, itemName) => { const pathParts = p.split('/'); const index = pathParts.indexOf(itemName); const newPath = pathParts.slice(index + 1, pathParts.length); return newPath.length < 1 ? `./${newPath}` : `./${newPath.join('/')}/`; }); plop.addHelper('createImports', (list) => { const items = list.map((item) => `import ${item} from 'grommet/components/${item}';`).join('\n'); return new SafeString(items); }); plop.addHelper('curly', (object, open) => (open ? '{' : '}')); }; ================================================ FILE: config/generators/component/es6class.tsx.hbs ================================================ import * as React from 'react'; import Component from './styles'; export interface Props extends React.HTMLProps { children: JSX.Element; } class {{ properCase name }} extends React.Component { public render() { const { children, } = this.props; return ( {children} ); } } export default {{ properCase name }}; ================================================ FILE: config/generators/component/export.ts.hbs ================================================ $1 {{ properCase name }}, ================================================ FILE: config/generators/component/import.ts.hbs ================================================ $1 import {{ properCase name }} from '{{ getPath path 'components' }}{{ properCase name }}'; ================================================ FILE: config/generators/component/index.js ================================================ const path = require('path'); const { trimTemplateFile } = require('../utils/'); module.exports = { description: 'Generate a component', prompts: [ { type: 'input', name: 'name', message: 'What is the name of the component?', default: 'Post', validate: (value) => { if ((/.+/).test(value)) { return true; } return 'The name is required.'; } }, { type: 'input', name: 'path', message: 'What directory would you like your component in? (relative)', default: './src/client/components', validate: (value) => { return true; } }, { type: 'list', name: 'type', message: 'Select the type of component', default: 'Stateless Function', choices: () => ['ES6 Class', 'Stateless Function'] }, ], actions: (data) => { const componentPath = path.resolve(process.cwd(), `${data.path}/{{properCase name}}/`); const rootPath = path.resolve(process.cwd(), `./src/client/components/index.ts`); const actions = [{ type: 'add', path: `${componentPath}/index.tsx`, templateFile: data.type === 'ES6 Class' ? './component/es6class.tsx.hbs' : './component/stateless.tsx.hbs', abortOnFail: true }, { type: 'add', path: `${componentPath}/types.ts`, templateFile: './component/types.ts.hbs', abortOnFail: true }, { type: 'add', path: `${componentPath}/styles.ts`, templateFile: './component/styles.ts.hbs', abortOnFail: true }, { type: 'modify', path: rootPath, pattern: /(\/\* GENERATOR-IMPORT \*\/)/g, template: trimTemplateFile('./config/generators/component/import.ts.hbs'), abortOnFail: false }, { type: 'modify', path: rootPath, pattern: /(\/\* GENERATOR-EXPORT \*\/)/g, template: trimTemplateFile('./config/generators/component/export.ts.hbs'), abortOnFail: false }]; return actions; } }; ================================================ FILE: config/generators/component/stateless.tsx.hbs ================================================ import * as React from 'react'; import Component from './styles'; export interface Props extends React.HTMLProps { children: JSX.Element; } export default function {{ properCase name }}({ children, }: Props): JSX.Element { return ( {children} ); }; ================================================ FILE: config/generators/component/styles.ts.hbs ================================================ import styled from 'styled-components'; export default styled.div` height: 100px; width: 200px; `; ================================================ FILE: config/generators/component/types.ts.hbs ================================================ export { Props } from './'; ================================================ FILE: config/generators/container/actionCreators.js.hbs ================================================ import * as T from './constants'; import { LoadInitiationAction, LoadSuccessAction, LoadFailureAction, LoadCancelAction, } from './actions'; import { ErrorType } from './types'; export const loadInitiation = (): LoadInitiationAction => ({ type: T.LOAD_INITIATION, }); export const loadSuccess = (data: string): LoadSuccessAction => ({ type: T.LOAD_SUCCESS, payload: data, }); export const loadFailure = (error: ErrorType): LoadFailureAction => ({ type: T.LOAD_FAILURE, payload: error, }); export const loadCancel = (): LoadCancelAction => ({ type: T.LOAD_CANCEL, }); export const actionCreators = { loadInitiation, loadSuccess, loadFailure, loadCancel, }; export default actionCreators; ================================================ FILE: config/generators/container/actions.js.hbs ================================================ import { PayloadAction } from 'root/types'; import { ErrorType } from './types'; import * as T from './constants'; export interface LoadInitiationAction extends PayloadAction { type: T.LOAD_INITIATION_TYPE; } export interface LoadSuccessAction extends PayloadAction { type: T.LOAD_SUCCESS_TYPE; payload: string; } export interface LoadFailureAction extends PayloadAction { type: T.LOAD_FAILURE_TYPE; payload: ErrorType; } export interface LoadCancelAction extends PayloadAction { type: T.LOAD_CANCEL_TYPE; } export type Action = LoadInitiationAction | LoadSuccessAction | LoadFailureAction | LoadCancelAction; ================================================ FILE: config/generators/container/constants.js.hbs ================================================ export type LOAD_INITIATION_TYPE = '{{ uppercase name }}/LOAD_INITIATION'; export const LOAD_INITIATION: LOAD_INITIATION_TYPE = '{{ uppercase name }}/LOAD_INITIATION'; export type LOAD_SUCCESS_TYPE = '{{ uppercase name }}/LOAD_SUCCESS'; export const LOAD_SUCCESS: LOAD_SUCCESS_TYPE = '{{ uppercase name }}/LOAD_SUCCESS'; export type LOAD_FAILURE_TYPE = '{{ uppercase name }}/LOAD_FAILURE'; export const LOAD_FAILURE: LOAD_FAILURE_TYPE = '{{ uppercase name }}/LOAD_FAILURE'; export type LOAD_CANCEL_TYPE = '{{ uppercase name }}/LOAD_CANCEL'; export const LOAD_CANCEL: LOAD_CANCEL_TYPE = '{{ uppercase name }}/LOAD_CANCEL'; export type ActionType = LOAD_INITIATION_TYPE | LOAD_SUCCESS_TYPE | LOAD_FAILURE_TYPE | LOAD_CANCEL_TYPE; ================================================ FILE: config/generators/container/export.js.hbs ================================================ $1 {{ properCase name }}, ================================================ FILE: config/generators/container/import.js.hbs ================================================ $1 import {{ properCase name }} from '{{ getPath path 'features' }}{{ properCase name }}'; ================================================ FILE: config/generators/container/index.js ================================================ const path = require('path'); const { trimTemplateFile } = require('../utils/'); module.exports = { description: 'Add a feature', prompts: [ { type: 'input', name: 'name', message: 'What should it be called?', default: 'Dashboard', validate: value => { if ((/.+/).test(value)) { return true; } return 'The name is required'; } }, { type: 'input', name: 'path', message: 'What directory would you like your container in? (relative)', default: './src/client/features', validate: (value) => { return true; } }, { type: 'confirm', name: 'wantActionsAndReducer', default: true, message: 'Do you want actions/constants/reducer for this feature?' }, { type: 'confirm', name: 'wantSelectors', default: true, message: 'Do you want to use reselect?' } ], actions: (data) => { const containerPath = path.resolve(process.cwd(), `${data.path}/{{properCase name}}/`); const rootPath = path.resolve(process.cwd(), `./src/client/features/index.ts`); const reducersPath = path.resolve(process.cwd(), './src/client/reducers.ts'); const typesPath = path.resolve(process.cwd(), './src/client/types.ts'); const statePath = path.resolve(process.cwd(), './src/client/state.ts'); const actions = [ { type: 'add', path: `${containerPath}/index.tsx`, templateFile: './container/index.js.hbs', abortOnFail: true }, { type: 'add', path: `${containerPath}/presentation.tsx`, templateFile: './container/presentation.js.hbs', abortOnFail: true }, { type: 'modify', path: rootPath, pattern: /(\/\* GENERATOR-IMPORT \*\/)/g, template: trimTemplateFile('./config/generators/container/import.js.hbs'), abortOnFail: false }, { type: 'modify', path: rootPath, pattern: /(\/\* GENERATOR-EXPORT \*\/)/g, template: trimTemplateFile('./config/generators/container/export.js.hbs'), abortOnFail: false }, { type: 'modify', path: typesPath, pattern: /(\/\* GENERATOR-IMPORT \*\/)/g, template: trimTemplateFile('./config/generators/container/types.import.ts.hbs'), abortOnFail: false }, { type: 'modify', path: typesPath, pattern: /(\/\* GENERATOR-EXPORT \*\/)/g, template: trimTemplateFile('./config/generators/container/types.export.ts.hbs'), abortOnFail: false } ]; actions.push({ type: 'add', path: `${containerPath}/styles.ts`, templateFile: './container/styles.js.hbs', abortOnFail: true }); actions.push( { type: 'add', path: `${containerPath}/types.ts`, templateFile: './container/types.ts.hbs', abortOnFail: true }); if (data.wantSelectors) { actions.push({ type: 'add', path: `${containerPath}/selectors.ts`, templateFile: './container/selectors.js.hbs', abortOnFail: true }); } // If they want actions and a reducer, generate actions.js, constants.js, // reducer.js and the corresponding tests for actions and the reducer if (data.wantActionsAndReducer) { // Actions actions.push({ type: 'add', path: `${containerPath}/actionCreators.ts`, templateFile: './container/actionCreators.js.hbs', abortOnFail: true }); actions.push({ type: 'add', path: `${containerPath}/state.ts`, templateFile: './container/state.js.hbs', abortOnFail: true }); actions.push({ type: 'add', path: `${containerPath}/actions.ts`, templateFile: './container/actions.js.hbs', abortOnFail: true }); // Constants actions.push({ type: 'add', path: `${containerPath}/constants.ts`, templateFile: './container/constants.js.hbs', abortOnFail: true }); // Reducer actions.push({ type: 'add', path: `${containerPath}/reducer.ts`, templateFile: './container/reducer.js.hbs', abortOnFail: true }); actions.push({ type: 'modify', path: reducersPath, pattern: /(\/\* GENERATOR-IMPORT-REDUCER \*\/)/g, template: trimTemplateFile('./config/generators/container/reducer.import.js.hbs'), abortOnFail: false }); actions.push({ type: 'modify', path: reducersPath, pattern: /(\/\* GENERATOR-EXPORT-REDUCER \*\/)/g, template: trimTemplateFile('./config/generators/container/reducer.export.js.hbs'), abortOnFail: false }); actions.push({ type: 'modify', path: statePath, pattern: /(\/\* GENERATOR-IMPORT-STATE \*\/)/g, template: trimTemplateFile('./config/generators/container/state.import.js.hbs'), abortOnFail: false }); actions.push({ type: 'modify', path: statePath, pattern: /(\/\* GENERATOR-EXPORT-STATE \*\/)/g, template: trimTemplateFile('./config/generators/container/reducer.export-state.js.hbs'), abortOnFail: false }); actions.push({ type: 'modify', path: statePath, pattern: /(\/\* GENERATOR-EXPORT-STATE-TYPE \*\/)/g, template: trimTemplateFile('./config/generators/container/state-type.js.hbs'), abortOnFail: false }); } return actions; } }; ================================================ FILE: config/generators/container/index.js.hbs ================================================ import * as React from 'react'; {{#if wantActionsAndReducer}} import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { State as GlobalState } from 'root/state'; import { Action, ActionMap, ErrorType } from './types'; import actionCreators from './actionCreators'; {{/if}} {{#if wantSelectors}} import * as selectors from './selectors'; {{/if}} import Presentation from './presentation'; {{#if wantActionsAndReducer}} {{#if wantSelectors}} type MapStateToProps = (state: GlobalState) => StateProps; const mapStateToProps: MapStateToProps = (state) => ({ isLoading: selectors.selectIsLoading(state), error: selectors.selectError(state), data: selectors.selectData(state), }); {{else}} type MapStateToProps = (state: GlobalState): StateProps; const mapStateToProps: MapStateToProps = (state) => ({ isLoading: state.{{ camelCase name }}.isLoading, error: state.{{ camelCase name }}.error, data: state.{{ camelCase name }}.data, }); {{/if}} type MapDispatchToProps = (dispatch: Dispatch) => DispatchProps; const mapDispatchToProps: MapDispatchToProps = (dispatch) => ({ actions: bindActionCreators( actionCreators, dispatch, ), }); export interface DispatchProps { actions: ActionMap; } export interface StateProps { isLoading: boolean; error?: ErrorType; data?: string; } export type Props = StateProps & DispatchProps; class {{ properCase name }} extends React.Component { {{else}} export interface Props { } class {{ properCase name }} extends React.Component { {{/if}} public render() { return ( ); } } {{#if wantActionsAndReducer}} export default connect( mapStateToProps, mapDispatchToProps, )({{ properCase name }}); {{else}} export default {{ properCase name }}; {{/if}} ================================================ FILE: config/generators/container/presentation.js.hbs ================================================ import * as React from 'react'; {{#if wantActionsAndReducer}} import { StateProps } from './types'; {{else}} import { Props as ParentProps } from './types'; {{/if}} import { Heading, Section } from './styles'; {{#if wantActionsAndReducer}} export default class Presentation extends React.Component { {{else}} export interface Props extends ParentProps { // own props } export default class Presentation extends React.Component { {{/if}} public render() { {{#if wantActionsAndReducer}} const { isLoading, error, data, } = this.props; {{/if}} return (
Best container ever!
); } } ================================================ FILE: config/generators/container/reducer.export-state.js.hbs ================================================ $1 {{ camelCase name }}: {{ camelCase name }}State, ================================================ FILE: config/generators/container/reducer.export.js.hbs ================================================ $1 {{ camelCase name }}, ================================================ FILE: config/generators/container/reducer.import.js.hbs ================================================ $1 import {{ camelCase name }} from './features/{{ properCase name }}/reducer'; ================================================ FILE: config/generators/container/reducer.js.hbs ================================================ import * as T from './constants'; import initialState from './state'; import { Action, State, } from './types'; const reducer = ( state: State = initialState, action: Action, ): State => { switch (action.type) { case T.LOAD_INITIATION: return Object.assign({}, state, { isLoading: true, }); case T.LOAD_SUCCESS: return Object.assign({}, state, { isLoading: false, data: action.payload, }); case T.LOAD_FAILURE: return Object.assign({}, state, { isLoading: false, error: action.payload, }); default: return state; } }; export default reducer; ================================================ FILE: config/generators/container/selectors.js.hbs ================================================ import { State as GlobalState } from '../../state'; import { State, ErrorType } from './types'; import { createSelector, Selector } from 'reselect'; export const select{{ properCase name }} = () => (state: GlobalState): State => state.{{ camelCase name }}; export type SelectIsLoading = Selector; export const selectIsLoading: SelectIsLoading = createSelector( select{{ properCase name }}(), ({{ camelCase name }}) => {{ camelCase name }}.isLoading, ); export type SelectError = Selector; export const selectError: SelectError = createSelector( select{{ properCase name }}(), ({{ camelCase name }}) => {{ camelCase name }}.error, ); export type SelectData = Selector; export const selectData: SelectData = createSelector( select{{ properCase name }}(), ({{ camelCase name }}) => {{ camelCase name }}.data, ); ================================================ FILE: config/generators/container/state-type.js.hbs ================================================ $1 {{ camelCase name }}: {{ properCase name }}State; ================================================ FILE: config/generators/container/state.import.js.hbs ================================================ $1 import { initialState as {{ camelCase name }}State, State as {{ properCase name }}State } from './features/{{ properCase name }}/state'; ================================================ FILE: config/generators/container/state.js.hbs ================================================ import { ErrorType } from './types'; export interface State { isLoading: boolean; error?: ErrorType; data?: string; } export const initialState: State = { isLoading: false, error: null, data: null, }; export default initialState; ================================================ FILE: config/generators/container/styles.js.hbs ================================================ import styled from 'styled-components'; export const Section = styled.section` padding: 60px; background-color: #f5f5f5; min-height: calc(100vh - 50px); `; export const Heading = styled.h1` text-align: center; `; ================================================ FILE: config/generators/container/types.export.ts.hbs ================================================ $1 {{ properCase name }}Types, ================================================ FILE: config/generators/container/types.import.ts.hbs ================================================ $1 import * as {{ properCase name }}Types from './features/{{ properCase name }}/types'; ================================================ FILE: config/generators/container/types.ts.hbs ================================================ {{#if wantActionsAndReducer}} import { ActionCreatorsMapObject } from 'redux'; import { LoadInitiationAction, LoadSuccessAction, LoadFailureAction, LoadCancelAction, } from './actions'; export { State } from './state'; export { Props, StateProps, DispatchProps } from './'; export { ActionType } from './constants'; export { Action } from './actions'; export interface ErrorType { message: string } export interface ActionMap extends ActionCreatorsMapObject { loadInitiation: () => LoadInitiationAction; loadSuccess: (data: string) => LoadSuccessAction; loadFailure: (error: ErrorType) => LoadFailureAction; loadCancel: () => LoadCancelAction; } {{else}} export { Props } from './'; {{/if}} ================================================ FILE: config/generators/utils/index.js ================================================ const fs = require('fs'); const trimTemplateFile = (template) => { // Loads the template file and trims the whitespace and then returns the content as a string. return fs.readFileSync(template, 'utf8').replace(/\s*$/, ''); }; module.exports = { trimTemplateFile }; ================================================ FILE: config/generators/utils/safeString.js ================================================ function SafeString(string) { this.string = string; } SafeString.prototype.toString = SafeString.prototype.toHTML = function() { return '' + this.string; }; module.exports = { SafeString }; ================================================ FILE: config/ignoreAssets.js ================================================ /* eslint-disable */ (function() { require.extensions['.scss'] = () => { return; }; require.extensions['.css'] = () => { return; }; require.extensions['.png'] = () => { return; }; require.extensions['.jpg'] = () => { return; }; require.extensions['.md'] = () => { return; }; })(); ================================================ FILE: config/scripts/clean.js ================================================ require('shelljs/global'); rm('-fr', './packages/docs'); ================================================ FILE: config/testing/__mocks__/fileMock.js ================================================ module.exports = 'test-file-stub'; ================================================ FILE: config/testing/__mocks__/styleMock.js ================================================ module.exports = {}; ================================================ FILE: config/testing/preprocessor.js ================================================ 'use strict'; const babel = require('babel-core'); const tsc = require('typescript'); const crypto = require('crypto'); const fs = require('fs'); const jestPreset = require('babel-preset-jest'); const es2015Preset = require('babel-preset-es2015'); const path = require('path'); const BABELRC_FILENAME = '.babelrc'; const cache = Object.create(null); const tsconfig = require('../../tsconfig.json'); const getBabelRC = (filename, {useCache}) => { const paths = []; let directory = filename; while (directory !== (directory = path.dirname(directory))) { if (useCache && cache[directory]) { break; } paths.push(directory); const configFilePath = path.join(directory, BABELRC_FILENAME); if (fs.existsSync(configFilePath)) { cache[directory] = fs.readFileSync(configFilePath, 'utf8'); break; } } paths.forEach(directoryPath => { cache[directoryPath] = cache[directory]; }); return cache[directory] || ''; }; const createTransformer = (options) => { options = Object.assign({}, options, { // auxiliaryCommentBefore: ' istanbul ignore next ', presets: ((options && options.presets) || []).concat([jestPreset]), retainLines: true, }); delete options.cacheDirectory; options.presets = options.presets.concat([es2015Preset]); return { canInstrument: true, getCacheKey( fileData, filename, configString, {instrument, watch} ) { return crypto.createHash('md5') .update(fileData) .update(configString) // Don't use the in-memory cache in watch mode because the .babelrc // file may be modified. .update(getBabelRC(filename, {useCache: !watch})) .update(instrument ? 'instrument' : '') .digest('hex'); }, process( src, filename, config, transformOptions ) { let plugins = options.plugins || []; if (transformOptions && transformOptions.instrument) { // Copied from jest-runtime transform.js plugins = plugins.concat([ [ require('babel-plugin-istanbul').default, { // files outside `cwd` will not be instrumented cwd: config.rootDir, exclude: [], }, ], ]); } // console.log(transformOptions); // console.log(JSON.stringify(options)); // console.log('src', src); // ts compile const diag = []; const tsOutput = tsc.transpileModule(src, {diagnostics: diag, filename, compilerOptions: tsconfig.compilerOptions, reportDiagnostics: true}); // console.log(tsOutput.outputText) if (babel.util.canCompile(filename) || true) { const babelOutput = babel.transform( tsOutput.outputText, Object.assign({}, options, {filename, plugins}) ); // console.log('babelOutput', babelOutput.code); return babelOutput.code; } // return src; }, }; }; module.exports = createTransformer(); ================================================ FILE: config/testing/templates/_index.prod.html ================================================ React TS Starter
================================================ FILE: config/types/require.d.ts ================================================ declare var require: { (path: string): any; (path: string): T; (paths: string[], callback: (...modules: any[]) => void): void; ensure: (paths: string[], callback: (require: (path: string) => T) => void) => void; }; ================================================ FILE: devServer.js ================================================ var path = require('path'); var webpack = require('webpack'); var express = require('express'); var devMiddleware = require('webpack-dev-middleware'); var hotMiddleware = require('webpack-hot-middleware'); var config = require('./webpack.config'); var app = express(); var compiler = webpack(config); app.use(devMiddleware(compiler, { publicPath: config.output.publicPath, historyApiFallback: true, })); app.use(hotMiddleware(compiler)); app.get('*', function (req, res) { res.sendFile(path.join(__dirname, 'index.html')); }); app.listen(1337, function (err) { if (err) { return console.error(err); } console.log('Listening at http://localhost:1337/'); }); ================================================ FILE: index.html ================================================ React TS Starter
================================================ FILE: lerna.json ================================================ { "lerna": "2.0.0-beta.38", "packages": [ "packages/*" ], "version": "independent" } ================================================ FILE: netlify.toml ================================================ [build] command = "npm run deploy" publish = "build/public" ================================================ FILE: package.json ================================================ { "name": "scalable-react-typescript-boilerplate", "version": "1.0.1", "description": "React + TypeScript boilerplate", "main": "index.js", "author": "RyanCCollins", "license": "MIT", "engines": { "node": "6.9.5", "npm": "3.10.8" }, "jest": { "moduleFileExtensions": [ "ts", "tsx", "js" ], "moduleNameMapper": { "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/config/testing/__mocks__/fileMock.js", "\\.(css|less)$": "/config/testing/__mocks__/styleMock.js" }, "testPathIgnorePatterns": [ "/(build|docs|node_modules)/", "/__mocks__/" ], "transform": { ".*": "/config/testing/preprocessor.js" }, "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$" }, "scripts": { "build": "webpack --config ./webpack.config.js", "start": "node devServer.js", "predeploy": "rimraf build/public/*.*", "deploy": "cross-env NODE_ENV=production ./node_modules/.bin/webpack --config ./webpack.config.prod.js", "postdeploy": "npm run compile", "compile": "./node_modules/.bin/tsc", "setup:yarn": "yarn", "preserve": "npm run compile", "lint": "tslint \"packages/**/*.ts{,x}\" && tslint \"src/**/*.ts{,x}\"", "serve": "cross-env NODE_ENV=server node server.js", "postinstall": "npm run deploy", "generator": "plop --plopfile ./config/generators/cli.js", "test": "cross-env NODE_ENV=test jest", "test:watch": "npm run test -- --watch", "test:update": "npm run test -- --u", "storybook": "start-storybook -p 9001 -c ./config/.storybook", "clean": "shjs ./config/scripts/clean.js" }, "dependencies": { "@kadira/react-storybook-addon-info": "^3.3.0", "@kadira/storybook": "^2.35.3", "@kadira/storybook-addon-knobs": "^1.7.1", "@types/body-parser": "^0.0.34", "@types/compression": "latest", "@types/express": "latest", "@types/graphql": "latest", "@types/isomorphic-fetch": "^0.0.33", "@types/mongoose": "^4.7.7", "@types/morgan": "latest", "@types/node": "^6.0.64", "@types/react": "^15.0.14", "@types/react-dom": "^0.14.23", "@types/react-ga": "^1.4.7", "@types/react-redux": "^4.4.35", "@types/react-router": "^3.0.6", "@types/react-router-redux": "^4.0.42", "@types/reselect": "^2.0.27", "@types/sinon": "^1.16.35", "@types/webpack": "^2.2.11", "apollo-client": "^0.10.1", "apollo-codegen": "^0.10.8", "autoprefixer": "^6.7.6", "axios": "^0.15.3", "babel-polyfill": "^6.23.0", "body-parser": "^1.17.0", "compression": "^1.6.2", "cors": "^2.8.1", "exports-loader": "^0.6.4", "express": "^4.15.0", "github-markdown-css": "^2.4.1", "graphql": "^0.9.1", "graphql-server-express": "^0.7.2", "graphql-tag": "^1.3.1", "html-webpack-plugin": "^2.28.0", "imports-loader": "^0.7.1", "isomorphic-fetch": "^2.2.1", "json-loader": "^0.5.4", "markdown-loader": "^2.0.0", "mongoose": "^4.8.5", "morgan": "^1.8.1", "node-env-file": "^0.1.8", "openui": "1.0.3", "plop": "^1.7.4", "precss": "^1.4.0", "react": "^15.4.1", "react-addons-test-utils": "^15.4.2", "react-apollo": "^0.13.2", "react-dom": "^15.4.1", "react-ga": "^2.1.2", "react-hot-loader": "3.0.0-beta.6", "react-markdown": "^2.4.5", "react-redux": "^5.0.3", "react-router": "^3.0.2", "react-router-redux": "^4.0.8", "redux": "^3.6.0", "redux-connect": "^5.0.0", "redux-logic": "^0.11.7", "redux-mock-provider": "^1.0.0", "reselect": "^2.5.4", "rxjs": "^5.2.0", "serialize-javascript": "^1.3.0", "shelljs": "^0.7.7", "shortid": "^2.2.6", "sinon": "^1.17.7", "styled-components": "^1.4.4" }, "devDependencies": { "@types/enzyme": "^2.7.5", "@types/jest": "^18.1.1", "babel-core": "^6.23.1", "babel-loader": "^6.3.2", "babel-plugin-webpack-alias": "^2.1.2", "babel-preset-es2015": "^6.18.0", "babel-preset-jest": "^19.0.0", "babel-preset-react": "^6.23.0", "babel-preset-stage-0": "^6.16.0", "cross-env": "^3.2.3", "css-loader": "^0.26.2", "enzyme": "^2.7.1", "enzyme-to-json": "^1.5.0", "extract-text-webpack-plugin": "^2.0.0", "html-loader": "^0.4.5", "jest": "^19.0.2", "lerna": "2.0.0-beta.38", "postcss-loader": "^1.3.3", "react-storybook-addon-props-combinations": "^0.3.0", "redux-logic-test": "^1.0.3", "redux-mock-store": "^1.2.2", "rimraf": "^2.6.1", "style-loader": "^0.13.2", "ts-jest": "^19.0.0", "ts-loader": "^2.0.1", "tslint": "^4.5.1", "tslint-eslint-rules": "^3.5.1", "tslint-loader": "^3.4.3", "tslint-react": "^2.5.0", "typescript": "^2.2.1", "webpack": "^2.2.1", "webpack-dev-middleware": "^1.10.1", "webpack-hot-middleware": "^2.17.1", "webpack-manifest-plugin": "^1.1.0" } } ================================================ FILE: packages/docs/.babelrc ================================================ { "presets": ["es2015", "react", "stage-0"], "env": { "development": { "plugins": [ "react-hot-loader/babel" ] }, "test": { "plugins": [ [ "babel-plugin-webpack-alias", { "config": "./webpack.config.prod.js" } ] ] }, "server": { "plugins": [ [ "babel-plugin-webpack-alias", { "config": "./webpack.config.server.js" } ] ] } } } ================================================ FILE: packages/docs/config/generators/cli.js ================================================ const componentGenerator = require('./component/index.js'); const SafeString = require('./utils/safeString').SafeString; const containerGenerator = require('./container/index.js'); module.exports = (plop) => { plop.setGenerator('component', componentGenerator); plop.setGenerator('container', containerGenerator); plop.addHelper('uppercase', (text) => { return text.toUpperCase(); }); plop.addHelper('getPath', (p, itemName) => { const pathParts = p.split('/'); const index = pathParts.indexOf(itemName); const newPath = pathParts.slice(index + 1, pathParts.length); return newPath.length < 1 ? `./${newPath}` : `./${newPath.join('/')}/`; }); plop.addHelper('createImports', (list) => { const items = list.map((item) => `import ${item} from 'grommet/components/${item}';`).join('\n'); return new SafeString(items); }); plop.addHelper('curly', (object, open) => (open ? '{' : '}')); }; ================================================ FILE: packages/docs/config/generators/component/es6class.tsx.hbs ================================================ import * as React from 'react'; import Component from './styles'; export interface Props extends React.HTMLProps { children: JSX.Element; } class {{ properCase name }} extends React.Component { public render() { const { children, } = this.props; return ( {children} ); } } export default {{ properCase name }}; ================================================ FILE: packages/docs/config/generators/component/export.ts.hbs ================================================ $1 {{ properCase name }}, ================================================ FILE: packages/docs/config/generators/component/import.ts.hbs ================================================ $1 import {{ properCase name }} from '{{ getPath path 'components' }}{{ properCase name }}'; ================================================ FILE: packages/docs/config/generators/component/index.js ================================================ const path = require('path'); const { trimTemplateFile } = require('../utils/'); module.exports = { description: 'Generate a component', prompts: [ { type: 'input', name: 'name', message: 'What is the name of the component?', default: 'Post', validate: (value) => { if ((/.+/).test(value)) { return true; } return 'The name is required.'; } }, { type: 'input', name: 'path', message: 'What directory would you like your component in? (relative)', default: './src/client/components', validate: (value) => { return true; } }, { type: 'list', name: 'type', message: 'Select the type of component', default: 'Stateless Function', choices: () => ['ES6 Class', 'Stateless Function'] }, ], actions: (data) => { const componentPath = path.resolve(process.cwd(), `${data.path}/{{properCase name}}/`); const rootPath = path.resolve(process.cwd(), `./src/client/components/index.ts`); const actions = [{ type: 'add', path: `${componentPath}/index.tsx`, templateFile: data.type === 'ES6 Class' ? './component/es6class.tsx.hbs' : './component/stateless.tsx.hbs', abortOnFail: true }, { type: 'add', path: `${componentPath}/types.ts`, templateFile: './component/types.ts.hbs', abortOnFail: true }, { type: 'add', path: `${componentPath}/styles.ts`, templateFile: './component/styles.ts.hbs', abortOnFail: true }, { type: 'modify', path: rootPath, pattern: /(\/\* GENERATOR-IMPORT \*\/)/g, template: trimTemplateFile('./config/generators/component/import.ts.hbs'), abortOnFail: false }, { type: 'modify', path: rootPath, pattern: /(\/\* GENERATOR-EXPORT \*\/)/g, template: trimTemplateFile('./config/generators/component/export.ts.hbs'), abortOnFail: false }]; return actions; } }; ================================================ FILE: packages/docs/config/generators/component/stateless.tsx.hbs ================================================ import * as React from 'react'; import Component from './styles'; export interface Props extends React.HTMLProps { children: JSX.Element; } export default function {{ properCase name }}({ children, }: Props): JSX.Element { return ( {children} ); }; ================================================ FILE: packages/docs/config/generators/component/styles.ts.hbs ================================================ import styled from 'styled-components'; export default styled.div` height: 100px; width: 200px; `; ================================================ FILE: packages/docs/config/generators/component/types.ts.hbs ================================================ export { Props } from './'; ================================================ FILE: packages/docs/config/generators/container/actionCreators.js.hbs ================================================ import * as T from './constants'; import { LoadInitiationAction, LoadSuccessAction, LoadFailureAction, LoadCancelAction, } from './actions'; import { ErrorType } from './types'; export const loadInitiation = (): LoadInitiationAction => ({ type: T.LOAD_INITIATION, }); export const loadSuccess = (data: string): LoadSuccessAction => ({ type: T.LOAD_SUCCESS, payload: data, }); export const loadFailure = (error: ErrorType): LoadFailureAction => ({ type: T.LOAD_FAILURE, payload: error, }); export const loadCancel = (): LoadCancelAction => ({ type: T.LOAD_CANCEL, }); export const actionCreators = { loadInitiation, loadSuccess, loadFailure, loadCancel, }; export default actionCreators; ================================================ FILE: packages/docs/config/generators/container/actions.js.hbs ================================================ import { PayloadAction } from 'root/types'; import { ErrorType } from './types'; import * as T from './constants'; export interface LoadInitiationAction extends PayloadAction { type: T.LOAD_INITIATION_TYPE; } export interface LoadSuccessAction extends PayloadAction { type: T.LOAD_SUCCESS_TYPE; payload: string; } export interface LoadFailureAction extends PayloadAction { type: T.LOAD_FAILURE_TYPE; payload: ErrorType; } export interface LoadCancelAction extends PayloadAction { type: T.LOAD_CANCEL_TYPE; } export type Action = LoadInitiationAction | LoadSuccessAction | LoadFailureAction | LoadCancelAction; ================================================ FILE: packages/docs/config/generators/container/constants.js.hbs ================================================ export type LOAD_INITIATION_TYPE = '{{ uppercase name }}/LOAD_INITIATION'; export const LOAD_INITIATION: LOAD_INITIATION_TYPE = '{{ uppercase name }}/LOAD_INITIATION'; export type LOAD_SUCCESS_TYPE = '{{ uppercase name }}/LOAD_SUCCESS'; export const LOAD_SUCCESS: LOAD_SUCCESS_TYPE = '{{ uppercase name }}/LOAD_SUCCESS'; export type LOAD_FAILURE_TYPE = '{{ uppercase name }}/LOAD_FAILURE'; export const LOAD_FAILURE: LOAD_FAILURE_TYPE = '{{ uppercase name }}/LOAD_FAILURE'; export type LOAD_CANCEL_TYPE = '{{ uppercase name }}/LOAD_CANCEL'; export const LOAD_CANCEL: LOAD_CANCEL_TYPE = '{{ uppercase name }}/LOAD_CANCEL'; export type ActionType = LOAD_INITIATION_TYPE | LOAD_SUCCESS_TYPE | LOAD_FAILURE_TYPE | LOAD_CANCEL_TYPE; ================================================ FILE: packages/docs/config/generators/container/export.js.hbs ================================================ $1 {{ properCase name }}, ================================================ FILE: packages/docs/config/generators/container/import.js.hbs ================================================ $1 import {{ properCase name }} from '{{ getPath path 'containers' }}{{ properCase name }}'; ================================================ FILE: packages/docs/config/generators/container/index.js ================================================ const path = require('path'); const { trimTemplateFile } = require('../utils/'); module.exports = { description: 'Add a container component', prompts: [ { type: 'input', name: 'name', message: 'What should it be called?', default: 'Dashboard', validate: value => { if ((/.+/).test(value)) { return true; } return 'The name is required'; } }, { type: 'input', name: 'path', message: 'What directory would you like your container in? (relative)', default: './src/client/containers', validate: (value) => { return true; } }, { type: 'confirm', name: 'wantActionsAndReducer', default: true, message: 'Do you want actions/constants/reducer for this container?' }, { type: 'confirm', name: 'wantSelectors', default: true, message: 'Do you want to use reselect?' } ], actions: (data) => { const containerPath = path.resolve(process.cwd(), `${data.path}/{{properCase name}}/`); const rootPath = path.resolve(process.cwd(), `./src/client/containers/index.ts`); const reducersPath = path.resolve(process.cwd(), './src/client/reducers.ts'); const typesPath = path.resolve(process.cwd(), './src/client/types.ts'); const statePath = path.resolve(process.cwd(), './src/client/state.ts'); const actions = [ { type: 'add', path: `${containerPath}/index.tsx`, templateFile: './container/index.js.hbs', abortOnFail: true }, { type: 'add', path: `${containerPath}/presentation.tsx`, templateFile: './container/presentation.js.hbs', abortOnFail: true }, { type: 'modify', path: rootPath, pattern: /(\/\* GENERATOR-IMPORT \*\/)/g, template: trimTemplateFile('./config/generators/container/import.js.hbs'), abortOnFail: false }, { type: 'modify', path: rootPath, pattern: /(\/\* GENERATOR-EXPORT \*\/)/g, template: trimTemplateFile('./config/generators/container/export.js.hbs'), abortOnFail: false }, { type: 'modify', path: typesPath, pattern: /(\/\* GENERATOR-IMPORT \*\/)/g, template: trimTemplateFile('./config/generators/container/types.import.ts.hbs'), abortOnFail: false }, { type: 'modify', path: typesPath, pattern: /(\/\* GENERATOR-EXPORT \*\/)/g, template: trimTemplateFile('./config/generators/container/types.export.ts.hbs'), abortOnFail: false } ]; actions.push({ type: 'add', path: `${containerPath}/styles.ts`, templateFile: './container/styles.js.hbs', abortOnFail: true }); actions.push( { type: 'add', path: `${containerPath}/types.ts`, templateFile: './container/types.ts.hbs', abortOnFail: true }); if (data.wantSelectors) { actions.push({ type: 'add', path: `${containerPath}/selectors.ts`, templateFile: './container/selectors.js.hbs', abortOnFail: true }); } // If they want actions and a reducer, generate actions.js, constants.js, // reducer.js and the corresponding tests for actions and the reducer if (data.wantActionsAndReducer) { // Actions actions.push({ type: 'add', path: `${containerPath}/actionCreators.ts`, templateFile: './container/actionCreators.js.hbs', abortOnFail: true }); actions.push({ type: 'add', path: `${containerPath}/state.ts`, templateFile: './container/state.js.hbs', abortOnFail: true }); actions.push({ type: 'add', path: `${containerPath}/actions.ts`, templateFile: './container/actions.js.hbs', abortOnFail: true }); // Constants actions.push({ type: 'add', path: `${containerPath}/constants.ts`, templateFile: './container/constants.js.hbs', abortOnFail: true }); // Reducer actions.push({ type: 'add', path: `${containerPath}/reducer.ts`, templateFile: './container/reducer.js.hbs', abortOnFail: true }); actions.push({ type: 'modify', path: reducersPath, pattern: /(\/\* GENERATOR-IMPORT-REDUCER \*\/)/g, template: trimTemplateFile('./config/generators/container/reducer.import.js.hbs'), abortOnFail: false }); actions.push({ type: 'modify', path: reducersPath, pattern: /(\/\* GENERATOR-EXPORT-REDUCER \*\/)/g, template: trimTemplateFile('./config/generators/container/reducer.export.js.hbs'), abortOnFail: false }); actions.push({ type: 'modify', path: statePath, pattern: /(\/\* GENERATOR-IMPORT-STATE \*\/)/g, template: trimTemplateFile('./config/generators/container/state.import.js.hbs'), abortOnFail: false }); actions.push({ type: 'modify', path: statePath, pattern: /(\/\* GENERATOR-EXPORT-STATE \*\/)/g, template: trimTemplateFile('./config/generators/container/reducer.export-state.js.hbs'), abortOnFail: false }); actions.push({ type: 'modify', path: statePath, pattern: /(\/\* GENERATOR-EXPORT-STATE-TYPE \*\/)/g, template: trimTemplateFile('./config/generators/container/state-type.js.hbs'), abortOnFail: false }); } return actions; } }; ================================================ FILE: packages/docs/config/generators/container/index.js.hbs ================================================ import * as React from 'react'; {{#if wantActionsAndReducer}} import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { State as GlobalState } from 'root/state'; import { Action, ActionMap, ErrorType } from './types'; import actionCreators from './actionCreators'; {{/if}} {{#if wantSelectors}} import * as selectors from './selectors'; {{/if}} import Presentation from './presentation'; {{#if wantActionsAndReducer}} {{#if wantSelectors}} type MapStateToProps = (state: GlobalState) => StateProps; const mapStateToProps: MapStateToProps = (state) => ({ isLoading: selectors.selectIsLoading(state), error: selectors.selectError(state), data: selectors.selectData(state), }); {{else}} type MapStateToProps = (state: GlobalState): StateProps; const mapStateToProps: MapStateToProps = (state) => ({ isLoading: state.{{ camelCase name }}.isLoading, error: state.{{ camelCase name }}.error, data: state.{{ camelCase name }}.data, }); {{/if}} type MapDispatchToProps = (dispatch: Dispatch) => DispatchProps; const mapDispatchToProps: MapDispatchToProps = (dispatch) => ({ actions: bindActionCreators( actionCreators, dispatch, ), }); export interface DispatchProps { actions: ActionMap; } export interface StateProps { isLoading: boolean; error?: ErrorType; data?: string; } export type Props = StateProps & DispatchProps; class {{ properCase name }} extends React.Component { {{else}} export interface Props { } class {{ properCase name }} extends React.Component { {{/if}} public render() { return ( ); } } {{#if wantActionsAndReducer}} export default connect( mapStateToProps, mapDispatchToProps, )({{ properCase name }}); {{else}} export default {{ properCase name }}; {{/if}} ================================================ FILE: packages/docs/config/generators/container/presentation.js.hbs ================================================ import * as React from 'react'; {{#if wantActionsAndReducer}} import { StateProps } from './types'; {{else}} import { Props as ParentProps } from './types'; {{/if}} import { Heading, Section } from './styles'; {{#if wantActionsAndReducer}} export default class Presentation extends React.Component { {{else}} export interface Props extends ParentProps { // own props } export default class Presentation extends React.Component { {{/if}} public render() { {{#if wantActionsAndReducer}} const { isLoading, error, data, } = this.props; {{/if}} return (
Best container ever!
); } } ================================================ FILE: packages/docs/config/generators/container/reducer.export-state.js.hbs ================================================ $1 {{ camelCase name }}: {{ camelCase name }}State, ================================================ FILE: packages/docs/config/generators/container/reducer.export.js.hbs ================================================ $1 {{ camelCase name }}, ================================================ FILE: packages/docs/config/generators/container/reducer.import.js.hbs ================================================ $1 import {{ camelCase name }} from './containers/{{ properCase name }}/reducer'; ================================================ FILE: packages/docs/config/generators/container/reducer.js.hbs ================================================ import * as T from './constants'; import initialState from './state'; import { Action, State, } from './types'; const reducer = ( state: State = initialState, action: Action, ): State => { switch (action.type) { case T.LOAD_INITIATION: return Object.assign({}, state, { isLoading: true, }); case T.LOAD_SUCCESS: return Object.assign({}, state, { isLoading: false, data: action.payload, }); case T.LOAD_FAILURE: return Object.assign({}, state, { isLoading: false, error: action.payload, }); default: return state; } }; export default reducer; ================================================ FILE: packages/docs/config/generators/container/selectors.js.hbs ================================================ import { State as GlobalState } from '../../state'; import { State, ErrorType } from './types'; import { createSelector, Selector } from 'reselect'; export const select{{ properCase name }} = () => (state: GlobalState): State => state.{{ camelCase name }}; export type SelectIsLoading = Selector; export const selectIsLoading: SelectIsLoading = createSelector( select{{ properCase name }}(), ({{ camelCase name }}) => {{ camelCase name }}.isLoading, ); export type SelectError = Selector; export const selectError: SelectError = createSelector( select{{ properCase name }}(), ({{ camelCase name }}) => {{ camelCase name }}.error, ); export type SelectData = Selector; export const selectData: SelectData = createSelector( select{{ properCase name }}(), ({{ camelCase name }}) => {{ camelCase name }}.data, ); ================================================ FILE: packages/docs/config/generators/container/state-type.js.hbs ================================================ $1 {{ camelCase name }}: {{ properCase name }}State; ================================================ FILE: packages/docs/config/generators/container/state.import.js.hbs ================================================ $1 import { initialState as {{ camelCase name }}State, State as {{ properCase name }}State } from './containers/{{ properCase name }}/state'; ================================================ FILE: packages/docs/config/generators/container/state.js.hbs ================================================ import { ErrorType } from './types'; export interface State { isLoading: boolean; error?: ErrorType; data?: string; } export const initialState: State = { isLoading: false, error: null, data: null, }; export default initialState; ================================================ FILE: packages/docs/config/generators/container/styles.js.hbs ================================================ import styled from 'styled-components'; export const Section = styled.section` padding: 60px; background-color: #f5f5f5; min-height: calc(100vh - 50px); `; export const Heading = styled.h1` text-align: center; `; ================================================ FILE: packages/docs/config/generators/container/types.export.ts.hbs ================================================ $1 {{ properCase name }}Types, ================================================ FILE: packages/docs/config/generators/container/types.import.ts.hbs ================================================ $1 import * as {{ properCase name }}Types from './containers/{{ properCase name }}/types'; ================================================ FILE: packages/docs/config/generators/container/types.ts.hbs ================================================ {{#if wantActionsAndReducer}} import { ActionCreatorsMapObject } from 'redux'; import { LoadInitiationAction, LoadSuccessAction, LoadFailureAction, LoadCancelAction, } from './actions'; export { State } from './state'; export { Props, StateProps, DispatchProps } from './'; export { ActionType } from './constants'; export { Action } from './actions'; export interface ErrorType { message: string } export interface ActionMap extends ActionCreatorsMapObject { loadInitiation: () => LoadInitiationAction; loadSuccess: (data: string) => LoadSuccessAction; loadFailure: (error: ErrorType) => LoadFailureAction; loadCancel: () => LoadCancelAction; } {{else}} export { Props } from './'; {{/if}} ================================================ FILE: packages/docs/config/generators/utils/index.js ================================================ const fs = require('fs'); const trimTemplateFile = (template) => { // Loads the template file and trims the whitespace and then returns the content as a string. return fs.readFileSync(template, 'utf8').replace(/\s*$/, ''); }; module.exports = { trimTemplateFile }; ================================================ FILE: packages/docs/config/generators/utils/safeString.js ================================================ function SafeString(string) { this.string = string; } SafeString.prototype.toString = SafeString.prototype.toHTML = function() { return '' + this.string; }; module.exports = { SafeString }; ================================================ FILE: packages/docs/config/ignoreAssets.js ================================================ /* eslint-disable */ (function() { require.extensions['.scss'] = () => { return; }; require.extensions['.css'] = () => { return; }; require.extensions['.png'] = () => { return; }; require.extensions['.jpg'] = () => { return; }; require.extensions['.md'] = () => { return; }; })(); ================================================ FILE: packages/docs/config/types/require.d.ts ================================================ declare var require: { (path: string): any; (path: string): T; (paths: string[], callback: (...modules: any[]) => void): void; ensure: (paths: string[], callback: (require: (path: string) => T) => void) => void; }; ================================================ FILE: packages/docs/devServer.js ================================================ var path = require('path'); var webpack = require('webpack'); var express = require('express'); var devMiddleware = require('webpack-dev-middleware'); var hotMiddleware = require('webpack-hot-middleware'); var config = require('./webpack.config'); var app = express(); var compiler = webpack(config); app.use(devMiddleware(compiler, { publicPath: config.output.publicPath, historyApiFallback: true, })); app.use(hotMiddleware(compiler)); app.get('*', function (req, res) { res.sendFile(path.join(__dirname, 'index.html')); }); app.listen(1339, function (err) { if (err) { return console.error(err); } console.log('Listening at http://localhost:1339/'); }); ================================================ FILE: packages/docs/index.html ================================================ React TS Starter
================================================ FILE: packages/docs/package.json ================================================ { "name": "srtb-docs", "version": "1.0.0", "description": "Docs for the scalable-react-typescript-boilerplate project", "main": "index.js", "author": "RyanCCollins", "license": "MIT", "engines": { "node": "6.9.5", "npm": "3.10.8" }, "scripts": { "precommit": "npm run lint --silent && npm run test", "build": "webpack --config ./webpack.config.js", "start": "node devServer.js", "predeploy": "rimraf build/public/*.*", "deploy": "cross-env NODE_ENV=production ./node_modules/.bin/webpack --config ./webpack.config.prod.js", "postdeploy": "npm run compile", "compile": "./node_modules/.bin/tsc", "setup:yarn": "yarn", "preserve": "npm run compile", "lint": "tslint \"packages/**/*.ts{,x}\" && tslint \"src/**/*.ts{,x}\"", "serve": "cross-env NODE_ENV=server node server.js", "postinstall": "npm run deploy", "generator": "plop --plopfile ./config/generators/cli.js" }, "dependencies": { "@types/body-parser": "^0.0.34", "@types/compression": "latest", "@types/express": "latest", "@types/graphql": "latest", "@types/isomorphic-fetch": "^0.0.33", "@types/mongoose": "^4.7.7", "@types/morgan": "latest", "@types/node": "^6.0.64", "@types/react": "^15.0.14", "@types/react-dom": "^0.14.23", "@types/react-ga": "^1.4.7", "@types/react-redux": "^4.4.35", "@types/react-router": "^3.0.6", "@types/react-router-redux": "^4.0.42", "@types/reselect": "^2.0.27", "@types/sinon": "^1.16.35", "@types/webpack": "^2.2.11", "apollo-client": "^0.10.1", "apollo-codegen": "^0.10.8", "autoprefixer": "^6.7.6", "axios": "^0.15.3", "babel-polyfill": "^6.23.0", "body-parser": "^1.17.0", "compression": "^1.6.2", "cors": "^2.8.1", "exports-loader": "^0.6.4", "express": "^4.15.0", "github-markdown-css": "^2.4.1", "graphql": "^0.9.1", "graphql-server-express": "0.7.2", "graphql-tag": "^1.3.1", "html-webpack-plugin": "^2.28.0", "imports-loader": "^0.7.1", "isomorphic-fetch": "^2.2.1", "json-loader": "^0.5.4", "markdown-loader": "^2.0.0", "mongoose": "^4.8.5", "morgan": "^1.8.1", "node-env-file": "^0.1.8", "openui": "1.0.4", "plop": "^1.7.4", "precss": "^1.4.0", "react": "^15.4.1", "react-addons-test-utils": "^15.4.2", "react-apollo": "^0.13.2", "react-dom": "^15.4.1", "react-ga": "^2.1.2", "react-hot-loader": "3.0.0-beta.6", "react-markdown": "^2.4.5", "react-redux": "^5.0.3", "react-router": "^3.0.2", "react-router-redux": "^4.0.8", "redux": "^3.6.0", "redux-connect": "^5.0.0", "redux-logic": "^0.11.7", "redux-mock-provider": "^1.0.0", "reselect": "^2.5.4", "rxjs": "^5.2.0", "serialize-javascript": "^1.3.0", "shortid": "^2.2.6", "sinon": "^1.17.7", "styled-components": "^1.4.4" }, "devDependencies": { "@types/enzyme": "^2.7.5", "babel-core": "^6.23.1", "babel-loader": "^6.3.2", "babel-plugin-webpack-alias": "^2.1.2", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.23.0", "babel-preset-stage-0": "^6.16.0", "cross-env": "^3.2.3", "css-loader": "^0.26.2", "enzyme": "^2.7.1", "enzyme-to-json": "^1.5.0", "extract-text-webpack-plugin": "^2.0.0", "html-loader": "^0.4.5", "husky": "^0.13.2", "lerna": "2.0.0-beta.38", "postcss-loader": "^1.3.3", "redux-logic-test": "^1.0.3", "redux-mock-store": "^1.2.2", "rimraf": "^2.6.1", "style-loader": "^0.13.2", "ts-loader": "^2.0.1", "tslint": "^4.5.1", "tslint-eslint-rules": "^3.5.1", "tslint-loader": "^3.4.3", "tslint-react": "^2.5.0", "typescript": "^2.2.1", "webpack": "^2.2.1", "webpack-dev-middleware": "^1.10.1", "webpack-hot-middleware": "^2.17.1", "webpack-manifest-plugin": "^1.1.0" } } ================================================ FILE: packages/docs/server.js ================================================ require('babel-core/register'); require('./config/ignoreAssets'); var app = require('./build/src/server/index.jsx'); ================================================ FILE: packages/docs/src/client/apolloClient.ts ================================================ import { ApolloClient, createNetworkInterface } from 'apollo-client'; declare var window: { __INITIAL_STATE__: string, }; const uri = process.env.API_URL || 'http://0.0.0.0:1338/api'; const client = new ApolloClient({ networkInterface: createNetworkInterface({ uri, }), initialState: typeof window !== 'undefined' ? window.__INITIAL_STATE__ : null, // eslint-disable-line ssrForceFetchDelay: 100, }); export default client; ================================================ FILE: packages/docs/src/client/components/AddComment/__tests__/__mocks__/addCommentMocks.mock.ts ================================================ export const addCommentProps = { input: 'Mock input for test', onSubmit: jest.fn(), onChange: jest.fn(), onKeyUp: jest.fn(), }; ================================================ FILE: packages/docs/src/client/components/AddComment/__tests__/__snapshots__/index.test.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` rendering should render with supplied props 1`] = `
Submit
`; ================================================ FILE: packages/docs/src/client/components/AddComment/__tests__/index.test.tsx ================================================ import { shallow, mount } from 'enzyme'; import { shallowToJson } from 'enzyme-to-json'; import * as React from 'react'; import AddComment from '../'; import { addCommentProps } from './__mocks__/addCommentMocks.mock'; describe(' rendering', () => { it('should render with supplied props', () => { const wrapper = shallow( , ); expect(shallowToJson(wrapper)).toMatchSnapshot(); }); }); describe(' onSubmit event testing', () => { let addCommentComponent; beforeEach(() => { addCommentComponent = mount( , ); }); it('AddComment requires onSubmit prop', () => { expect(addCommentComponent.props().onSubmit).toBeDefined(); }); it('AddComment renders an anchor tag for OnClick event', () => { const renderedAnchorTag = addCommentComponent.find('a').first(); expect(renderedAnchorTag).toBeDefined(); }); it('Clicking anchor tag calls onSubmit()', () => { const renderedAnchorTag = addCommentComponent.find('a').first(); renderedAnchorTag.simulate('click'); expect(addCommentProps.onSubmit).toBeCalled(); }); }); describe(' onChange event testing', () => { let addCommentComponent; beforeEach(() => { addCommentComponent = mount( , ); }); it('AddComment requires onChange prop', () => { expect(addCommentComponent.props().onChange).toBeDefined(); }); it('AddComment renders textarea for OnChange event', () => { const renderedTextArea = addCommentComponent.find('textarea').first(); expect(renderedTextArea).toBeDefined(); }); it('Changing textarea triggers OnChange()', () => { const renderedTextArea = addCommentComponent.find('textarea').first(); renderedTextArea.simulate('change'); expect(addCommentProps.onChange).toBeCalled(); }); }); describe(' onKeyUp event testing', () => { let addCommentComponent; beforeEach(() => { addCommentComponent = mount( , ); }); it('AddComment requires onKeyUp prop', () => { expect(addCommentComponent.props().onKeyUp).toBeDefined(); }); it('AddComment renders textarea for OnKeyUp event', () => { const renderedTextArea = addCommentComponent.find('textarea').first(); expect(renderedTextArea).toBeDefined(); }); it('KeyUp in textarea triggers OnKeyUp()', () => { const renderedTextArea = addCommentComponent.find('textarea').first(); renderedTextArea.simulate('keyup'); expect(addCommentProps.onKeyUp).toBeCalled(); }); }); ================================================ FILE: packages/docs/src/client/components/AddComment/index.tsx ================================================ import * as React from 'react'; import { AddCommentProps } from './types'; import { Wrapper, CommentWrapper, PicWrapper, Pic, TextArea, Footer, Actions, Action, ActionButton, } from './styles'; export default function AddComment({ onSubmit, input, onChange, onKeyUp, }: AddCommentProps): JSX.Element { return (