├── src ├── components │ ├── molecules │ │ └── .gitkeep │ ├── atoms │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── TestSample.test.js.snap │ │ │ ├── TestSample.test.js │ │ │ └── StorySample.stories.js │ │ ├── Text.js │ │ ├── Pane.js │ │ └── Link.js │ ├── pages │ │ ├── About.js │ │ ├── Home.js │ │ └── Counter.js │ ├── organisms │ │ ├── Header.js │ │ └── Menu.js │ └── App.js ├── reducers │ ├── index.js │ └── counter.js ├── store │ └── createStore.js ├── hocs │ ├── WithState.js │ └── withCounter.js ├── index.js ├── helpers │ └── scrollHelpers.js ├── router.js └── routes │ └── index.js ├── .prettierignore ├── public ├── _redirects └── index.html ├── .eslintignore ├── netlify.toml ├── flow-typed ├── global.js.flow └── npm │ ├── redux.js │ ├── react-redux.js │ ├── recompose.js │ └── jest.js ├── .flowconfig ├── .storybook ├── config.js └── webpack.config.js ├── script └── start-dev-layout ├── test └── helpers.js ├── .eslintrc.yml ├── README.md ├── webpack.config.js ├── .babelrc.js ├── .gitignore └── package.json /src/components/molecules/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | flow-typed 3 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | flow-typed/npm 2 | webpack.config.js 3 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "yarn build:ci" 4 | -------------------------------------------------------------------------------- /flow-typed/global.js.flow: -------------------------------------------------------------------------------- 1 | declare type __ReturnType Promise | B> = B 2 | declare type $ReturnType = __ReturnType<*, F> 3 | -------------------------------------------------------------------------------- /src/components/atoms/__tests__/__snapshots__/TestSample.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should match snapshot 1`] = `undefined`; 4 | -------------------------------------------------------------------------------- /src/components/pages/About.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import * as React from 'react' 3 | 4 | export default function About() { 5 | return

This is a react-redux SPA project exmaple.

6 | } 7 | -------------------------------------------------------------------------------- /src/components/pages/Home.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import * as React from 'react' 3 | import Text from '~/components/atoms/Text' 4 | 5 | export default function Home() { 6 | return Hello1! 7 | } 8 | -------------------------------------------------------------------------------- /src/components/organisms/Header.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import Pane from '~/components/atoms/Pane' 4 | 5 | export default function Header() { 6 | return header 7 | } 8 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | module.name_mapper='^~/\(.*\)$' -> '/src/\1' 11 | module.name_mapper='^test/\(.*\)$' -> '/test/\1' 12 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react' 2 | 3 | const req = require.context('../src/components/', true, /stories\.js$/) 4 | const loadStories = req.keys().forEach(req) 5 | 6 | configure(loadStories, module) 7 | -------------------------------------------------------------------------------- /script/start-dev-layout: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # for tmux user 3 | tmux split-pane -h \; send-keys "yarn glow" Enter 4 | tmux split-pane -v \; send-keys "yarn test:watch" Enter 5 | tmux split-pane -v \; send-keys "yarn watch" Enter 6 | -------------------------------------------------------------------------------- /src/components/atoms/__tests__/TestSample.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import { snapshot } from 'test/helpers' 4 | 5 | function TestSample() { 6 | return test-sample 7 | } 8 | 9 | snapshot('test sample', ) 10 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/atoms/Text.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import styled from 'styled-components' 3 | 4 | export default styled.div` 5 | width: 100%; 6 | height: 100%; 7 | box-sizing: border-box; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | ` 12 | -------------------------------------------------------------------------------- /src/components/atoms/__tests__/StorySample.stories.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import { storiesOf } from '@storybook/react' 4 | 5 | export default function StorySample() { 6 | return story-sample 7 | } 8 | 9 | storiesOf('StorySample', module).add('to Storybook', () => ) 10 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { combineReducers } from 'redux' 3 | import counter, { type Action as CounterAction } from './counter' 4 | 5 | export type Action = CounterAction 6 | 7 | export type State = { 8 | counter: $Call 9 | } 10 | 11 | export default combineReducers({ counter }) 12 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | const defaultConfig = require('../webpack.config') 3 | const getStorybookConfig = require('@storybook/react/dist/server/config/defaults/webpack.config.js'); 4 | 5 | module.exports = (config, env) => { 6 | const newConfig = getStorybookConfig(config, env) 7 | newConfig.module.rules = defaultConfig.module.rules 8 | return newConfig 9 | } 10 | -------------------------------------------------------------------------------- /src/components/pages/Counter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import withCounter from '~/hocs/withCounter' 4 | import Pane from '~/components/atoms/Pane' 5 | 6 | export default withCounter(props => { 7 | return ( 8 | 9 | 10 | {props.value} 11 | 12 | ) 13 | }) 14 | -------------------------------------------------------------------------------- /src/components/organisms/Menu.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import Link from '~/components/atoms/Link' 4 | import Pane from '~/components/atoms/Pane' 5 | 6 | export default function Menu() { 7 | return ( 8 | 9 | Home 10 | | 11 | Counter 12 | | 13 | About 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { shallow, configure } from 'enzyme' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | import toJson from 'enzyme-to-json' 5 | 6 | configure({ adapter: new Adapter() }) 7 | 8 | export const elementToJson = (element: any) => toJson(shallow(element)) 9 | 10 | export const snapshot = (title: string, element: any) => 11 | it('should match snapshot', () => { 12 | expect(elementToJson(element)).toMatchSnapshot() 13 | }) 14 | -------------------------------------------------------------------------------- /src/store/createStore.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { createStore, applyMiddleware } from 'redux' 3 | import promiseMiddleware from 'redux-promise' 4 | import loggerMiddleware from 'redux-logger' 5 | import thunkMiddleware from 'redux-thunk' 6 | import rootReducer from '~/reducers' 7 | 8 | let _store 9 | export default () => { 10 | if (_store) { 11 | return _store 12 | } 13 | return (_store = createStore( 14 | rootReducer, 15 | applyMiddleware(loggerMiddleware, promiseMiddleware, thunkMiddleware) 16 | )) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/atoms/Pane.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import styled from 'styled-components' 3 | 4 | export default styled.div` 5 | width: 100%; 6 | height: 100%; 7 | box-sizing: border-box; 8 | border-right: ${props => 9 | props.noBorder ? 'none' : '1px solid rgba(0, 0, 0, 0.2)'}; 10 | border-bottom: ${props => 11 | props.noBorder ? 'none' : '1px solid rgba(0, 0, 0, 0.2)'}; 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | background-color: ${props => props.backgroundColor || 'transparent'}; 16 | ` 17 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parser: 'babel-eslint' 2 | plugins: 3 | - react 4 | - prettier 5 | extends: 6 | - plugin:react/recommended 7 | - prettier 8 | - prettier/flowtype 9 | - prettier/react 10 | rules: 11 | prettier/prettier: 12 | - 2 13 | - 14 | trailingComma: none 15 | singleQuote: true 16 | semi: false 17 | no-empty-pattern: 0 18 | prefer-const: 2 19 | prefer-arrow-callback: 2 20 | no-unused-vars: 21 | - 2 22 | - 23 | argsIgnorePattern: ^_ 24 | varsIgnorePattern: ^_ 25 | react/jsx-uses-vars: 2 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fe-base 2 | 3 | @mizchi's frontend boilerplate 4 | 5 | ## Stack 6 | 7 | * babel + flowtype 8 | * react 9 | * react-redux 10 | * react-router 11 | * recompose 12 | * webpack 13 | * prettier 14 | * jest 15 | * netlify 16 | 17 | ## Start to develop 18 | 19 | ```sh 20 | yarn install 21 | yarn watch 22 | # open localhost:4444 23 | ``` 24 | 25 | ## Deploy to netlify 26 | 27 | ``` 28 | npm i -g netlify-cli 29 | netlify create 30 | yarn deploy 31 | ``` 32 | 33 | ## TODO 34 | 35 | * HMR is broken by webpack v4 & react-hot-loader 36 | 37 | ## License 38 | 39 | MIT 40 | -------------------------------------------------------------------------------- /src/hocs/WithState.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React, { type Node } from 'react' 3 | 4 | // Generic State Manager 5 | export default class WithState extends React.Component< 6 | { 7 | render: (State, ((State) => State) => void) => Node 8 | }, 9 | State 10 | > { 11 | render() { 12 | return this.props.render(this.state, this.setState.bind(this)) 13 | } 14 | } 15 | 16 | /* Example: 17 | type State = { value: number } 18 | const initialState: State = { value: 0 } 19 | State) => void) => { 21 | return {state.value} 22 | }} 23 | /> 24 | */ 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import 'babel-polyfill' 3 | import { injectGlobal } from 'styled-components' 4 | import { startHistory } from './router' 5 | 6 | injectGlobal` 7 | html, body, main { 8 | margin: 0; 9 | padding: 0; 10 | width: 100%; 11 | height: 100vh; 12 | } 13 | 14 | ::-webkit-scrollbar { 15 | width: 4px; 16 | } 17 | 18 | ::-webkit-scrollbar-track { 19 | border-radius: 4px; 20 | box-shadow: inset 0 0 5px rgba(255, 255, 255, .1); 21 | } 22 | 23 | ::-webkit-scrollbar-thumb { 24 | background-color: rgba(255, 255, 255, .5); 25 | border-radius: 4px; 26 | box-shadow:0 0 0 1px rgba(255, 255, 255, .3); 27 | } 28 | ` 29 | 30 | startHistory() 31 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const pkg = require('./package') 4 | const DEV_PORT = process.env.PORT || 4444 5 | 6 | module.exports = { 7 | entry: { 8 | app: [ 9 | './src/index.js' 10 | ] 11 | }, 12 | output: { 13 | filename: '[name].bundle.js', 14 | chunkFilename: '[name].bundle.js', 15 | path: __dirname + '/public', 16 | publicPath: '/' 17 | }, 18 | devServer: { 19 | contentBase: 'public/', 20 | historyApiFallback: true, 21 | port: DEV_PORT 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.js$/, 27 | use: 'babel-loader', 28 | exclude: /node_modules/ 29 | } 30 | ] 31 | }, 32 | plugins: [] 33 | } 34 | -------------------------------------------------------------------------------- /src/reducers/counter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | // Constants 4 | export const INCREMENT = 'counter/increment' 5 | 6 | // Action Creators 7 | export function increment() { 8 | return { 9 | type: INCREMENT 10 | } 11 | } 12 | 13 | export async function incrementAsync() { 14 | return { 15 | type: INCREMENT 16 | } 17 | } 18 | 19 | // Reducer 20 | export type Action = $Call 21 | 22 | export type State = { 23 | value: number 24 | } 25 | 26 | const initialState: State = { 27 | value: 0 28 | } 29 | 30 | // Reducer 31 | export default (state: State = initialState, action: Action): State => { 32 | switch (action.type) { 33 | case INCREMENT: { 34 | return { ...state, value: state.value + 1 } 35 | } 36 | default: { 37 | return state 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/hocs/withCounter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { compose, lifecycle, pure, type HOC } from 'recompose' 5 | import * as CounterActions from '~/reducers/counter' 6 | import { type State as RootState } from '~/reducers' 7 | 8 | type OuterProps = {} 9 | 10 | type Props = { 11 | value: number, 12 | actions: typeof CounterActions 13 | } 14 | 15 | const connector = connect( 16 | (state: RootState, _props) => { 17 | return state.counter 18 | }, 19 | dispatch => ({ actions: bindActionCreators({ ...CounterActions }, dispatch) }) 20 | ) 21 | 22 | const withCounter: HOC = compose( 23 | connector, 24 | pure, 25 | lifecycle({ 26 | componentDidMount() { 27 | console.log('mounted') 28 | } 29 | }) 30 | ) 31 | 32 | export default withCounter 33 | -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | const plugins = [ 2 | 'react-hot-loader/babel', 3 | '@babel/plugin-proposal-object-rest-spread', 4 | [ 5 | 'module-resolver', 6 | { 7 | root: ['./src'], 8 | alias: { 9 | '~': './src', 10 | test: './test' 11 | } 12 | } 13 | ] 14 | ] 15 | 16 | const presets = ['@babel/preset-flow', '@babel/preset-react'] 17 | 18 | const presetsByEnv = { 19 | development: [['@babel/preset-env', { targets: { chrome: 65 } }]], 20 | test: [['@babel/preset-env', { targets: { node: '8.5' } }]], 21 | production: [ 22 | [ 23 | '@babel/preset-env', 24 | { 25 | modules: false, 26 | browsers: ['last 2 versions', 'ie >= 11'] 27 | } 28 | ] 29 | ] 30 | } 31 | 32 | module.exports = { 33 | plugins, 34 | presets: [...presets, ...presetsByEnv[process.env.NODE_ENV || 'development']] 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/160d27e2bebf784c4f4a1e070df057f3868b62bc/Node.gitignore 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional eslint cache 40 | .eslintcache 41 | 42 | # Optional REPL history 43 | .node_repl_history 44 | 45 | # Output of 'npm pack' 46 | *.tgz 47 | 48 | # Yarn Integrity file 49 | .yarn-integrity 50 | 51 | # netlify 52 | public/* 53 | !public/_redirects 54 | !public/index.html 55 | .netlify 56 | -------------------------------------------------------------------------------- /src/components/atoms/Link.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import { history } from '../../router' 4 | 5 | function isLeftClickEvent(event: any) { 6 | return event.button === 0 7 | } 8 | 9 | function isModifiedEvent(event: any) { 10 | return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) 11 | } 12 | 13 | const handleClick = ({ onClick, to }) => event => { 14 | if (onClick) { 15 | onClick(event) 16 | } 17 | 18 | if (isModifiedEvent(event) || !isLeftClickEvent(event)) { 19 | return 20 | } 21 | 22 | if (event.defaultPrevented === true) { 23 | return 24 | } 25 | 26 | event.preventDefault() 27 | if ( 28 | to !== history.createHref(history.location).replace(location.origin, '') 29 | ) { 30 | history.push(to) 31 | } else { 32 | // console.info('reject transition by same url') 33 | } 34 | } 35 | 36 | type Props = { 37 | to: string, 38 | children: any, 39 | onClick?: Function 40 | } 41 | 42 | export default function Link(props: Props) { 43 | const { to, children, onClick, ...others } = props 44 | return ( 45 | 46 | {children} 47 | 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import { Provider } from 'react-redux' 5 | import Header from './organisms/Header' 6 | import Menu from './organisms/Menu' 7 | import { hot } from 'react-hot-loader' 8 | 9 | // TODO: HMR does not work 10 | export default hot(module)((props: { store: any, children: any }) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | {props.children} 21 | 22 | 23 | ) 24 | }) 25 | 26 | // prettier-ignore 27 | export const Layout = styled.div` 28 | width: 100%; 29 | height: 100%; 30 | display: grid; 31 | grid-template-columns: 230px 1fr; 32 | grid-template-rows: 33 | 5vh 34 | 95vh 35 | ; 36 | grid-template-areas: 37 | 'menu header' 38 | 'menu content' 39 | ; 40 | ` 41 | 42 | export const Layout$Menu = styled.div` 43 | background-color: #ddd; 44 | grid-area: menu; 45 | ` 46 | 47 | export const Layout$Header = styled.div` 48 | background-color: #eee; 49 | grid-area: header; 50 | ` 51 | 52 | export const Layout$Content = styled.div` 53 | grid-area: content; 54 | ` 55 | -------------------------------------------------------------------------------- /src/helpers/scrollHelpers.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* Scroll position controller */ 3 | const scrollPositionsHistory: { 4 | [string]: { scrollX: number, scrollY: number } 5 | } = {} 6 | export const updateScrollPosition = (location: { key: string }) => { 7 | scrollPositionsHistory[location.key] = { 8 | scrollX: (window: any).pageXOffset, 9 | scrollY: (window: any).pageYOffset 10 | } 11 | } 12 | 13 | export const deletePosition = (location: { key: string }) => { 14 | delete scrollPositionsHistory[location.key] 15 | } 16 | 17 | export const restoreScollPosition = (location: { hash: string }) => { 18 | let scrollX = 0 19 | let scrollY = 0 20 | const pos = scrollPositionsHistory[(location: any).key] 21 | if (pos) { 22 | scrollX = (pos: any).scrollX 23 | scrollY = (pos: any).scrollY 24 | } else { 25 | const targetHash = location.hash.substr(1) 26 | if (targetHash) { 27 | const target = document.getElementById(targetHash) 28 | if (target) { 29 | scrollY = window.pageYOffset + target.getBoundingClientRect().top 30 | } 31 | } 32 | } 33 | // Restore the scroll position if it was saved into the state 34 | // or scroll to the given #hash anchor 35 | // or scroll to top of the page 36 | window.scrollTo(scrollX, scrollY) 37 | } 38 | 39 | let _off = false 40 | export const switchOffScrollRestorationOnce = () => { 41 | if (_off) { 42 | return 43 | } 44 | // Switch off the native scroll restoration behavior and handle it manually 45 | // https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration 46 | if (window.history && 'scrollRestoration' in window.history) { 47 | window.history.scrollRestoration = 'manual' 48 | } 49 | _off = true 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import UniversalRouter from 'universal-router' 5 | import routes from './routes' 6 | import createHistory from 'history/createBrowserHistory' 7 | import createStore from './store/createStore' 8 | import * as scrollHelpers from './helpers/scrollHelpers' 9 | 10 | const router = new UniversalRouter(routes, { 11 | context: { 12 | store: createStore() 13 | } 14 | }) 15 | 16 | export default router 17 | 18 | export const history = createHistory() 19 | 20 | let _rootElement // root element 21 | const getRootElement = () => 22 | _rootElement || (_rootElement = document.querySelector('.root')) 23 | 24 | const onLocationChange = async (location, action): Promise => { 25 | scrollHelpers.updateScrollPosition(location) 26 | try { 27 | const result = await router.resolve(location) 28 | if (result.redirect) { 29 | history.replace(result.redirect) 30 | return 31 | } 32 | 33 | if (action === 'PUSH') { 34 | scrollHelpers.deletePosition(location) 35 | } 36 | 37 | if (result.title && typeof document !== 'undefined') { 38 | document.title = result.title 39 | } 40 | 41 | const el = getRootElement() 42 | if (el && React.isValidElement(result.component)) { 43 | // For HMR 44 | // https://github.com/nozzle/react-static/issues/144#issuecomment-348270365 45 | // const render = !!module.hot ? ReactDOM.render : ReactDOM.hydrate 46 | ReactDOM.render(result.component, el, () => { 47 | scrollHelpers.switchOffScrollRestorationOnce() 48 | scrollHelpers.restoreScollPosition(history.location) 49 | }) 50 | } else { 51 | // not react 52 | } 53 | } catch (e) { 54 | // or render 404 55 | console.error(e) 56 | } 57 | } 58 | 59 | export const startHistory = () => { 60 | history.listen(onLocationChange) 61 | onLocationChange(history.location) 62 | } 63 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable react/display-name */ 3 | import React from 'react' 4 | import App from '~/components/App' 5 | import Home from '~/components/pages/Home' 6 | import About from '~/components/pages/About' 7 | import Counter from '~/components/pages/Counter' 8 | import * as CounterActions from '~/reducers/counter' 9 | 10 | type Context = { 11 | store: { 12 | dispatch: Function 13 | } 14 | } 15 | 16 | type RouteAction = 17 | | { 18 | title: string, 19 | component: any 20 | } 21 | | { 22 | redirect: string 23 | } 24 | | void 25 | 26 | const routes: Array<{ 27 | path: string, 28 | action: Context => RouteAction | Promise 29 | }> = [ 30 | { 31 | path: '', 32 | action: ctx => { 33 | return { 34 | title: 'Home', 35 | component: ( 36 | 37 | 38 | 39 | ) 40 | } 41 | } 42 | }, 43 | { 44 | path: '/about', 45 | action: ctx => { 46 | return { 47 | title: 'About', 48 | component: ( 49 | 50 | 51 | 52 | ) 53 | } 54 | } 55 | }, 56 | { 57 | path: '/counter', 58 | action: async ctx => { 59 | const dispatch = ctx.store.dispatch 60 | dispatch(CounterActions.increment()) // sync 61 | await dispatch(CounterActions.incrementAsync()) // async 62 | return { 63 | title: 'Counter', 64 | component: ( 65 | 66 | 67 | 68 | ) 69 | } 70 | } 71 | }, 72 | { 73 | path: '/nested', 74 | children: [ 75 | { 76 | path: '/a', 77 | action: () => { 78 | console.log('a') 79 | return { 80 | title: 'a', 81 | component:

