├── .nvmrc ├── internals ├── templates │ ├── translations │ │ └── en.json │ ├── containers │ │ ├── LanguageProvider │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ ├── selectors.js │ │ │ ├── reducer.js │ │ │ └── index.js │ │ ├── HomePage │ │ │ ├── Loadable.js │ │ │ ├── messages.js │ │ │ ├── tests │ │ │ │ └── index.test.js │ │ │ └── index.js │ │ ├── NotFoundPage │ │ │ ├── Loadable.js │ │ │ ├── messages.js │ │ │ ├── tests │ │ │ │ └── index.test.js │ │ │ └── index.js │ │ └── App │ │ │ ├── selectors.js │ │ │ ├── tests │ │ │ ├── index.test.js │ │ │ └── selectors.test.js │ │ │ ├── constants.js │ │ │ └── index.js │ ├── utils │ │ ├── constants.js │ │ ├── checkStore.js │ │ ├── tests │ │ │ ├── checkStore.test.js │ │ │ └── injectReducer.test.js │ │ ├── injectReducer.js │ │ ├── reducerInjectors.js │ │ └── injectSaga.js │ ├── global-styles.js │ ├── tests │ │ ├── reducers.test.js │ │ ├── i18n.test.js │ │ └── store.test.js │ ├── index.html │ ├── reducers.js │ ├── i18n.js │ └── configureStore.js ├── generators │ ├── language │ │ ├── translations-json.hbs │ │ ├── app-locale.hbs │ │ ├── add-locale-data.hbs │ │ ├── polyfill-intl-locale.hbs │ │ ├── intl-locale-data.hbs │ │ ├── translation-messages.hbs │ │ └── format-translation-messages.hbs │ ├── container │ │ ├── constants.js.hbs │ │ ├── saga.js.hbs │ │ ├── actions.js.hbs │ │ ├── selectors.test.js.hbs │ │ ├── reducer.test.js.hbs │ │ ├── test.js.hbs │ │ ├── messages.js.hbs │ │ ├── actions.test.js.hbs │ │ ├── saga.test.js.hbs │ │ ├── reducer.js.hbs │ │ ├── selectors.js.hbs │ │ └── index.js.hbs │ ├── component │ │ ├── loadable.js.hbs │ │ ├── test.js.hbs │ │ ├── messages.js.hbs │ │ ├── stateless.js.hbs │ │ └── class.js.hbs │ ├── utils │ │ └── componentExists.js │ └── index.js ├── mocks │ ├── cssModule.js │ └── image.js ├── testing │ ├── test-bundler.js │ └── enzyme-setup.js ├── scripts │ ├── helpers │ │ ├── xmark.js │ │ ├── checkmark.js │ │ └── progress.js │ ├── npmcheckversion.js │ ├── electron-rebuild.js │ ├── analyze.js │ ├── dependencies.js │ └── clean.js ├── webpack │ ├── webpack.dll.babel.js │ └── webpack.main.prod.js └── config.js ├── resources ├── icon.ico ├── icon.png ├── appdmg.png ├── icon.icns ├── reactron.png ├── icons │ ├── 16x16.png │ ├── 24x24.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 64x64.png │ ├── 96x96.png │ ├── 128x128.png │ ├── 256x256.png │ ├── 512x512.png │ └── 1024x1024.png └── entitlements.mas.plist ├── app ├── images │ ├── favicon.ico │ └── icon-512x512.png ├── resources │ ├── Icon.icns │ ├── icon.ico │ ├── icon.png │ ├── reactron.png │ ├── sounds │ │ └── ding.ogg │ └── tray │ │ ├── trayosx.png │ │ ├── traywin.ico │ │ ├── traylinux.png │ │ ├── trayosx@2x.png │ │ ├── trayosx@3x.png │ │ └── trayosx@4x.png ├── components │ ├── Header │ │ ├── banner.jpg │ │ ├── NavBar.js │ │ ├── A.js │ │ ├── Img.js │ │ ├── tests │ │ │ ├── __snapshots__ │ │ │ │ ├── A.test.js.snap │ │ │ │ └── Img.test.js.snap │ │ │ ├── index.test.js │ │ │ ├── A.test.js │ │ │ └── Img.test.js │ │ ├── messages.js │ │ ├── HeaderLink.js │ │ └── index.js │ ├── H3 │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── H2 │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── H1 │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── Toggle │ │ ├── Select.js │ │ ├── index.js │ │ └── tests │ │ │ ├── Select.test.js │ │ │ └── index.test.js │ ├── Button │ │ ├── A.js │ │ ├── Wrapper.js │ │ ├── StyledButton.js │ │ ├── buttonStyles.js │ │ ├── tests │ │ │ ├── A.test.js │ │ │ ├── Wrapper.test.js │ │ │ ├── StyledButton.test.js │ │ │ └── index.test.js │ │ └── index.js │ ├── LoadingIndicator │ │ ├── Wrapper.js │ │ ├── tests │ │ │ ├── index.test.js │ │ │ ├── Circle.test.js │ │ │ └── Wrapper.test.js │ │ ├── index.js │ │ └── Circle.js │ ├── List │ │ ├── Ul.js │ │ ├── Wrapper.js │ │ ├── index.js │ │ └── tests │ │ │ ├── Ul.test.js │ │ │ ├── Wrapper.test.js │ │ │ └── index.test.js │ ├── Footer │ │ ├── Wrapper.js │ │ ├── messages.js │ │ ├── index.js │ │ └── tests │ │ │ ├── Wrapper.test.js │ │ │ └── index.test.js │ ├── ListItem │ │ ├── Item.js │ │ ├── Wrapper.js │ │ ├── index.js │ │ └── tests │ │ │ ├── index.test.js │ │ │ ├── Item.test.js │ │ │ └── Wrapper.test.js │ ├── A │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── IssueIcon │ │ ├── tests │ │ │ └── index.test.js │ │ └── index.js │ ├── ToggleOption │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── Img │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ └── ReposList │ │ ├── index.js │ │ └── tests │ │ └── index.test.js ├── containers │ ├── HomePage │ │ ├── Form.js │ │ ├── CenteredSection.js │ │ ├── AtPrefix.js │ │ ├── Input.js │ │ ├── Section.js │ │ ├── Loadable.js │ │ ├── selectors.js │ │ ├── tests │ │ │ ├── actions.test.js │ │ │ ├── reducer.test.js │ │ │ ├── selectors.test.js │ │ │ ├── Form.test.js │ │ │ ├── Input.test.js │ │ │ ├── AtPrefix.test.js │ │ │ ├── Section.test.js │ │ │ ├── CenteredSection.test.js │ │ │ ├── __snapshots__ │ │ │ │ └── saga.test.js.snap │ │ │ └── saga.test.js │ │ ├── constants.js │ │ ├── reducer.js │ │ ├── actions.js │ │ ├── messages.js │ │ └── saga.js │ ├── FeaturePage │ │ ├── ListItem.js │ │ ├── ListItemTitle.js │ │ ├── List.js │ │ ├── Loadable.js │ │ └── tests │ │ │ ├── index.test.js │ │ │ ├── List.test.js │ │ │ ├── ListItem.test.js │ │ │ └── ListItemTitle.test.js │ ├── LanguageProvider │ │ ├── constants.js │ │ ├── actions.js │ │ ├── tests │ │ │ ├── selectors.test.js │ │ │ ├── actions.test.js │ │ │ ├── reducer.test.js │ │ │ └── index.test.js │ │ ├── selectors.js │ │ ├── reducer.js │ │ └── index.js │ ├── LocaleToggle │ │ ├── Wrapper.js │ │ ├── messages.js │ │ ├── tests │ │ │ ├── Wrapper.test.js │ │ │ └── index.test.js │ │ └── index.js │ ├── RepoListItem │ │ ├── Wrapper.js │ │ ├── RepoLink.js │ │ ├── IssueIcon.js │ │ ├── IssueLink.js │ │ ├── tests │ │ │ ├── __snapshots__ │ │ │ │ ├── IssueIcon.test.js.snap │ │ │ │ ├── RepoLink.test.js.snap │ │ │ │ └── IssueLink.test.js.snap │ │ │ ├── Wrapper.test.js │ │ │ ├── IssueIcon.test.js │ │ │ ├── RepoLink.test.js │ │ │ ├── IssueLink.test.js │ │ │ └── index.test.js │ │ └── index.js │ ├── NotFoundPage │ │ ├── Loadable.js │ │ ├── messages.js │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── SettingsPage │ │ ├── Loadable.js │ │ ├── tests │ │ │ └── index.test.js │ │ └── messages.js │ └── App │ │ ├── constants.js │ │ ├── tests │ │ ├── index.test.js │ │ ├── actions.test.js │ │ └── reducer.test.js │ │ ├── selectors.js │ │ ├── reducer.js │ │ ├── index.js │ │ └── actions.js ├── utils │ ├── constants.js │ ├── electronSentryUtil.js │ ├── checkStore.js │ ├── tests │ │ ├── checkStore.test.js │ │ ├── injectReducer.test.js │ │ └── request.test.js │ ├── injectReducer.js │ ├── reducerInjectors.js │ ├── electronSystemUtil.js │ ├── request.js │ └── injectSaga.js ├── global-styles.js ├── tests │ ├── reducers.test.js │ ├── i18n.test.js │ └── store.test.js ├── electron │ └── startup.js ├── package.json ├── reducers.js ├── index.html ├── i18n.js ├── .htaccess └── configureStore.js ├── .prettierignore ├── .prettierrc ├── .stylelintrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── appveyor.yml └── .gitattributes /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/carbon 2 | -------------------------------------------------------------------------------- /internals/templates/translations/en.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /internals/generators/language/translations-json.hbs: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /internals/mocks/cssModule.js: -------------------------------------------------------------------------------- 1 | module.exports = 'CSS_MODULE'; 2 | -------------------------------------------------------------------------------- /internals/mocks/image.js: -------------------------------------------------------------------------------- 1 | module.exports = 'IMAGE_MOCK'; 2 | -------------------------------------------------------------------------------- /internals/generators/language/app-locale.hbs: -------------------------------------------------------------------------------- 1 | $1 '{{language}}', 2 | -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icon.png -------------------------------------------------------------------------------- /resources/appdmg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/appdmg.png -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icon.icns -------------------------------------------------------------------------------- /app/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/images/favicon.ico -------------------------------------------------------------------------------- /app/resources/Icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/Icon.icns -------------------------------------------------------------------------------- /app/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/icon.ico -------------------------------------------------------------------------------- /app/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/icon.png -------------------------------------------------------------------------------- /internals/generators/language/add-locale-data.hbs: -------------------------------------------------------------------------------- 1 | $1addLocaleData({{language}}LocaleData); 2 | -------------------------------------------------------------------------------- /resources/reactron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/reactron.png -------------------------------------------------------------------------------- /resources/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/16x16.png -------------------------------------------------------------------------------- /resources/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/24x24.png -------------------------------------------------------------------------------- /resources/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/32x32.png -------------------------------------------------------------------------------- /resources/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/48x48.png -------------------------------------------------------------------------------- /resources/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/64x64.png -------------------------------------------------------------------------------- /resources/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/96x96.png -------------------------------------------------------------------------------- /app/images/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/images/icon-512x512.png -------------------------------------------------------------------------------- /app/resources/reactron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/reactron.png -------------------------------------------------------------------------------- /resources/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/128x128.png -------------------------------------------------------------------------------- /resources/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/256x256.png -------------------------------------------------------------------------------- /resources/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/512x512.png -------------------------------------------------------------------------------- /app/resources/sounds/ding.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/sounds/ding.ogg -------------------------------------------------------------------------------- /app/resources/tray/trayosx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/tray/trayosx.png -------------------------------------------------------------------------------- /app/resources/tray/traywin.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/tray/traywin.ico -------------------------------------------------------------------------------- /resources/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/resources/icons/1024x1024.png -------------------------------------------------------------------------------- /app/components/Header/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/components/Header/banner.jpg -------------------------------------------------------------------------------- /app/resources/tray/traylinux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/tray/traylinux.png -------------------------------------------------------------------------------- /app/resources/tray/trayosx@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/tray/trayosx@2x.png -------------------------------------------------------------------------------- /app/resources/tray/trayosx@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/tray/trayosx@3x.png -------------------------------------------------------------------------------- /app/resources/tray/trayosx@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjangir/reactron/HEAD/app/resources/tray/trayosx@4x.png -------------------------------------------------------------------------------- /internals/generators/language/polyfill-intl-locale.hbs: -------------------------------------------------------------------------------- 1 | $1 import('intl/locale-data/jsonp/{{language}}.js'), 2 | -------------------------------------------------------------------------------- /internals/generators/language/intl-locale-data.hbs: -------------------------------------------------------------------------------- 1 | $&const {{language}}LocaleData = require('react-intl/locale-data/{{language}}'); 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | internals/generators/ 4 | internals/scripts/ 5 | package-lock.json 6 | yarn.lock 7 | package.json 8 | -------------------------------------------------------------------------------- /app/components/Header/NavBar.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.div` 4 | text-align: center; 5 | `; 6 | -------------------------------------------------------------------------------- /internals/generators/language/translation-messages.hbs: -------------------------------------------------------------------------------- 1 | $1const {{language}}TranslationMessages = require('./translations/{{language}}.json'); 2 | -------------------------------------------------------------------------------- /app/components/H3/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function H3(props) { 4 | return

; 5 | } 6 | 7 | export default H3; 8 | -------------------------------------------------------------------------------- /app/components/Header/A.js: -------------------------------------------------------------------------------- 1 | import NormalA from 'components/A'; 2 | 3 | const A = NormalA.extend` 4 | padding: 2em 0; 5 | `; 6 | 7 | export default A; 8 | -------------------------------------------------------------------------------- /internals/testing/test-bundler.js: -------------------------------------------------------------------------------- 1 | // needed for regenerator-runtime 2 | // (ES7 generator support is required by redux-saga) 3 | import 'babel-polyfill'; 4 | -------------------------------------------------------------------------------- /app/components/H2/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const H2 = styled.h2` 4 | font-size: 1.5em; 5 | `; 6 | 7 | export default H2; 8 | -------------------------------------------------------------------------------- /internals/generators/language/format-translation-messages.hbs: -------------------------------------------------------------------------------- 1 | $1 {{language}}: formatTranslationMessages('{{language}}', {{language}}TranslationMessages), 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /app/containers/HomePage/Form.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Form = styled.form` 4 | margin-bottom: 1em; 5 | `; 6 | 7 | export default Form; 8 | -------------------------------------------------------------------------------- /internals/testing/enzyme-setup.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /app/containers/FeaturePage/ListItem.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const ListItem = styled.li` 4 | margin: 1em 0; 5 | `; 6 | 7 | export default ListItem; 8 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider constants 4 | * 5 | */ 6 | 7 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE'; 8 | -------------------------------------------------------------------------------- /app/containers/LocaleToggle/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | padding: 2px; 5 | `; 6 | 7 | export default Wrapper; 8 | -------------------------------------------------------------------------------- /app/components/H1/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const H1 = styled.h1` 4 | font-size: 2em; 5 | margin-bottom: 0.25em; 6 | `; 7 | 8 | export default H1; 9 | -------------------------------------------------------------------------------- /app/components/Toggle/Select.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Select = styled.select` 4 | line-height: 1em; 5 | height: 20px; 6 | `; 7 | 8 | export default Select; 9 | -------------------------------------------------------------------------------- /internals/generators/container/constants.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} constants 4 | * 5 | */ 6 | 7 | export const DEFAULT_ACTION = 'app/{{ properCase name }}/DEFAULT_ACTION'; 8 | -------------------------------------------------------------------------------- /internals/templates/containers/LanguageProvider/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider constants 4 | * 5 | */ 6 | 7 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE'; 8 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "processors": ["stylelint-processor-styled-components"], 3 | "extends": [ 4 | "stylelint-config-recommended", 5 | "stylelint-config-styled-components" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /app/containers/FeaturePage/ListItemTitle.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const ListItemTitle = styled.p` 4 | font-weight: bold; 5 | `; 6 | 7 | export default ListItemTitle; 8 | -------------------------------------------------------------------------------- /app/containers/HomePage/CenteredSection.js: -------------------------------------------------------------------------------- 1 | import Section from './Section'; 2 | 3 | const CenteredSection = Section.extend` 4 | text-align: center; 5 | `; 6 | 7 | export default CenteredSection; 8 | -------------------------------------------------------------------------------- /app/containers/HomePage/AtPrefix.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const AtPrefix = styled.span` 4 | color: black; 5 | margin-left: 0.4em; 6 | `; 7 | 8 | export default AtPrefix; 9 | -------------------------------------------------------------------------------- /app/containers/HomePage/Input.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Input = styled.input` 4 | outline: none; 5 | border-bottom: 1px dotted #999; 6 | `; 7 | 8 | export default Input; 9 | -------------------------------------------------------------------------------- /app/components/Button/A.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import buttonStyles from './buttonStyles'; 4 | 5 | const A = styled.a` 6 | ${buttonStyles}; 7 | `; 8 | 9 | export default A; 10 | -------------------------------------------------------------------------------- /app/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount'; 2 | export const DAEMON = '@@saga-injector/daemon'; 3 | export const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount'; 4 | -------------------------------------------------------------------------------- /app/components/Button/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | width: 100%; 5 | text-align: center; 6 | margin: 4em 0; 7 | `; 8 | 9 | export default Wrapper; 10 | -------------------------------------------------------------------------------- /app/containers/FeaturePage/List.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const List = styled.ul` 4 | font-family: Georgia, Times, 'Times New Roman', serif; 5 | padding-left: 1.75em; 6 | `; 7 | 8 | export default List; 9 | -------------------------------------------------------------------------------- /internals/templates/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount'; 2 | export const DAEMON = '@@saga-injector/daemon'; 3 | export const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount'; 4 | -------------------------------------------------------------------------------- /app/containers/HomePage/Section.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Section = styled.section` 4 | margin: 3em auto; 5 | 6 | &:first-child { 7 | margin-top: 0; 8 | } 9 | `; 10 | 11 | export default Section; 12 | -------------------------------------------------------------------------------- /app/components/Button/StyledButton.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import buttonStyles from './buttonStyles'; 4 | 5 | const StyledButton = styled.button` 6 | ${buttonStyles}; 7 | `; 8 | 9 | export default StyledButton; 10 | -------------------------------------------------------------------------------- /internals/generators/container/saga.js.hbs: -------------------------------------------------------------------------------- 1 | // import { take, call, put, select } from 'redux-saga/effects'; 2 | 3 | // Individual exports for testing 4 | export default function* defaultSaga() { 5 | // See example in containers/HomePage/saga.js 6 | } 7 | -------------------------------------------------------------------------------- /app/components/LoadingIndicator/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | margin: 2em auto; 5 | width: 40px; 6 | height: 40px; 7 | position: relative; 8 | `; 9 | 10 | export default Wrapper; 11 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | align-items: space-between; 8 | `; 9 | 10 | export default Wrapper; 11 | -------------------------------------------------------------------------------- /app/components/Header/Img.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import NormalImg from 'components/Img'; 4 | 5 | const Img = styled(NormalImg)` 6 | width: 100%; 7 | margin: 0 auto; 8 | display: block; 9 | `; 10 | 11 | export default Img; 12 | -------------------------------------------------------------------------------- /app/components/List/Ul.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Ul = styled.ul` 4 | list-style: none; 5 | margin: 0; 6 | width: 100%; 7 | max-height: 30em; 8 | overflow-y: auto; 9 | padding: 0 1em; 10 | `; 11 | 12 | export default Ul; 13 | -------------------------------------------------------------------------------- /app/components/Footer/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.footer` 4 | display: flex; 5 | justify-content: space-between; 6 | padding: 3em 0; 7 | border-top: 1px solid #666; 8 | `; 9 | 10 | export default Wrapper; 11 | -------------------------------------------------------------------------------- /app/components/ListItem/Item.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Item = styled.div` 4 | display: flex; 5 | justify-content: space-between; 6 | width: 100%; 7 | height: 100%; 8 | align-items: center; 9 | `; 10 | 11 | export default Item; 12 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/RepoLink.js: -------------------------------------------------------------------------------- 1 | import NormalA from 'components/A'; 2 | 3 | const RepoLink = NormalA.extend` 4 | height: 100%; 5 | color: black; 6 | display: flex; 7 | align-items: center; 8 | width: 100%; 9 | `; 10 | 11 | export default RepoLink; 12 | -------------------------------------------------------------------------------- /internals/generators/container/actions.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} actions 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | export function defaultAction() { 10 | return { 11 | type: DEFAULT_ACTION, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /internals/scripts/helpers/xmark.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | /** 4 | * Adds mark cross symbol 5 | */ 6 | function addXMark(callback) { 7 | process.stdout.write(chalk.red(' ✘')); 8 | if (callback) callback(); 9 | } 10 | 11 | module.exports = addXMark; 12 | -------------------------------------------------------------------------------- /internals/templates/containers/HomePage/Loadable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronously loads the component for HomePage 3 | */ 4 | import Loadable from 'react-loadable'; 5 | 6 | export default Loadable({ 7 | loader: () => import('./index'), 8 | loading: () => null, 9 | }); 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /app/components/A/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A link to a certain page, an anchor tag 3 | */ 4 | 5 | import styled from 'styled-components'; 6 | 7 | const A = styled.a` 8 | color: #41addd; 9 | 10 | &:hover { 11 | color: #6cc0e5; 12 | } 13 | `; 14 | 15 | export default A; 16 | -------------------------------------------------------------------------------- /internals/templates/containers/NotFoundPage/Loadable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronously loads the component for NotFoundPage 3 | */ 4 | import Loadable from 'react-loadable'; 5 | 6 | export default Loadable({ 7 | loader: () => import('./index'), 8 | loading: () => null, 9 | }); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | coverage 3 | app/build 4 | node_modules 5 | stats.json 6 | app/node_modules 7 | app/electron/main.prod.js 8 | app/electron/main.prod.js.map 9 | release 10 | 11 | # Cruft 12 | .DS_Store 13 | npm-debug.log 14 | .idea 15 | .vscode -------------------------------------------------------------------------------- /app/containers/RepoListItem/IssueIcon.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import NormalIssueIcon from 'components/IssueIcon'; 4 | 5 | const IssueIcon = styled(NormalIssueIcon)` 6 | fill: #ccc; 7 | margin-right: 0.25em; 8 | `; 9 | 10 | export default IssueIcon; 11 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/IssueLink.js: -------------------------------------------------------------------------------- 1 | import NormalA from 'components/A'; 2 | 3 | const IssueLink = NormalA.extend` 4 | height: 100%; 5 | color: black; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | `; 10 | 11 | export default IssueLink; 12 | -------------------------------------------------------------------------------- /internals/scripts/helpers/checkmark.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | /** 4 | * Adds mark check symbol 5 | */ 6 | function addCheckMark(callback) { 7 | process.stdout.write(chalk.green(' ✓')); 8 | if (callback) callback(); 9 | } 10 | 11 | module.exports = addCheckMark; 12 | -------------------------------------------------------------------------------- /internals/generators/component/loadable.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for {{ properCase name }} 4 | * 5 | */ 6 | 7 | import Loadable from 'react-loadable'; 8 | 9 | export default Loadable({ 10 | loader: () => import('./index'), 11 | loading: () => null, 12 | }); 13 | -------------------------------------------------------------------------------- /internals/templates/containers/App/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | const selectRoute = state => state.get('route'); 4 | 5 | const makeSelectLocation = () => 6 | createSelector(selectRoute, routeState => routeState.get('location').toJS()); 7 | 8 | export { makeSelectLocation }; 9 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider actions 4 | * 5 | */ 6 | 7 | import { CHANGE_LOCALE } from './constants'; 8 | 9 | export function changeLocale(languageLocale) { 10 | return { 11 | type: CHANGE_LOCALE, 12 | locale: languageLocale, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /internals/scripts/npmcheckversion.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec; 2 | exec('npm -v', function(err, stdout, stderr) { 3 | if (err) throw err; 4 | if (parseFloat(stdout) < 3) { 5 | throw new Error('[ERROR: React Boilerplate] You need npm version @>=3'); 6 | process.exit(1); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /app/components/List/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | padding: 0; 5 | margin: 0; 6 | width: 100%; 7 | background-color: white; 8 | border: 1px solid #ccc; 9 | border-radius: 3px; 10 | overflow: hidden; 11 | `; 12 | 13 | export default Wrapper; 14 | -------------------------------------------------------------------------------- /app/components/Header/tests/__snapshots__/A.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` should match the snapshot 1`] = ` 4 | .c0 { 5 | color: #41addd; 6 | padding: 2em 0; 7 | } 8 | 9 | .c0:hover { 10 | color: #6cc0e5; 11 | } 12 | 13 | 16 | `; 17 | -------------------------------------------------------------------------------- /internals/templates/containers/LanguageProvider/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider actions 4 | * 5 | */ 6 | 7 | import { CHANGE_LOCALE } from './constants'; 8 | 9 | export function changeLocale(languageLocale) { 10 | return { 11 | type: CHANGE_LOCALE, 12 | locale: languageLocale, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /internals/generators/container/selectors.test.js.hbs: -------------------------------------------------------------------------------- 1 | // import { fromJS } from 'immutable'; 2 | // import { select{{ properCase name }}Domain } from '../selectors'; 3 | 4 | describe('select{{ properCase name }}Domain', () => { 5 | it('Expect to have unit tests specified', () => { 6 | expect(true).toEqual(false); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /app/containers/HomePage/Loadable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronously loads the component for HomePage 3 | */ 4 | import Loadable from 'react-loadable'; 5 | 6 | import LoadingIndicator from 'components/LoadingIndicator'; 7 | 8 | export default Loadable({ 9 | loader: () => import('./index'), 10 | loading: LoadingIndicator, 11 | }); 12 | -------------------------------------------------------------------------------- /app/containers/FeaturePage/Loadable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronously loads the component for FeaturePage 3 | */ 4 | import Loadable from 'react-loadable'; 5 | 6 | import LoadingIndicator from 'components/LoadingIndicator'; 7 | 8 | export default Loadable({ 9 | loader: () => import('./index'), 10 | loading: LoadingIndicator, 11 | }); 12 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/Loadable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronously loads the component for NotFoundPage 3 | */ 4 | import Loadable from 'react-loadable'; 5 | 6 | import LoadingIndicator from 'components/LoadingIndicator'; 7 | 8 | export default Loadable({ 9 | loader: () => import('./index'), 10 | loading: LoadingIndicator, 11 | }); 12 | -------------------------------------------------------------------------------- /app/containers/SettingsPage/Loadable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronously loads the component for FeaturePage 3 | */ 4 | import Loadable from 'react-loadable'; 5 | 6 | import LoadingIndicator from 'components/LoadingIndicator'; 7 | 8 | export default Loadable({ 9 | loader: () => import('./index'), 10 | loading: LoadingIndicator, 11 | }); 12 | -------------------------------------------------------------------------------- /internals/generators/component/test.js.hbs: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { shallow } from 'enzyme'; 3 | 4 | // import {{ properCase name }} from '../index'; 5 | 6 | describe('<{{ properCase name }} />', () => { 7 | it('Expect to have unit tests specified', () => { 8 | expect(true).toEqual(false); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /internals/generators/container/reducer.test.js.hbs: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | import {{ camelCase name }}Reducer from '../reducer'; 3 | 4 | describe('{{ camelCase name }}Reducer', () => { 5 | it('returns the initial state', () => { 6 | expect({{ camelCase name }}Reducer(undefined, {})).toEqual(fromJS({})); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /internals/generators/container/test.js.hbs: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { shallow } from 'enzyme'; 3 | 4 | // import { {{ properCase name }} } from '../index'; 5 | 6 | describe('<{{ properCase name }} />', () => { 7 | it('Expect to have unit tests specified', () => { 8 | expect(true).toEqual(false); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /app/components/Header/tests/__snapshots__/Img.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` should match the snapshot 1`] = ` 4 | .c0 { 5 | width: 100%; 6 | margin: 0 auto; 7 | display: block; 8 | } 9 | 10 | test 15 | `; 16 | -------------------------------------------------------------------------------- /app/components/ListItem/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.li` 4 | width: 100%; 5 | height: 3em; 6 | display: flex; 7 | align-items: center; 8 | position: relative; 9 | border-top: 1px solid #eee; 10 | 11 | &:first-child { 12 | border-top: none; 13 | } 14 | `; 15 | 16 | export default Wrapper; 17 | -------------------------------------------------------------------------------- /app/components/Header/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Header from '../index'; 5 | 6 | describe('
', () => { 7 | it('should render a div', () => { 8 | const renderedComponent = shallow(
); 9 | expect(renderedComponent.find('div').length).toEqual(1); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /app/components/IssueIcon/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import IssueIcon from '../index'; 5 | 6 | describe('', () => { 7 | it('should render a SVG', () => { 8 | const renderedComponent = shallow(); 9 | expect(renderedComponent.find('svg').length).toBe(1); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NotFoundPage Messages 3 | * 4 | * This contains all the text for the NotFoundPage component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | header: { 10 | id: 'boilerplate.containers.NotFoundPage.header', 11 | defaultMessage: 'Page not found.', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /internals/templates/containers/HomePage/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomePage Messages 3 | * 4 | * This contains all the text for the HomePage component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | header: { 10 | id: 'app.components.HomePage.header', 11 | defaultMessage: 'This is HomePage component!', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /internals/templates/containers/NotFoundPage/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NotFoundPage Messages 3 | * 4 | * This contains all the text for the NotFoundPage component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | header: { 10 | id: 'app.components.NotFoundPage.header', 11 | defaultMessage: 'This is NotFoundPage component!', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /app/containers/HomePage/selectors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Homepage selectors 3 | */ 4 | 5 | import { createSelector } from 'reselect'; 6 | import { initialState } from './reducer'; 7 | 8 | const selectHome = state => state.get('home', initialState); 9 | 10 | const makeSelectUsername = () => 11 | createSelector(selectHome, homeState => homeState.get('username')); 12 | 13 | export { selectHome, makeSelectUsername }; 14 | -------------------------------------------------------------------------------- /app/utils/electronSentryUtil.js: -------------------------------------------------------------------------------- 1 | import { init, captureException } from '@sentry/electron'; 2 | import isDev from 'electron-is-dev'; 3 | 4 | const sentryInit = () => { 5 | if (!isDev) { 6 | init({ 7 | dsn: 'https://8cebf30e818541fcbf04cd9a9ce2becc@sentry.io/1278563', 8 | sendTimeout: 0, 9 | }); 10 | } 11 | }; 12 | 13 | export default { 14 | sentryInit, 15 | captureException, 16 | }; 17 | -------------------------------------------------------------------------------- /app/components/ListItem/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Item from './Item'; 5 | import Wrapper from './Wrapper'; 6 | 7 | function ListItem(props) { 8 | return ( 9 | 10 | {props.item} 11 | 12 | ); 13 | } 14 | 15 | ListItem.propTypes = { 16 | item: PropTypes.any, 17 | }; 18 | 19 | export default ListItem; 20 | -------------------------------------------------------------------------------- /internals/templates/containers/App/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { Route } from 'react-router-dom'; 4 | 5 | import App from '../index'; 6 | 7 | describe('', () => { 8 | it('should render some routes', () => { 9 | const renderedComponent = shallow(); 10 | expect(renderedComponent.find(Route).length).not.toBe(0); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /internals/generators/container/messages.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * {{properCase name }} Messages 3 | * 4 | * This contains all the text for the {{properCase name }} component. 5 | */ 6 | 7 | import { defineMessages } from 'react-intl'; 8 | 9 | export default defineMessages({ 10 | header: { 11 | id: 'app.containers.{{properCase name }}.header', 12 | defaultMessage: 'This is {{properCase name}} container !', 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /internals/generators/component/messages.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * {{ properCase name }} Messages 3 | * 4 | * This contains all the text for the {{ properCase name }} component. 5 | */ 6 | 7 | import { defineMessages } from 'react-intl'; 8 | 9 | export default defineMessages({ 10 | header: { 11 | id: 'app.components.{{ properCase name }}.header', 12 | defaultMessage: 'This is the {{ properCase name}} component !', 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/tests/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { selectLanguage } from '../selectors'; 4 | 5 | describe('selectLanguage', () => { 6 | it('should select the global state', () => { 7 | const globalState = fromJS({}); 8 | const mockedState = fromJS({ 9 | language: globalState, 10 | }); 11 | expect(selectLanguage(mockedState)).toEqual(globalState); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /internals/generators/container/actions.test.js.hbs: -------------------------------------------------------------------------------- 1 | import { defaultAction } from '../actions'; 2 | import { DEFAULT_ACTION } from '../constants'; 3 | 4 | describe('{{ properCase name }} actions', () => { 5 | describe('Default Action', () => { 6 | it('has a type of DEFAULT_ACTION', () => { 7 | const expected = { 8 | type: DEFAULT_ACTION, 9 | }; 10 | expect(defaultAction()).toEqual(expected); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /internals/generators/container/saga.test.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * Test sagas 3 | */ 4 | 5 | /* eslint-disable redux-saga/yield-effects */ 6 | // import { take, call, put, select } from 'redux-saga/effects'; 7 | // import { defaultSaga } from '../saga'; 8 | 9 | // const generator = defaultSaga(); 10 | 11 | describe('defaultSaga Saga', () => { 12 | it('Expect to have unit tests specified', () => { 13 | expect(true).toEqual(false); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/components/LoadingIndicator/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import 'jest-styled-components'; 4 | 5 | import LoadingIndicator from '../index'; 6 | 7 | describe('', () => { 8 | it('should match the snapshot', () => { 9 | const renderedComponent = renderer.create().toJSON(); 10 | expect(renderedComponent).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /app/containers/LocaleToggle/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * LocaleToggle Messages 3 | * 4 | * This contains all the text for the LanguageToggle component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | en: { 10 | id: 'boilerplate.containers.LocaleToggle.en', 11 | defaultMessage: 'en', 12 | }, 13 | de: { 14 | id: 'boilerplate.containers.LocaleToggle.de', 15 | defaultMessage: 'de', 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/tests/actions.test.js: -------------------------------------------------------------------------------- 1 | import { changeLocale } from '../actions'; 2 | 3 | import { CHANGE_LOCALE } from '../constants'; 4 | 5 | describe('LanguageProvider actions', () => { 6 | describe('Change Local Action', () => { 7 | it('has a type of CHANGE_LOCALE', () => { 8 | const expected = { 9 | type: CHANGE_LOCALE, 10 | locale: 'de', 11 | }; 12 | expect(changeLocale('de')).toEqual(expected); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /internals/templates/containers/App/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * AppConstants 3 | * Each action has a corresponding type, which the reducer knows and picks up on. 4 | * To avoid weird typos between the reducer and the actions, we save them as 5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid 6 | * reducers accidentally picking up actions they shouldn't. 7 | * 8 | * Follow this format: 9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT'; 10 | */ 11 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { initialState } from './reducer'; 3 | 4 | /** 5 | * Direct selector to the languageToggle state domain 6 | */ 7 | const selectLanguage = state => state.get('language', initialState); 8 | 9 | /** 10 | * Select the language locale 11 | */ 12 | 13 | const makeSelectLocale = () => 14 | createSelector(selectLanguage, languageState => languageState.get('locale')); 15 | 16 | export { selectLanguage, makeSelectLocale }; 17 | -------------------------------------------------------------------------------- /internals/generators/container/reducer.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | import { DEFAULT_ACTION } from './constants'; 9 | 10 | export const initialState = fromJS({}); 11 | 12 | function {{ camelCase name }}Reducer(state = initialState, action) { 13 | switch (action.type) { 14 | case DEFAULT_ACTION: 15 | return state; 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export default {{ camelCase name }}Reducer; 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 9 5 | - 8 6 | 7 | script: 8 | - node ./internals/scripts/generate-templates-for-linting 9 | - npm test -- --maxWorkers=4 10 | - npm run build 11 | 12 | before_install: 13 | - export CHROME_BIN=chromium-browser 14 | - export DISPLAY=:99.0 15 | - sh -e /etc/init.d/xvfb start 16 | 17 | notifications: 18 | email: 19 | on_failure: change 20 | 21 | after_success: 'npm run coveralls' 22 | 23 | cache: 24 | yarn: true 25 | directories: 26 | - node_modules 27 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NotFoundPage 3 | * 4 | * This is the page we show when the user visits a url that doesn't have a route 5 | */ 6 | 7 | import React from 'react'; 8 | import { FormattedMessage } from 'react-intl'; 9 | 10 | import H1 from 'components/H1'; 11 | import messages from './messages'; 12 | 13 | export default function NotFound() { 14 | return ( 15 |
16 |

17 | 18 |

19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /internals/templates/containers/LanguageProvider/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { initialState } from './reducer'; 3 | 4 | /** 5 | * Direct selector to the languageToggle state domain 6 | */ 7 | const selectLanguage = state => state.get('language', initialState); 8 | 9 | /** 10 | * Select the language locale 11 | */ 12 | 13 | const makeSelectLocale = () => 14 | createSelector(selectLanguage, languageState => languageState.get('locale')); 15 | 16 | export { selectLanguage, makeSelectLocale }; 17 | -------------------------------------------------------------------------------- /internals/templates/containers/App/tests/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { makeSelectLocation } from 'containers/App/selectors'; 4 | 5 | describe('makeSelectLocation', () => { 6 | it('should select the location', () => { 7 | const route = fromJS({ 8 | location: { pathname: '/foo' }, 9 | }); 10 | const mockedState = fromJS({ 11 | route, 12 | }); 13 | expect(makeSelectLocation()(mockedState)).toEqual( 14 | route.get('location').toJS(), 15 | ); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/tests/__snapshots__/IssueIcon.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` should match the snapshot 1`] = ` 4 | .c0 { 5 | fill: #ccc; 6 | margin-right: 0.25em; 7 | } 8 | 9 | 14 | 17 | 18 | `; 19 | -------------------------------------------------------------------------------- /internals/templates/containers/HomePage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | import { shallow } from 'enzyme'; 4 | 5 | import HomePage from '../index'; 6 | import messages from '../messages'; 7 | 8 | describe('', () => { 9 | it('should render the page message', () => { 10 | const renderedComponent = shallow(); 11 | expect( 12 | renderedComponent.contains(), 13 | ).toEqual(true); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/containers/HomePage/tests/actions.test.js: -------------------------------------------------------------------------------- 1 | import { CHANGE_USERNAME } from '../constants'; 2 | 3 | import { changeUsername } from '../actions'; 4 | 5 | describe('Home Actions', () => { 6 | describe('changeUsername', () => { 7 | it('should return the correct type and the passed name', () => { 8 | const fixture = 'Max'; 9 | const expectedResult = { 10 | type: CHANGE_USERNAME, 11 | name: fixture, 12 | }; 13 | 14 | expect(changeUsername(fixture)).toEqual(expectedResult); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /app/components/IssueIcon/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | function IssueIcon(props) { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | 12 | IssueIcon.propTypes = { 13 | className: PropTypes.string, 14 | }; 15 | 16 | export default IssueIcon; 17 | -------------------------------------------------------------------------------- /internals/templates/containers/NotFoundPage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | import { shallow } from 'enzyme'; 4 | 5 | import NotFoundPage from '../index'; 6 | import messages from '../messages'; 7 | 8 | describe('', () => { 9 | it('should render the page message', () => { 10 | const renderedComponent = shallow(); 11 | expect( 12 | renderedComponent.contains(), 13 | ).toEqual(true); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/components/Footer/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Footer Messages 3 | * 4 | * This contains all the text for the Footer component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | licenseMessage: { 10 | id: 'boilerplate.components.Footer.license.message', 11 | defaultMessage: 'This project is licensed under the MIT license.', 12 | }, 13 | authorMessage: { 14 | id: 'boilerplate.components.Footer.author.message', 15 | defaultMessage: ` 16 | Made with love by {author}. 17 | `, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /app/components/ToggleOption/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * ToggleOption 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import { injectIntl, intlShape } from 'react-intl'; 10 | 11 | const ToggleOption = ({ value, message, intl }) => ( 12 | 13 | ); 14 | 15 | ToggleOption.propTypes = { 16 | value: PropTypes.string.isRequired, 17 | message: PropTypes.object, 18 | intl: intlShape.isRequired, 19 | }; 20 | 21 | export default injectIntl(ToggleOption); 22 | -------------------------------------------------------------------------------- /app/components/Header/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomePage Messages 3 | * 4 | * This contains all the text for the HomePage component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | home: { 10 | id: 'boilerplate.components.Header.home', 11 | defaultMessage: 'Home', 12 | }, 13 | features: { 14 | id: 'boilerplate.components.Header.features', 15 | defaultMessage: 'Features', 16 | }, 17 | settings: { 18 | id: 'boilerplate.components.Header.settings', 19 | defaultMessage: 'Settings', 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /app/containers/HomePage/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomeConstants 3 | * Each action has a corresponding type, which the reducer knows and picks up on. 4 | * To avoid weird typos between the reducer and the actions, we save them as 5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid 6 | * reducers accidentally picking up actions they shouldn't. 7 | * 8 | * Follow this format: 9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT'; 10 | */ 11 | 12 | export const CHANGE_USERNAME = 'boilerplate/Home/CHANGE_USERNAME'; 13 | -------------------------------------------------------------------------------- /resources/entitlements.mas.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | com.apple.security.files.user-selected.read-write 12 | 13 | com.apple.security.files.downloads.read-write 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/components/H1/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import H1 from '../index'; 5 | 6 | describe('

', () => { 7 | it('should render a prop', () => { 8 | const id = 'testId'; 9 | const renderedComponent = shallow(

); 10 | expect(renderedComponent.prop('id')).toEqual(id); 11 | }); 12 | 13 | it('should render its text', () => { 14 | const children = 'Text'; 15 | const renderedComponent = shallow(

{children}

); 16 | expect(renderedComponent.contains(children)).toBe(true); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /app/components/H2/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import H2 from '../index'; 5 | 6 | describe('

', () => { 7 | it('should render a prop', () => { 8 | const id = 'testId'; 9 | const renderedComponent = shallow(

); 10 | expect(renderedComponent.prop('id')).toEqual(id); 11 | }); 12 | 13 | it('should render its text', () => { 14 | const children = 'Text'; 15 | const renderedComponent = shallow(

{children}

); 16 | expect(renderedComponent.contains(children)).toBe(true); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /app/components/H3/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import H3 from '../index'; 5 | 6 | describe('

', () => { 7 | it('should render a prop', () => { 8 | const id = 'testId'; 9 | const renderedComponent = shallow(

); 10 | expect(renderedComponent.prop('id')).toEqual(id); 11 | }); 12 | 13 | it('should render its text', () => { 14 | const children = 'Text'; 15 | const renderedComponent = shallow(

{children}

); 16 | expect(renderedComponent.contains(children)).toBe(true); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/tests/__snapshots__/RepoLink.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` should match the snapshot 1`] = ` 4 | .c0 { 5 | color: #41addd; 6 | height: 100%; 7 | color: black; 8 | display: -webkit-box; 9 | display: -webkit-flex; 10 | display: -ms-flexbox; 11 | display: flex; 12 | -webkit-align-items: center; 13 | -webkit-box-align: center; 14 | -ms-flex-align: center; 15 | align-items: center; 16 | width: 100%; 17 | } 18 | 19 | .c0:hover { 20 | color: #6cc0e5; 21 | } 22 | 23 |
26 | `; 27 | -------------------------------------------------------------------------------- /app/components/Img/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Img.js 4 | * 5 | * Renders an image, enforcing the usage of the alt="" tag 6 | */ 7 | 8 | import React from 'react'; 9 | import PropTypes from 'prop-types'; 10 | 11 | function Img(props) { 12 | return {props.alt}; 13 | } 14 | 15 | // We require the use of src and alt, only enforced by react in dev mode 16 | Img.propTypes = { 17 | src: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, 18 | alt: PropTypes.string.isRequired, 19 | className: PropTypes.string, 20 | }; 21 | 22 | export default Img; 23 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | 9 | import { CHANGE_LOCALE } from './constants'; 10 | import { DEFAULT_LOCALE } from '../../i18n'; 11 | 12 | export const initialState = fromJS({ 13 | locale: DEFAULT_LOCALE, 14 | }); 15 | 16 | function languageProviderReducer(state = initialState, action) { 17 | switch (action.type) { 18 | case CHANGE_LOCALE: 19 | return state.set('locale', action.locale); 20 | default: 21 | return state; 22 | } 23 | } 24 | 25 | export default languageProviderReducer; 26 | -------------------------------------------------------------------------------- /app/containers/SettingsPage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { FormattedMessage } from 'react-intl'; 4 | 5 | import H1 from 'components/H1'; 6 | import messages from '../messages'; 7 | import FeaturePage from '../index'; 8 | 9 | describe('', () => { 10 | it('should render its heading', () => { 11 | const renderedComponent = shallow(); 12 | expect( 13 | renderedComponent.contains( 14 |

15 | 16 |

, 17 | ), 18 | ).toBe(true); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /app/components/ListItem/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | 4 | import ListItem from '../index'; 5 | 6 | describe('', () => { 7 | it('should have a className', () => { 8 | const renderedComponent = mount(); 9 | expect(renderedComponent.find('li').prop('className')).toBeDefined(); 10 | }); 11 | 12 | it('should render the content passed to it', () => { 13 | const content =
Hello world!
; 14 | const renderedComponent = mount(); 15 | expect(renderedComponent.contains(content)).toBe(true); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /internals/generators/component/stateless.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | // import PropTypes from 'prop-types'; 9 | // import styled from 'styled-components'; 10 | 11 | {{#if wantMessages}} 12 | import { FormattedMessage } from 'react-intl'; 13 | import messages from './messages'; 14 | {{/if}} 15 | 16 | function {{ properCase name }}() { 17 | return ( 18 |
19 | {{#if wantMessages}} 20 | 21 | {{/if}} 22 |
23 | ); 24 | } 25 | 26 | {{ properCase name }}.propTypes = {}; 27 | 28 | export default {{ properCase name }}; 29 | -------------------------------------------------------------------------------- /internals/generators/utils/componentExists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * componentExists 3 | * 4 | * Check whether the given component exist in either the components or containers directory 5 | */ 6 | 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | const pageComponents = fs.readdirSync( 10 | path.join(__dirname, '../../../app/components'), 11 | ); 12 | const pageContainers = fs.readdirSync( 13 | path.join(__dirname, '../../../app/containers'), 14 | ); 15 | const components = pageComponents.concat(pageContainers); 16 | 17 | function componentExists(comp) { 18 | return components.indexOf(comp) >= 0; 19 | } 20 | 21 | module.exports = componentExists; 22 | -------------------------------------------------------------------------------- /internals/templates/containers/LanguageProvider/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | 9 | import { CHANGE_LOCALE } from './constants'; 10 | import { DEFAULT_LOCALE } from '../../i18n'; // eslint-disable-line 11 | 12 | export const initialState = fromJS({ 13 | locale: DEFAULT_LOCALE, 14 | }); 15 | 16 | function languageProviderReducer(state = initialState, action) { 17 | switch (action.type) { 18 | case CHANGE_LOCALE: 19 | return state.set('locale', action.locale); 20 | default: 21 | return state; 22 | } 23 | } 24 | 25 | export default languageProviderReducer; 26 | -------------------------------------------------------------------------------- /app/global-styles.js: -------------------------------------------------------------------------------- 1 | import { injectGlobal } from 'styled-components'; 2 | 3 | /* eslint no-unused-expressions: 0 */ 4 | injectGlobal` 5 | html, 6 | body { 7 | height: 100%; 8 | width: 100%; 9 | } 10 | 11 | body { 12 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 13 | } 14 | 15 | body.fontLoaded { 16 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 17 | } 18 | 19 | #app { 20 | background-color: #fafafa; 21 | min-height: 100%; 22 | min-width: 100%; 23 | } 24 | 25 | p, 26 | label { 27 | font-family: Georgia, Times, 'Times New Roman', serif; 28 | line-height: 1.5em; 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /app/components/Header/HeaderLink.js: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import styled from 'styled-components'; 3 | 4 | export default styled(Link)` 5 | display: inline-flex; 6 | padding: 0.25em 2em; 7 | margin: 1em; 8 | text-decoration: none; 9 | border-radius: 4px; 10 | -webkit-font-smoothing: antialiased; 11 | -webkit-touch-callout: none; 12 | user-select: none; 13 | cursor: pointer; 14 | outline: 0; 15 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 16 | font-weight: bold; 17 | font-size: 16px; 18 | border: 2px solid #41addd; 19 | color: #41addd; 20 | 21 | &:active { 22 | background: #41addd; 23 | color: #fff; 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /internals/templates/global-styles.js: -------------------------------------------------------------------------------- 1 | import { injectGlobal } from 'styled-components'; 2 | 3 | /* eslint no-unused-expressions: 0 */ 4 | injectGlobal` 5 | html, 6 | body { 7 | height: 100%; 8 | width: 100%; 9 | } 10 | 11 | body { 12 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 13 | } 14 | 15 | body.fontLoaded { 16 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 17 | } 18 | 19 | #app { 20 | background-color: #fafafa; 21 | min-height: 100%; 22 | min-width: 100%; 23 | } 24 | 25 | p, 26 | label { 27 | font-family: Georgia, Times, 'Times New Roman', serif; 28 | line-height: 1.5em; 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /app/components/Button/buttonStyles.js: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components'; 2 | 3 | const buttonStyles = css` 4 | display: inline-block; 5 | box-sizing: border-box; 6 | padding: 0.25em 2em; 7 | text-decoration: none; 8 | border-radius: 4px; 9 | -webkit-font-smoothing: antialiased; 10 | -webkit-touch-callout: none; 11 | user-select: none; 12 | cursor: pointer; 13 | outline: 0; 14 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 15 | font-weight: bold; 16 | font-size: 16px; 17 | border: 2px solid #41addd; 18 | color: #41addd; 19 | 20 | &:active { 21 | background: #41addd; 22 | color: #fff; 23 | } 24 | `; 25 | 26 | export default buttonStyles; 27 | -------------------------------------------------------------------------------- /app/utils/checkStore.js: -------------------------------------------------------------------------------- 1 | import conformsTo from 'lodash/conformsTo'; 2 | import isFunction from 'lodash/isFunction'; 3 | import isObject from 'lodash/isObject'; 4 | import invariant from 'invariant'; 5 | 6 | /** 7 | * Validate the shape of redux store 8 | */ 9 | export default function checkStore(store) { 10 | const shape = { 11 | dispatch: isFunction, 12 | subscribe: isFunction, 13 | getState: isFunction, 14 | replaceReducer: isFunction, 15 | runSaga: isFunction, 16 | injectedReducers: isObject, 17 | injectedSagas: isObject, 18 | }; 19 | invariant( 20 | conformsTo(store, shape), 21 | '(app/utils...) injectors: Expected a valid redux store', 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import languageProviderReducer from '../reducer'; 4 | import { CHANGE_LOCALE } from '../constants'; 5 | 6 | describe('languageProviderReducer', () => { 7 | it('returns the initial state', () => { 8 | expect(languageProviderReducer(undefined, {})).toEqual( 9 | fromJS({ 10 | locale: 'en', 11 | }), 12 | ); 13 | }); 14 | 15 | it('changes the locale', () => { 16 | expect( 17 | languageProviderReducer(undefined, { 18 | type: CHANGE_LOCALE, 19 | locale: 'de', 20 | }).toJS(), 21 | ).toEqual({ 22 | locale: 'de', 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/containers/App/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * AppConstants 3 | * Each action has a corresponding type, which the reducer knows and picks up on. 4 | * To avoid weird typos between the reducer and the actions, we save them as 5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid 6 | * reducers accidentally picking up actions they shouldn't. 7 | * 8 | * Follow this format: 9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT'; 10 | */ 11 | 12 | export const LOAD_REPOS = 'boilerplate/App/LOAD_REPOS'; 13 | export const LOAD_REPOS_SUCCESS = 'boilerplate/App/LOAD_REPOS_SUCCESS'; 14 | export const LOAD_REPOS_ERROR = 'boilerplate/App/LOAD_REPOS_ERROR'; 15 | -------------------------------------------------------------------------------- /internals/generators/container/selectors.js.hbs: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { initialState } from './reducer'; 3 | 4 | /** 5 | * Direct selector to the {{ camelCase name }} state domain 6 | */ 7 | 8 | const select{{ properCase name }}Domain = state => 9 | state.get('{{ camelCase name }}', initialState); 10 | 11 | /** 12 | * Other specific selectors 13 | */ 14 | 15 | /** 16 | * Default selector used by {{ properCase name }} 17 | */ 18 | 19 | const makeSelect{{ properCase name }} = () => 20 | createSelector(select{{ properCase name }}Domain, substate => substate.toJS()); 21 | 22 | export default makeSelect{{ properCase name }}; 23 | export { select{{ properCase name }}Domain }; 24 | -------------------------------------------------------------------------------- /internals/templates/utils/checkStore.js: -------------------------------------------------------------------------------- 1 | import conformsTo from 'lodash/conformsTo'; 2 | import isFunction from 'lodash/isFunction'; 3 | import isObject from 'lodash/isObject'; 4 | import invariant from 'invariant'; 5 | 6 | /** 7 | * Validate the shape of redux store 8 | */ 9 | export default function checkStore(store) { 10 | const shape = { 11 | dispatch: isFunction, 12 | subscribe: isFunction, 13 | getState: isFunction, 14 | replaceReducer: isFunction, 15 | runSaga: isFunction, 16 | injectedReducers: isObject, 17 | injectedSagas: isObject, 18 | }; 19 | invariant( 20 | conformsTo(store, shape), 21 | '(app/utils...) injectors: Expected a valid redux store', 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/tests/__snapshots__/IssueLink.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` should match the snapshot 1`] = ` 4 | .c0 { 5 | color: #41addd; 6 | height: 100%; 7 | color: black; 8 | display: -webkit-box; 9 | display: -webkit-flex; 10 | display: -ms-flexbox; 11 | display: flex; 12 | -webkit-align-items: center; 13 | -webkit-box-align: center; 14 | -ms-flex-align: center; 15 | align-items: center; 16 | -webkit-box-pack: center; 17 | -webkit-justify-content: center; 18 | -ms-flex-pack: center; 19 | justify-content: center; 20 | } 21 | 22 | .c0:hover { 23 | color: #6cc0e5; 24 | } 25 | 26 |
29 | `; 30 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Testing the NotFoundPage 3 | */ 4 | 5 | import React from 'react'; 6 | import { shallow } from 'enzyme'; 7 | import { FormattedMessage } from 'react-intl'; 8 | 9 | import H1 from 'components/H1'; 10 | import NotFound from '../index'; 11 | 12 | describe('', () => { 13 | it('should render the Page Not Found text', () => { 14 | const renderedComponent = shallow(); 15 | expect( 16 | renderedComponent.contains( 17 |

18 | 22 |

, 23 | ), 24 | ).toEqual(true); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /internals/generators/component/class.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | // import PropTypes from 'prop-types'; 9 | // import styled from 'styled-components'; 10 | 11 | {{#if wantMessages}} 12 | import { FormattedMessage } from 'react-intl'; 13 | import messages from './messages'; 14 | {{/if}} 15 | 16 | /* eslint-disable react/prefer-stateless-function */ 17 | class {{ properCase name }} extends {{{ type }}} { 18 | render() { 19 | return ( 20 |
21 | {{#if wantMessages}} 22 | 23 | {{/if}} 24 |
25 | ); 26 | } 27 | } 28 | 29 | {{ properCase name }}.propTypes = {}; 30 | 31 | export default {{ properCase name }}; 32 | -------------------------------------------------------------------------------- /internals/scripts/helpers/progress.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const readline = require('readline'); 4 | 5 | /** 6 | * Adds an animated progress indicator 7 | * 8 | * @param {string} message The message to write next to the indicator 9 | * @param {number} amountOfDots The amount of dots you want to animate 10 | */ 11 | function animateProgress(message, amountOfDots) { 12 | if (typeof amountOfDots !== 'number') { 13 | amountOfDots = 3; 14 | } 15 | 16 | let i = 0; 17 | return setInterval(function() { 18 | readline.cursorTo(process.stdout, 0); 19 | i = (i + 1) % (amountOfDots + 1); 20 | const dots = new Array(i + 1).join('.'); 21 | process.stdout.write(message + dots); 22 | }, 500); 23 | } 24 | 25 | module.exports = animateProgress; 26 | -------------------------------------------------------------------------------- /app/components/LoadingIndicator/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Circle from './Circle'; 4 | import Wrapper from './Wrapper'; 5 | 6 | const LoadingIndicator = () => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | 23 | export default LoadingIndicator; 24 | -------------------------------------------------------------------------------- /internals/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { execSync } from 'child_process'; 3 | import fs from 'fs'; 4 | import { dependencies } from '../../app/package.json'; 5 | 6 | const nodeModulesPath = path.join(__dirname, '..', '..', 'app', 'node_modules'); 7 | 8 | if ( 9 | Object.keys(dependencies || {}).length > 0 && 10 | fs.existsSync(nodeModulesPath) 11 | ) { 12 | const electronRebuildCmd = 13 | '../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .'; 14 | 15 | const cmd = 16 | process.platform === 'win32' 17 | ? electronRebuildCmd.replace(/\//g, '\\') 18 | : electronRebuildCmd; 19 | 20 | execSync(cmd, { 21 | cwd: path.join(__dirname, '..', '..', 'app'), 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /app/containers/HomePage/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import homeReducer from '../reducer'; 4 | import { changeUsername } from '../actions'; 5 | 6 | describe('homeReducer', () => { 7 | let state; 8 | beforeEach(() => { 9 | state = fromJS({ 10 | username: '', 11 | }); 12 | }); 13 | 14 | it('should return the initial state', () => { 15 | const expectedResult = state; 16 | expect(homeReducer(undefined, {})).toEqual(expectedResult); 17 | }); 18 | 19 | it('should handle the changeUsername action correctly', () => { 20 | const fixture = 'mxstbr'; 21 | const expectedResult = state.set('username', fixture); 22 | 23 | expect(homeReducer(state, changeUsername(fixture))).toEqual(expectedResult); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/containers/SettingsPage/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SettingsPage Messages 3 | * 4 | * This contains all the text for the SettingsPage component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | header: { 10 | id: 'boilerplate.containers.SettingsPage.header', 11 | defaultMessage: 'Settings', 12 | }, 13 | autoLaunch: { 14 | id: 'boilerplate.containers.SettingsPage.autoLaunch', 15 | defaultMessage: 'Toggle Auto Launch At Start', 16 | }, 17 | showTrayIcon: { 18 | id: 'boilerplate.containers.SettingsPage.showTrayIcon', 19 | defaultMessage: 'Show Tray Icon', 20 | }, 21 | startMinimized: { 22 | id: 'boilerplate.containers.SettingsPage.startMinimized', 23 | defaultMessage: 'Start Minimized', 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /app/components/LoadingIndicator/tests/Circle.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | 4 | import Circle from '../Circle'; 5 | 6 | describe('', () => { 7 | it('should render an
tag', () => { 8 | const renderedComponent = mount(); 9 | expect(renderedComponent.find('div').length).toEqual(1); 10 | }); 11 | 12 | it('should have a className attribute', () => { 13 | const renderedComponent = mount(); 14 | expect(renderedComponent.find('div').prop('className')).toBeDefined(); 15 | }); 16 | 17 | it('should not adopt attributes', () => { 18 | const id = 'test'; 19 | const renderedComponent = mount(); 20 | expect(renderedComponent.find('div').prop('id')).toBeUndefined(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /internals/templates/containers/HomePage/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomePage 3 | * 4 | * This is the first thing users see of our App, at the '/' route 5 | * 6 | * NOTE: while this component should technically be a stateless functional 7 | * component (SFC), hot reloading does not currently support SFCs. If hot 8 | * reloading is not a necessity for you then you can refactor it and remove 9 | * the linting exception. 10 | */ 11 | 12 | import React from 'react'; 13 | import { FormattedMessage } from 'react-intl'; 14 | import messages from './messages'; 15 | 16 | /* eslint-disable react/prefer-stateless-function */ 17 | export default class HomePage extends React.PureComponent { 18 | render() { 19 | return ( 20 |

21 | 22 |

23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/components/List/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Ul from './Ul'; 5 | import Wrapper from './Wrapper'; 6 | 7 | function List(props) { 8 | const ComponentToRender = props.component; 9 | let content =
; 10 | 11 | // If we have items, render them 12 | if (props.items) { 13 | content = props.items.map(item => ( 14 | 15 | )); 16 | } else { 17 | // Otherwise render a single component 18 | content = ; 19 | } 20 | 21 | return ( 22 | 23 |
    {content}
24 |
25 | ); 26 | } 27 | 28 | List.propTypes = { 29 | component: PropTypes.func.isRequired, 30 | items: PropTypes.array, 31 | }; 32 | 33 | export default List; 34 | -------------------------------------------------------------------------------- /app/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | 4 | import A from 'components/A'; 5 | import LocaleToggle from 'containers/LocaleToggle'; 6 | import Wrapper from './Wrapper'; 7 | import messages from './messages'; 8 | 9 | function Footer() { 10 | return ( 11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | Max Stoiber, 23 | }} 24 | /> 25 |
26 | 27 | ); 28 | } 29 | 30 | export default Footer; 31 | -------------------------------------------------------------------------------- /app/containers/App/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { Route } from 'react-router-dom'; 4 | 5 | import Header from 'components/Header'; 6 | import Footer from 'components/Footer'; 7 | import App from '../index'; 8 | 9 | describe('', () => { 10 | it('should render the header', () => { 11 | const renderedComponent = shallow(); 12 | expect(renderedComponent.find(Header).length).toBe(1); 13 | }); 14 | 15 | it('should render some routes', () => { 16 | const renderedComponent = shallow(); 17 | expect(renderedComponent.find(Route).length).not.toBe(0); 18 | }); 19 | 20 | it('should render the footer', () => { 21 | const renderedComponent = shallow(); 22 | expect(renderedComponent.find(Footer).length).toBe(1); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /app/containers/HomePage/tests/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { selectHome, makeSelectUsername } from '../selectors'; 4 | 5 | describe('selectHome', () => { 6 | it('should select the home state', () => { 7 | const homeState = fromJS({ 8 | userData: {}, 9 | }); 10 | const mockedState = fromJS({ 11 | home: homeState, 12 | }); 13 | expect(selectHome(mockedState)).toEqual(homeState); 14 | }); 15 | }); 16 | 17 | describe('makeSelectUsername', () => { 18 | const usernameSelector = makeSelectUsername(); 19 | it('should select the username', () => { 20 | const username = 'mxstbr'; 21 | const mockedState = fromJS({ 22 | home: { 23 | username, 24 | }, 25 | }); 26 | expect(usernameSelector(mockedState)).toEqual(username); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /internals/templates/containers/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NotFoundPage 3 | * 4 | * This is the page we show when the user visits a url that doesn't have a route 5 | * 6 | * NOTE: while this component should technically be a stateless functional 7 | * component (SFC), hot reloading does not currently support SFCs. If hot 8 | * reloading is not a necessity for you then you can refactor it and remove 9 | * the linting exception. 10 | */ 11 | 12 | import React from 'react'; 13 | import { FormattedMessage } from 'react-intl'; 14 | 15 | import messages from './messages'; 16 | 17 | /* eslint-disable react/prefer-stateless-function */ 18 | export default class NotFound extends React.PureComponent { 19 | render() { 20 | return ( 21 |

22 | 23 |

24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/tests/reducers.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test route reducer 3 | */ 4 | 5 | import { fromJS } from 'immutable'; 6 | import { LOCATION_CHANGE } from 'react-router-redux'; 7 | import { routeReducer } from '../reducers'; 8 | 9 | describe('route reducer', () => { 10 | it('should return the initial state', () => { 11 | const initialState = { foo: 'bar' }; 12 | expect(routeReducer(initialState, {})).toEqual(initialState); 13 | }); 14 | 15 | it('should handle the location_change action correctly', () => { 16 | const state = fromJS({ location: 'somewhere' }); 17 | const payload = 'elsewhere'; 18 | const action = { type: LOCATION_CHANGE, payload }; 19 | 20 | const expectedState = { location: payload }; 21 | const resultState = routeReducer(state, action).toJS(); 22 | expect(resultState).toEqual(expectedState); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /internals/templates/tests/reducers.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test route reducer 3 | */ 4 | 5 | import { fromJS } from 'immutable'; 6 | import { LOCATION_CHANGE } from 'react-router-redux'; 7 | import { routeReducer } from '../reducers'; 8 | 9 | describe('route reducer', () => { 10 | it('should return the initial state', () => { 11 | const initialState = { foo: 'bar' }; 12 | expect(routeReducer(initialState, {})).toEqual(initialState); 13 | }); 14 | 15 | it('should handle the location_change action correctly', () => { 16 | const state = fromJS({ location: 'somewhere' }); 17 | const payload = 'elsewhere'; 18 | const action = { type: LOCATION_CHANGE, payload }; 19 | 20 | const expectedState = { location: payload }; 21 | const resultState = routeReducer(state, action).toJS(); 22 | expect(resultState).toEqual(expectedState); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /app/containers/FeaturePage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { FormattedMessage } from 'react-intl'; 4 | 5 | import H1 from 'components/H1'; 6 | import messages from '../messages'; 7 | import FeaturePage from '../index'; 8 | 9 | describe('', () => { 10 | it('should render its heading', () => { 11 | const renderedComponent = shallow(); 12 | expect( 13 | renderedComponent.contains( 14 |

15 | 16 |

, 17 | ), 18 | ).toBe(true); 19 | }); 20 | 21 | it('should never re-render the component', () => { 22 | const renderedComponent = shallow(); 23 | const inst = renderedComponent.instance(); 24 | expect(inst.shouldComponentUpdate()).toBe(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /app/components/Toggle/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * LocaleToggle 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | 10 | import Select from './Select'; 11 | import ToggleOption from '../ToggleOption'; 12 | 13 | function Toggle(props) { 14 | let content = ; 15 | 16 | // If we have items, render them 17 | if (props.values) { 18 | content = props.values.map(value => ( 19 | 20 | )); 21 | } 22 | 23 | return ( 24 | 27 | ); 28 | } 29 | 30 | Toggle.propTypes = { 31 | onToggle: PropTypes.func, 32 | values: PropTypes.array, 33 | value: PropTypes.string, 34 | messages: PropTypes.object, 35 | }; 36 | 37 | export default Toggle; 38 | -------------------------------------------------------------------------------- /app/tests/i18n.test.js: -------------------------------------------------------------------------------- 1 | import { formatTranslationMessages } from '../i18n'; 2 | 3 | jest.mock('../translations/en.json', () => ({ 4 | message1: 'default message', 5 | message2: 'default message 2', 6 | })); 7 | 8 | const esTranslationMessages = { 9 | message1: 'mensaje predeterminado', 10 | message2: '', 11 | }; 12 | 13 | describe('formatTranslationMessages', () => { 14 | it('should build only defaults when DEFAULT_LOCALE', () => { 15 | const result = formatTranslationMessages('en', { a: 'a' }); 16 | 17 | expect(result).toEqual({ a: 'a' }); 18 | }); 19 | 20 | it('should combine default locale and current locale when not DEFAULT_LOCALE', () => { 21 | const result = formatTranslationMessages('', esTranslationMessages); 22 | 23 | expect(result).toEqual({ 24 | message1: 'mensaje predeterminado', 25 | message2: 'default message 2', 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /app/containers/HomePage/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomeReducer 3 | * 4 | * The reducer takes care of our data. Using actions, we can change our 5 | * application state. 6 | * To add a new action, add it to the switch statement in the reducer function 7 | * 8 | * Example: 9 | * case YOUR_ACTION_CONSTANT: 10 | * return state.set('yourStateVariable', true); 11 | */ 12 | import { fromJS } from 'immutable'; 13 | 14 | import { CHANGE_USERNAME } from './constants'; 15 | 16 | // The initial state of the App 17 | export const initialState = fromJS({ 18 | username: '', 19 | }); 20 | 21 | function homeReducer(state = initialState, action) { 22 | switch (action.type) { 23 | case CHANGE_USERNAME: 24 | // Delete prefixed '@' from the github username 25 | return state.set('username', action.name.replace(/@/gi, '')); 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | export default homeReducer; 32 | -------------------------------------------------------------------------------- /internals/templates/tests/i18n.test.js: -------------------------------------------------------------------------------- 1 | import { formatTranslationMessages } from '../i18n'; 2 | 3 | jest.mock('../translations/en.json', () => ({ 4 | message1: 'default message', 5 | message2: 'default message 2', 6 | })); 7 | 8 | const esTranslationMessages = { 9 | message1: 'mensaje predeterminado', 10 | message2: '', 11 | }; 12 | 13 | describe('formatTranslationMessages', () => { 14 | it('should build only defaults when DEFAULT_LOCALE', () => { 15 | const result = formatTranslationMessages('en', { a: 'a' }); 16 | 17 | expect(result).toEqual({ a: 'a' }); 18 | }); 19 | 20 | it('should combine default locale and current locale when not DEFAULT_LOCALE', () => { 21 | const result = formatTranslationMessages('', esTranslationMessages); 22 | 23 | expect(result).toEqual({ 24 | message1: 'mensaje predeterminado', 25 | message2: 'default message 2', 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /internals/templates/tests/store.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test store addons 3 | */ 4 | 5 | import { browserHistory } from 'react-router'; 6 | import configureStore from '../configureStore'; 7 | 8 | describe('configureStore', () => { 9 | let store; 10 | 11 | beforeAll(() => { 12 | store = configureStore({}, browserHistory); 13 | }); 14 | 15 | describe('injectedReducers', () => { 16 | it('should contain an object for reducers', () => { 17 | expect(typeof store.injectedReducers).toBe('object'); 18 | }); 19 | }); 20 | 21 | describe('injectedSagas', () => { 22 | it('should contain an object for sagas', () => { 23 | expect(typeof store.injectedSagas).toBe('object'); 24 | }); 25 | }); 26 | 27 | describe('runSaga', () => { 28 | it('should contain a hook for `sagaMiddleware.run`', () => { 29 | expect(typeof store.runSaga).toEqual('function'); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /app/components/Button/tests/A.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import A from '../A'; 5 | 6 | describe('', () => { 7 | it('should render an tag', () => { 8 | const renderedComponent = shallow(); 9 | expect(renderedComponent.type()).toEqual('a'); 10 | }); 11 | 12 | it('should have a className attribute', () => { 13 | const renderedComponent = shallow(); 14 | expect(renderedComponent.prop('className')).toBeDefined(); 15 | }); 16 | 17 | it('should adopt a valid attribute', () => { 18 | const id = 'test'; 19 | const renderedComponent = shallow(); 20 | expect(renderedComponent.prop('id')).toEqual(id); 21 | }); 22 | 23 | it('should not adopt an invalid attribute', () => { 24 | const renderedComponent = shallow(); 25 | expect(renderedComponent.prop('attribute')).toBeUndefined(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /app/components/List/tests/Ul.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Ul from '../Ul'; 5 | 6 | describe('