a

82 | } 83 | } 84 | }, 85 | { 86 | path: '/b', 87 | action: _ctx => { 88 | console.log('b') 89 | return { 90 | title: 'b', 91 | component:

b

92 | } 93 | } 94 | }, 95 | { 96 | path: '/c', 97 | action: _ctx => { 98 | return { redirect: '/nested/a' } 99 | } 100 | } 101 | ], 102 | action: _ctx => { 103 | console.log('nested action') 104 | } 105 | } 106 | ] 107 | 108 | export default routes 109 | -------------------------------------------------------------------------------- /flow-typed/npm/redux.js: -------------------------------------------------------------------------------- 1 | declare module 'redux' { 2 | /* 3 | S = State 4 | A = Action 5 | D = Dispatch 6 | */ 7 | 8 | declare export type DispatchAPI = (action: A) => A 9 | declare export type Dispatch }> = DispatchAPI 10 | 11 | declare export type MiddlewareAPI> = { 12 | dispatch: D, 13 | getState(): S 14 | } 15 | 16 | declare export type Store> = { 17 | // rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages) 18 | dispatch: D, 19 | getState(): S, 20 | subscribe(listener: () => void): () => void, 21 | replaceReducer(nextReducer: Reducer): void 22 | } 23 | 24 | declare export type Reducer = (state: S, action: A) => S 25 | 26 | declare export type CombinedReducer = ( 27 | state: ($Shape & {}) | void, 28 | action: A 29 | ) => S 30 | 31 | declare export type Middleware> = ( 32 | api: MiddlewareAPI 33 | ) => (next: D) => D 34 | 35 | declare export type StoreCreator> = { 36 | (reducer: Reducer, enhancer?: StoreEnhancer): Store, 37 | ( 38 | reducer: Reducer, 39 | preloadedState: S, 40 | enhancer?: StoreEnhancer 41 | ): Store 42 | } 43 | 44 | declare export type StoreEnhancer> = ( 45 | next: StoreCreator 46 | ) => StoreCreator 47 | 48 | declare export function createStore( 49 | reducer: Reducer, 50 | enhancer?: StoreEnhancer 51 | ): Store 52 | declare export function createStore( 53 | reducer: Reducer, 54 | preloadedState: S, 55 | enhancer?: StoreEnhancer 56 | ): Store 57 | 58 | declare export function applyMiddleware( 59 | ...middlewares: Array> 60 | ): StoreEnhancer 61 | 62 | declare export type ActionCreator = (...args: Array) => A 63 | declare export type ActionCreators = { [key: K]: ActionCreator } 64 | 65 | declare export function bindActionCreators< 66 | A, 67 | C: ActionCreator, 68 | D: DispatchAPI 69 | >( 70 | actionCreator: C, 71 | dispatch: D 72 | ): C 73 | declare export function bindActionCreators< 74 | A, 75 | K, 76 | C: ActionCreators, 77 | D: DispatchAPI 78 | >( 79 | actionCreators: C, 80 | dispatch: D 81 | ): C 82 | 83 | declare export function combineReducers( 84 | reducers: O 85 | ): CombinedReducer<$ObjMap(r: Reducer) => S>, A> 86 | 87 | declare export var compose: $Compose 88 | } 89 | 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fe-base", 3 | "private": true, 4 | "author": "mizchi ", 5 | "license": "MIT", 6 | "jest": { 7 | "testMatch": [ 8 | "**/__tests__/*.test.js" 9 | ] 10 | }, 11 | "scripts": { 12 | "lint": "eslint src", 13 | "test": "NODE_ENV=test jest", 14 | "test:watch": "yarn test --watch", 15 | "glow": "glow --watch", 16 | "test:cov": "yarn test --collectCoverage", 17 | "watch": "webpack-dev-server --hot --mode development", 18 | "build:dev": "webpack --mode development", 19 | "build": "NODE_ENV=production webpack --mode production", 20 | "build:size": "NODE_ENV=production webpack --json | webpack-bundle-size-analyzer", 21 | "build:ci": "yarn lint && yarn test:cov && yarn build", 22 | "deploy": "yarn build && netlify deploy", 23 | "storybook": "start-storybook -p 6006" 24 | }, 25 | "prettier": { 26 | "semi": false, 27 | "singleQuote": true 28 | }, 29 | "dependencies": { 30 | "axios": "^0.17.1", 31 | "babel-polyfill": "^6.26.0", 32 | "history": "^4.7.2", 33 | "react": "^16.2.0", 34 | "react-dom": "^16.2.0", 35 | "recompose": "^0.26.0", 36 | "redux": "^3.7.2", 37 | "redux-logger": "^3.0.6", 38 | "redux-promise": "^0.5.3", 39 | "redux-thunk": "^2.2.0", 40 | "styled-components": "^3.0.2", 41 | "universal-router": "^5.1.0" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.0.0-beta.40", 45 | "@babel/plugin-proposal-object-rest-spread": "7.0.0-beta.38", 46 | "@babel/plugin-syntax-dynamic-import": "7.0.0-beta.38", 47 | "@babel/polyfill": "^7.0.0-beta.40", 48 | "@babel/preset-env": "7.0.0-beta.38", 49 | "@babel/preset-flow": "7.0.0-beta.38", 50 | "@babel/preset-react": "7.0.0-beta.38", 51 | "@babel/register": "^7.0.0-beta.40", 52 | "@storybook/addon-actions": "^3.3.10", 53 | "@storybook/addon-links": "^3.3.10", 54 | "@storybook/addon-storyshots": "^3.3.10", 55 | "@storybook/react": "^3.3.10", 56 | "babel-core": "7.0.0-bridge.0", 57 | "babel-eslint": "^8.2.1", 58 | "babel-jest": "^22.2.2", 59 | "babel-loader": "^8.0.0-beta.0", 60 | "babel-plugin-module-resolver": "^3.0.0", 61 | "enzyme": "^3.3.0", 62 | "enzyme-adapter-react-16": "^1.0.1", 63 | "enzyme-to-json": "^3.3.1", 64 | "eslint": "^4.16.0", 65 | "eslint-config-prettier": "^2.6.0", 66 | "eslint-plugin-prettier": "^2.5.0", 67 | "eslint-plugin-react": "^7.6.0", 68 | "flow-bin": "^0.65.0", 69 | "glow": "^1.2.2", 70 | "jest": "^22.3.0", 71 | "netlify-cli": "^1.2.2", 72 | "prettier": "^1.10.2", 73 | "react-hot-loader": "^4.0.0-beta.22", 74 | "react-redux": "^5.0.6", 75 | "react-test-renderer": "^16.0.0", 76 | "regenerator-runtime": "^0.11.1", 77 | "webpack": "^4.0.0-beta.1", 78 | "webpack-cli": "^2.0.6", 79 | "webpack-dev-server": "3.0.0-beta.1" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /flow-typed/npm/react-redux.js: -------------------------------------------------------------------------------- 1 | import type { Dispatch, Store } from 'redux' 2 | 3 | declare module 'react-redux' { 4 | /* 5 | S = State 6 | A = Action 7 | OP = OwnProps 8 | SP = StateProps 9 | DP = DispatchProps 10 | */ 11 | 12 | declare type MapStateToProps = ( 13 | state: S, 14 | ownProps: OP 15 | ) => ((state: S, ownProps: OP) => SP) | SP 16 | 17 | declare type MapDispatchToProps = 18 | | ((dispatch: Dispatch, ownProps: OP) => DP) 19 | | DP 20 | 21 | declare type MergeProps = ( 22 | stateProps: SP, 23 | dispatchProps: DP, 24 | ownProps: OP 25 | ) => P 26 | 27 | declare type Context = { store: Store<*, *> } 28 | 29 | declare type ComponentWithDefaultProps = Class< 30 | React$Component 31 | > & { defaultProps: DP } 32 | 33 | declare class ConnectedComponentWithDefaultProps< 34 | OP, 35 | DP, 36 | CP 37 | > extends React$Component { 38 | static defaultProps: DP; // <= workaround for https://github.com/facebook/flow/issues/4644 39 | static WrappedComponent: Class>; 40 | getWrappedInstance(): React$Component; 41 | props: OP; 42 | state: void; 43 | } 44 | 45 | declare class ConnectedComponent extends React$Component { 46 | static WrappedComponent: Class>; 47 | getWrappedInstance(): React$Component

; 48 | props: OP; 49 | state: void; 50 | } 51 | 52 | declare type ConnectedComponentWithDefaultPropsClass = Class< 53 | ConnectedComponentWithDefaultProps 54 | > 55 | 56 | declare type ConnectedComponentClass = Class> 57 | 58 | declare type Connector = (( 59 | component: ComponentWithDefaultProps 60 | ) => ConnectedComponentWithDefaultPropsClass) & 61 | ((component: React$ComponentType

) => ConnectedComponentClass) 62 | 63 | declare class Provider extends React$Component<{ 64 | store: Store, 65 | children?: any 66 | }> {} 67 | 68 | declare function createProvider( 69 | storeKey?: string, 70 | subKey?: string 71 | ): Provider<*, *> 72 | 73 | declare type ConnectOptions = { 74 | pure?: boolean, 75 | withRef?: boolean 76 | } 77 | 78 | declare type Null = null | void 79 | 80 | declare function connect( 81 | ...rest: Array // <= workaround for https://github.com/facebook/flow/issues/2360 82 | ): Connector } & OP>> 83 | 84 | declare function connect( 85 | mapStateToProps: Null, 86 | mapDispatchToProps: Null, 87 | mergeProps: Null, 88 | options: ConnectOptions 89 | ): Connector } & OP>> 90 | 91 | declare function connect( 92 | mapStateToProps: MapStateToProps, 93 | mapDispatchToProps: Null, 94 | mergeProps: Null, 95 | options?: ConnectOptions 96 | ): Connector } & OP>> 97 | 98 | declare function connect( 99 | mapStateToProps: Null, 100 | mapDispatchToProps: MapDispatchToProps, 101 | mergeProps: Null, 102 | options?: ConnectOptions 103 | ): Connector> 104 | 105 | declare function connect( 106 | mapStateToProps: MapStateToProps, 107 | mapDispatchToProps: MapDispatchToProps, 108 | mergeProps: Null, 109 | options?: ConnectOptions 110 | ): Connector> 111 | 112 | declare function connect( 113 | mapStateToProps: MapStateToProps, 114 | mapDispatchToProps: Null, 115 | mergeProps: MergeProps, 116 | options?: ConnectOptions 117 | ): Connector 118 | 119 | declare function connect( 120 | mapStateToProps: MapStateToProps, 121 | mapDispatchToProps: MapDispatchToProps, 122 | mergeProps: MergeProps, 123 | options?: ConnectOptions 124 | ): Connector 125 | } 126 | 127 | -------------------------------------------------------------------------------- /flow-typed/npm/recompose.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | /** 4 | * 1) Types give additional constraint on a language, recompose was written on the untyped language 5 | * as a consequence of this fact 6 | * for some recompose HOCs is near impossible to add correct typings. 7 | * 2) flow sometimes does not work as expected. 8 | * 9 | * So any help and suggestions will be very appreciated. 10 | * 11 | * ----------------------------------------------------------------------------------- 12 | * Type definition of recompose HOCs are splitted into 2 parts, 13 | * "HOCs with good flow support" - in most cases you can use them without big issues, 14 | * see `test_${hocName}.js` for the idea. 15 | * Some known issues: 16 | * see test_mapProps.js - inference work but type errors are not detected in hocs 17 | * 18 | * SUPPORTED HOCs: 19 | * defaultProps, mapProps, withProps, withStateHandlers, withHandlers, pure, 20 | * onlyUpdateForKeys, shouldUpdate, renderNothing, renderComponent, branch, withPropsOnChange, 21 | * onlyUpdateForPropTypes, toClass, withContext, getContext, 22 | * setStatic, setPropTypes, setDisplayName, 23 | * ----------------------------------------------------------------------------------- 24 | * "TODO (UNSUPPORTED) HOCs" - you need to provide type information 25 | * (no automatic type inference), voodoo dancing etc 26 | * see `test_voodoo.js` for the idea 27 | * 28 | * remember that: 29 | * flattenProp,renameProp, renameProps can easily be replaced with withProps 30 | * withReducer, withState -> use withStateHandlers instead 31 | * lifecycle -> you don't need recompose if you need a lifecycle, just use React class instead 32 | * mapPropsStream -> see test_mapPropsStream.js 33 | * ----------------------------------------------------------------------------------- 34 | * 35 | * utils: 36 | * getDisplayName, wrapDisplayName, shallowEqual, 37 | * isClassComponent, createSink, componentFromProp, 38 | * nest, hoistStatics, 39 | */ 40 | 41 | //------------------- 42 | 43 | declare module 'recompose' { 44 | // ----------------------------------------------------------------- 45 | // Private declarations 46 | // ----------------------------------------------------------------- 47 | 48 | declare type Void_ R> = ( 49 | A, 50 | B, 51 | C, 52 | D 53 | ) => void 54 | 55 | declare type Void = Void_<*, *, *, *, *, T> 56 | 57 | declare type ExtractStateHandlersCodomain = ( 58 | v: (state: State, props: Enhanced) => V 59 | ) => Void 60 | 61 | declare type ExtractHandlersCodomain = ( 62 | v: (props: Enhanced) => V 63 | ) => V 64 | 65 | declare type UnaryFn = (a: A) => R 66 | 67 | // ----------------------------------------------------------------- 68 | // Public declarations 69 | // ----------------------------------------------------------------- 70 | 71 | declare export type Component = React$ComponentType 72 | 73 | declare export type HOC = UnaryFn< 74 | Component, 75 | Component 76 | > 77 | 78 | declare export var compose: $Compose 79 | 80 | // --------------------------------------------------------------------------- 81 | // ----------------===<<>>===-------------------- 82 | // --------------------------------------------------------------------------- 83 | 84 | declare export function defaultProps( 85 | defProps: Default 86 | ): HOC<{ ...$Exact, ...Default }, Enhanced> 87 | 88 | declare export function mapProps( 89 | propsMapper: (ownerProps: Enhanced) => Base 90 | ): HOC 91 | 92 | declare export function withProps( 93 | propsMapper: ((ownerProps: Enhanced) => BaseAdd) | BaseAdd 94 | ): HOC<{ ...$Exact, ...BaseAdd }, Enhanced> 95 | 96 | declare export function withStateHandlers< 97 | State, 98 | Enhanced, 99 | StateHandlers: { 100 | [key: string]: ( 101 | state: State, 102 | props: Enhanced 103 | ) => (...payload: any[]) => $Shape 104 | } 105 | >( 106 | initialState: ((props: Enhanced) => State) | State, 107 | stateUpdaters: StateHandlers 108 | ): HOC< 109 | { 110 | ...$Exact, 111 | ...$Exact, 112 | ...$ObjMap 113 | }, 114 | Enhanced 115 | > 116 | 117 | declare export function withHandlers< 118 | Enhanced, 119 | Handlers: 120 | | (( 121 | props: Enhanced 122 | ) => { 123 | [key: string]: (props: Enhanced) => Function 124 | }) 125 | | { 126 | [key: string]: (props: Enhanced) => Function 127 | } 128 | >( 129 | handlers: ((props: Enhanced) => Handlers) | Handlers 130 | ): HOC< 131 | { 132 | ...$Exact, 133 | ...$ObjMap 134 | }, 135 | Enhanced 136 | > 137 | 138 | declare export function pure(a: Component): Component 139 | declare export function onlyUpdateForPropTypes( 140 | a: Component 141 | ): Component 142 | declare export function onlyUpdateForKeys(Array<$Keys>): HOC 143 | declare export function shouldUpdate( 144 | (props: A, nextProps: A) => boolean 145 | ): HOC 146 | 147 | declare export function toClass(a: Component): Component 148 | 149 | declare export function withContext( 150 | childContextTypes: ContextPropTypes, 151 | getChildContext: (props: A) => ContextObj 152 | ): HOC 153 | 154 | declare export function getContext( 155 | contextTypes: CtxTypes 156 | ): HOC<{ ...$Exact, ...CtxTypes }, Enhanced> 157 | 158 | /** 159 | * It's wrong declaration but having that renderNothing and renderComponent are somehow useless 160 | * outside branch enhancer, we just give it an id type 161 | * so common way of using branch like 162 | * `branch(testFn, renderNothing | renderComponent(Comp))` will work as expected. 163 | * Tests are placed at test_branch. 164 | */ 165 | declare export function renderNothing(C: Component): Component 166 | declare export function renderComponent(a: Component): HOC 167 | 168 | /** 169 | * We make an assumtion that left and right have the same type if exists 170 | */ 171 | declare export function branch( 172 | testFn: (props: Enhanced) => boolean, 173 | // not a HOC because of inference problems, this works but HOC is not 174 | left: (Component) => Component, 175 | // I never use right part and it can be a problem with inference as should be same type as left 176 | right?: (Component) => Component 177 | ): HOC 178 | 179 | // test_statics 180 | declare export function setStatic(key: string, value: any): HOC 181 | declare export function setPropTypes(propTypes: Object): HOC 182 | declare export function setDisplayName(displayName: string): HOC 183 | 184 | declare export function withPropsOnChange( 185 | shouldMapOrKeys: 186 | | ((props: Enhanced, nextProps: Enhanced) => boolean) 187 | | Array<$Keys>, 188 | propsMapper: (ownerProps: Enhanced) => BaseAdd 189 | ): HOC<{ ...$Exact, ...BaseAdd }, Enhanced> 190 | 191 | // --------------------------------------------------------------------------- 192 | // ----------------===<<>>===------------------------ 193 | // --------------------------------------------------------------------------- 194 | 195 | // use withProps instead 196 | declare export function flattenProp( 197 | propName: $Keys 198 | ): HOC 199 | 200 | // use withProps instead 201 | declare export function renameProp( 202 | oldName: $Keys, 203 | newName: $Keys 204 | ): HOC 205 | 206 | // use withProps instead 207 | declare export function renameProps(nameMap: { 208 | [key: $Keys]: $Keys 209 | }): HOC 210 | 211 | // use withStateHandlers instead 212 | declare export function withState( 213 | stateName: string, 214 | stateUpdaterName: string, 215 | initialState: T | ((props: Enhanced) => T) 216 | ): HOC 217 | 218 | // use withStateHandlers instead 219 | declare export function withReducer( 220 | stateName: string, 221 | dispatchName: string, 222 | reducer: (state: State, action: Action) => State, 223 | initialState: State 224 | ): HOC 225 | 226 | // lifecycle use React instead 227 | declare export function lifecycle(spec: Object): HOC 228 | 229 | // Help needed, as explicitly providing the type 230 | // errors not detected, see TODO at test_mapPropsStream.js 231 | declare export function mapPropsStream( 232 | (props$: any) => any 233 | ): HOC 234 | 235 | // --------------------------------------------------------------------------- 236 | // -----------------------------===<<>>===----------------------------- 237 | // --------------------------------------------------------------------------- 238 | 239 | declare export function getDisplayName(C: Component): string 240 | 241 | declare export function wrapDisplayName( 242 | C: Component, 243 | wrapperName: string 244 | ): string 245 | 246 | declare export function shallowEqual(objA: mixed, objB: mixed): boolean 247 | 248 | declare export function isClassComponent(value: any): boolean 249 | 250 | declare export function createSink( 251 | callback: (props: A) => void 252 | ): Component 253 | 254 | declare export function componentFromProp(propName: string): Component 255 | 256 | declare export function nest( 257 | ...Components: Array | string> 258 | ): Component 259 | 260 | declare export function hoistStatics>(hoc: H): H 261 | 262 | declare export function componentFromStream( 263 | (props$: any) => any 264 | ): T => React$Element 265 | 266 | declare export function createEventHandler(): { 267 | stream: any, 268 | handler: Function 269 | } 270 | } 271 | 272 | -------------------------------------------------------------------------------- /flow-typed/npm/jest.js: -------------------------------------------------------------------------------- 1 | type JestMockFn, TReturn> = { 2 | (...args: TArguments): TReturn, 3 | /** 4 | * An object for introspecting mock calls 5 | */ 6 | mock: { 7 | /** 8 | * An array that represents all calls that have been made into this mock 9 | * function. Each call is represented by an array of arguments that were 10 | * passed during the call. 11 | */ 12 | calls: Array, 13 | /** 14 | * An array that contains all the object instances that have been 15 | * instantiated from this mock function. 16 | */ 17 | instances: Array 18 | }, 19 | /** 20 | * Resets all information stored in the mockFn.mock.calls and 21 | * mockFn.mock.instances arrays. Often this is useful when you want to clean 22 | * up a mock's usage data between two assertions. 23 | */ 24 | mockClear(): void, 25 | /** 26 | * Resets all information stored in the mock. This is useful when you want to 27 | * completely restore a mock back to its initial state. 28 | */ 29 | mockReset(): void, 30 | /** 31 | * Removes the mock and restores the initial implementation. This is useful 32 | * when you want to mock functions in certain test cases and restore the 33 | * original implementation in others. Beware that mockFn.mockRestore only 34 | * works when mock was created with jest.spyOn. Thus you have to take care of 35 | * restoration yourself when manually assigning jest.fn(). 36 | */ 37 | mockRestore(): void, 38 | /** 39 | * Accepts a function that should be used as the implementation of the mock. 40 | * The mock itself will still record all calls that go into and instances 41 | * that come from itself -- the only difference is that the implementation 42 | * will also be executed when the mock is called. 43 | */ 44 | mockImplementation( 45 | fn: (...args: TArguments) => TReturn 46 | ): JestMockFn, 47 | /** 48 | * Accepts a function that will be used as an implementation of the mock for 49 | * one call to the mocked function. Can be chained so that multiple function 50 | * calls produce different results. 51 | */ 52 | mockImplementationOnce( 53 | fn: (...args: TArguments) => TReturn 54 | ): JestMockFn, 55 | /** 56 | * Just a simple sugar function for returning `this` 57 | */ 58 | mockReturnThis(): void, 59 | /** 60 | * Deprecated: use jest.fn(() => value) instead 61 | */ 62 | mockReturnValue(value: TReturn): JestMockFn, 63 | /** 64 | * Sugar for only returning a value once inside your mock 65 | */ 66 | mockReturnValueOnce(value: TReturn): JestMockFn 67 | }; 68 | 69 | type JestAsymmetricEqualityType = { 70 | /** 71 | * A custom Jasmine equality tester 72 | */ 73 | asymmetricMatch(value: mixed): boolean 74 | }; 75 | 76 | type JestCallsType = { 77 | allArgs(): mixed, 78 | all(): mixed, 79 | any(): boolean, 80 | count(): number, 81 | first(): mixed, 82 | mostRecent(): mixed, 83 | reset(): void 84 | }; 85 | 86 | type JestClockType = { 87 | install(): void, 88 | mockDate(date: Date): void, 89 | tick(milliseconds?: number): void, 90 | uninstall(): void 91 | }; 92 | 93 | type JestMatcherResult = { 94 | message?: string | (() => string), 95 | pass: boolean 96 | }; 97 | 98 | type JestMatcher = (actual: any, expected: any) => JestMatcherResult; 99 | 100 | type JestPromiseType = { 101 | /** 102 | * Use rejects to unwrap the reason of a rejected promise so any other 103 | * matcher can be chained. If the promise is fulfilled the assertion fails. 104 | */ 105 | rejects: JestExpectType, 106 | /** 107 | * Use resolves to unwrap the value of a fulfilled promise so any other 108 | * matcher can be chained. If the promise is rejected the assertion fails. 109 | */ 110 | resolves: JestExpectType 111 | }; 112 | 113 | /** 114 | * Plugin: jest-enzyme 115 | */ 116 | type EnzymeMatchersType = { 117 | toBeChecked(): void, 118 | toBeDisabled(): void, 119 | toBeEmpty(): void, 120 | toBePresent(): void, 121 | toContainReact(element: React$Element): void, 122 | toHaveClassName(className: string): void, 123 | toHaveHTML(html: string): void, 124 | toHaveProp(propKey: string, propValue?: any): void, 125 | toHaveRef(refName: string): void, 126 | toHaveState(stateKey: string, stateValue?: any): void, 127 | toHaveStyle(styleKey: string, styleValue?: any): void, 128 | toHaveTagName(tagName: string): void, 129 | toHaveText(text: string): void, 130 | toIncludeText(text: string): void, 131 | toHaveValue(value: any): void, 132 | toMatchElement(element: React$Element): void, 133 | toMatchSelector(selector: string): void 134 | }; 135 | 136 | type JestExpectType = { 137 | not: JestExpectType & EnzymeMatchersType, 138 | /** 139 | * If you have a mock function, you can use .lastCalledWith to test what 140 | * arguments it was last called with. 141 | */ 142 | lastCalledWith(...args: Array): void, 143 | /** 144 | * toBe just checks that a value is what you expect. It uses === to check 145 | * strict equality. 146 | */ 147 | toBe(value: any): void, 148 | /** 149 | * Use .toHaveBeenCalled to ensure that a mock function got called. 150 | */ 151 | toBeCalled(): void, 152 | /** 153 | * Use .toBeCalledWith to ensure that a mock function was called with 154 | * specific arguments. 155 | */ 156 | toBeCalledWith(...args: Array): void, 157 | /** 158 | * Using exact equality with floating point numbers is a bad idea. Rounding 159 | * means that intuitive things fail. 160 | */ 161 | toBeCloseTo(num: number, delta: any): void, 162 | /** 163 | * Use .toBeDefined to check that a variable is not undefined. 164 | */ 165 | toBeDefined(): void, 166 | /** 167 | * Use .toBeFalsy when you don't care what a value is, you just want to 168 | * ensure a value is false in a boolean context. 169 | */ 170 | toBeFalsy(): void, 171 | /** 172 | * To compare floating point numbers, you can use toBeGreaterThan. 173 | */ 174 | toBeGreaterThan(number: number): void, 175 | /** 176 | * To compare floating point numbers, you can use toBeGreaterThanOrEqual. 177 | */ 178 | toBeGreaterThanOrEqual(number: number): void, 179 | /** 180 | * To compare floating point numbers, you can use toBeLessThan. 181 | */ 182 | toBeLessThan(number: number): void, 183 | /** 184 | * To compare floating point numbers, you can use toBeLessThanOrEqual. 185 | */ 186 | toBeLessThanOrEqual(number: number): void, 187 | /** 188 | * Use .toBeInstanceOf(Class) to check that an object is an instance of a 189 | * class. 190 | */ 191 | toBeInstanceOf(cls: Class<*>): void, 192 | /** 193 | * .toBeNull() is the same as .toBe(null) but the error messages are a bit 194 | * nicer. 195 | */ 196 | toBeNull(): void, 197 | /** 198 | * Use .toBeTruthy when you don't care what a value is, you just want to 199 | * ensure a value is true in a boolean context. 200 | */ 201 | toBeTruthy(): void, 202 | /** 203 | * Use .toBeUndefined to check that a variable is undefined. 204 | */ 205 | toBeUndefined(): void, 206 | /** 207 | * Use .toContain when you want to check that an item is in a list. For 208 | * testing the items in the list, this uses ===, a strict equality check. 209 | */ 210 | toContain(item: any): void, 211 | /** 212 | * Use .toContainEqual when you want to check that an item is in a list. For 213 | * testing the items in the list, this matcher recursively checks the 214 | * equality of all fields, rather than checking for object identity. 215 | */ 216 | toContainEqual(item: any): void, 217 | /** 218 | * Use .toEqual when you want to check that two objects have the same value. 219 | * This matcher recursively checks the equality of all fields, rather than 220 | * checking for object identity. 221 | */ 222 | toEqual(value: any): void, 223 | /** 224 | * Use .toHaveBeenCalled to ensure that a mock function got called. 225 | */ 226 | toHaveBeenCalled(): void, 227 | /** 228 | * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact 229 | * number of times. 230 | */ 231 | toHaveBeenCalledTimes(number: number): void, 232 | /** 233 | * Use .toHaveBeenCalledWith to ensure that a mock function was called with 234 | * specific arguments. 235 | */ 236 | toHaveBeenCalledWith(...args: Array): void, 237 | /** 238 | * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called 239 | * with specific arguments. 240 | */ 241 | toHaveBeenLastCalledWith(...args: Array): void, 242 | /** 243 | * Check that an object has a .length property and it is set to a certain 244 | * numeric value. 245 | */ 246 | toHaveLength(number: number): void, 247 | /** 248 | * 249 | */ 250 | toHaveProperty(propPath: string, value?: any): void, 251 | /** 252 | * Use .toMatch to check that a string matches a regular expression or string. 253 | */ 254 | toMatch(regexpOrString: RegExp | string): void, 255 | /** 256 | * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. 257 | */ 258 | toMatchObject(object: Object | Array): void, 259 | /** 260 | * This ensures that a React component matches the most recent snapshot. 261 | */ 262 | toMatchSnapshot(name?: string): void, 263 | /** 264 | * Use .toThrow to test that a function throws when it is called. 265 | * If you want to test that a specific error gets thrown, you can provide an 266 | * argument to toThrow. The argument can be a string for the error message, 267 | * a class for the error, or a regex that should match the error. 268 | * 269 | * Alias: .toThrowError 270 | */ 271 | toThrow(message?: string | Error | Class | RegExp): void, 272 | toThrowError(message?: string | Error | Class | RegExp): void, 273 | /** 274 | * Use .toThrowErrorMatchingSnapshot to test that a function throws a error 275 | * matching the most recent snapshot when it is called. 276 | */ 277 | toThrowErrorMatchingSnapshot(): void 278 | }; 279 | 280 | type JestObjectType = { 281 | /** 282 | * Disables automatic mocking in the module loader. 283 | * 284 | * After this method is called, all `require()`s will return the real 285 | * versions of each module (rather than a mocked version). 286 | */ 287 | disableAutomock(): JestObjectType, 288 | /** 289 | * An un-hoisted version of disableAutomock 290 | */ 291 | autoMockOff(): JestObjectType, 292 | /** 293 | * Enables automatic mocking in the module loader. 294 | */ 295 | enableAutomock(): JestObjectType, 296 | /** 297 | * An un-hoisted version of enableAutomock 298 | */ 299 | autoMockOn(): JestObjectType, 300 | /** 301 | * Clears the mock.calls and mock.instances properties of all mocks. 302 | * Equivalent to calling .mockClear() on every mocked function. 303 | */ 304 | clearAllMocks(): JestObjectType, 305 | /** 306 | * Resets the state of all mocks. Equivalent to calling .mockReset() on every 307 | * mocked function. 308 | */ 309 | resetAllMocks(): JestObjectType, 310 | /** 311 | * Restores all mocks back to their original value. 312 | */ 313 | restoreAllMocks(): JestObjectType, 314 | /** 315 | * Removes any pending timers from the timer system. 316 | */ 317 | clearAllTimers(): void, 318 | /** 319 | * The same as `mock` but not moved to the top of the expectation by 320 | * babel-jest. 321 | */ 322 | doMock(moduleName: string, moduleFactory?: any): JestObjectType, 323 | /** 324 | * The same as `unmock` but not moved to the top of the expectation by 325 | * babel-jest. 326 | */ 327 | dontMock(moduleName: string): JestObjectType, 328 | /** 329 | * Returns a new, unused mock function. Optionally takes a mock 330 | * implementation. 331 | */ 332 | fn, TReturn>( 333 | implementation?: (...args: TArguments) => TReturn 334 | ): JestMockFn, 335 | /** 336 | * Determines if the given function is a mocked function. 337 | */ 338 | isMockFunction(fn: Function): boolean, 339 | /** 340 | * Given the name of a module, use the automatic mocking system to generate a 341 | * mocked version of the module for you. 342 | */ 343 | genMockFromModule(moduleName: string): any, 344 | /** 345 | * Mocks a module with an auto-mocked version when it is being required. 346 | * 347 | * The second argument can be used to specify an explicit module factory that 348 | * is being run instead of using Jest's automocking feature. 349 | * 350 | * The third argument can be used to create virtual mocks -- mocks of modules 351 | * that don't exist anywhere in the system. 352 | */ 353 | mock( 354 | moduleName: string, 355 | moduleFactory?: any, 356 | options?: Object 357 | ): JestObjectType, 358 | /** 359 | * Returns the actual module instead of a mock, bypassing all checks on 360 | * whether the module should receive a mock implementation or not. 361 | */ 362 | requireActual(moduleName: string): any, 363 | /** 364 | * Returns a mock module instead of the actual module, bypassing all checks 365 | * on whether the module should be required normally or not. 366 | */ 367 | requireMock(moduleName: string): any, 368 | /** 369 | * Resets the module registry - the cache of all required modules. This is 370 | * useful to isolate modules where local state might conflict between tests. 371 | */ 372 | resetModules(): JestObjectType, 373 | /** 374 | * Exhausts the micro-task queue (usually interfaced in node via 375 | * process.nextTick). 376 | */ 377 | runAllTicks(): void, 378 | /** 379 | * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), 380 | * setInterval(), and setImmediate()). 381 | */ 382 | runAllTimers(): void, 383 | /** 384 | * Exhausts all tasks queued by setImmediate(). 385 | */ 386 | runAllImmediates(): void, 387 | /** 388 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 389 | * or setInterval() and setImmediate()). 390 | */ 391 | runTimersToTime(msToRun: number): void, 392 | /** 393 | * Executes only the macro-tasks that are currently pending (i.e., only the 394 | * tasks that have been queued by setTimeout() or setInterval() up to this 395 | * point) 396 | */ 397 | runOnlyPendingTimers(): void, 398 | /** 399 | * Explicitly supplies the mock object that the module system should return 400 | * for the specified module. Note: It is recommended to use jest.mock() 401 | * instead. 402 | */ 403 | setMock(moduleName: string, moduleExports: any): JestObjectType, 404 | /** 405 | * Indicates that the module system should never return a mocked version of 406 | * the specified module from require() (e.g. that it should always return the 407 | * real module). 408 | */ 409 | unmock(moduleName: string): JestObjectType, 410 | /** 411 | * Instructs Jest to use fake versions of the standard timer functions 412 | * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, 413 | * setImmediate and clearImmediate). 414 | */ 415 | useFakeTimers(): JestObjectType, 416 | /** 417 | * Instructs Jest to use the real versions of the standard timer functions. 418 | */ 419 | useRealTimers(): JestObjectType, 420 | /** 421 | * Creates a mock function similar to jest.fn but also tracks calls to 422 | * object[methodName]. 423 | */ 424 | spyOn(object: Object, methodName: string): JestMockFn, 425 | /** 426 | * Set the default timeout interval for tests and before/after hooks in milliseconds. 427 | * Note: The default timeout interval is 5 seconds if this method is not called. 428 | */ 429 | setTimeout(timeout: number): JestObjectType 430 | }; 431 | 432 | type JestSpyType = { 433 | calls: JestCallsType 434 | }; 435 | 436 | /** Runs this function after every test inside this context */ 437 | declare function afterEach( 438 | fn: (done: () => void) => ?Promise, 439 | timeout?: number 440 | ): void; 441 | /** Runs this function before every test inside this context */ 442 | declare function beforeEach( 443 | fn: (done: () => void) => ?Promise, 444 | timeout?: number 445 | ): void; 446 | /** Runs this function after all tests have finished inside this context */ 447 | declare function afterAll( 448 | fn: (done: () => void) => ?Promise, 449 | timeout?: number 450 | ): void; 451 | /** Runs this function before any tests have started inside this context */ 452 | declare function beforeAll( 453 | fn: (done: () => void) => ?Promise, 454 | timeout?: number 455 | ): void; 456 | 457 | /** A context for grouping tests together */ 458 | declare var describe: { 459 | /** 460 | * Creates a block that groups together several related tests in one "test suite" 461 | */ 462 | (name: string, fn: () => void): void, 463 | 464 | /** 465 | * Only run this describe block 466 | */ 467 | only(name: string, fn: () => void): void, 468 | 469 | /** 470 | * Skip running this describe block 471 | */ 472 | skip(name: string, fn: () => void): void 473 | }; 474 | 475 | /** An individual test unit */ 476 | declare var it: { 477 | /** 478 | * An individual test unit 479 | * 480 | * @param {string} Name of Test 481 | * @param {Function} Test 482 | * @param {number} Timeout for the test, in milliseconds. 483 | */ 484 | ( 485 | name: string, 486 | fn?: (done: () => void) => ?Promise, 487 | timeout?: number 488 | ): void, 489 | /** 490 | * Only run this test 491 | * 492 | * @param {string} Name of Test 493 | * @param {Function} Test 494 | * @param {number} Timeout for the test, in milliseconds. 495 | */ 496 | only( 497 | name: string, 498 | fn?: (done: () => void) => ?Promise, 499 | timeout?: number 500 | ): void, 501 | /** 502 | * Skip running this test 503 | * 504 | * @param {string} Name of Test 505 | * @param {Function} Test 506 | * @param {number} Timeout for the test, in milliseconds. 507 | */ 508 | skip( 509 | name: string, 510 | fn?: (done: () => void) => ?Promise, 511 | timeout?: number 512 | ): void, 513 | /** 514 | * Run the test concurrently 515 | * 516 | * @param {string} Name of Test 517 | * @param {Function} Test 518 | * @param {number} Timeout for the test, in milliseconds. 519 | */ 520 | concurrent( 521 | name: string, 522 | fn?: (done: () => void) => ?Promise, 523 | timeout?: number 524 | ): void 525 | }; 526 | declare function fit( 527 | name: string, 528 | fn: (done: () => void) => ?Promise, 529 | timeout?: number 530 | ): void; 531 | /** An individual test unit */ 532 | declare var test: typeof it; 533 | /** A disabled group of tests */ 534 | declare var xdescribe: typeof describe; 535 | /** A focused group of tests */ 536 | declare var fdescribe: typeof describe; 537 | /** A disabled individual test */ 538 | declare var xit: typeof it; 539 | /** A disabled individual test */ 540 | declare var xtest: typeof it; 541 | 542 | /** The expect function is used every time you want to test a value */ 543 | declare var expect: { 544 | /** The object that you want to make assertions against */ 545 | (value: any): JestExpectType & JestPromiseType & EnzymeMatchersType, 546 | /** Add additional Jasmine matchers to Jest's roster */ 547 | extend(matchers: { [name: string]: JestMatcher }): void, 548 | /** Add a module that formats application-specific data structures. */ 549 | addSnapshotSerializer(serializer: (input: Object) => string): void, 550 | assertions(expectedAssertions: number): void, 551 | hasAssertions(): void, 552 | any(value: mixed): JestAsymmetricEqualityType, 553 | anything(): void, 554 | arrayContaining(value: Array): void, 555 | objectContaining(value: Object): void, 556 | /** Matches any received string that contains the exact expected string. */ 557 | stringContaining(value: string): void, 558 | stringMatching(value: string | RegExp): void 559 | }; 560 | 561 | // TODO handle return type 562 | // http://jasmine.github.io/2.4/introduction.html#section-Spies 563 | declare function spyOn(value: mixed, method: string): Object; 564 | 565 | /** Holds all functions related to manipulating test runner */ 566 | declare var jest: JestObjectType; 567 | 568 | /** 569 | * The global Jasmine object, this is generally not exposed as the public API, 570 | * using features inside here could break in later versions of Jest. 571 | */ 572 | declare var jasmine: { 573 | DEFAULT_TIMEOUT_INTERVAL: number, 574 | any(value: mixed): JestAsymmetricEqualityType, 575 | anything(): void, 576 | arrayContaining(value: Array): void, 577 | clock(): JestClockType, 578 | createSpy(name: string): JestSpyType, 579 | createSpyObj( 580 | baseName: string, 581 | methodNames: Array 582 | ): { [methodName: string]: JestSpyType }, 583 | objectContaining(value: Object): void, 584 | stringMatching(value: string): void 585 | }; 586 | --------------------------------------------------------------------------------